一、C语言中的结构体:面向对象的基石

1.1 结构体的本质

在C语言中,struct(结构体)是一种用户自定义的复合数据类型,允许将不同类型的数据成员组织成一个逻辑整体。这种特性使得结构体成为抽象现实事物的有力工具——例如,一个"学生"可以抽象为包含姓名、学号、成绩的结构体;一个"传感器"可以抽象为包含温度、压力、状态码的结构体。

// 传感器数据的抽象

typedef struct {

    uint16_t speed; // 转速

    int32_t position; // 位置

    uint8_t status; // 状态标志

} SensorData_t;

1.2 结构体与面向对象的关系

虽然C语言不是面向对象语言,但结构体为实现面向对象编程(OOP)的四大特性提供了基础:

  • 封装:通过结构体将数据成员捆绑在一起,配合函数指针模拟方法
  • 继承:在子结构体中包含父结构体作为第一个成员
  • 多态:通过函数指针实现运行时动态绑定
  • 组合:结构体嵌套结构体,构建复杂对象

"结构体是C语言最接近'类'的概念,它是理解面向对象编程的重要跳板。"

二、MBD开发中的数据组织:Simulink.Bus

2.1 MBD开发有没有结构体?

答案是:有,但形式不同。在基于Simulink的模型驱动开发(MBD)中,并没有直接书写struct关键字,而是通过Bus(总线)机制来实现等效的数据抽象。

Simulink.Bus对象是MBD开发中的"结构体",它定义了一组信号的集合,包括每个信号的名称、数据类型、维度等属性。与C语言结构体定义类似,Bus对象在模型中扮演着数据契约的角色。

% 创建Bus对象(等价于C中的结构体定义)

% 定义三个信号元素
elems(1) = Simulink.BusElement;
elems(1).Name = 'speed';
elems(1).DataType = 'uint16';   

elems(2) = Simulink.BusElement;
elems(2).Name = 'torque';
elems(2).DataType = 'uint16';   

elems(3) = Simulink.BusElement;
elems(3).Name = 'enable';
elems(3).DataType = 'boolean';    

% 创建 Bus 对象
MyBus = Simulink.Bus;
MyBus.Elements = elems;

2.2 BUS总线的介绍与使用

在Simulink中,Bus Creator模块用于将多个信号打包成总线信号。总线分为两类:虚拟总线(Virtual Bus)和非虚拟总线(Nonvirtual Bus)。

创建BUS的基本流程:

  1. 定义Bus对象:使用Bus Editor或MATLAB脚本定义总线结构
  2. 配置Bus Creator:将多个信号输入Bus Creator,选择输出总线类型
  3. 使用Bus Selector:在下游提取总线中的特定信号
  4. 绑定数据类型:将信号线数据类型设置为定义的Bus对象

三、非虚拟总线(Nonvirtual Bus):真正的"结构体"

3.1 非虚拟总线的本质

非虚拟总线在Simulink中是一种真实存在的数据结构,它在生成的代码中会表现为C语言的结构体(struct)。

核心特征:

  • 分配实际内存空间
  • 信号被打包为结构体统一传输
  • 参与代码生成和内存布局
  • 适合与外部C代码接口对接

例如下图所示:

3.2 代码生成后的效果(核心部分)

当使用非虚拟总线时,Embedded Coder生成的C代码会自动创建对应的结构体定义:

typedef struct {
    uint16_T speed;      /* 总线信号:速度值 */
    uint16_T torque;     /* 总线信号:扭矩值 */
    boolean_T enable;    /* 总线信号:使能标志 */
} B_TestBus_T;

/* 声明工作区结构体实例,用于存储从Chart或其他模块计算得到的原始总线信号 */
extern B_TestBus_T TestBus_B;

/* 模型主步进函数,将内部总线信号分别映射到三个顶层输出端口 */
void TestBus_step(void)
{
    /* Outport: '<Root>/Outport' 输出 speed 信号 */
    TestBus_Y.Outport = TestBus_B.speed;

    /* Outport: '<Root>/Outport1' 输出 torque 信号 */
    TestBus_Y.Outport1 = TestBus_B.torque;

    /* Outport: '<Root>/Outport2' 输出 enable 信号 */
    TestBus_Y.Outport2 = TestBus_B.enable;
}

这种生成方式确保了模型与代码之间数据结构的一致性,便于追溯和调试。

四、虚拟总线(Virtual Bus):图形化的管理艺术

4.1 虚拟总线的本质

虚拟总线仅仅提供结构化的视图,而不分配实际内存。它类似于C语言中的结构体指针struct*,只是对现有信号进行逻辑分组,不会产生数据拷贝开销。

核心特征:

  • 不创建新的数据副本
  • 仅在Simulink编辑器和仿真时存在逻辑组合
  • 仿真性能高,零额外开销
  • 不改变底层信号的存储方式

4.2 虚拟总线的使用场景

在MBD的图形化界面中,虚拟总线的价值在于接口管理:

  • 简化连线:将 dozens 根信号线合并为一根总线,保持模型整洁
  • 逻辑分组:按功能模块(如传感器、执行器、控制参数)组织信号
  • 接口标准化:定义统一的输入输出端口规范

例如下图所示:

4.3 代码生成后的效果

虚拟总线在代码生成后不会生成结构体,各个信号仍然是独立的变量:

/* 模型初始化函数:设置错误状态、清零输出结构体,并为各输出端口赋初始值 */
void TestBus_initialize(void)
{

    /* 初始化错误状态为空 */
    rtmSetErrorStatus(TestBus_M, (NULL));

    /* 将所有外部输出(根级 Outport 结构体)清零 */
    (void) memset((void *)&TestBus_Y, 0,
                  sizeof(ExtY_TestBus_T));

    /* 系统初始化:为 Outport 端口 '<Root>/Outport'   */
    TestBus_Y.Outport = 180U;    /*直接将模型设置的常量180赋值给输出*/

    /* 系统初始化:为 Outport1 端口 '<Root>/Outport1' */
    TestBus_Y.Outport1 = 1600U;    /*直接将模型设置的常量1600赋值给输出*/

    /* 系统初始化:为 Outport2 端口 '<Root>/Outport2' */
    TestBus_Y.Outport2 = true;    /*直接将模型设置的常量true赋值给输出*/
}

五、虚拟总线 vs 非虚拟总线:如何选择?

5.1 核心差异对比

维度 虚拟总线 (Virtual Bus) 非虚拟总线 (Nonvirtual Bus)
内存分配 不分配,仅逻辑视图 分配实际内存,生成结构体 
代码生成 各信号分开生成 生成C语言结构体 
仿真性能 高,无打包解包开销 略低,涉及结构体操作 
外部接口 不适合直接对接C代码 可直接对接C结构体
AUTOSAR/嵌入式集成 不推荐 强烈推荐

5.2 场景化选择指南

使用虚拟总线的场景

  1. 纯仿真与调试阶段:仅用于模型验证,不生成生产代码
  2. 模型架构设计:顶层模型划分,保持框图清晰可读
  3. 信号可视化:连接Scope、Dashboard等观测工具
  4. 快速原型搭建:初期探索性建模,接口尚未固化

"在一个控制系统模型中,如果你只是希望在模块间方便传递多个信号用于仿真 —— 使用虚拟总线即可。"

使用非虚拟总线的场景

  1. 代码生成需求:需要将模型生成可部署的C代码
  2. 外部C代码集成:与手写代码、Legacy代码交互
  3. Stateflow图表输入:Stateflow需要非虚拟总线作为输入
  4. Function-Call子系统:触发子系统的总线输入
  5. AUTOSAR/SWC接口:软件组件间标准化接口定义
  6. 硬件在环测试(HIL):与真实ECU的数据结构对齐

5.3 典型错误与转换方法

常见错误:下游模块需要非虚拟总线,但上游输出虚拟总线,导致报错"nonvirtual bus required"。

解决方案:在模块中选择"Nonvirtual bus"选项,即可将虚拟总线转换为非虚拟总线,同时生成对应的结构体代码。

六、最佳实践总结

6.1 渐进式开发策略

开发阶段 推荐总线类型 理由
需求分析与架构设计 虚拟总线 快速迭代,关注功能逻辑
单元模型开发 虚拟总线 专注算法实现,减少配置负担
集成与接口定义 非虚拟总线 固化接口,生成结构体契约
代码生成与部署 非虚拟总线 确保代码与模型一致
维护与升级 混合使用 新功能用虚拟,已固化用非虚拟

6.2 工程化建议

  1. 统一命名规范:Bus对象名称应符合C语言标识符规范,便于代码生成
  2. 数据字典管理:将Bus对象存储在sldd数据字典中,实现团队共享
  3. 类型严格匹配:BusElement的DataType必须与目标平台C类型严格对应
  4. 采样时间一致性:非虚拟总线中的所有元素必须使用相同的采样时间
  5. 文档化接口:为每个Bus对象添加Description,说明其业务含义

结语

从C语言的结构体到Simulink的Bus总线,数据抽象的核心理念始终未变:将相关的数据组织为有机整体,以清晰的接口管理复杂度。虚拟总线让我们在图形化世界中优雅地组织信号,而非虚拟总线则架起了模型与真实代码之间的桥梁。理解二者的本质差异,并在正确的场景做出选择,是每一位MBD开发者从"建模"走向"工程化"的关键一步。

正如C语言用结构体为面向对象铺就了道路,Simulink的总线机制也在为复杂嵌入式系统的模型驱动开发奠定坚实的数据基础。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐