3. Simulink基础:模型基础、信号与总线、子系统与封装、仿真配置
好,咱们进入第三章。这一章是Simulink的基石,说白了就是搭积木的基本功。你想想看,飞控算法再牛,最后不都得在Simulink里跑起来?我见过不少新手,一上来就拖模块,结果模型乱得像蜘蛛网,仿真跑不通还找不到原因。嗯,这一章就是帮你打好地基。
3.1 模型基础:从空白画布开始
打开Simulink,你会看到一个空白画布。我个人习惯,第一步不是拖模块,而是先想清楚:这个模型要干什么?输入是什么?输出是什么?
模型文件(.slx)本质上是一个有向图。节点是模块,连线是信号。你拖一个Constant模块,再拖一个Scope模块,连起来,就是一个最简单的模型。但别小看这个,我在项目中遇到过有人把几百个模块堆在一个画布上,连个注释都没有。结果换个人接手,直接崩溃。
3.2 信号与总线:数据流动的血管
信号就是模块之间的连线。但Simulink里的信号,远不止一根线那么简单。
3.2.1 信号属性
每条信号都有数据类型、维度、采样时间。默认是double,但飞控里经常要用到single、int16、boolean。为什么?因为嵌入式芯片算力有限,double运算太慢了。我曾经在一个Pixhawk项目里,把所有double改成single,CPU负载直接降了30%。
3.2.2 总线(Bus)
当信号多起来,比如姿态数据有roll、pitch、yaw、quaternion,你总不能拉四根线吧?这时候总线就派上用场了。
总线说白了就是把一堆信号打包成一个结构体。在Simulink里用Bus Creator创建,用Bus Selector拆开。我建议你养成习惯:所有相关的信号都走总线。比如IMU数据、电机指令、状态估计,各用一个总线。这样模型看起来清爽,而且后期改起来也方便。
3.3 子系统与封装:模块化的艺术
子系统就是把一堆模块打包成一个模块。这不仅仅是美观,更是工程化的核心。
3.3.1 子系统类型
| 类型 | 用途 | 我常用的场景 |
|---|---|---|
| Subsystem | 普通子系统 | 把控制律、滤波器等独立功能打包 |
| Atomic Subsystem | 原子子系统 | 需要保证执行顺序时用,比如状态机 |
| Triggered Subsystem | 触发子系统 | 只在特定事件发生时执行,比如外部中断 |
| Function-Call Subsystem | 函数调用子系统 | 代码生成时对应一个函数,效率最高 |
3.3.2 封装(Mask)
封装是子系统的进阶玩法。你右键子系统 -> Mask -> Create Mask,就能给子系统加一个自定义界面。比如你做一个PID控制器子系统,封装后可以暴露Kp、Ki、Kd参数,别人用的时候直接填数字就行,不用进到里面改。
我个人习惯,封装时一定要加文档页(Documentation)。把输入输出说明、参数范围、注意事项都写清楚。你想想看,半年后你自己回来看这个模型,如果没有文档,你可能都忘了这个模块是干嘛的。
3.4 仿真配置:让模型跑起来
模型搭好了,怎么跑?仿真配置就是告诉Simulink:用什么求解器、跑多久、步长多少。
3.4.1 求解器选择
飞控仿真一般用定步长求解器。为什么?因为代码生成后,嵌入式代码也是定步长运行的。你用变步长仿真,结果可能很漂亮,但生成代码后跑出来完全不一样。
- 离散求解器(discrete):没有连续状态时用,比如纯数字滤波器
- ode3(Bogacki-Shampine):我常用的,精度和速度平衡
- ode4(Runge-Kutta):精度更高,但计算量大
3.4.2 步长设置
步长就是仿真一次的时间间隔。飞控一般用1ms(0.001)或10ms(0.01)。步长越小越精确,但仿真速度越慢。我建议先设1ms,跑通了再调大。
3.4.3 仿真时间
仿真时间不是越长越好。比如你要测试一个姿态控制器的响应,跑10秒就够了。跑100秒纯属浪费时间。我一般先设10秒,看结果再调整。
3.5 知识体系总览
下面这张图是我自己整理的Simulink基础知识结构。你可以把它当作一个检查清单,看看自己哪些地方还没掌握。
嗯,这一章的内容就这些。记住,Simulink不是画图工具,是工程工具。你画的每一根线、建的每一个子系统,都要有明确的目的。别为了好看而建模,要为了好用而建模。