3. Embedded Coder快速上手

前两节我们已经理解了Embedded Coder在Simulink代码生成工具链中的定位。本节不再停留在概念层面,而是完整走通一次“从模型到可部署C代码”的最小闭环:创建一个足够简单但结构完整的模型,配置代码生成目标,生成C代码,并检查生成的文件结构、入口函数与数据接口。走完这一遍,你会对“模型里的什么设置,最终变成了C代码里的什么”建立起直观对应。

一、先明确本节要交付的“可部署模型”长什么样

所谓“可部署”,并不是指模型有多复杂,而是指它生成的代码具备三个特征:有明确的周期入口函数、有清晰的数据接口、不依赖Simulink运行时环境。为了聚焦这条主线,我们构造一个教学示例:一个带增益和饱和限幅的信号处理模块,输入为 In1,输出为 Out1,内部对输入乘以增益 K = 2.5,再限制在 [-100, 100] 区间内。这个模型小到可以一眼看懂,又包含了输入、参数、运算、限幅和输出,足以体现代码生成的关键环节。

在Simulink中新建模型后,放入以下模块并连线:Inport(命名为 In1)→ Gain(增益设为 2.5)→ Saturation(上限 100,下限 -100)→ Outport(命名为 Out1)。将求解器设置为固定步长,步长设为 0.01 秒,因为嵌入式目标通常运行在确定的周期节拍上,变步长求解器不适合直接部署。这一步看似简单,却决定了后续代码是否具备周期可调度性。

二、配置代码生成目标:从“仿真模型”切换到“嵌入式目标”

模型搭好后,按 Ctrl+E 打开配置参数对话框,重点修改以下几项。第一,在 Solver 页把 Type 设为 Fixed-step,Solver 选 discrete (no continuous states),Fixed-step size 填 0.01。第二,在 Code Generation 页把 System target file 选为 ert.tlc,这是Embedded Coder提供的嵌入式目标,生成的代码精简、无操作系统依赖。第三,在 Code Generation → Interface 中把 Code interface packaging 设为 Nonreusable function,这样每个模型生成独立的入口函数,便于第一次上手时观察结构。第四,在 Code Generation → Report 中勾选 Create code generation report,生成后可以直接在浏览器里查看文件与函数索引。

这里有一个容易忽略但很关键的设置:Code Generation → Optimization 中的 Default parameter behavior。若设为 Inlined,增益 2.5 会直接以字面量形式写进C代码;若设为 Tunable,则会生成可调参数变量。第一次上手建议先用 Inlined,让代码更直观;等理解了参数接口后再切换为 Tunable,体会标定接口的生成方式。

从模型到可部署C代码的映射关系 Simulink 模型层 Inport: In1 double, 标量输入 Gain: K = 2.5 Inlined 参数 Saturation 上限 100 / 下限 -100 Outport: Out1 double, 标量输出 求解器: 固定步长 步长 = 0.01 s discrete (no continuous states) 代码生成配置 System target file ert.tlc Interface packaging Nonreusable function Parameter behavior Inlined Code generation report 已勾选 生成语言: C 目标: 嵌入式 无OS依赖 生成的C代码层 model.h 数据结构与函数声明 model.c step 函数与算法实现 model_data.c 参数与常量定义 model_types.h 类型定义 入口函数 model_step() 周期调用 模型结构决定代码结构,配置选项决定接口形态与优化方式

三、生成C代码并检查文件结构

配置完成后,按 Ctrl+B 启动代码生成。若模型没有错误,Simulink会在当前工作目录下创建一个以模型名命名的文件夹,例如模型名为 first_deploy,则生成 first_deploy_ert_rtw 目录。打开该目录,你会看到一组分工明确的文件:first_deploy.c 和 first_deploy.h 是核心算法与接口文件;first_deploy_data.c 存放参数与常量;first_deploy_types.h 定义数据类型;ert_main.c 是一个示例主程序,展示如何以固定周期调用入口函数;此外还有 rtwtypes.h、rtmodel.h 等支撑文件。如果勾选了代码生成报告,还会生成 html 子目录,里面是可点击索引的网页报告。

检查文件结构时,重点看三件事。第一,first_deploy.h 中是否声明了 extern void first_deploy_step(void);,这是模型的周期入口函数。第二,first_deploy.h 中是否定义了输入输出结构体,例如 ExtU_first_deploy_T 和 ExtY_first_deploy_T,它们分别承载 In1 和 Out1。第三,first_deploy_data.c 中是否出现了增益参数。由于我们选择了 Inlined,增益 2.5 不会作为独立变量出现,而是直接写在 first_deploy.c 的运算语句里。如果你把参数行为改为 Tunable 再生成一次,就会在 _data.c 中看到对应的可调参数定义,这正是参数接口随配置变化的直接体现。

四、读懂入口函数与数据接口

打开 first_deploy.c,找到 first_deploy_step 函数。它的典型结构如下(示例代码,实际名称随模型名变化):

void first_deploy_step(void)
{
  real_T rtb_Saturation;

  rtb_Saturation = 2.5 * first_deploy_U.In1;
  if (rtb_Saturation > 100.0) {
    rtb_Saturation = 100.0;
  } else if (rtb_Saturation < -100.0) {
    rtb_Saturation = -100.0;
  }

  first_deploy_Y.Out1 = rtb_Saturation;
}

这段代码把模型里的三个环节一一对应了出来:2.5 * first_deploy_U.In1 对应增益,两个 if 分支对应饱和限幅,最后的赋值对应输出端口。输入通过 first_deploy_U.In1 读取,输出通过 first_deploy_Y.Out1 写出,二者都是结构体成员,这就是Embedded Coder默认的数据接口形式。理解这一点后,你就能明白:外部系统只需要在调用 first_deploy_step 之前把输入写入 first_deploy_U,调用之后从 first_deploy_Y 读取输出,整个模型就完成了与外部代码的对接。

再看 ert_main.c,它给出了一个最小可运行框架:初始化模型、进入 while 循环、按固定周期调用 first_deploy_step。在真实嵌入式工程中,这个循环通常由定时器中断或RTOS任务替代,但调用关系完全一致。至此,你已经完成了从模型到可部署代码的第一次完整穿越。

第一次上手时,建议把模型名、增益值、限幅上下限都记录下来,生成后逐项在C代码中定位。这种“模型元素—代码元素”的对照练习,比反复看文档更能建立工程直觉。
若生成时报错“Model reference targets not supported”或“Cannot generate code for a model with continuous states”,优先检查求解器是否为固定步长、模型中是否含有连续状态模块。嵌入式目标通常要求离散化模型。