Документация Engee

Интеграция C кода в модели Engee с использованием блока C Function

В данном примере представлены различные способы интеграции C кода в модели Engee с использованием блока C Function.

Перед началом работы подключим необходимые библиотеки:

using Plots
using MATLAB
plotlyjs();

И определим переменные, которые потребуются для запуска симуляций:

Ts = 0.001;
Q = 1e-3;
R = 1e-4;

processNoisePower = Q * Ts;
measurementNoisePower = R * Ts;

Используем макрос @DIR для того, чтобы узнать папку, в которой лежит интерактивный скрипт:

demoroot = @__DIR__
"/user/start/examples/base_simulation/ccodeintegration"

Основы использования блока C Function

Рассмотрим простые примеры использования блока C Function, иллюстрирующие основы интеграции C кода в модель Engee.

1. Работа с многомерными сигналами, передача параметров и использование директивы #define

В модель cCodeIntegration_basics.engee добавлен блок C Function, на вход которого поступает синусоидальный сигнал in:

basics_1.PNG

Предположим, что мы хотим увеличить амплитуду синусоиды in в gain раз и сдвинуть её на величину bias. То есть выходной сигнал должен определяться по формуле out = gain * in + bias.

Перейдём в настройки блока C Function, нажмём кнопку "Редактировать исходный код" и проанализируем содержимое вкладки Output code редактора исходного кода:

basics_2.PNG

Директива // build defines задаёт идентификатор #define bias, определяющий величину смещения bias. В данном примере смещение равно 5.

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

basics_3.PNG

Стоит отметить, что после передачи переменной в качестве параметра она становится:

  1. Глобальной - к gain можно обратиться из любой вкладки редактора исходного кода;

  2. С квалификатором const - при попытке изменения значения gain будет получена ошибка.

Комментарии, начинающиеся с токена // symbol, предназначены для связывания переменных исходного кода с портами блока C Function:

  1. Скалярная переменная x типа double связывается с первым входным портом in;

  2. Векторная (3 элемента) переменная y типа double связывается с выходным портом out.

В цикле for (для каждого из трёх выходных сигналов) определяется значение переменной y на каждом шаге расчёта модели.

Промоделируем систему:

cCodeIntegration_basics = engee.load("$demoroot/models/cCodeIntegration_basics.engee", force = true)
simulationResults_basics = engee.run(cCodeIntegration_basics)
Dict{String, DataFrames.DataFrame} with 1 entry:
  "out" => 1001×2 DataFrame…

Закроем модель:

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!("Амплитуда")

interactive-scripts/images/base_simulation_cCodeIntegration/676b2c16efdbf9fa6eb53cc51095667293044c9a

2. Использование целочисленных типов данных и статических переменных

Реализуем при помощи блока C Function простой счётчик, выходом которого будет положительное целое число, обозначающее номер текущей итерации:

cCodeIntegration_integers_1.PNG

Проанализируем содержимое вкладки Output code редактора исходного кода блока C Function:

cCodeIntegration_integers_2.PNG

Названия целочисленных типов данных в блоке C Function задаются в соответствии с заголовочным файлом <stdint.h> стандартной библиотеки языка C. Директива // build headers подключает этот файл.

Комментарий, начинающийся с токена // symbol, предназначен для связывания 64-битовой переменной без знака out (тип задаётся как uint64_t, а не long, см. предыдущий абзац про <stdint.h>) исходного кода с выходным портом блока C Function.

Статическая переменная counter инициализируется в 0 на первом шаге симуляции, после чего на каждой итерации её значение увеличивается на 1 и присваивается переменной out.

Промоделируем систему:

cCodeIntegration_integers = engee.load("$demoroot/models/cCodeIntegration_integers.engee", force = true)
simulationResults_integers = engee.run(cCodeIntegration_integers)
Dict{String, DataFrames.DataFrame} with 1 entry:
  "counter" => 1001×2 DataFrame…

Закроем модель:

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!("Номер итерации")

interactive-scripts/images/base_simulation_cCodeIntegration/35d34b0e42ebbfcc8236c084e619277fc059dbdf

Более подробная информация о назначении вкладок редактора исходного кода, директивах блока специальных комментариев и доступных типах данных представлена в документации 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:

source_1.PNG

Данная модель состоит из:

  1. Объекта управления Plant;

  2. Подсистемы, реализующей ПИД-регулятор второго порядка Controller;

  3. Блоков Band-Limited White Noise ProcessNoise и MeasurementNoise, моделирующих внешнее воздействие и шум измерений;

  4. Блока C Function Filter, реализующего альфа-бета фильтр.

Параметр Sample Time блока Filter равен шагу решателя модели Ts:

source_2.PNG

Проанализируем содержимое вкладки Output code редактора исходного кода блока Filter:

source_3.PNG

Директивы:

  • // build source_files подключает файл исходного кода alphabetafilter.c;

  • // build include_directories задаёт путь до папки, содержащей заголовочный файл;

  • // build headers подключает заголовочный файл alphabetafilter.h.

Комментарии, начинающиеся с токена // symbol, связывают переменные исходного кода с входными и выходными портами блока Filter.

На каждом шаге расчёта модели последовательно вызываются функции determineAlphaBeta() и alphabetafilter(), после чего отфильтрованное значение сохраняется в переменную out, соответствующую выходному порту x блока Filter.

Запуск симуляции

Промоделируем систему:

cCodeIntegration_source = engee.load("$demoroot/models/cCodeIntegration_source.engee", force = true)
simulationResults_source = engee.run(cCodeIntegration_source)
Dict{String, DataFrames.DataFrame} with 2 entries:
  "filtered" => 6001×2 DataFrame…
  "noisy"    => 6001×2 DataFrame…

Закроем модель:

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!("Амплитуда")

interactive-scripts/images/base_simulation_cCodeIntegration/8697b4dffe4be19f140bfe5b3951a4b6eb9b1d6e

Интеграция статических и динамических библиотек

Постановка задачи

Зачастую исходный код поставляется не в явном виде, а в составе статических или динамических библиотек. Библиотеки с интерфейсом 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`)
make: Entering directory '/user/start/examples/base_simulation/ccodeintegration/source'
cc -c -fPIC alphabetafilter.c -o alphabetafilter.o
ar rcs alphabetafilter.a alphabetafilter.o
cc -shared alphabetafilter.o -o alphabetafilter.so
make: Leaving directory '/user/start/examples/base_simulation/ccodeintegration/source'
Process(`make -C /user/start/examples/base_simulation/ccodeintegration/source`, ProcessExited(0))

Интегрируем статическую и динамическую библиотеку в нашу модель Engee.

Статическая библиотека: Анализ модели

По своей структуре модель cCodeIntegration_library_static.engee не отличается от рассмотренной ранее cCodeIntegration_source.engee; отличаются лишь директивы блока специальных комментариев блока Filter.

Проанализируем содержимое вкладки Output code редактора исходного кода блока Filter:

static_1.PNG

Директивы:

  • // build include_directories задаёт путь до папки, содержащей заголовочный файл;

  • // build library_directories задаёт путь до папки, содержащей статическую библиотеку;

  • // build headers подключает заголовочный файл alphabetafilter.h;

  • // build libraries подключает статическую библиотеку alphabetafilter.a.

Статическая библиотека: Запуск симуляции

Промоделируем систему:

cCodeIntegration_library_static = engee.load("$demoroot/models/cCodeIntegration_library_static.engee", force = true)
simulationResults_library_static = engee.run(cCodeIntegration_library_static)
Dict{String, DataFrames.DataFrame} with 2 entries:
  "filtered" => 6001×2 DataFrame…
  "noisy"    => 6001×2 DataFrame…

Закроем модель:

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!("Амплитуда")

interactive-scripts/images/base_simulation_cCodeIntegration/83d3813ce9e70e05705893af36ace3d474e15eba

Динамическая библиотека: Анализ модели

По своей структуре модель cCodeIntegration_library_dynamic.engee не отличается от рассмотренной ранее cCodeIntegration_source.engee; отличаются лишь директивы блока специальных комментариев блока Filter.

Проанализируем содержимое вкладки Output code редактора исходного кода блока Filter:

dynamic_1.PNG

Директивы:

  • // build include_directories задаёт путь до папки, содержащей заголовочный файл;

  • // build library_directories задаёт путь до папки, содержащей статическую библиотеку;

  • // build headers подключает заголовочный файл alphabetafilter.h;

  • // build libraries подключает статическую библиотеку alphabetafilter.so.

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

dynamic_2.PNG

Функция 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)
Dict{String, DataFrames.DataFrame} with 2 entries:
  "filtered" => 6001×2 DataFrame…
  "noisy"    => 6001×2 DataFrame…

Закроем модель:

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!("Амплитуда")

interactive-scripts/images/base_simulation_cCodeIntegration/d69827e3f5e3ac19a78d35a25a3a53775ef7971f

Постановка задачи

Сгенерируем C код из Simulink модели alphabetafilter.slx и интегрируем его в нашу модель Engee cCodeIntegration_cg_simulink.engee.

Модель alphabetafilter.slx, реализующая альфа-бета фильтр, собрана в Simulink из простых блоков и состоит из подсистем determineAlphaBeta и filterInputSignal.

Верхний уровень модели:

codegen_simulink_2.png

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

codegen_simulink_3.png

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

codegen_simulink_4.png

Сгенерируем код с использованием 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', '')
"""
>> >> >> >> >> >> >> >> >> ### Starting build procedure for: alphabetafilter
### Generating code and artifacts to 'Model specific' folder structure
### Generating code into build folder: /user/start/examples/base_simulation/ccodeintegration/cg/simulink/alphabetafilter_ert_rtw
### Invoking Target Language Compiler on alphabetafilter.rtw
### Using System Target File: /matlab/rtw/c/ert/ert.tlc
### Loading TLC function libraries
.......
### Generating TLC interface API for custom data
.
### Initial pass through model to cache user defined code
### Caching model source code
...................................
### Writing header file alphabetafilter_types.h
.
### Writing header file alphabetafilter.h
### Writing header file rtwtypes.h
### Writing source file alphabetafilter.c
### Writing header file alphabetafilter_private.h
### Writing source file alphabetafilter_data.c
.
### Writing source file ert_main.c
### TLC code generation complete (took 10.375s).
[Warning: P-code file /matlab/toolbox/coder/simulinkcoder_core/addStandardInfo.p
is older than source code file
/matlab/toolbox/coder/simulinkcoder_core/addStandardInfo.m.
/matlab/toolbox/coder/simulinkcoder_core/addStandardInfo.p may be obsolete and
may need to be regenerated.
Type "help pcode" for information about generating P-code.]
### Saving binary information cache.
### Using toolchain: GNU gcc/g++ | gmake (64-bit Linux)
### Creating '/user/start/examples/base_simulation/ccodeintegration/cg/simulink/alphabetafilter_ert_rtw/alphabetafilter.mk' ...
### Building 'alphabetafilter': "/matlab/bin/glnxa64/gmake"  -f alphabetafilter.mk all
gcc -c -fwrapv -fPIC -O0 -msse2 -DCLASSIC_INTERFACE=0 -DALLOCATIONFCN=0 -DTERMFCN=1 -DONESTEPFCN=1 -DMAT_FILE=0 -DMULTI_INSTANCE_CODE=0 -DINTEGER_CODE=0 -DMT=0  -DTID01EQ=0 -DMODEL=alphabetafilter -DNUMST=1 -DNCSTATES=0 -DHAVESTDIO -DMODEL_HAS_DYNAMICALLY_LOADED_SFCNS=0 -I/user/start/examples/base_simulation/ccodeintegration/cg/simulink -I/user/start/examples/base_simulation/ccodeintegration/cg/simulink/alphabetafilter_ert_rtw -I/matlab/extern/include -I/matlab/simulink/include -I/matlab/rtw/c/src -I/matlab/rtw/c/src/ext_mode/common -I/matlab/rtw/c/ert -o "alphabetafilter.o" "/user/start/examples/base_simulation/ccodeintegration/cg/simulink/alphabetafilter_ert_rtw/alphabetafilter.c"
gcc -c -fwrapv -fPIC -O0 -msse2 -DCLASSIC_INTERFACE=0 -DALLOCATIONFCN=0 -DTERMFCN=1 -DONESTEPFCN=1 -DMAT_FILE=0 -DMULTI_INSTANCE_CODE=0 -DINTEGER_CODE=0 -DMT=0  -DTID01EQ=0 -DMODEL=alphabetafilter -DNUMST=1 -DNCSTATES=0 -DHAVESTDIO -DMODEL_HAS_DYNAMICALLY_LOADED_SFCNS=0 -I/user/start/examples/base_simulation/ccodeintegration/cg/simulink -I/user/start/examples/base_simulation/ccodeintegration/cg/simulink/alphabetafilter_ert_rtw -I/matlab/extern/include -I/matlab/simulink/include -I/matlab/rtw/c/src -I/matlab/rtw/c/src/ext_mode/common -I/matlab/rtw/c/ert -o "alphabetafilter_data.o" "/user/start/examples/base_simulation/ccodeintegration/cg/simulink/alphabetafilter_ert_rtw/alphabetafilter_data.c"
gcc -c -fwrapv -fPIC -O0 -msse2 -DCLASSIC_INTERFACE=0 -DALLOCATIONFCN=0 -DTERMFCN=1 -DONESTEPFCN=1 -DMAT_FILE=0 -DMULTI_INSTANCE_CODE=0 -DINTEGER_CODE=0 -DMT=0  -DTID01EQ=0 -DMODEL=alphabetafilter -DNUMST=1 -DNCSTATES=0 -DHAVESTDIO -DMODEL_HAS_DYNAMICALLY_LOADED_SFCNS=0 -I/user/start/examples/base_simulation/ccodeintegration/cg/simulink -I/user/start/examples/base_simulation/ccodeintegration/cg/simulink/alphabetafilter_ert_rtw -I/matlab/extern/include -I/matlab/simulink/include -I/matlab/rtw/c/src -I/matlab/rtw/c/src/ext_mode/common -I/matlab/rtw/c/ert -o "ert_main.o" "/user/start/examples/base_simulation/ccodeintegration/cg/simulink/alphabetafilter_ert_rtw/ert_main.c"
### Creating standalone executable ../alphabetafilter ...
g++  -o ../alphabetafilter alphabetafilter.o alphabetafilter_data.o ert_main.o
### Created: ../alphabetafilter
### Successfully generated all binary outputs.
gmake: Nothing to be done for `all'.
### Successful completion of build procedure for: alphabetafilter
### Simulink cache artifacts for 'alphabetafilter' were created in '/user/start/examples/base_simulation/ccodeintegration/cg/simulink/alphabetafilter.slxc'.

Build Summary

Top model targets built:

Model            Action                        Rebuild Reason
=================================================================================================
alphabetafilter  Code generated and compiled.  Code generation information file does not exist.

1 of 1 models built (0 models already up to date)
Build duration: 0h 0m 36.011s

Анализ модели

По своей структуре модель cCodeIntegration_cg_simulink.engee не отличается от рассмотренной ранее cCodeIntegration_source.engee; отличается лишь содержимое вкладок редактора исходного исходного кода блока Filter.

Проанализируем содержимое вкладки Output code редактора исходного кода блока Filter:

codegen_simulink_5.PNG

Директивы:

  • // build source_files подключает файлы исходного кода alphabetafilter.c и alphabetafilter_data.c;

  • // build include_directories задаёт путь до папки, содержащей заголовочные файлы;

  • // build headers подключает заголовочные файлы alphabetafilter.h, alphabetafilter_types.h и rtwtypes.h.

На каждом шаге расчёта модели выполняется:

  • Инициализация структуры alphabetafilter_U переменными in, processNoiseVariance и measurementNoiseVariance (входные сигналы);

  • Вызов функции alphabetafilter_step();

  • Присваивание переменной out (выходной сигнал) результата x, сохранённого в структуре alphabetafilter_Y.

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

codegen_simulink_6.png

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

codegen_simulink_7.png

Запуск симуляции

Промоделируем систему:

cCodeIntegration_cg_simulink = engee.load("$demoroot/models/cCodeIntegration_cg_simulink.engee", force = true)
simulationResults_cg_simulink = engee.run(cCodeIntegration_cg_simulink)
Dict{String, DataFrames.DataFrame} with 2 entries:
  "filtered" => 6001×2 DataFrame…
  "noisy"    => 6001×2 DataFrame…

Закроем модель:

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!("Амплитуда")

interactive-scripts/images/base_simulation_cCodeIntegration/0b4e9daa21ccf4330d43efca282c141b09c3fbd7

Интеграция кода, сгенерированного из модели Engee

Постановка задачи

Сгенерируем C код из модели Engee alphabetafilter.engee, собранной из простых блоков и реализующей альфа-бета фильтр, и интегрируем его в нашу модель Engee cCodeIntegration_cg_engee.engee.

Верхний уровень модели:

codegen_engee_2.PNG

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

codegen_engee_3.PNG

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

codegen_engee_4.PNG

Сгенерируем код с использованием генератора кода Engee:

engee.generate_code("$demoroot/cg/engee/alphabetafilter.engee", "$demoroot/cg/engee/alphabetafilter_cg")
"Created directory - /user/start/examples/base_simulation/ccodeintegration/cg/engee/alphabetafilter_cg"

Анализ модели

По своей структуре модель cCodeIntegration_cg_engee.engee не отличается от рассмотренной ранее cCodeIntegration_source.engee; отличается лишь содержимое вкладок редактора исходного исходного кода блока Filter.

Проанализируем содержимое вкладки Output code редактора исходного кода блока Filter:

codegen_engee_5.PNG

Директивы:

  • // build source_files подключает файл исходного кода alphabetafilter.c;

  • // build include_directories задаёт путь до папки, содержащей заголовочные файлы;

  • // build headers подключает заголовочный файл alphabetafilter.h.

На каждом шаге расчёта модели выполняется:

  • Инициализация структуры alphabetafilter_U переменными in, processNoiseVariance и measurementNoiseVariance (входные сигналы);

  • Вызов функции alphabetafilter_step();

  • Присваивание переменной out (выходной сигнал) результата x, сохранённого в структуре alphabetafilter_Y.

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

codegen_engee_6.PNG

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

codegen_engee_7.PNG

Запуск симуляции

Промоделируем систему:

cCodeIntegration_cg_engee = engee.load("$demoroot/models/cCodeIntegration_cg_engee.engee", force = true)
simulationResults_cg_engee = engee.run(cCodeIntegration_cg_engee)
Dict{String, DataFrames.DataFrame} with 2 entries:
  "filtered" => 6001×2 DataFrame…
  "noisy"    => 6001×2 DataFrame…

Закроем модель:

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!("Амплитуда")

interactive-scripts/images/base_simulation_cCodeIntegration/da75669a04edd3b69d87df175d07f1a2c2ac8a0b

Выводы

Данный пример демонстрирует особенности использования блока C Function и различные способы интеграции C кода в модели Engee:

  1. Интеграцию внешнего исходного кода;

  2. Интеграцию статических и динамических библиотек;

  3. Интеграцию кода, сгенерированного из модели Simulink;

  4. Интеграцию кода, сгенерированного из модели Engee.

Блоки, использованные в примере