Генерация кода в Engee
Генерация кода для конечных автоматов
Введение в конечные автоматы
Конечный автомат — это модель вычислений, которая последовательно переходит между различными состояниями на основе входных данных или событий. Конечный автомат включает в себя два подхода к построению моделей — машину состояний и граф переходов.
Библиотека конечных автоматов Engee является эффективным инструментом разработки таких пользовательских алгоритмов для целевых устройств, где необходимо использовать сложные конструкции условий, циклов и переключателей. Конечные автоматы применяются, например, при моделировании систем управления и изучения мультиагентных систем.
В библиотеку входит блок Chart, который позволяет описывать диаграммы состояний, графы переходов и их комбинации.
Внутри блока Chart имеются следующие элементы конечных автоматов:
Подробнее о конечных автоматах вы можете прочитать в документации, а также пройти учебный курс Конечные автоматы.
Особенности генерации кода для конечных автоматов
Engee поддерживает генерацию кода для блока Chart. В состояниях, действиях и условиях блока Chart генератором кода Engee поддерживаются следующие конструкции языка Julia:
- оператор
if-else/elseif; - оператор
while; - оператор
for; - арифметические операторы
+и-(унарные и бинарные),*,/,%; - логические операторы
&&,||и!; - операторы сравнения
<,<=,>,>=,==,!=(поддерживаются только бинарные, с двумя операндами).
Поддерживаемые генератором кода Engee возможности блока Chart:
-
безусловные и условные переходы;
-
входные, локальные и выходные переменные типа
v(представлен типомFloat64в Julia); -
несколько несвязанных диаграмм состояний, графов переходов;
-
несколько инструкций, записанных в одну строку через разделитель (
;) в теле действий переходов и в состояниях; -
многострочные инструкции в состояниях;
-
группы операторов
entry,during,exit,on; -
сокращенные наименования групп операторов Chart (
en,du,ex); -
операторы темпоральной логики
after,at,before,elapsed,et,every,t,temporalCount; -
функции
floor(Int64,…)иceil(Int64,…). -
комментирование в состояниях, теле условий и действий переходов;
-
поддержка механизма Backtracking (когда между состояниями есть промежуточные узлы);
-
поддержка механизма суперпереходов, когда переходы между состояниями пересекают границы блоков-состояний. Это могут быть переходы между:
- внешним состоянием и его вложенным состоянием, когда глубина вложенности больше единицы;
- вложенными состояниями двух различных внешних состояний, не вложенных друг в друга.
-
Также поддерживаются суперпереходы по умолчанию, когда суперпереход начинается не в родительском состоянии, а приходит снаружи родительского состояния.
Практическая часть. Мигающий светодиод на конечных автоматах
Описание модели
Рассмотрим модель для простейшей программы управления цифровым выходом микроконтроллера STM32F446RE с использованием вложенных состояний библиотеки конечных автоматов Engee.
Модель примера, stm32_blink.engee, состоит из двух блоков – блока Chart Blink и C Function GPIO_5_OUTPUT (LED).
Первый блок реализует управляющий алгоритм – изменение состояния Out на выходе блока, а второй блок инициализирует цифровой канал управления GPIO_5, соединенный с встроенным светодиодом отладочной платы NUCLEO-F446RE, и изменяет его состояние в ходе работы программы.
Диаграмма состояний, содержащаяся в блоке Chart, включает в себя следующие состояния:
Материнское состояние Period_and_Clock задает период импульсов FullTime (в шагах расчета модели) для мигания встроенного светодиода и инкрементирует счетчик периода CurrentTime.
Дочерние состояния:
HIGH– при значениях счетчикаCurrentTime, меньших или равных половине периода импульса, формирует высокий уровень сигнала на выходе блока.LOW– при значениях счетчикаCurrentTime, больших половины периода импульса, формирует высокий уровень сигнала на выходе блока.
В случае равенства значения счетчика времени периода импульса счетчик сбрасывается в переходе между состояниями LOW и HIGH.
Запустим симуляцию модели и построим график сигнала Out – состояния встроенного светодиода:
Как видно из графика, на выходе управляющего алгоритма формируется периодический сигнал с заданной частотой и длительностью импульсов.
✏️Задание
Сгенерируйте Си-код для модели stm32_blink.engee:
- Откройте модель
stm32_blink.engee, находящуюся в папке данного курса. - В окне настроек модели проверьте, что во вкладке Генерация кода значение параметра Целевая платформа выбрано равным
Си. - Сгенерируйте Си-код для модели. Для этого в интерактивном скрипте выполните следующую команду:
engee.generate_code( "$(@__DIR__)/stm32_blink.engee", "$(@__DIR__)/stm32_blink_code")
Решение
Файл main.c:
/* Code generated by Engee
* Model name: stm32_blink.engee
* Code generator: release-1.1.24
* Date: Wed Sep 24 10:55:57 2025 GMT
*/
#include "stm32_blink.h"
void do_step(void)
{
static bool OverrunFlag = false;
/* Check for overrun */
if (OverrunFlag) {
return;
}
OverrunFlag = true;
/* Step the model */
stm32_blink_step();
/* Indicate task complete */
OverrunFlag = false;
}
int main(int argc, char *argv[])
{
(void) argc;
(void) argv;
/* Initialize model */
stm32_blink_init();
while (1) {
/* Perform application tasks here */
}
/* Terminate model */
stm32_blink_term();
return 0;
}
Файл stm32_blink.c:
/* Code generated by Engee
* Model name: stm32_blink.engee
* Code generator: release-1.1.24
* Date: Wed Sep 24 10:55:57 2025 GMT
*/
#include "stdint.h"
#include "math.h"
#include "stm32f4xx_hal.h"
#include "stm32_blink.h"
/* Block states */
Ext_stm32_blink_S stm32_blink_S;
/* В этом коде можно объявить статические данные и функции */
typedef struct GPIO_5_OUTPUTLED_cfunc_symbols_t {
double PinState;
} GPIO_5_OUTPUTLED_cfunc_symbols_t;
static void GPIO_5_OUTPUTLED_cfunc_init() {
//
// Код, который будет исполняться на STM32F4
//
#ifdef STM32F4
//
// Включение тактирования периферии
//
__HAL_RCC_GPIOA_CLK_ENABLE();
//
// Определение переменных структуры инициализации GPIO
//
GPIO_InitTypeDef GPIO_InitStruct; // Именование структуры
GPIO_InitStruct.Pin = GPIO_PIN_5; // Номер выходного пина
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // Режим пина - выход
GPIO_InitStruct.Pull = GPIO_PULLUP; // Активация подтягиванием
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // Максимальная частота работы пина (50-200 МГц)
//
// Инициализация GPIO с заданной структурой инициализации
//
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
#else
//
// Код, который будет исполняться при моделировании
//
#endif
}
static void GPIO_5_OUTPUTLED_cfunc(GPIO_5_OUTPUTLED_cfunc_symbols_t* cfunc_symbols) {
double PinState = cfunc_symbols->PinState;
//
// Код, который будет исполняться на STM32F4
//
#ifdef STM32F4
//
// Запись в пин GPIO_PIN_5 состояния PinState - вход блока
//
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, PinState);
#else
//
// Код, который будет исполняться при моделировании
//
#endif
}
static void GPIO_5_OUTPUTLED_cfunc_term() {
//
// Код, который будет исполняться на STM32F4
//
#ifdef STM32F4
#else
//
// Код, который будет исполняться при моделировании
//
#endif
}
/* Model initialize function */
void stm32_blink_init() {
stm32_blink_S.Blink.output.Out = 0.0;
stm32_blink_S.Blink.local.FullTime = 0.0;
stm32_blink_S.Blink.local.CurrentTime = 0.0;
memset(&stm32_blink_S.Blink.state, 0, sizeof(stm32_blink_S.Blink.state));
GPIO_5_OUTPUTLED_cfunc_init();
}
/* Model terminate function */
void stm32_blink_term() {
GPIO_5_OUTPUTLED_cfunc_term();
}
/* Model step function */
void stm32_blink_step() {
/* Chart: /Blink incorporates:
*/
double Blink; /* /Blink */
{
int Blink_selector;
if (!stm32_blink_S.Blink.state.active /* Chart is not initialized */) {
stm32_blink_S.Blink.state.active = true;
if (!stm32_blink_S.Blink.state.Period_and_Clock.active /* state Period_and_Clock is not active */) {
stm32_blink_S.Blink.state.Period_and_Clock.active = true; /* activate state Period_and_Clock */
stm32_blink_S.Blink.local.FullTime = 200L;
}
Blink_selector = 11;
}
else if (stm32_blink_S.Blink.state.Period_and_Clock.active /* state Period_and_Clock is active */) {
stm32_blink_S.Blink.local.CurrentTime = stm32_blink_S.Blink.local.CurrentTime + (double)1L;
if (stm32_blink_S.Blink.state.Period_and_Clock.LOW.active /* state Period_and_Clock.LOW is active */) {
if (stm32_blink_S.Blink.local.CurrentTime == stm32_blink_S.Blink.local.FullTime) {
stm32_blink_S.Blink.local.CurrentTime = 0L;
stm32_blink_S.Blink.state.Period_and_Clock.LOW.active = false; /* deactivate state Period_and_Clock.LOW */
if (!stm32_blink_S.Blink.state.Period_and_Clock.HIGH.active /* state Period_and_Clock.HIGH is not active */) {
stm32_blink_S.Blink.state.Period_and_Clock.HIGH.active = true; /* activate state Period_and_Clock.HIGH */
stm32_blink_S.Blink.output.Out = 1L;
}
Blink_selector = 36;
}
else {
goto Blink_done;
}
}
else if (stm32_blink_S.Blink.state.Period_and_Clock.HIGH.active /* state Period_and_Clock.HIGH is active */) {
if (stm32_blink_S.Blink.local.CurrentTime > stm32_blink_S.Blink.local.FullTime / (double)2L) {
stm32_blink_S.Blink.state.Period_and_Clock.HIGH.active = false; /* deactivate state Period_and_Clock.HIGH */
if (!stm32_blink_S.Blink.state.Period_and_Clock.LOW.active /* state Period_and_Clock.LOW is not active */) {
stm32_blink_S.Blink.state.Period_and_Clock.LOW.active = true; /* activate state Period_and_Clock.LOW */
stm32_blink_S.Blink.output.Out = 0L;
}
Blink_selector = 49;
}
else {
goto Blink_done;
}
}
else {
goto Blink_done;
}
}
else {
goto Blink_done;
}
switch (Blink_selector) {
case 49:
{
goto Blink_done;
break;
}
case 36:
{
goto Blink_done;
break;
}
case 11:
{
stm32_blink_S.Blink.state.Period_and_Clock.HIGH.active = true; /* activate state Period_and_Clock.HIGH */
stm32_blink_S.Blink.output.Out = 1L;
goto Blink_done;
break;
}
}
Blink_done:
/* Blink's output variables: */
Blink = stm32_blink_S.Blink.output.Out;
}
/* CFunction: /GPIO_5_OUTPUT (LED) incorporates:
* Chart: /Blink
*/
GPIO_5_OUTPUTLED_cfunc_symbols_t GPIO_5_OUTPUTLED_cfunc_symbols = {
Blink,
};
GPIO_5_OUTPUTLED_cfunc(&GPIO_5_OUTPUTLED_cfunc_symbols);
}
Файл stm32_blink.h:
/* Code generated by Engee
* Model name: stm32_blink.engee
* Code generator: release-1.1.24
* Date: Wed Sep 24 10:55:57 2025 GMT
*/
#ifndef HEADER_stm32_blink_h
#define HEADER_stm32_blink_h
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include <float.h>
#include <limits.h>
#include <string.h>
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
typedef struct {
struct {
struct {
double FullTime; /* /Blink */
double CurrentTime; /* /Blink */
} local;
struct {
double Out; /* /Blink */
} output;
struct {
struct {
struct {
bool active;
} LOW;
struct {
bool active;
} HIGH;
bool active;
} Period_and_Clock;
bool active;
} state;
} Blink;
} Ext_stm32_blink_S;
/* Block states */
extern Ext_stm32_blink_S stm32_blink_S;
/* Model entry point functions */
extern void stm32_blink_init();
extern void stm32_blink_step();
extern void stm32_blink_term();
/* Here is the system hierarchy for this model:
*
* 'stm32_blink' : '/'
*/
#ifdef __cplusplus
} /* extern "C" */
#endif /* __cplusplus */
#endif /* HEADER_stm32_blink_h */
Сборка проекта
Созданные в папке stm32_blink_code файлы – заголовочный stm32_blink.h и исходный stm32_blink.c будут использованы далее при сборке проекта.
Среда разработки, через которую ведется сборка проекта и его загрузка в целевое устройство, – VS Code с надстройкой PlatformIO. При работе с STM32 в этом примере также используется платформа ST STM32 и фреймворк STM32Cube для PlatformIO. Файл конфигурации проекта PlatformIO platformio.ini содержит следующие настройки:
[env:nucleo_f446re]
platform = ststm32
board = nucleo_f446re
framework = stm32cube
После создания нового проекта нужно добавить в него сгенерированные в Engee файлы и файл с кодом основной программы main.c (добавлен в папку примера):
После этого можно перейти к сборке проекта и загрузке программы.
Выполнение модели на STM
Подключим отладочную плату NUCLEO-F446RE к USB-порту компьютера, после чего в PlatformIO можем наблюдать подключенное устройство. Для корректной идентификации подключения этой платы требуется драйвер ST-Link V2.
После успешного подключения можно перейти к сборке проекта: "PLATFORMIO -> PROJECT TASKS -> nucleo_f446re -> General -> Build". При отсутствии ошибок сборки загрузим скомпилированный код в микроконтроллер: "PLATFORMIO -> PROJECT TASKS -> nucleo_f446re -> General -> Upload".
В результате загрузки программы на отладочной плате можно наблюдать мигание светодиода с частотой 0,5 Гц.
Для демонстрации в примере к соответствующему выводу микроконтроллера был подключен цифровой осциллограф Hantec DSO, осциллограмма выведена в последовательный порт компьютера с помощью оболочки DSO Analyzer. Как можно видеть на рисунке, полученный сигнал имеет заданную в модели периодичность и длительность импульса.