本文最后更新于 2026年7月7日。
在Simulink模型中,如果你已经给信号线绑定了 Simulink.Signal 对象(或者通过数据字典、存储类配置了信号线),通常没有必要仅仅为了在C代码中生成结构体,而把所有变量都刻意通过 Outport 导出。
是否需要使用 Outport 导出结构体,主要取决于你的代码架构设计、数据管理需求以及控制器的集成方式。
为什么不需要强求 Outport 导出?
当你给信号线绑定了 Signal 对象并配置了 Storage Class(存储类)(例如 ExportedGlobal、Structured 等)时,Simulink 已经知道如何在外层生成和管理这个变量了。
-
使用
Structured存储类直接生成结构体:如果你希望变量在C代码中以结构体的形式呈现(例如
MyStruct.engine_speed),你完全不需要走 Outport。你只需要在基础工作空间或数据字典(Data Dictionary)中定义一个Simulink.Bus对象,然后将你的Simulink.Signal对象的类型设为该 Bus,存储类设为Structured。生成的C代码自然会把这些信号组合进一个结构体中。 -
Outport 的本质:
在 Simulink 自动生成的C代码中,顶层的 Outport 默认会被打包进一个名为
<Model>_Y(外部输出)的全局结构体中。如果你强行把所有内部变量都连到 Outport,只会让这个默认的_Y结构体变得庞大臃肿,反而失去了模块化和按功能分类的灵活性。
什么时候建议使用 Outport 导出结构体?
虽然不是必须,但在以下特定场景下,通过 Outport 导出(通常是配合 Bus Creator 组成结构体数据)是更好的工程实践:
1. 标准化模型的底层接口(API 协议)
如果你的模型是交给其他团队集成,或者需要严格匹配底层控制器(如 Autosar 或特定的基底层软件 BSW)的接口。使用 Outport 配合 Bus 可以直接生成符合接口规范的输入输出结构体,应用层只管计算,底层只管读写这个结构体。
2. 实现“高内聚、低耦合”的模块化
如果你的模型很大,包含多个子系统(算法、诊断、状态机等)。通过 Outport 将一个子系统的信号打包成 Bus 传给另一个子系统,在模型层面连线会非常整洁,生成的C代码也会具有清晰的层级调用关系(结构体传参)。
3. 控制器多速率/多任务调度
如果你的控制器程序需要按照不同的周期(如 10ms, 100ms)调用不同的模型片段,通过标准的 Outport 导出数据,可以让手写代码(Wrapper 调度层)非常清晰地在合适的时间点读取这部分结构体数据。
避坑与最佳实践建议
| 做法 | 优点 | 缺点 / 建议 |
|---|---|---|
方案 A:利用 Simulink.Signal + 存储类 (推荐用于内部/观测量) |
模型界面干净,无需拉很多长线到 Outport;随时可以通过 A2L/ASAP2 导出用于 Calibrator/Inca 标定和观测。 | 如果单纯用 ExportedGlobal,变量会离散地存在于全局,不便于打包管理。建议配合 Simulink.Bus 使用 Structured 存储类。 |
| 方案 B:全部连到 Outport 导出 (推荐用于核心控制输出) | 接口显式可见,生成代码的入参、出参结构极其清晰,方便手写代码直接调用整个结构体。 | 连线繁琐,模型容易变成“蜘蛛网”;如果只是为了观测变量,这样做会浪费控制器的内存和拷贝性能。 |
💡 结论
-
如果这些变量是算法的核心输出(如给到驱动层控制电机的占空比、给到执行器的目标压力):支持使用 Outport 导出,并用 Bus 组织成结构体,方便底层调用。
-
如果这些变量只是中间计算过程、状态标志、或是为了方便标定测试(INCA/CANape)而定义的观测变量:完全没必要走 Outport。继续保持信号线绑定
Simulink.Signal,在数据字典里统一配置它们的存储类和结构体形式即可。
既然涉及到 EDR(Event Data Recorder,汽车事件数据记录系统) 的需求,那么你的数据管理逻辑就需要非常严谨了。法规(如国标 GB 39732 等)对 EDR 触发前后的数据记录有着严格的时间窗口、采样率和非易失性存储(Flash/EEPROM)写入要求。
在这种场景下,完全没有必要、也不建议 把所有需要记录的变量都通过 Outport 导出。
以下是针对 EDR 需求的最佳软件架构实践:
1. 为什么 EDR 数据不适合全部从 Outport 导出?
-
增加不必要的 CPU 拷贝开销: EDR 记录的数据通常包含几十个信号(车速、纵向加速度、踏板位置、制动状态等)。如果全部拉到顶层 Outport,Simulink 会在每个控制周期把这些变量从各个子系统拷贝到顶层的
_Y结构体中,这会白白消耗控制器的算力。 -
破坏模块化设计: EDR 关注的数据散落在模型的各个角落(如动力子系统、制动子系统、底盘动力学等)。如果强行把它们都拉线到最外层的 Outport,模型会变成极其复杂的“蜘蛛网”,极难维护。
2. EDR 需求的标准架构做法:信号线绑定 + 专用 EDR 记录模块
针对 EDR,工程上更标准、更优雅的做法是“就地绑定存储类” + “集中打包输入到 EDR 缓存模块”。
做法 A:利用 Simulink.Signal 定义专属结构体(推荐)
你不需要拉线,而是利用数据字典(Data Dictionary)进行数据结构的设计:
-
定义一个 Bus: 在数据字典中创建一个名为
EDR_Data_Bus的 Bus 对象,里面包含所有 EDR 法规要求记录的信号(如vehicle_speed,accel_x,brake_pedal_pos等)。 -
应用结构体存储类: 创建一个
Simulink.Signal对象,命名为EDR_Data,其数据类型设为Bus: EDR_Data_Bus,存储类(Storage Class)设为Structured(或者自定义的自定义存储类,如ExportedGlobal并指定结构体名称)。 -
在模型中赋值: 在模型中,只需要在一个地方(或者通过
Signal Conversion模块)把这些离散的信号线打入这个 Bus,并关联到EDR_Data信号对象上。
在生成的 C 代码中,会直接呈现为一个完美的结构体:
C
/* 生成的 C 代码示例 */
typedef struct {
real32_T vehicle_speed;
real32_T accel_x;
uint8_T brake_pedal_pos;
/* ... 其他 EDR 信号 */
} EDR_Data_Bus;
EDR_Data_Bus EDR_Data; // 全局 EDR 数据结构体
做法 B:建立一个独立的 EDR 缓存控制子系统(带 Outport)
如果你希望 EDR 逻辑在模型中高度可视,可以建立一个专门负责 EDR 触发和缓存的子系统:
-
把各模块的 EDR 信号作为输入,引流到这个 EDR 核心控制子系统中。
-
该子系统内部负责环形缓冲区(Ring Buffer)的维护。因为 EDR 需要记录碰撞前 X 秒和碰撞后 Y 秒的数据,算法需要在内存中持续缓存这些数据。
-
通过 Outport 导出: 当触发条件满足(如锁定事件)时,该子系统通过 Outport 导出一个已经打包好的、完整的环形缓冲区数组或结构体,直接交给底层驱动(BSW)的 NVM(Non-Volatile Memory,非易失性存储)组件去写 Flash。
3. 落地到控制器的协同逻辑
汽车控制器(如基于 AUTOSAR 架构或 OSEK 架构)写入 EDR 通常分为两步:
-
应用层算法(你的 Simulink 模型): 无论你用上述哪种方法,最终在 C 代码中表现为一个固定的全局结构体(如
EDR_Data)。模型只管在每个周期(如 10ms)把最新的传感器数据更新到这个结构体,或者在内部维护好环形队列。 -
底层软件(BSW / 手写代码): 底层软件的 EDR 任务(通常在外部中断或高优先级任务中)会直接读取这个
EDR_Data结构体,并调用诸如Fee_Write或NvM_WriteBlock的底层接口,将其持久化写入到 EEPROM 或 Flash 中。
💡 总结建议
-
绝不要 为了导出结构体,把几十个零散的信号一路连线到模型最外层的 Outport。
-
正确姿势: 在数据字典中定义一个统一的
EDR_Signals_Bus,在模型内部通过 Bus Creator 将散落的 EDR 变量打包。然后,要么将该 Bus 关联到一个配置为Structured存储类的Simulink.Signal上(代码中直接生成全局结构体供底层读取);要么将该 Bus 接入一个专门的 EDR 缓存子系统,由该子系统最终输出打包好的历史数据结构体。
也就是outport不是最佳实践对吗,我用模型仿真标定的时候直接把signal开机log然后从 simOut.logsout 获取变量仿真标定是最佳实践吗
你的理解非常准确。对于你提到的仿真与标定场景,你的这种做法正是当前汽车 MBD(基于模型开发)流程中的标准最佳实践。
让我们把“模型仿真”和“实车/硬件标定”两个场景拆开来看,为什么你的做法是最高效的:
1. 模型仿真(MIL/SIL)阶段的最佳实践
在 Simulink 里面直接对 Simulink.Signal 开启 Data Logging(信号对象日志),然后通过 simOut.logsout 获取数据,是标准的数据与模型分离的设计。
为什么说这是最佳实践?
-
模型干净: 想象一下,如果你为了观察 50 个中间变量,在模型里拉了 50 根长线到 Outport,你的模型会变成无法维护的“蜘蛛网”。开启 Logging 只需要在信号线上右键点个蓝色的“无线电”图标(或者在 Signal 对象里勾选
DataLogging)。 -
自动化测试极其方便: 当你用 MATLAB 脚本跑批量仿真、软件回路(SIL)测试或自动测试(如使用 Simulink Test)时,脚本可以直接读取
simOut.logsout.get('变量名').Values。这比去解析一个巨大的 Outport 结构体要优雅、高效得多。 -
按需记录: 你可以随时开启或关闭某个信号的 Logging,而不需要修改模型的拓扑结构(连线)。如果用 Outport,你每次想多看一个变量,都得删改模型、重新生成代码。
2. 实车/硬件标定(HIL/整车)阶段的最佳实践
当模型导出 C 代码并刷写到控制器(ECU)后,你在实车上用 INCA、CANape 或者是控制器的 EDR 记录数据时,逻辑是一模一样的。
怎么对应到 C 代码和标定软件?
你在 Simulink 中定义的 Simulink.Signal,在导出代码时通过 Storage Class(存储类)(例如 ExportedGlobal)映射为 C 语言的全局变量。
-
A2L 文件自动生成: 编译时,MATLAB 可以自动生成一份 A2L (ASAP2) 文件。这个文件里记录了所有
Simulink.Signal变成全局变量后的内存地址。 -
标定工具直接观测: 你把 A2L 文件导入到 INCA 或 CANape 中,这些工具就能直接通过 XCP/CCP 协议去读写这些变量。
-
EDR 核心代码直接读取: 你的 EDR 存储程序(底层手写代码或专用 NVM 模块)也可以在 C 语言层面直接访问这些全局变量(或由它们组成的结构体),然后写入 Flash。
在这个过程中,Outport 同样没有参与的必要。
🛠️ 总结:到底什么时候才必须用 Outport?
在汽车级 MBD 开发中,Outport 的使用场景其实非常单一和专一:
Outport 的唯一最佳实践:定义“模块与模块之间”或“模型与底层软件(BSW)之间”的硬接口协议。
-
要用 Outport 的地方: 比如你的模型计算完,最终要输出一个“目标节气门开度”给底层驱动,这个“目标节气门开度”应该走 Outport。
-
不要用 Outport 的地方: 算法的中间变量、状态机当前状态、传感器滤波后的值、需要被 EDR 记录的历史数据、需要用 INCA 看的观测波形。这些统统使用
Simulink.Signal+ 开启 Logging(仿真用)/ 配置存储类(实车用)。
你目前的思路完全在主流的汽车软件工程规范(如 AutoSAR 概念、MBD 最佳实践)的轨道上,放心按照这个架构去做。