Engee documentation
Notebook

Creating a main function specific to the target platform

This example shows how to use Engee code generator templates to generate a function. main(). This allows you to generate a function main containing custom code (for example, platform-dependent code with MC timer settings, etc.). This approach also allows you to embed the model code in the RTOS.

An example is a counter model on a finite state machine

As an example, let's take a counter implemented using finite state machines. The implementation is contained in the Counter_PWM model:

In [ ]:
demoroot = @__DIR__
mdl = engee.load(joinpath(demoroot,"Counter_PWM.engee"); force = true)
engee.open(mdl)
Out[0]:
System(
	name: root
	id: 7a75407c-b1ea-4bb4-af3a-1767064410f7
)

Suppose you need to call this counter with a frequency of 20 Hz and implement it on different hardware platforms: STM32F4 and Arduino.

How the counter works

image.png

The principle of operation of the meter is that the battery is incremented at each step of the simulation. If the battery value reaches the threshold THRSHLD set by the input constant, the battery is reset and the ISR flag is raised. For THRSHLD equal to 4, the result looks like this:

image_2.png

To make sure that the system is working correctly, we will turn on or off the LED using the ISR flag.

Templates for main

We already know that the code for the blocks can be customized using the source code generator templates. The same can be done for main. The main difference is that the amount of target code in the main templates will be significantly larger than the amount of code in the host language. Also, the main template can only be used when using command control for source code generation.

As an example, let's look at the simplest template for Arduino:

In [ ]:
run(`cat "arduino_main.jl"`) 
#include "$(model_name).h"


void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    $model_init
}

void loop() {
  static unsigned long lastCallTime = 0;
  const unsigned long interval = 50; // 50 ms = 20 Hz

  unsigned long currentTime = millis();

  // Check if 50 ms has elapsed
  if (currentTime - lastCallTime >= interval) {
    $model_step
    digitalWrite(LED_BUILTIN,(uint8_t)$(output(2)))
    lastCallTime = currentTime; // Update last call time
  }
}
Out[0]:
Process(`cat arduino_main.jl`, ProcessExited(0))

As you can see, the template is almost pure C code, with special directives like $model_<>

В отличии от обычных функций, применяемых в шаблонах, эти директивы раскрываются как вызовы соответствующих функций API модели. Например, вызов $model_step for the Counter_PWM model, it will open as Counter_PWM_step();

To generate the main for each of the test platforms, use the switch:

image.png

and run the code cells below:

In [ ]:
target = "Arduino" # @param ["STM","Arduino"]
Out[0]:
"Arduino"
In [ ]:
hw = lowercase(target);
engee.generate_code(joinpath(demoroot,"Counter_PWM.engee"),
                    joinpath(demoroot,"$(hw)_code"),
                    target = "c",
                    template_path = "$demoroot/$(hw)_main.jl");
[ Info: Generated code and artifacts: /user/work/code_generation/main_template/arduino_code

You can view the generated code using the command below:

In [ ]:
src = "$(hw)_code/main.c";
run(`cat $src`)
/* Code generated by Engee
 * Model name: Counter_PWM.engee
 * Code generator: release-1.1.17
 * Date: Fri Jun 20 12:36:46 2025
 */

#include "Counter_PWM.h"


void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    Counter_PWM_init();
}

void loop() {
  static unsigned long lastCallTime = 0;
  const unsigned long interval = 50; // 50 ms = 20 Hz
unsigned long currentTime = millis();

// Check if 50 ms has elapsed
  if (currentTime - lastCallTime >= interval) {
Counter_PWM_step();
digitalWrite(LED_BUILTIN,(uint8_t)Counter_PWM_Y.ISR)
lastCallTime = currentTime; // Update last call time
  }
}
Out[0]:
Process(`cat arduino_code/main.c`, ProcessExited(0))

Assembling the resulting code

Assembly for STM

The steps for assembly are described in the Flashing LED example on the STM32F4 in the section Executing the model on STM32

Assembly for Arduino

The steps for assembly are described in the example Code Generation for Arduino (PWM signal generation) in the section Transferring the model to Arduino. The only improvement is that we will immediately rename main.c to arduino_code.ino.

In [ ]:
mv("./arduino_code/main.c", "./arduino_code/arduino_code.ino";force=true)
Out[0]:
"./arduino_code/arduino_code.ino"

Demonstration of operation (STM32)

Finally, let's make sure that the example works. To do this, we will use the STM32F4 Discovery card. We will assemble the project and upload it to the board. It can be seen that the LED starts flashing:

![Untitled Project(1).gif](attachment:Untitled Project(1).gif)

Conclusion

Templates for the main function allow you to generate a binding for the model code that is specific to the hardware platform. This ensures full portability of the algorithms.

Blocks used in example