Engee documentation
Notebook

Code generation for Arduino (PWM signal generation)

In this example we will show you how to generate programme code in Engee to generate a PWM signal on the Arduino platform.

Introduction

There are many ways to create a pulse width modulated (PWM) signal. We will implement a model from basic components, as similar as possible to the demo example arduino_blink. That is, we are going to control the time delay between the on and off events of the LED.

Other ways that can be used:

  • Compare a smoothly varying analogue signal with a sawtooth signal and switch the diode at the moment of intersection
  • Use one of the inbuilt functions of Arduino

Platform preparation

For this example we need an Arduino-compatible platform (Uno, Leonardo, Iskra, etc.) and a suitable USB cable. You will also need to install the Arduino IDE on your computer, find and install additional drivers (if necessary) and connect the existing board via USB.

image_2.png

Model Description

In this example, we will generate code from the model pwm.engee.

The interface of the model is the same as in the example arduino_blink. At each step of the computation it:

  1. switches the state of the LED using the output of out_LED_BUILTIN (initially equal to 1, changes to "opposite" with each cycle),
  2. returns the time delay value to Arduino using the output param_WAIT_MS (value in milliseconds).

image_2.png

The RMS will change in a sine wave with the frequency set in the block Sine. The maximum value is set as amplitude in the same block, and the offset set there allows the delay value to never become less than 0, which would cause an error on the Arduino platform and stop the programme execution.

It is with the help of the value param_WAIT_MS that we will control the pulse width, switching on and off the "on-board" LED of the Arduino platform with pulses of different lengths.

PWM-signal is characterised by the fact that during a fixed period $T_d$ it changes state 1 and state 0, the duration of which in total is equal to $T_d$, but the ratio of which is changed using the parameter $w$ (pulse width).

.

image.png

We will use this signal to simulate the smooth control of LED brightness on the Arduino platform.

Data type conversion

For the most part, the Engee model blocks are directly reflected in the code for Arduino. Not all platforms use the same C/C++ language standards. Naturally, you always have to think about low-level implementation details when creating code.

In this example, the problem we overcome in Engee is automatic type conversion. On the Arduino platform:

  • When adding a boolean data type to a numeric data type, the output is boolean
  • When adding a double data type to an unsigned integer, the output is an unsigned integer (the sine wave does not work correctly unless a special data type is specified).

Where do problems with data types occur?

image_3.png

Therefore, we need to envisage how the operation of data types will manifest itself in C code, and configure the model blocks (marked with green ticks) accordingly:

  • The Constant block should return a data type Float32
  • The Compare To Zero block should return not bool, but rather uint8

All these settings are set in the corresponding blocks. Sometimes these problems can be solved by using Data Type Conversion blocks, such conversions are also passed into the generated code.

Model testing

The process of semi-natural modelling involves running the model in a simulated environment for debugging and optimisation with subsequent control of the same algorithm on a hardware platform.

In our case it is worth considering that Arduino runs on its own clock frequency, and different commands can be executed for different number of clock cycles. In order to rely on real physical time, it is better to create a template for code generation based on a real-time system.

Nevertheless, we can already show how our solution will work.

In [ ]:
if "pwm" in [m.name for m in engee.get_all_models()]
    m = engee.open( "pwm" );
else
    m = engee.load( "$(@__DIR__)/pwm.engee" );
end

data = engee.run(m);

Let's build a graph of LED switching delay time (parameter that is passed to delay()).

In [ ]:
using Plots
plot( data["param_WAIT_MS"].time, data["param_WAIT_MS"].value,
      label="param_WAIT_MS", st=:step, size=(900,300))

# Подкрасим переменное время задержки (LED вкл/выкл)
plot!( data["param_WAIT_MS"].time, [iseven(i) ? d : NaN for (i,d) in enumerate(data["param_WAIT_MS"].value)],
      label="задержка при выключенном LED", st=:step, size=(900,300), c=:black, lw=2)
plot!( data["param_WAIT_MS"].time, [isodd(i) ? d : NaN for (i,d) in enumerate(data["param_WAIT_MS"].value)],
      label="задержка при включенном LED", st=:step, size=(900,300), c=:red, lw=2)
Out[0]:

What do we see here? The time delay param_WAIT_MS varies in time in such a way that every two neighbouring values always add up to about 10 milliseconds. The law to modulate this process is given by a sinusoid.

If we combine the two output signals correctly, we will see exactly the PWM signal that controls the switching on and off of the current supplied to the output to which the LED is connected.

In [ ]:
plot( cumsum(data["param_WAIT_MS"].value),
             data["out_LED_BUILTIN"].value,
      st=:step, size=(900,200))
Out[0]:

This model is not set up to simulate real operation time, so you may notice that the model time (2 seconds - simulation duration) is not equal to the CPU time (according to the graph it is about 1 second, and that with large tolerances). To provide this property, you need to build the model a little differently.

Code generation

In this demonstration, we propose to generate code using the following command:

In [ ]:
engee.generate_code( "$(@__DIR__)/pwm.engee",
                     "$(@__DIR__)/sketch_pwm_custom/pwm_code" )
Out[0]:
"Created directory - /user/start/examples/codegen/arduino_pwm/sketch_pwm_custom/pwm_code"

As a result, in the directory sketch_pwm_custom we will find the folder pwm_code, in which we will find .c and .h files with the code of our model.

Project files

There is only one model in our project. From the generated files we need only files with the code of this model: source code pwm.c and header file pwm.h.

It is worth paying attention to the structure of files and directories of the project:

image_2.png

  • file sketch_pwm_custom.ino contains description of the Model-Arduino interface,
  • the sketch_pwm_custom level directory must be named the same as the *.ino file (Arduino IDE requirements),
  • the directory pwm_code contains files with generated code, which we will include in the file *.ino.

Transferring the model to Arduino

To transfer a project to Arduino, the standard steps are as follows:

  1. Download the catalogue sketch_pwm_custom from a file browser, unzip the archive
  2. open the file sketch_pwm_custom.ino and click on the button Upload

The result should be as follows (LED flickers smoothly):

20240127_042956_2.GIF

Conclusion

We have created a slightly more complex model than a blinking LED. This way of creating a PWM signal will allow you to control simple machines, such as LEDs or servo motors.

We had to consider how data type casting works on the target platform. Nevertheless, we have seen that the Engee platform makes it possible to model components that can be almost seamlessly run on a hardware platform through code generation.

Blocks used in example