Интеграция C кода в модели Engee с использованием блока C Function
В данном примере представлены различные способы интеграции C кода в модели Engee с использованием блока C Function.
Перед началом работы подключим библиотеку для сравнения с MATLAB:
using MATLAB
Используем макрос @__DIR__
для того, чтобы узнать папку, в которой лежит интерактивный скрипт:
demoroot = @__DIR__
Основы использования блока C Function
Рассмотрим простые примеры использования блока C Function, иллюстрирующие основы интеграции C кода в модель Engee.
1. Работа с многомерными сигналами, передача параметров и использование директивы #define
В модель cCodeIntegration_basics.engee
добавлен блок C Function, на вход которого поступает синусоидальный сигнал in
:
Предположим, что мы хотим увеличить амплитуду синусоиды in
в gain
раз и сдвинуть её на величину bias
. То есть выходной сигнал должен определяться по формуле out = gain * in + bias
.
Перейдём в настройки блока C Function, где во вкладке Ports
мы ознакомимся с определением входа и выхода блока. Здесь связываются переменные исходного кода с портами блока C Function:
- Скалярная переменная
x
типаdouble
связывается с первым входным портомin
; - Векторная (3 элемента) переменная
y
типаdouble
связывается с первым выходным портомout
.

Массив gain
также определяется в настройках блока C Function (вкладка Parameters
):

Стоит отметить, что после передачи переменной в качестве параметра она становится:
- Глобальной - к
gain
можно обратиться из любой вкладки редактора исходного кода; - С квалификатором
const
- при попытке изменения значенияgain
будет получена ошибка.
Величина смещения bias
определяется идентификатором #define bias
, который задается во вкладке Build options
:

В данном примере смещение равно 5
.
Перейдём во владку Main
настроек блока C Function и нажмём кнопку "Редактировать исходный код". В открывшемся редакторе исходного кода на вкладке OutputCode
приведён код, исполняемый на каждом шаге расчёта модели:

В цикле for
(для каждого из трёх выходных сигналов) на каждом шаге расчёта модели определяется значение переменной y
.
Промоделируем систему:
cCodeIntegration_basics = engee.load("$demoroot/models/cCodeIntegration_basics.engee", force = true)
simulationResults_basics = engee.run(cCodeIntegration_basics)
Закроем модель:
engee.close(cCodeIntegration_basics, force = true);
Импортируем результаты моделирования:
cCodeIntegration_basics_t = simulationResults_basics["out"].time;
cCodeIntegration_basics_y1 = [y[1] for y in simulationResults_basics["out"].value];
cCodeIntegration_basics_y2 = [y[2] for y in simulationResults_basics["out"].value];
cCodeIntegration_basics_y3 = [y[3] for y in simulationResults_basics["out"].value];
Построим график:
plot(cCodeIntegration_basics_t, cCodeIntegration_basics_y1)
plot!(cCodeIntegration_basics_t, cCodeIntegration_basics_y2)
plot!(cCodeIntegration_basics_t, cCodeIntegration_basics_y3)
title!("Выходной сигнал блока C Function <br> (Многомерные сигналы, параметры и #define)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
2. Использование целочисленных типов данных и статических переменных
В модели cCodeIntegration_integers.engee
реализуем при помощи блока C Function простой счётчик, выходом которого будет положительное целое число, обозначающее номер текущей итерации:
Проанализируем содержимое вкладок Ports
и Build Options
в настройках блока C Function:


Названия целочисленных типов данных в блоке C Function задаются в соответствии с заголовочным файлом <stdint.h>
стандартной библиотеки языка C. Этот файл подключается в строке Headers
вкладки Build options
.
Во вкладке Ports
связывается 64-битовая переменная без знака out
(тип задаётся как uint64_t
, а не long
, см. предыдущий абзац про <stdint.h>
) исходного кода с первым выходным портом блока C Function.
Во вкладке Output code
редактора исходного кода блока C Function приведён код, исполняемый на каждом шаге моделирования:

Статическая переменная counter
инициализируется в 0 на первом шаге симуляции, после чего на каждой итерации её значение увеличивается на 1
и присваивается переменной out
.
Промоделируем систему:
cCodeIntegration_integers = engee.load("$demoroot/models/cCodeIntegration_integers.engee", force = true)
simulationResults_integers = engee.run(cCodeIntegration_integers)
Закроем модель:
engee.close(cCodeIntegration_integers, force = true);
Импортируем результаты моделирования:
cCodeIntegration_integers_t = simulationResults_integers["counter"].time;
cCodeIntegration_integers_y = simulationResults_integers["counter"].value;
Построим график:
plot(cCodeIntegration_integers_t, cCodeIntegration_integers_y, legend = false)
title!("Выходной сигнал блока C Function <br> (Целочисленные типы и статические переменные)")
xlabel!("Время, [с]")
ylabel!("Номер итерации")
Более подробная информация о назначении вкладок редактора исходного кода, возможностях построения и доступных типах данных представлена в документации Engee.
Интеграция внешнего исходного кода
Постановка задачи
Рассмотрим более интересную инженерную задачу.
Предположим, что нам необходимо, учитывая шум измерений, управлять устойчивым объектом второго порядка, на который воздействуют внешние возмущения. Следовательно, нам потребуется решить задачу фильтрации выходного сигнала объекта управления, и, к счастью, в Engee есть инструмент, что отлично подходит для этого!
В файлах alphabetafilter.c
и alphabetafilter.h
предложена реализация альфа-бета фильтра на языке C.
Содержимое файла alphabetafilter.c
:
#include "alphabetafilter.h"
#include <math.h>
double dt = 0.01;
static double xk_1, vk_1;
Parameters determineAlphaBeta(double processNoiseVariance, double measurementNoiseVariance)
{
double lambda, r;
Parameters p;
lambda = (processNoiseVariance * pow(dt, 2)) / measurementNoiseVariance;
r = (4 + lambda - sqrt(8 * lambda + pow(lambda, 2))) / 4;
p.alpha = 1 - pow(r, 2);
p.beta = 2 * (2 - p.alpha) - 4 * sqrt(1 - p.alpha);
return p;
}
double alphaBetaFilter(double value, Parameters p)
{
double xk, vk, rk;
xk = xk_1 + (vk_1 * dt);
vk = vk_1;
rk = value - xk;
xk += p.alpha * rk;
vk += (p.beta * rk) / dt;
xk_1 = xk;
vk_1 = vk;
return xk;
}
void initializeStaticVariables()
{
xk_1 = 0;
vk_1 = 0;
}
Содержимое файла alphabetafilter.h
:
typedef struct {
double alpha;
double beta;
} Parameters;
Parameters determineAlphaBeta(double, double);
double alphaBetaFilter(double, Parameters);
void initializeStaticVariables();
Интегрируем данный исходный код в нашу модель Engee.
Анализ модели
Модель cCodeIntegration_source.engee
:
Данная модель состоит из:
- Объекта управления
Plant
; - Подсистемы, реализующей ПИД-регулятор второго порядка
Controller
; - Блоков Band-Limited White Noise
ProcessNoise
иMeasurementNoise
, моделирующих внешнее воздействие и шум измерений; - Блока C Function
Filter
, реализующего альфа-бета фильтр.
Параметр Sample Time
блока Filter
равен шагу решателя модели Ts
:

Проанализируем содержимое вкладки Build options
настроек блока Filter
:

В строках этой вкладки:
Source files
- подключен файл исходного кодаalphabetafilter.c
;Iinclude directories
- задан путь до папки, содержащей заголовочный файл;Headers
- подключен заголовочный файлalphabetafilter.h
.
Во вкладке Ports
настроек блока связываются переменные исходного кода с входными и выходными портами блока Filter
.
В исходном коде блока Filter
на вкладке OutputCode
приведён циклически исполняемый код:

На каждом шаге расчёта модели последовательно вызываются функции determineAlphaBeta()
и alphabetafilter()
, после чего отфильтрованное значение сохраняется в переменную out
, соответствующую выходному порту x
блока Filter
.
Запуск симуляции
Промоделируем систему:
cCodeIntegration_source = engee.load("$demoroot/models/cCodeIntegration_source.engee", force = true)
simulationResults_source = engee.run(cCodeIntegration_source)
Закроем модель:
engee.close(cCodeIntegration_source, force = true);
Результаты моделирования
Импортируем результаты моделирования:
cCodeIntegration_source_noisy_t = simulationResults_source["noisy"].time;
cCodeIntegration_source_noisy_y = simulationResults_source["noisy"].value;
cCodeIntegration_source_filtered_t = simulationResults_source["filtered"].time;
cCodeIntegration_source_filtered_y = simulationResults_source["filtered"].value;
Построим график:
plot(cCodeIntegration_source_noisy_t, cCodeIntegration_source_noisy_y, label = "Исходный сигнал")
plot!(cCodeIntegration_source_filtered_t, cCodeIntegration_source_filtered_y, label = "Отфильтрованный сигнал")
plot!(legend = :bottomright)
title!("Альфа-бета фильтр <br> (Интеграция внешнего исходного кода)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
Интеграция статических и динамических библиотек
Постановка задачи
Зачастую исходный код поставляется не в явном виде, а в составе статических или динамических библиотек. Библиотеки с интерфейсом C API интегрируются в модели Engee также легко, как и внешний исходный код!
Скомпилируем статическую (alphabetafilter.a
) и динамическую (alphabetafilter.so
) библиотеку из файлов alphabetafilter.c
и alphabetafilter.h
с использованием MakeFile
.
Содержимое файла Makefile
:
all: alphabetafilter.a alphabetafilter.so
alphabetafilter.o: alphabetafilter.c
$(CC) -c -fPIC $^ -o $@
alphabetafilter.a: alphabetafilter.o
ar rcs $@ $^
alphabetafilter.so: alphabetafilter.o
$(CC) -shared $^ -o $@
clean:
rm -f *.o *.a *.so
Запустим компиляцию библиотек:
run(`make -C $demoroot/source`)
Интегрируем статическую и динамическую библиотеку в нашу модель Engee.
Статическая библиотека: Анализ модели
По своей структуре модель cCodeIntegration_library_static.engee
не отличается от рассмотренной ранее cCodeIntegration_source.engee
; отличаются лишь директивы на вкладке Build options
настроек блока Filter
. Проанализируем её содержимое:

Здесь определены следующие опции построения:
Include directories
- задан путь до папки, содержащей заголовочный файл;Library directories
- задан путь до папки, содержащей статическую библиотеку;Headers
- подключен заголовочный файлalphabetafilter.h
;Libraries
- подключена статическая библиотекаalphabetafilter.a
.
Статическая библиотека: Запуск симуляции
Промоделируем систему:
cCodeIntegration_library_static = engee.load("$demoroot/models/cCodeIntegration_library_static.engee", force = true)
simulationResults_library_static = engee.run(cCodeIntegration_library_static)
Закроем модель:
engee.close(cCodeIntegration_library_static, force = true);
Статическая библиотека: Результаты моделирования
Импортируем результаты моделирования:
cCodeIntegration_library_static_noisy_t = simulationResults_library_static["noisy"].time;
cCodeIntegration_library_static_noisy_y = simulationResults_library_static["noisy"].value;
cCodeIntegration_library_static_filtered_t = simulationResults_library_static["filtered"].time;
cCodeIntegration_library_static_filtered_y = simulationResults_library_static["filtered"].value;
Построим график:
plot(cCodeIntegration_library_static_noisy_t, cCodeIntegration_library_static_noisy_y, label = "Исходный сигнал")
plot!(cCodeIntegration_library_static_filtered_t, cCodeIntegration_library_static_filtered_y, label = "Отфильтрованный сигнал")
plot!(legend = :bottomright)
title!("Альфа-бета фильтр <br> (Интеграция статической библиотеки)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
Динамическая библиотека: Анализ модели
По своей структуре модель cCodeIntegration_library_dynamic.engee
не отличается от рассмотренной ранее cCodeIntegration_source.engee
; отличаются лишь директивы на вкладке Build options
настроек блока Filter
. Проанализируем её содержимое:

Здесь определены следующие опции построения:
Include directories
- задан путь до папки, содержащей заголовочный файл;Library directories
- задан путь до папки, содержащей динамическую библиотеку;Headers
- подключен заголовочный файлalphabetafilter.h
;Libraries
- подключена динамическая библиотекаalphabetafilter.so
.
Во вкладке Start code редактора исходного кода блока Filter
выполняется вызов функции initializeStaticVariables()
:
Функция initializeStaticVariables()
объявлена в файле alphabetafilter.h
и предназначена для инициализации статических переменных xk_1
и vk_1
, использующихся в динамической библиотеке. Это необходимо для того, чтобы при повторном запуске симуляции, переменные не сохраняли своё последнее рассчитанное значение, а сбрасывались к исходному (в данном случае в 0
).
Динамическая библиотека: Запуск симуляции
Промоделируем систему:
cCodeIntegration_library_dynamic = engee.load("$demoroot/models/cCodeIntegration_library_dynamic.engee", force = true)
simulationResults_library_dynamic = engee.run(cCodeIntegration_library_dynamic)
Закроем модель:
engee.close(cCodeIntegration_library_dynamic, force = true);
Динамическая библиотека: Результаты моделирования
Импортируем результаты моделирования:
cCodeIntegration_library_dynamic_noisy_t = simulationResults_library_dynamic["noisy"].time;
cCodeIntegration_library_dynamic_noisy_y = simulationResults_library_dynamic["noisy"].value;
cCodeIntegration_library_dynamic_filtered_t = simulationResults_library_dynamic["filtered"].time;
cCodeIntegration_library_dynamic_filtered_y = simulationResults_library_dynamic["filtered"].value;
Построим график:
plot(cCodeIntegration_library_dynamic_noisy_t, cCodeIntegration_library_dynamic_noisy_y, label = "Исходный сигнал")
plot!(cCodeIntegration_library_dynamic_filtered_t, cCodeIntegration_library_dynamic_filtered_y, label = "Отфильтрованный сигнал")
plot!(legend = :bottomright)
title!("Альфа-бета фильтр <br> (Интеграция динамической библиотеки)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
Интеграция кода, сгенерированного из модели Simulink
Постановка задачи
Сгенерируем C код из Simulink модели alphabetafilter.slx
и интегрируем его в нашу модель Engee cCodeIntegration_cg_simulink.engee
.
Модель alphabetafilter.slx
, реализующая альфа-бета фильтр, собрана в Simulink из простых блоков и состоит из подсистем determineAlphaBeta
и filterInputSignal
.
Верхний уровень модели:

Подсистема determineAlphaBeta
:

Подсистема filterInputSignal
:

Сгенерируем код с использованием Simulink Embedded Coder:
cgPath = joinpath(demoroot,"cg/simulink")
cgModel = joinpath(demoroot,"cg/simulink/alphabetafilter")
mat"""
model = load_system($cgModel);
path = $cgPath;
set_param(0, 'CacheFolder', path)
set_param(0, 'CodeGenFolder', path)
slbuild(model)
set_param(0, 'CacheFolder', '')
set_param(0, 'CodeGenFolder', '')
"""
Анализ модели
По своей структуре модель cCodeIntegration_cg_simulink.engee
не отличается от рассмотренной ранее cCodeIntegration_source.engee
; отличаются лишь директивы на вкладке Build options
настроек блока Filter
. Проанализируем её содержимое:

Здесь определены следующие опции построения:
Source files
- подключены файлы исходного кодаalphabetafilter.c
иalphabetafilter_data.c
;Include directories
- задан путь до папки, содержащей заголовочные файлы;Headers
- подключены заголовочные файлыalphabetafilter.h
,alphabetafilter_types.h
иrtwtypes.h
.
Циклически исполняемый код приведён на вкладке Output code
редактора исходного кода блока Filter
:

На каждом шаге расчёта модели выполняется:
-
Инициализация структуры
alphabetafilter_U
переменнымиin
,processNoiseVariance
иmeasurementNoiseVariance
(входные сигналы); -
Вызов функции
alphabetafilter_step()
; -
Присваивание переменной
out
(выходной сигнал) результатаx
, сохранённого в структуреalphabetafilter_Y
.
Во вкладке Start code редактора исходного кода блока Filter
выполняется вызов функции alphabetafilter_initialize()
:

Во вкладке Terminate code редактора исходного кода блока Filter
выполняется вызов функции alphabetafilter_terminate()
:

Запуск симуляции
Промоделируем систему:
cCodeIntegration_cg_simulink = engee.load("$demoroot/models/cCodeIntegration_cg_simulink.engee", force = true)
simulationResults_cg_simulink = engee.run(cCodeIntegration_cg_simulink)
Закроем модель:
engee.close(cCodeIntegration_cg_simulink, force = true);
Результаты моделирования
Импортируем результаты моделирования:
cCodeIntegration_cg_simulink_noisy_t = simulationResults_cg_simulink["noisy"].time;
cCodeIntegration_cg_simulink_noisy_y = simulationResults_cg_simulink["noisy"].value;
cCodeIntegration_cg_simulink_filtered_t = simulationResults_cg_simulink["filtered"].time;
cCodeIntegration_cg_simulink_filtered_y = simulationResults_cg_simulink["filtered"].value;
Построим график:
plot(cCodeIntegration_cg_simulink_noisy_t, cCodeIntegration_cg_simulink_noisy_y, label = "Исходный сигнал")
plot!(cCodeIntegration_cg_simulink_filtered_t, cCodeIntegration_cg_simulink_filtered_y, label = "Отфильтрованный сигнал")
plot!(legend = :bottomright)
title!("Альфа-бета фильтр <br> (Интеграция кода, сгенерированного из модели Simulink)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
Интеграция кода, сгенерированного из модели Engee
Постановка задачи
Сгенерируем C код из модели Engee alphabetafilter.engee
, собранной из простых блоков и реализующей альфа-бета фильтр, и интегрируем его в нашу модель Engee cCodeIntegration_cg_engee.engee
.
Верхний уровень модели:
Подсистема determineAlphaBeta
:
Подсистема filterInputSignal
:
Сгенерируем код с использованием генератора кода Engee:
engee.generate_code("$demoroot/cg/engee/alphabetafilter.engee", "$demoroot/cg/engee/alphabetafilter_cg")
Анализ модели
По своей структуре модель cCodeIntegration_cg_engee.engee
не отличается от рассмотренной ранее cCodeIntegration_source.engee
; отличаются лишь директивы на вкладке Build options
настроек блока Filter
. Проанализируем её содержимое:

Здесь определены следующие опции построения:
Source files
- подключен файл исходного кодаalphabetafilter.c
;Include directories
- задан путь до папки, содержащей заголовочные файлы;Headers
- подключен заголовочный файлalphabetafilter.h
.
Циклически исполняемый код приведён на вкладке Output code
редактора исходного кода блока Filter
:

На каждом шаге расчёта модели выполняется:
-
Инициализация структуры
alphabetafilter_U
переменнымиin
,processNoiseVariance
иmeasurementNoiseVariance
(входные сигналы); -
Вызов функции
alphabetafilter_step()
; -
Присваивание переменной
out
(выходной сигнал) результатаx
, сохранённого в структуреalphabetafilter_Y
.
Во вкладке Start code редактора исходного кода блока Filter
выполняется вызов функции alphabetafilter_init()
:
Во вкладке Terminate code редактора исходного кода блока Filter
выполняется вызов функции alphabetafilter_term()
:
Запуск симуляции
Промоделируем систему:
cCodeIntegration_cg_engee = engee.load("$demoroot/models/cCodeIntegration_cg_engee.engee", force = true)
simulationResults_cg_engee = engee.run(cCodeIntegration_cg_engee)
Закроем модель:
engee.close(cCodeIntegration_cg_engee, force = true);
Результаты моделирования
Импортируем результаты моделирования:
cCodeIntegration_cg_engee_noisy_t = simulationResults_cg_engee["noisy"].time;
cCodeIntegration_cg_engee_noisy_y = simulationResults_cg_engee["noisy"].value;
cCodeIntegration_cg_engee_filtered_t = simulationResults_cg_engee["filtered"].time;
cCodeIntegration_cg_engee_filtered_y = simulationResults_cg_engee["filtered"].value;
Построим график:
plot(cCodeIntegration_cg_engee_noisy_t, cCodeIntegration_cg_engee_noisy_y, label = "Исходный сигнал")
plot!(cCodeIntegration_cg_engee_filtered_t, cCodeIntegration_cg_engee_filtered_y, label = "Отфильтрованный сигнал")
plot!(legend = :bottomright)
title!("Альфа-бета фильтр <br> (Интеграция кода, сгенерированного из модели Engee)")
xlabel!("Время, [с]")
ylabel!("Амплитуда")
Выводы
Данный пример демонстрирует особенности использования блока C Function и различные способы интеграции C кода в модели Engee:
- Интеграцию внешнего исходного кода;
- Интеграцию статических и динамических библиотек;
- Интеграцию кода, сгенерированного из модели Simulink;
- Интеграцию кода, сгенерированного из модели Engee.