使用 C 功能块将 C 代码集成到 Engee 模型中
本例展示了使用C 功能块将 C 代码集成到恩吉模型中的不同方法。
在开始之前,我们先连接必要的库:
using Plots
using MATLAB
plotlyjs();
使用宏@__DIR__
查找交互式脚本所在的文件夹:
demoroot = @__DIR__
使用 C 功能块的基础知识
让我们来看一些使用 C 功能块的简单示例,以说明将 C 代码集成到 **Engee ** 模型中的基础知识。
1.处理多维信号、传递参数和使用 #define 指令
在模型cCodeIntegration_basics.engee
中,我们添加了C 函数块,输入正弦信号in
:
假设我们要将正弦波in
的振幅增加一个系数gain
,并将其移位一个值bias
。也就是说,输出信号应由公式out = gain * in + bias
确定。
让我们进入C 功能块的设置,在Ports
标签中,我们将熟悉块输入和输出的定义。在这里,源代码变量与 **C 功能块的端口相连:
1.将double
类型的标量变量x
与第一个输入端口in
绑定;
2.类型为double
的矢量(3 个元素)变量y
与第一个输出端口out
绑定。

gain
数组也在 C 功能块(Parameters
标签)的设置中定义:

值得注意的是,变量一旦作为参数传递,就会变成
1.全局变量 -gain
可以从源代码编辑器的任何选项卡中访问;
2.带有限定符const
- 尝试更改gain
的值时会出现错误。
偏移值bias
由标识符#define bias
决定,该标识符在选项卡Build options
中设置:

在本例中,偏移量为5
。
让我们进入 C 功能块设置的Main
,点击 "编辑源代码 "按钮。在打开的源代码编辑器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 函数块实现了一个简单的计数器,其输出是一个表示当前迭代次数的正整数:
让我们分析一下 C 功能块设置中Ports
和Build Options
选项卡的内容:


C 语言标准库的头文件<stdint.h>
定义了C 函数块中整数数据类型的名称。该文件连接在Headers
行的选项卡Build options
中。
选项卡Ports
将源代码中的一个不带符号的 64 位变量out
(类型设置为uint64_t
,而不是long
,参见前一段关于<stdint.h>
的内容)绑定到 C 功能块的第一个输出端口。
在 C 功能块源代码编辑器的Output code
选项卡中,包含了仿真过程中每一步执行的代码:

在模拟的第一步,静态变量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
文件提供了[alpha-beta 滤波器]的 C 语言实现(https://en.wikipedia.org/wiki/Alpha_beta_filter)。
文件alphabetafilter.c
:
#include "alphabetafilter.h"
#include <math.h>
double dt = 0.01;
static double xk_1, vk_1;
参数 determineAlphaBeta(double processNoiseVariance, double measurementNoiseVariance)
{
双 lambda, r;
参数 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);
返回 p;
}
double alphaBetaFilter(double value, Parameters p)
{
double xk, vk, rk;
xk = xk_1 + (vk_1 * dt);
vk = vk_1;
rk = 值 - xk;
xk += p.alpha * rk;
vk += (p.beta * rk) / dt;
xk_1 = xk;
vk_1 = vk;
return xk;
}
void initialiseStaticVariables()
{
xk_1 = 0;
vk_1 = 0;
}
文件alphabetafilter.h
的内容:
typedef struct {
double alpha;
double beta;
} 参数;
参数 determineAlphaBeta(double, double);
double alphaBetaFilter(double, Parameters);
void initialiseStaticVariables();
将这些源代码整合到我们的 Engee 模型中。
分析模型
模型cCodeIntegration_source.engee
:
该模型包括
控制对象Plant
;
2.实现二阶 PID 控制器的子系统Controller
;
3. 限带白噪声块ProcessNoise
和MeasurementNoise
,模拟外部影响和测量噪声;
4.块 C 功能Filter
,实现阿尔法-贝塔滤波器。
程序块Filter
的参数Sample Time
等于模型求解器Ts
的步长:

让我们来分析一下Filter
区块设置中Build options
标签的内容:

在该选项卡的行中:
*Source files
- 连接的源代码文件alphabetafilter.c
;
*Iinclude directories
- 设置包含头文件的文件夹路径;
*Headers
- 连接的头文件alphabetafilter.h
。
在程序块设置的Ports
标签中,源代码变量与程序块的输入和输出端口相连Filter
。
在Filter
程序块的源代码中,OutputCode
标签包含循环可执行代码:

在模型计算的每一步中,函数determineAlphaBeta()
和alphabetafilter()
被依次调用,之后滤波值被存储在变量out
中,与模块Filter
的输出端口x
相对应。
模拟开始
让我们模拟系统:
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模型中!
让我们使用MakeFile
从文件alphabetafilter.c
和alphabetafilter.h
编译一个静态 (alphabetafilter.a
) 和动态 (alphabetafilter.so
) 库。
文件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 $@
清除:
rm -f *.o *.a *.so
让我们开始编译库:
run(`make -C $demoroot/source`)
将静态库和动态库整合到我们的 Engee 模型中。
静态库:分析模型
从结构上看,模型cCodeIntegration_library_static.engee
与之前考虑的cCodeIntegration_source.engee
没有区别;只有Filter
块设置的Build options
选项卡上的指令不同。让我们来分析一下它的内容:

这里定义了以下结构选项:
*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
没有区别;只有Filter
块设置的Build options
选项卡上的指令不同。让我们来分析一下它的内容:

这里定义了以下结构选项:
*Include directories
- 指定包含头文件的文件夹路径;
*Library directories
- 设置包含动态链接库的文件夹路径;
*Headers
- 连接头文件alphabetafilter.h
;
*Libraries
- 连接的动态链接库alphabetafilter.so
。
在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 模型生成的代码
问题陈述
让我们从 Simulink 模型alphabetafilter.slx
生成 C 代码,并将其集成到我们的 Engee 模型cCodeIntegration_cg_simulink.engee
中。
实现阿尔法-贝塔滤波器的模型alphabetafilter.slx
是在Simulink中由简单模块构建的,由子系统determineAlphaBeta
和filterInputSignal
组成。
模型的顶层:

子系统determineAlphaBeta
:

子系统filterInputSignal
:

让我们使用Simulink 嵌入式编码器生成代码:
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
并无不同;只有Filter
块设置的Build options
选项卡上的指令不同。让我们来分析一下它的内容:

这里定义了以下结构选项:
*Source files
- 连接源代码文件alphabetafilter.c
和alphabetafilter_data.c
;
*Include directories
- 设置包含头文件的文件夹路径;
*Headers
- 连接头文件alphabetafilter.h
、alphabetafilter_types.h
和rtwtypes.h
。
可循环执行的代码位于Filter
块源代码编辑器的Output code
选项卡上:

模型的每一步都要进行计算:
-
使用变量
in
、processNoiseVariance
和measurementNoiseVariance
(输入信号)对结构alphabetafilter_U
进行初始化; -
调用函数
alphabetafilter_step()
; -
将存储在结构
alphabetafilter_Y
中的结果x
(输出信号)赋值给变量out
。
在Filter
块源代码编辑器的开始代码选项卡中,函数alphabetafilter_initialize()
被调用:

在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 模型生成的代码
任务声明
让我们从模型 Engeealphabetafilter.engee
生成 C 代码,该代码由简单的块构建,实现了阿尔法-贝塔滤波器,并将其集成到我们的模型 EngeecCodeIntegration_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
没有区别;只有Filter
块设置的Build options
选项卡上的指令不同。让我们来分析一下它的内容:

这里定义了以下结构选项:
*Source files
- 连接源代码文件alphabetafilter.c
;
*Include directories
- 设置包含头文件的文件夹路径;
*Headers
- 连接的头文件alphabetafilter.h
。
可循环执行的代码位于Filter
块源代码编辑器的Output code
选项卡上:

模型的每一步都要进行计算:
-
使用变量
in
、processNoiseVariance
和measurementNoiseVariance
(输入信号)对结构alphabetafilter_U
进行初始化; -
调用函数
alphabetafilter_step()
; -
将存储在结构
alphabetafilter_Y
中的结果x
(输出信号)赋值给变量out
。
在Filter
块源代码编辑器的开始代码选项卡中,函数alphabetafilter_init()
被调用:
在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 功能块的功能,以及将 C 代码集成到Engee模型中的不同方法:
- 集成外部源代码; 2;
- 集成静态和动态库; 3;
- 集成从Simulink模型生成的代码;
- 整合从Engee模型生成的代码。