4. 数据对象与存储类设计
在Embedded Coder的代码生成流程中,模型里的信号和参数最终都要落到C代码里的变量、常量、结构体或宏上。决定“落成什么形态”的关键,不是模块本身,而是数据对象及其存储类(Storage Class)。如果只把模型搭好就直接生成代码,往往会得到一堆自动命名的局部变量,既无法标定,也无法与底层驱动、AUTOSAR接口或既有工程代码对接。因此,本节的核心任务是:用Simulink.Parameter、Simulink.Signal和总线对象把数据“显式化”,再通过存储类控制它们生成成什么样的C对象。
三类数据对象的分工
Simulink默认的数据管理是“隐式”的:参数值写在模块对话框里,信号线只有名字没有属性。工程化管理的做法是把它们提升为工作区中的对象,让代码生成器能读到类型、初值、存储类等元信息。
- Simulink.Parameter:用于标定量和可调参数。它承载数值、数据类型、最小值/最大值、单位,以及最关键的
StorageClass。例如把增益模块的Gain从字面量2.5改为参数对象Kp,后续就能生成可标定的全局变量。 - Simulink.Signal:用于信号、状态和测量量。它描述信号的数据类型、维度、采样时间、初始值以及存储类。把关键信号解析为
Simulink.Signal后,才能让它生成具名全局变量,供测量或跨模块访问。 - 总线对象(Simulink.Bus / BusElement):用于把一组相关信号组织成结构体。总线对象定义元素名、数据类型、维度、采样时间,代码生成时对应C结构体类型,使接口清晰、可复用。
三者的关系可以这样理解:总线对象定义“结构体类型”,Simulink.Signal定义“结构体变量或普通信号变量”,Simulink.Parameter定义“标定/参数变量”。它们共同把模型数据从“图形连线”翻译成“工程可管理的C数据”。
存储类:控制变量生成方式的总开关
存储类决定一个数据对象在生成代码中的存在形式。常用的几类及其典型用途如下:
| 存储类 | 生成形态 | 典型用途 |
|---|---|---|
| Auto | 由代码生成器决定(常为局部变量或内联) | 纯内部中间量,无需外部访问 |
| ExportedGlobal | 全局变量,带extern声明 | 需要被其他C文件或测量工具访问的信号 |
| ImportedExtern | 引用外部已定义的全局变量 | 与手写代码、驱动共享的变量 |
| Const / Volatile | const或volatile修饰的变量 | 只读标定表、被中断或硬件修改的变量 |
| Define | 宏定义(#define) | 编译期常量,如数组长度、固定阈值 |
| Struct | 结构体成员 | 把多个标定量打包成标定结构体 |
选择存储类时先问三个问题:这个数据要不要在模型外部被读写?要不要在运行时被标定?要不要被测量或观测?答案决定了它是全局、结构体成员还是宏。需要特别注意的是,ExportedGlobal会引入全局命名空间,命名必须遵循工程规范,避免与手写代码冲突。
配置存储类的实际操作
以把增益参数Kp做成可标定全局变量为例,操作路径是:在模型资源管理器中新建Simulink.Parameter对象Kp,设置Value=2.5、DataType='single'、Min=0、Max=10,然后把StorageClass设为ExportedGlobal。将增益模块的增益值改为Kp后,生成代码中会出现类似:
/* Exported global parameter */
single Kp = 2.5F;
/* 在Step函数中使用 */
Y = Kp * U;
若把StorageClass改为Define,则生成#define Kp (2.5F),此时它不再是可标定量,而是编译期常量。可见存储类直接改变了变量的工程语义。
对于信号,把关键输出信号y_out解析为Simulink.Signal,设置StorageClass='ExportedGlobal'、DataType='single',生成代码中就会出现全局变量y_out,可在测量工具或其它模块中按名访问。若希望它只在本文件可见,可选用FileScope类存储类。
总线对象与结构体接口
当接口信号较多时,逐个定义全局变量会让接口散乱。此时用总线对象把一组信号打包。例如定义总线CtrlBus,包含元素ref、fb、err,类型均为single。代码生成后得到:
typedef struct {
real32_T ref;
real32_T fb;
real32_T err;
} CtrlBus;
CtrlBus CtrlBus_DWork; /* 若信号存储类为ExportedGlobal */
总线对象的价值在于:类型定义集中、接口稳定、便于跨模型复用。修改总线元素时,所有引用该总线的模型和代码会同步更新,减少手工维护结构体的错误。
标定与测量数据的工程化管理
标定量和测量量的管理目标不同。标定量要求“可改、可追溯、有范围保护”,因此通常使用Simulink.Parameter配合ExportedGlobal或Struct存储类,并设置Min/Max,让代码生成器在需要时插入范围检查。测量量要求“可观测、命名稳定”,因此使用Simulink.Signal配合ExportedGlobal或Volatile,保证变量不会被优化掉。
Struct存储类打包成标定结构体,例如Cal_Param,成员为Kp、Ki、Kd。这样在标定工具中只需导入一个结构体,接口也更整洁。下面这张图展示了从模型数据对象到生成C代码的映射关系,以及存储类在其中的控制作用。
工程化管理的检查要点
完成数据对象和存储类配置后,建议按以下要点自查,避免生成代码与预期不符:
- 所有需要外部访问的标定量和测量量,是否都已解析为
Simulink.Parameter或Simulink.Signal,而不是停留在字面量或自动命名信号? - 存储类是否与用途匹配?可标定量不要误设为
Define,否则无法在线修改;纯内部量不要滥用ExportedGlobal,否则污染全局命名空间。 - 标定量的
Min/Max是否设置?这关系到代码生成器能否插入范围保护,也影响标定工具的安全边界。 - 总线对象的元素类型、维度、采样时间是否与模型实际信号一致?类型不匹配会导致生成代码编译错误或隐式转换。
- 命名是否符合工程规范?全局变量建议加模块前缀,如
Ctrl_Kp,降低与手写代码冲突的概率。
把数据对象和存储类管理好,代码生成就从“能跑通”升级为“可标定、可测量、可集成”。这也是Embedded Coder工程化落地中最基础、却最容易被忽视的一环。