4、基础建模模块:常用模块库介绍、数学运算模块、逻辑运算模块、信号路由模块

好,咱们今天聊聊Simulink里最基础、也最常用的那几类模块。说实话,很多新手一打开Simulink库浏览器,看到密密麻麻的模块列表就头大。我当年也一样,对着屏幕愣了半天,不知道该从哪儿下手。

其实你想想看,再复杂的ADAS算法,拆到最底层,无非就是做几件事:算数、做判断、把信号送到该去的地方。今天我就带你把这三大类模块捋一遍,以后你看到模型,心里就有底了。

4.1 常用模块库概览

Simulink的模块库是按功能分组的。我个人习惯把最常用的几个库放在快速访问栏里,分别是:Math Operations(数学运算)、Logic and Bit Operations(逻辑运算)、Signal Routing(信号路由),还有SourcesSinks。这几个库,基本覆盖了日常建模80%的需求。

嗯,这里要注意:别一上来就翻那些花里胡哨的专用库。先把基础模块玩熟了,后面学什么Vehicle Dynamics、Vision Processing都顺手得多。

核心原则:能用基础模块解决的问题,就别用复杂模块。模型越简单,代码生成越稳定,调试越容易。

4.2 数学运算模块

数学运算模块,说白了就是做加减乘除、三角函数、积分微分这些事。在ADAS里,你算个车速、算个距离、做个滤波,都离不开它们。

4.2.1 常用数学模块清单

模块名称 图标/路径 典型用途
Add / Sum Math Operations → Add 信号求和、误差计算
Gain Math Operations → Gain 比例缩放、单位换算
Product / Divide Math Operations → Product 乘除运算、扭矩计算
Abs Math Operations → Abs 取绝对值、距离计算
Trigonometric Function Math Operations → Trigonometric Function 角度转换、坐标变换
MinMax Math Operations → MinMax 限幅、取最值
Saturation Math Operations → Saturation 信号限幅、防止溢出

4.2.2 实战经验:Gain模块的陷阱

Gain模块看着简单,但坑不少。我在项目中遇到过一个问题:用Gain模块做单位换算,把米每秒转成公里每小时,系数是3.6。结果生成代码后,发现算出来的速度总是偏大一点点。

查了半天,原来是Gain模块默认的数据类型是double,而我的输入信号是single。Simulink自动做了类型转换,但转换过程中有精度损失。解决方案很简单:在Gain模块的参数里,把Output data type显式设为Inherit: Same as input,或者直接设成single。

小技巧:做定点数模型时,Gain模块的系数最好用2的幂次,比如0.5、0.25。这样代码生成后,编译器会把乘法优化成移位操作,效率高很多。

4.2.3 数学运算的代码生成示例

咱们看一个实际例子。下面是一个简单的车速计算模型,用到了Add和Gain模块:

// 模型逻辑:车速 = (轮速脉冲 / 时间) * 轮胎周长 * 单位换算系数
// Simulink中:Product模块做除法,Gain模块做乘法

// 生成的C代码(简化版):
float VehicleSpeed_Calc(float wheelPulse, float timeInterval) {
    float speed;
    // 轮速脉冲除以时间间隔,得到每秒脉冲数
    float pulsePerSec = wheelPulse / timeInterval;
    // 乘以轮胎周长(米/脉冲)再乘以3.6(m/s -> km/h)
    speed = pulsePerSec * 0.615f * 3.6f;
    return speed;
}

你看,代码生成后就是很干净的C代码。但如果你在模型里用了Product模块做除法,记得把Number of inputs设成*/,这样第一个输入是乘,第二个是除。我刚开始用的时候,老是把除数和被除数搞反,后来养成了习惯:每次连完线,都手动算一遍结果对不对。

4.3 逻辑运算模块

逻辑运算模块,就是做与、或、非、比较这些事。在ADAS里,你判断「前车距离是否小于安全距离」「车速是否超过限速」「刹车信号是否有效」,都得靠它们。

4.3.1 常用逻辑模块清单

模块名称 路径 典型用途
Relational Operator Logic and Bit Operations → Relational Operator 大于、小于、等于判断
Logical Operator Logic and Bit Operations → Logical Operator AND、OR、NOT组合逻辑
Detect Rise / Detect Fall Logic and Bit Operations → Detect Rise / Detect Fall 边沿检测、触发信号
Switch Signal Routing → Switch 条件选择、模式切换
Compare To Constant Logic and Bit Operations → Compare To Constant 与固定阈值比较

4.3.2 避坑指南:逻辑运算的数据类型

我曾经在做一个AEB(自动紧急制动)功能时,遇到一个诡异的bug:明明前车距离已经小于安全阈值了,但刹车就是不触发。

查到最后,发现是Relational Operator模块的输出类型问题。默认情况下,比较模块输出的是boolean类型。但我的下游模块(一个Switch)期望的是double类型。Simulink自动插了一个Data Type Conversion模块,把boolean转成了double。问题在于,这个转换模块默认的Output data typedouble,但Inputboolean,转换后true变成1.0,false变成0.0。看起来没问题对吧?

但我的Switch模块的阈值设的是0.5,按理说1.0 > 0.5应该触发。可实际上,因为模型里还有一层使能逻辑,那个使能信号在某个条件下被强制拉低了。最终导致刹车条件永远不满足。

教训:逻辑运算模块的输出,尽量保持为boolean类型。如果下游需要double,在最后一个环节再转换。中间环节混用数据类型,很容易出问题。

4.3.3 逻辑运算的典型应用

咱们看一个FCW(前向碰撞预警)的逻辑判断:

// 条件:车速 > 30 km/h 且 前车距离 < 50米 且 刹车未踩下
// Simulink中用三个Relational Operator + 一个Logical Operator (AND)

// 生成的C代码(简化版):
bool FCW_Trigger(float vehicleSpeed, float frontDistance, bool brakePressed) {
    bool cond1 = (vehicleSpeed > 30.0f);      // 车速条件
    bool cond2 = (frontDistance < 50.0f);     // 距离条件
    bool cond3 = !brakePressed;               // 刹车未踩
    
    return (cond1 && cond2 && cond3);         // 三个条件同时满足
}

你看,逻辑清晰,代码也干净。但有一点要注意:Relational Operator模块的Relational operator参数,默认是~=(不等于)。我刚开始用的时候,经常忘记改成<>,结果模型跑出来的结果完全不对。后来我每次放完比较模块,第一件事就是双击检查运算符。

4.4 信号路由模块

信号路由模块,说白了就是「信号怎么走」。在ADAS模型里,信号源多、目的地也多。怎么把信号高效、清晰地送到该去的地方,是建模水平的分水岭。

4.4.1 常用信号路由模块清单

模块名称 路径 典型用途
Mux / Demux Signal Routing → Mux / Demux 信号合并与拆分
Bus Creator / Bus Selector Signal Routing → Bus Creator / Bus Selector 总线信号创建与选择
Selector Signal Routing → Selector 从向量/矩阵中选取元素
Switch / Multiport Switch Signal Routing → Switch / Multiport Switch 信号选择、模式切换
Goto / From Signal Routing → Goto / From 跨层级信号传递
Data Store Memory / Read / Write Signal Routing → Data Store Memory 全局变量、状态保持

4.4.2 总线 vs Mux:我该用哪个?

这个问题,几乎每个初学者都会问。我个人的建议是:能用Bus就别用Mux

为什么?Mux合并后的信号是一个向量,你只能通过索引访问。比如Mux合并了车速、距离、角度三个信号,输出是一个3元素的向量。你要取车速,就得知道它是第几个元素。模型一复杂,索引很容易搞错。

Bus就不一样了。Bus Creator创建的信号是结构体,每个信号有名字。你用Bus Selector选信号时,直接按名字选,清晰明了。而且代码生成后,Bus会映射成C语言的结构体,可读性高得多。

实战建议:在ADAS模型里,建议把传感器数据(雷达、摄像头、IMU)都封装成Bus。比如RadarBus包含targetDistancetargetSpeedtargetAngle等字段。这样模型层次清晰,代码生成也规范。

4.4.3 Goto/From模块的正确用法

Goto和From模块,可以在不同子系统之间传递信号,不用画长长的连线。但这个东西,用好了是神器,用不好是灾难。

我曾经接手过一个模型,里面用了上百个Goto/From,信号满天飞。想查一个信号从哪里来,得满模型找Goto标签。调试起来简直要命。

我的原则是:Goto/From只在同一层级内使用,且标签名要有意义。比如在顶层模型里,用Goto/From把「系统使能信号」从控制面板传到各个子系统,这个可以。但在子系统内部,尽量用端口连线,别用Goto。

命名规范:Goto标签用大写+下划线,比如SYS_ENABLEBRAKE_ACTIVE。这样一眼就能看出是全局信号。我团队里规定:所有Goto标签必须写在模型文档里,不然代码审查不过。

4.4.4 信号路由的代码生成示例

咱们看一个Bus使用的例子:

// Simulink模型:Bus Creator创建RadarBus,Bus Selector提取targetDistance
// 生成的C代码(简化版):

// 定义结构体
typedef struct {
    float targetDistance;
    float targetSpeed;
    float targetAngle;
    bool targetValid;
} RadarBus;

// 使用结构体
void FCW_Algorithm(RadarBus radarData, float* warningLevel) {
    // 直接从结构体取数据,按名字访问
    if (radarData.targetValid && radarData.targetDistance < 50.0f) {
        *warningLevel = 2.0f;  // 严重警告
    } else {
        *warningLevel = 0.0f;
    }
}

你看,代码生成后就是干净的结构体操作。如果换成Mux,生成的代码会是一堆数组索引,可读性差很多。

4.5 本章小结

好,咱们今天聊了三大类基础模块:数学运算、逻辑运算、信号路由。这些东西看着简单,但真要用好,还是得靠实践。

我最后给你三个建议:

  • 数学运算:注意数据类型,Gain模块的系数尽量用2的幂次
  • 逻辑运算:保持输出为boolean,别混用数据类型
  • 信号路由:能用Bus就别用Mux,Goto/From要克制使用

下一章,咱们会讲更高级的模块——连续系统和离散系统模块。到时候你会看到,今天这些基础模块是怎么搭建成完整的ADAS控制算法的。嗯,先消化这些,咱们下章见。