Настройка main() в сгенерированном коде

Автор
avatar-nikfilaretovnikfilaretov
Notebook

Создание функции main специфичной для целевой платформы

В данном примере показано как использовать шаблоны генератора кода Engee для генерации функции main(). Это позволяет сгенерировать функцию main содержащую пользовательский код (например, платформозависимый код с настройками таймеров МК и т.д.). Так же этот подход позволяет встроить код модели в ОСРВ.

Пример - модель счетчика на конечном автомате

В качестве примера возьмем счетчик, реализованный с помощью конечных автоматов. Реализация содержится в модели Counter_PWM:

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
)

Предположим, требуется вызывать этот счетчик с частотой 20 Гц и имплементировать его на разных аппаратных платформах: STM32F4 и Arduino.

Принцип работы счетчика

image.png

Принцип работы счетчика заключается в том, что аккумулятор инкрементируется на каждом шаге симуляции. Если значение аккумулятора достигает порога THRSHLD, задаваемого входной константой, то аккумулятор сбрасывается и поднимается флаг ISR. Для THRSHLD равного 4 результат работы выглядит следующим образом:

image_2.png

Что бы удостоверится, что система работает корректно, по флагу ISR будем включать или отключать светодиод.

Шаблоны для main

Мы уже знаем, что код для блоков можно настроить при помощи шаблонов генератора исходного кода. Тоже самое можно сделать и для main. Главное отличие - объем целевого кода в шаблонах main будет значительно больше, чем объем кода на хост-языке. Так же, применение шаблона main возможно только при использовании командного управления генерацией исходного кода.

В качестве примера рассмотрим простейший шаблон для 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))

Как видно, шаблон представляет собой почти чистый Си-код, с включениями специальных директив вида $model_<>

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

Для генерации main для каждой из тестовых платформ воспользуйтесь переключателем:

image.png

и запустите кодовые ячейки ниже:

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

Посмотреть сгенерированный код можно с помощью команды ниже:

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))

Сборка результирующего кода

Сборка для STM

Шаги для сборки описаны в примере Мигающий светодиод на STM32F4 в разделе Выполнение модели на STM32

Cборка для Arduino

Шаги для сборки описаны в примере Генерация кода для Arduino (генерация ШИМ сигнала) в разделе Перенос модели на Arduino. Единственное улучшение - мы сразу переименуем main.c в arduino_code.ino

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

Демонстрация работы (STM32)

Наконец, убедимся в том, что пример работает. Для этого воспользуемся платой STM32F4 Discovery. Соберем проект и загрузим его на плату. Видно, что светодиод начинает мигать:

untitled_project_1.gif

Вывод

Шаблоны для функции main позволяют генерировать обвязку для кода модели, специфичную для аппаратной платформы. Таким образом обеспечивается полная переносимость алгоритмов.