3、MATLAB/Simulink基础:Simulink环境介绍、常用模块库、信号线与总线、子系统与封装

好,咱们开始聊Simulink。说实话,很多刚入行的工程师觉得Simulink就是个画框框的工具,拖几个模块连上线就完事了。嗯,这种想法很危险。我在项目里见过太多因为基础不牢,后期模型跑不动、代码生成报错的情况。

这一章,我带你把Simulink的底子打扎实。咱们不讲花架子,只讲实战中天天要用的东西。

3.1 Simulink环境介绍

打开Simulink,你首先看到的是启动界面。我个人习惯直接点「Blank Model」创建新模型。但你要知道,Simulink里有很多模板,比如针对AUTOSAR的、针对嵌入式代码生成的。不过刚开始,空白模型就够了。

模型窗口有几个关键区域:

  • 菜单栏:仿真参数、代码生成、调试工具都在这里
  • 模块库浏览器:左侧或通过快捷键Ctrl+Shift+L打开
  • 画布:拖模块、连线的地方
  • 状态栏:显示仿真进度、求解器信息

这里有个小技巧——按空格键可以自动缩放画布。我刚开始做项目时,模型画大了经常找不到模块在哪,后来发现这个快捷键,效率提升不少。

我的习惯:每次新建模型,第一件事就是去「Model Settings」里把求解器改成固定步长(Fixed-step),离散求解器。做嵌入式开发,连续求解器基本用不上。

3.2 常用模块库

Simulink的模块库很多,但咱们做ECU开发,常用的就那几类。我按使用频率给你排个序。

3.2.1 Sources(信号源)

Sources库是信号的起点。最常用的几个:

  • Inport:模型输入端口,代码生成时会变成函数参数
  • Constant:常数信号,我常用来做标定量
  • Step:阶跃信号,测试响应速度用
  • Sine Wave:正弦波,做信号处理时常用
  • From Workspace:从MATLAB工作空间读数据,做仿真验证时很实用

举个例子,你想测试一个低通滤波器的效果,可以用Sine Wave加Step,看看输出怎么变化。

3.2.2 Sinks(信号接收器)

Sinks是信号的终点。记住:每个信号最终都要接到Sink上,不然模型会报错。

  • Outport:模型输出端口,代码生成时变成函数返回值
  • Scope:示波器,看波形用。但我个人更推荐用To Workspace,把数据存到工作空间,然后用MATLAB脚本画图,更灵活
  • Display:显示数值,调试时很方便
  • Terminator:终止未连接的信号,临时调试用
注意:千万不要在最终代码生成的模型里用Scope!Scope是仿真专用模块,代码生成时会报错。我曾经见过有人把Scope留在模型里,结果代码生成失败,找了半天才发现。

3.2.3 Math Operations(数学运算)

做控制算法,数学运算逃不掉。常用模块:

  • Add/Sum:加减法,可以配置多个输入
  • Product:乘法,注意数据类型匹配
  • Gain:增益,就是乘一个常数。我常用来做标定系数
  • Abs:绝对值
  • Trigonometric Function:三角函数,做角度计算时用
  • MinMax:取最大值/最小值,做限幅保护

这里有个坑:Gain模块默认是double类型。如果你做定点数开发,记得手动改数据类型。我刚开始做发动机控制时,就因为数据类型没配好,算出来的喷油量差了10%。

3.3 信号线与总线

信号线就是模块之间的连线。看起来简单,但用不好会让模型乱成一团。

3.3.1 信号线基础

  • 普通线:直接拖拽连接
  • 分支线:按住Ctrl键拖拽,可以分出支路
  • 信号标签:双击信号线,可以命名。我强烈建议每条重要信号都命名,不然代码生成后你根本不知道变量对应什么

3.3.2 总线(Bus)

当信号多起来,比如一个传感器模块输出温度、压力、转速三个信号,用三条线连出去?太乱了。这时候用总线。

总线说白了就是把多个信号打包在一起。操作步骤:

  1. Bus Creator模块把信号合并
  2. Bus Selector模块从总线里取出需要的信号
  3. 建议用Bus Object定义总线类型,这样代码生成更规范
实战经验:我在做BMS(电池管理系统)项目时,把电压、电流、温度、SOC四个信号打包成一个「电池状态总线」。这样整个模型清晰多了,而且代码生成后结构也一目了然。

3.4 子系统与封装

模型大了怎么办?拆成子系统。这是模块化设计的基础。

3.4.1 创建子系统

两种方式:

  • 自顶向下:先拖一个Subsystem模块,再双击进去画内容
  • 自底向上:选中多个模块,右键→Create Subsystem

我个人喜欢自底向上。先把功能画好,验证没问题了,再打包成子系统。这样不容易漏掉连接。

3.4.2 子系统类型

类型 用途 代码生成行为
Atomic Subsystem 原子子系统,不可拆分 生成独立函数
Virtual Subsystem 虚拟子系统,仅用于组织 不生成独立函数
Triggered Subsystem 触发执行,事件驱动 生成条件执行函数

做嵌入式代码生成时,尽量用Atomic Subsystem。这样每个子系统对应一个C函数,调试起来方便。

3.4.3 封装(Mask)

封装是子系统的进阶用法。说白了,就是给子系统做个「外壳」,让使用者只看到参数接口,看不到内部实现。

封装的好处:

  • 保护知识产权:内部逻辑可以隐藏
  • 简化使用:只暴露必要的参数
  • 统一接口:团队协作时,大家用同一套封装

怎么封装?右键子系统→Mask→Create Mask。然后可以添加参数、图标、文档。

我的建议:封装时一定要写文档说明。哪怕只有两句话,说明这个模块是干什么的、输入输出是什么。不然三个月后你自己都忘了这个模块怎么用。我吃过这个亏。

3.5 避坑指南

最后,分享几个我踩过的坑:

  • 信号线交叉:尽量用总线或Goto/From模块,别让线在画布上乱飞
  • 数据类型不匹配:double和single混用,仿真没问题,代码生成可能报错
  • 代数环:反馈回路里没有延迟模块,仿真会报错。加个Unit Delay就能解决
  • 子系统命名:别用中文!代码生成不支持中文模块名。我见过有人用「PID控制器」命名,结果代码生成失败

好了,这一章的内容就这些。Simulink基础打牢了,后面做控制算法、代码生成才能顺手。下一章咱们聊状态机建模,那才是做逻辑控制的核心。