4、模型搭建与部署:从Simulink模型到实时C代码、代码生成工具链、部署到实时目标机

好,咱们这一章聊点硬核的。模型搭好了,仿真跑通了,然后呢?

然后就得把它变成能在硬件上跑的代码。这一步,我称之为“从图纸到实物”的关键一跃。很多人在算法开发上花了大把时间,结果卡在代码生成和部署上,那可就太冤了。

4.1 为什么不能手写代码?

你可能会问:“我C语言写得溜,干嘛非要用自动代码生成?”

嗯,我刚开始做项目时也这么想。有一次,我手动移植了一个卡尔曼滤波算法,花了整整三天。结果一跑硬件在环测试,发现有个数据类型定义错了,导致整个状态估计偏差了10%。

从那以后,我学乖了。自动代码生成的好处,说白了就三点:

  • 一致性:模型里怎么算,代码就怎么算。不会出现“模型里是浮点,代码里写成整型”这种低级错误。
  • 效率:你想想看,一个复杂的SOC算法,状态机加查表加滤波,手写要多久?自动生成,点一下按钮,几分钟搞定。
  • 可追溯:生成的代码和模型一一对应。出了问题,能快速定位到模型里的哪个模块。

核心原则:模型是“源”,代码是“影”。永远只修改模型,不要直接改生成的代码。

4.2 代码生成工具链:你需要准备什么?

工具链这事儿,其实没那么玄乎。我习惯把它分成三块:

环节 工具/组件 我的经验
模型准备 Simulink + Stateflow 确保模型是“可配置”的,别写死参数
代码生成 Embedded Coder 这是核心,别用普通的Simulink Coder
编译部署 目标机编译器 + 下载工具 比如TI的CCS,或者NI的VeriStand

我个人习惯,在模型里就把数据类型、采样时间、存储类都定义好。这样生成代码时,几乎不用额外配置。

4.3 从模型到代码:三步走

这一步,我把它拆成三个动作。每一步都有坑,我一个个说。

4.3.1 第一步:模型配置

打开你的Simulink模型,按 Ctrl+E 进入配置参数。这里有几个关键项:

  • Solver:选固定步长,离散求解器。别用变步长,那是给仿真用的。
  • Hardware Implementation:设置目标芯片的字长、字节序。比如ARM Cortex-M4,就选“ARM Compatible”。
  • Code Generation:系统目标文件选 ert.tlc(Embedded Real-Time)。

注意:我曾经因为忘了改“Hardware Implementation”里的字节序,导致生成的结构体在目标机上解析错误。排查了整整一个下午。嗯,这种低级错误,犯一次就够了。

4.3.2 第二步:生成代码

配置好了,点一下 Ctrl+B。你会看到Simulink开始“编译”模型,然后生成一堆 .c.h 文件。

生成的代码结构大概是这样的:

model.c       // 主算法逻辑
model.h       // 接口声明
model_private.h // 内部变量
ert_main.c    // 主函数(通常需要自己重写)

这里有个小技巧:我一般会生成一个“代码报告”,里面会列出所有生成的函数和变量。方便后续集成时查阅。

4.3.3 第三步:集成与编译

生成的代码,不能直接扔到目标机上跑。你得把它和你的硬件驱动、操作系统(如果有)集成在一起。

我通常的做法是:

  1. 把生成的 .c/.h 文件复制到我的工程目录。
  2. 写一个“包装函数”,把模型里的 step() 函数放到定时中断里调用。
  3. 用目标机的编译器(比如GCC或者CCS)编译整个工程。

避坑指南:我曾经直接把 ert_main.c 拿过来用,结果发现它默认用了 printf 打印数据。我的目标机根本没有串口驱动,一运行就卡死。所以,主函数一定要自己写,别偷懒。

4.4 部署到实时目标机

代码编译通过了,接下来就是部署。这一步,说白了就是把可执行文件下载到目标机上,然后让它跑起来。

不同的目标机,部署方式不一样。我拿两种常见的来说:

4.4.1 基于NI PXI/VeriStand的部署

如果你用的是NI的硬件,流程会简单很多:

  • 生成代码后,用NI的 VeriStand Model Framework 打包成 .dll
  • 在VeriStand里导入这个 .dll,配置好I/O映射。
  • 点击“Deploy”,代码就下载到PXI的实时控制器上了。

我做过一个项目,用PXI跑电池模型在环测试。从生成代码到部署运行,前后不到半小时。嗯,这种“一键部署”的感觉,确实爽。

4.4.2 基于TI DSP的部署

如果是嵌入式芯片,比如TI的C2000系列,步骤会多一些:

  • 用Embedded Coder生成针对C2000的优化代码。
  • 在CCS(Code Composer Studio)里导入工程。
  • 编译、链接,生成 .out 文件。
  • 通过仿真器(比如XDS100)下载到DSP的Flash或RAM里。

关键点:部署到Flash和部署到RAM,启动时间和运行速度都不一样。调试阶段我建议先下载到RAM,跑通了再固化到Flash。

4.5 验证:代码跑起来之后

代码部署上去,不代表万事大吉。你还需要验证:

  • 时序:模型的一个步长,在目标机上实际跑了多久?有没有超时?
  • 数值:目标机上算出来的结果,和Simulink仿真结果是否一致?
  • 稳定性:连续跑几个小时,会不会有内存泄漏或者堆栈溢出?

我习惯在代码里加一些“监视点”,把关键变量的值通过串口或者DAC输出出来。然后和Simulink里的仿真数据做对比。误差在1%以内,就算通过。

好了,这一章的内容就这些。从模型到代码,再到部署,每一步都有它的门道。你想想看,如果这些坑都提前避开了,后面的硬件在环测试是不是就顺畅多了?