3. 量产代码生成流程与配置

在Stateflow量产开发中,模型本身只是设计意图的载体,真正交付给ECU的是经过代码生成器翻译、配置、裁剪后的C代码。这一节把“从模型到C代码”的完整链路拆开,重点讲清楚代码生成目标配置、存储类、函数定制和集成接口这四个决定量产代码形态的关键环节。

一、从模型到C代码的完整流程

量产代码生成不是点一下“Build”就结束的动作,而是一条有明确输入输出的流水线。典型链路如下:

  1. 模型检查与一致性确认:确认Stateflow状态机、Simulink子系统、数据字典中的信号类型、采样时间、初始值均已定版,避免生成后再回头改模型。
  2. 代码生成目标配置:在Configuration Parameters中设定系统目标文件(如ert.tlc)、语言标准(C99)、硬件实现、优化选项等。
  3. 存储类与数据接口配置:为每个信号、参数、状态分配存储类,决定它们在C代码中是全局变量、结构体成员、还是通过函数接口访问。
  4. 函数定制:设定Step函数、初始化函数、终止函数的命名、参数、复用方式,以及是否生成独立的子函数。
  5. 生成与集成:生成.c/.h文件,通过集成接口与手写代码、AUTOSAR RTE或底层驱动对接。

这条链路中,前两步决定“生成什么形态的代码”,后两步决定“代码如何被外部世界调用”。下面这张图展示了从模型到集成代码的数据流与配置作用点:

Stateflow模型 状态机 / 数据字典 采样时间 / 初始值 代码生成目标配置 系统目标文件 ert.tlc 语言标准 C99 / 优化选项 硬件实现 / 浮点支持 存储类配置 ExportedGlobal / ImportedExtern Struct / GetSet / Volatile 参数与信号分别定义 函数定制 Step / Init / Term 命名 参数传递方式 子函数复用与内联 生成C代码 model.c / model.h rtwtypes.h / 接口头文件 静态/全局变量定义 集成接口 手写代码调用 / AUTOSAR RTE / 底层驱动对接 输入输出端口映射 / 周期调度 配置作用于生成 存储类决定变量形态

二、代码生成目标配置

代码生成目标配置的核心是选择正确的系统目标文件。量产项目通常使用ert.tlc(Embedded Coder),而不是grt.tlc。两者的关键差异在于:ert.tlc生成的是面向嵌入式环境的紧凑代码,支持静态内存分配、可重入函数、代码裁剪;grt.tlc更适合快速原型验证。

在Configuration Parameters中,需要重点确认以下配置项:

配置项量产推荐值影响
System target fileert.tlc决定代码生成器后端
LanguageC生成C而非C++
Language standardC99 (ISO)支持混合声明与代码
Code replacement libraryGNU C99 extensions 或项目指定用目标编译器优化指令替换标准运算
Generate reusable codeOn多实例复用同一套函数
Pass root-level I/O asStructure reference减少参数传递开销
MAT-file loggingOff去除量产不需要的日志代码

一个容易被忽略的点是“硬件实现”配置。如果目标MCU是32位且支持硬件浮点,应在Hardware Implementation中把Device vendor设为对应厂商,Signed integer division rounds to设为Zero或Floor,这会影响除法运算的代码生成方式。若配置错误,生成的除法代码可能引入额外的运行时库调用,增加代码体积和执行时间。

三、存储类:决定变量在C代码中的形态

存储类是量产代码生成中最关键的配置之一。它决定了模型中的信号、参数、状态在生成的C代码中如何被声明和访问。常用的存储类包括:

  • Auto:由代码生成器自动决定,通常优化为局部变量或内联,适合中间计算量。
  • ExportedGlobal:生成全局变量定义,可在.c文件中被外部直接访问,适合需要被其他模块读取的标定量。
  • ImportedExtern:只生成extern声明,变量定义在外部手写代码中,适合底层驱动提供的传感器原始值。
  • Struct:将多个信号打包为结构体成员,适合按功能域组织接口。
  • GetSet:生成Get/Set访问函数,适合需要封装保护或跨核通信的变量。
  • Volatile:配合中断或DMA访问的变量,防止编译器优化掉必要的读写。

举例来说,假设Stateflow中有一个输入信号VehicleSpeed,来自CAN接收模块。如果把它配置为ImportedExtern,生成的model.h中会出现:

extern real32_T VehicleSpeed;

而如果配置为ExportedGlobal,则会在model.c中生成定义,并在model.h中生成extern声明。选择哪种存储类,取决于该信号的所有权归属:由模型内部产生的输出用ExportedGlobal,由外部提供的输入用ImportedExtern。

四、函数定制与集成接口

函数定制决定了生成代码的调用形态。默认情况下,Embedded Coder会生成model_step()、model_initialize()、model_terminate()三个入口函数。量产项目中通常需要做以下定制:

  1. 函数命名:将model替换为项目约定的模块名,如VCU_Control_step(),便于集成时识别。
  2. Step函数参数:选择“void”或“结构体指针”。若选择结构体指针,需要在集成时传入RT_MODEL或自定义的I/O结构体。
  3. 函数复用:若同一模型被多个实例调用,开启“Generate reusable code”后,Step函数会接受实例指针,实现多实例共享代码段。
  4. 子函数生成:对于Stateflow中的并行状态或大型子状态机,可配置为生成独立子函数,便于调试和代码审查。

集成接口方面,量产项目通常有三种对接方式:

  • 直接调用:手写调度器按周期调用model_step(),输入输出通过全局变量或结构体传递。
  • AUTOSAR RTE:通过AUTOSAR目标配置生成RTE接口,模型作为SWC被RTE调度,端口映射到Rte_Write/Rte_Read。
  • 函数接口:将模型封装为void Model_Step(const Inputs *in, Outputs *out)形式,由集成层负责数据搬运。
在实际项目中,建议在代码生成前先完成一次“接口冻结”:确认所有输入输出信号的存储类、数据类型、初始值、单位均已定版,并记录在数据字典中。接口一旦冻结,后续模型修改若涉及接口变化,必须走变更评审,否则生成代码与集成代码之间会出现静默的不一致。

最后需要强调的是,代码生成配置不是一次性的工作。每次模型接口变更、目标硬件更换或编译器升级,都需要重新检查系统目标文件、存储类和函数定制配置。建议将Configuration Parameters导出为.mat或脚本文件,纳入版本管理,确保每次生成使用的配置可追溯、可复现。