目录
一、前言
最近从事MBD的软件开发工作,一直没什么时间写博客,现在模型做的差不多,可以总结一些所用知识了。接下来应该都会写一些关于这方面的知识,包括模型相关知识,代码生成,MIL用例等。
首先为什么要使用MBD,直接撸代码不好吗?
当你的状态机比较复杂,互斥、交织、并行,各个管理模块(输入输出)相互影响,写代码可以,但是维护起来太难啦(万一他半路辞职咋办),所以基于模型的开发优势就发挥出来
使用MBD需要涉及哪些工具/知识?
建模(simulink/stateflow ),基本m语言编程(M脚本),模型测试用例(test case),代码生成技术及SIL测试等
这里先简单介绍stateflow基本应用。
二、stateflow和simulink
2.1stateflow基本介绍
Simulink® 是对现实世界动态系统(基于采样时间序列)进行建模、仿真和分析的工具环境。Simulink 建立在MATLAB 数值计算,图形和编程功能之上,以方框图建模的界面方式提供了一个系统级设计环境。
Real-Time Workshop® 可以直接从Simulink框图模型中生成定制化的C 代码,以进行快速原型、硬件在回路仿真。
Stateflow® 是事件驱动逻辑系统的图形化建模和仿真环境。Stateflow 为Simulink 用户提供了设计嵌入式系统中应用的控制和协议逻辑的一流解决方案。
Stateflow® Coder可以直接从Simulink模型的Stateflow部分生成定制化的C 代码
Stateflow 是一个交互式设计工具,用来对复杂的事件驱动模型系统进行建模与仿真。通过紧密地与Simulink® 和MATLAB® 集成,Stateflow®
将复杂的控制和监督逻辑有效地结合到Simulink模型中,为Simulink 用户提供了一个设计嵌入式系统的完美的解决方案
创建子系统 :在已有的系统之中建立子系统时,框选待封装的区域,然后选择Edit菜单下的Create Subsystem。可以为子系统起一个名字来反映它所包含的内容。
2.2更改检测操作
检测数据值是否更改:Stateflow可以检测时间步之间数据值的更改,可以使用更改检测运算符来确定变量的值何时发生更改。(要在图设置变量值时生成隐式本地事件,请使用Change运算符)
要检测 Stateflow 数据中的更改,请使用下表列出的运算符。
运算符 | 语法 | 说明 | 示例 |
---|---|---|---|
hasChanged | tf = hasChanged(data_name) | 如果当前时间步开始时 data_name 的值不同于前一时间步开始时 data_name 的值,则返回 1 (true )。否则,运算符返回 0 (false )。 | 如果矩阵 |
如果矩阵 在使用 MATLAB® 作为动作语言的图中,请使用: 在使用 C 语言作为动作语言的图中,请使用: | |||
hasChangedFrom | tf = hasChangedFrom(data_name,value) | 如果 data_name 的值等于在上一时间步开始时指定的 value 并且不同于在当前时间步开始时的值,则返回 1 (true )。否则,运算符返回 0 (false )。 | 如果结构体 |
hasChangedTo | tf = hasChangedTo(data_name,value) | 如果 data_name 的值不等于在上一时间步开始时指定的 value 并且等于在当前时间步开始时的 value ,则返回 1 (true )。否则,运算符返回 0 (false )。 | 如果结构体字段 |
注:如果多个输入事件在同一时间步内发生,这些运算符可以检测输入事件之间数据值的更改
2.3 总线BUS creator & selector
Bus Creator:由几路输入信号合成为一条总线信号
Bus Selector:由总线信号中选取需要的一路或几路信号输出
Mux:信号合成
Demux:信号分解
区别:
Bus的可选择性较多,如Bus Selector可以选取总线信号中的某路信号进行输出,而Demux则则是按顺序输出,不能自定义选取。
Bus是非虚拟总线,在代码生成中,可以以一个结构体的形式输出,而Mux是虚拟总线,看起来像是合在一起的,实际上还是一个个单独的信号。
2.4 模型间引用
打开simulink浏览器搜索reference,找到对应的model
模型1 addFxn.slx
模型2 mainModel.slx
2.5 m文件导入模型
为了更好的实现代码生成的可读性,使用mpt结构体,下面使用mpt创建信号和参数:
%signal, use 'get(mpt.Signal)' to get properties, or in workspace
%属性按照表格从上到下进行
ts_bInput = mpt.Signal;
ts_bInput.CoderInfo.StorageClass='Custom'
ts_bInput.CoderInfo.Alias='';
ts_bInput.CoderInfo.Alignment=-1;
ts_bInput.CoderInfo.CustomStorageClass='Struct'; %存储类型
ts_bInput.CoderInfo.CustomAttributes.StructName='TS_t';
ts_bInput.CoderInfo.CustomAttributes.ConcurrentAccess=false;
(
%getset存储类型
ts_bInput.CoderInfo.CustomStorageClass='GetSet';
ts_bInput.CoderInfo.CustomAttributes.HeaderFile = 'TS.h'
ts_bInput.CoderInfo.CustomAttributes.GetFunction='TS_get';
ts_bInput.CoderInfo.CustomAttributes.SetFunction='TS_set';
%这块虽然有了接口,但是不会自动生成代码,需要手写
)
ts_bInput.DataType='boolean';%在表格中可以看到全部类型,包括Enum,double等
ts_bInput.Min =0;
ts_bInput.Max =1;
ts_bInput.DocUnits='';%空
ts_bInput.Dimensions=1;
ts_bInput.DimensionsMode='auto';
ts_bInput.Complexity='real';
ts_bInput.SampleTime=-1;
ts_bInput.SamplingMode='auto';
ts_bInput.InitialValue='';
ts_bInput.Description = 'balabala';
%Parameters,define a enum type, get(mpt.Parameter)
ts_Rain_E=mpt.Parameter;
ts_Rain_E.Value=0;
ts_Rain_E.DataType='Enum:Weather';
ts_Rain_E.Min=[];
ts_Rain_E.Max=[];
ts_Rain_E.Unit='';
ts_Rain_E.CoderInfo.StorageClass='Custom';
ts_Rain_E.CoderInfo.CustomStorageClass = 'ExportToFile';
ts_Rain_E.CoderInfo.CustomAttributes.HeaderFile='TS';
ts_Rain_E.CoderInfo.CustomAttributes.DefinitionFile='TS';
ts_Rain_E.Description = 'balabala';
2)需要封装stateflow建立虚拟子系统函数(atomic subsystem),右击block parameters,可以设置函数的类型inline,auto,reusable(单独文件中生成代码),non-reusable(同一文件中生成代码)
3) base workspace: global,Model workspace: local to model, Mask workspace: local to subsystem,优先级Mask workspace>Model workspace> base workspace
4)Bus:virtual没有函数效果,nonvirtual有函数效果
5)persistencelevel: storage class 是global
2.6调试
Simulink调试中无法像M脚本一样随便打断点,所以整理了几点调试方法:
1.使用连接scope直接查看结果
2.点击信号线,右击选择Log Select Signals,显示信号类型
3.点击信号线,右击选择Create&Connect Viewer,出现一个示波器
4.直接在Stateflow中节点连线选择断点
5.运行按钮旁边有单步调试
6.右击转移线可以选择Add to watch window
7.可以在Simulation中选择debug选项按钮打开watch window
2.7时间计时计数
计时:after(n,sec) ,after(n,msec) , after(n,usec)过了n时间后为真
chart 计数:after(n,tick)如果自关联状态变为活动状态以来,chart至少醒来了n次,则返回true。 否则,运算符返回false。
事件计数:after(n,E)发生了n次事件后为真
on after(n,tick): %something you want to do .
2.8 Merge与Mux
对于多个if action subsystem的输出,merge模块会检测哪一个在更新,不更新的那个就被舍弃,不进行输出,所以输出的总是变化的值。
而mux没有这个判断,就是简单将数据信号线合并为多维而已
2.9转移状态类型
转移可以有不同的动作类型,包括事件或消息触发器、条件、条件动作和转移动作。动作类型采用标签表示法,一般格式如下:
event_or_message trigger[condition]{condition_action}/{transition_action}
转移动作与条件动作的区别:
转移动作只在获取完整的转移路径之后才会执行。它们在转移目标确定为有效且条件(如果指定)为 true 后执行(状态机跳转)
只要条件计算结果为 true,条件动作即开始执行,无论转移至目标的路径是否有效(流程图中)
2.10触发事件
广播事件:
事件广播的作用:可以在某个状态内部触发其他并行状态的执行,从而可以实现系统在不同状态之间的交互。
明确事件,直接事件广播:send(event_name, state_name); %一般写在动作语句中,避免仿真时出现不必要的循环或者递归,有效提高代码效率
隐含事件: change,chg,tick,wakeup;
用事件名直接广播:对于全局事件,可以直接在进入或者离开某个状态时,广播事件,如:en: LedOn;
隐含事件:
隐含事件是一种内置事件,不是由用户显示的定义或触发,而是当状态图执行时就会自动发生。例如状态图被唤醒,进入/退出一个状态或向内部数据对象赋值等。隐含事件是它们发生时所在的状态的子对象,并且只对其父状态可见。使用隐含事件和条件有助于简化并行状态之间的依赖关系,减少数据字典中事件的定义,降低状态图的复杂程度。如exit(Led.On);%离开Led父状态下的On状态事件。
接收事件,然后执行:
on event name: doing sth; %当某事件发生时执行
类似的还有on before(n,event name):doing sth ,on after(n,event name):doing sth ,on at(n,event name):doing sth ,on every(n,event name):doing sth ;(可以单独使用after,at等在状态转移过程中当作条件使用,比如说:after(1,sec))
bind: 将事件和数据约束在某个状态和其子状态中,其他状态只能读取或者监听,无法修改(不允许不同状态约束相同的变量和事件,它是全局有效的)
事件在状态转移中的使用:event[condition]{action}/{transition action}
外部输入多事件触发:
选择Mux综合事件,每个事件都会激活chart
State Activity Operator:
in(), 检查另外平行状态是否激活,in(状态机路径全名)
2.11stateflow函数模块
与m函数类似,语法为function [x,y,z] = function(a,b,c); 左边为输出变量,右边为输入变量。
2.12信号线显示信号名字
首先双击信号线,填写信号名,然后右击打开信号线属性,勾选'Signal name must resolve to Simulink signal object',这时必须在m脚本里定义好相应的信号对象
表象上在信号线上会出现一个蓝色的分岔。
注意: 模块之间传递信号时保证传递信号的变量名一致(输入端口和输出端口),右击相连的信号线,勾选Show propagated signals,保证传递信号无误。
2.13代码生成属性配置
Solver求解器:选择Fixed-step , discrete
Code Generation: System target file选择:ert.tlc,language :C
Code Generation展开选择Report,勾选Create code generation report和Open report automatically
Ctrl+B编译即可
在Code Generation展开选择Templates:取消勾选Generate an example main program.Simulink不生成这个主函数,以减少生成代码所用的时间。
在Code Generation展开选择Comments: 可以自定义生成注释
Code Generation展开选择Optimization: 关于零初始化,可以通过勾选Data Initialization中的Remove root level I/O zero initialization取消生成代码的零初始化
Code Generation展开选择Interface: 关于这个rtmSetErrorStatus函数,其实没什么用处。可以通过勾选Remove error status field in real-time model data structure不生成错误状态监测函数。
Code Generation: Code Generation过程不仅仅生成了C文件和头文件,还执行了一下makefile,把代码编译成了可执行文件,通过勾选Generate code only可以不编译,节约时间。
2.14 代码加密P文件
P文件:后缀为.p的matlab文件。p意思是预解析(preparsed version),将.m文件里的代码预解析一遍,生成p文件。当再次调用时,实际上调用的就是.p文件,目的是提升调用速度
作用:用来做代码保护,将m文件生成对应的p文件,然后使用命令’help 文件名’,则可以看到文件里有哪些方法可以调用,当然你得把这些方法以注释方式写在m文件头部
生成.p文件命令: pcode m文件名
2.15 建立Library文件和封装技术
1.锁定/解锁库文件:点击左下角有一个锁的小图标即可
2.对话框inputdlg用法:
answer = inputdlg(prompt,title,dims,defInput,opts);%分别表示提示({‘size’,'name'}),对话框标题,输入框大小([高1,宽35]),默认输入,opts可选
3.Edit控件:设置Name即可,使用get_param(gcb,'name'),可以得到Value
4.sprintf函数:将数字或字符串按照指定格式填充起来,输出一个字符串;str = sprintf('you are: %d:%d %s\r%s',11,22,'good','man');
5.set_param函数:设置控件的值Value.如 set_param(gcb,'EditName',str);
6.maskObj = Simulink.Mask.get(gcb):获得当前模块的句柄,通过该句柄可以获得各个控件
7.[diagH,~]=maskObj.getDialogControl('控件名'),获得特定控件句柄,如diagH.Prompt=str;
8.newStr = strrep(str,old, new): 表示在str中搜索old并使用new来替代
9.text: 在Icon&Ports的选项卡下使用文本显示函数text(x=0.05,y=0.95,'textStr','horizontalalignment','left','verticaalignment','top'):设置文本显示的坐标和方位;
选择visible, Opaque,Normalized,Fixed,Default,On选项。
2.16代码生成之求解器配置
在init脚本中设置采样时间变量:SaTe = 0.02s(假设20ms一个task)
Solver options: Fixed-step, discrete
Additional options: Fixed-step size: SaTe,
Tasking and sample time constraint : Ensure sample time independent.
2.17获取模块属性和参数值
get_param(‘文件名/组件模块名’,‘参数名称’)
set_param(‘文件名/组件模块名’,‘参数名称’,‘值’)
sim('文件名')%进行仿真
2.17Block annotation
在模块众多的复杂模型中,依次确认每个模块的参数对于建模者来说无疑是一个麻烦事。
可以打开模块的属性Block Anotation, 选择左边需要显示的值,如%<InitialCondition>,InitVal = %<InitialCondition>
这种格式是目标语言编译器TLC的一种语法格式(%<>类似于M语言中eval_r()函数的作用,TLC语言中直接输入字符串可以原原本本输出),它表示将变量InitialCondition的值显示出来。
2.18 StateFlow执行顺序
1)同层次的图执行顺序是从上到下,从左到右,during 和on先写的先执行
2)层次化迁移的优先级规则:从高层次到低层次检测;从外部迁移到内部迁移检测;同一层次内超转移优先;
总结:高层级到低层级,外部转移大于内部转移
2.19 历史节点
历史节点:在状态图的顶层或者父状态放置一个历史节点,它能距离退出父状态时正在激活的子状态,再次进入父状态时,进到上次退出时激活的子状态。
2.20巧用真值表
当判断条件是由几个关联的因素共同决定,而这几个因素又存在不同的组合时使用真值表可以简化设计过程。真值表的表达形式为条件,决策和动作
2.21Matlab Function& Simulink Function
Matlab Function:matlab脚本在描述算法上优于stateFlow,因此SF提供了内嵌的matlab函数脚本嵌入功能, 可以说很好用了。
Simulink Function:一般在进入离开状态和状态/迁移动作中调用,当需要lookup table,离散信号处理模块,多控制器调度等时使用该模块。
可以说实现了stateflow与matlab脚本和simulink模型的无缝衔接。
2.22图形盒Box
图形盒是Stateflow中的一种图形对象,可以用来组织图表中的图形对象。图形盒中的图形对象的可视性和并行状态的激活顺序都会发生变化。这个功能对于其内部的函数有些类似C++中的namespace。
2.23为生成代码设置Chart目标属性
将Action Language由matlab设置为C;
勾选掉:Support Variable-size arrays, Saturate on integer overflow
勾选:Enable C-bit operations, user specified state, use strong data typing
其余保持默认即可。
Setting 中Diagnostic 除了execute-at-initiazation为warning,其余全为error
2.24 固定步长和变步长
固定步长:求解器在固定时间间隔求解模型;当需要增加时间来仿真系统时,减少步长可以提高结果的准确度
可变步长:在仿真过程中求解器使用变化的时间步长;当模型状态快速变化,自动减少步长来提高准确度,当变化慢时,增加步长来避免不必要的时步;
Step size: Fixed Step
State Update: Discrete Continuous
Integration Scheme: Explicit Implicit
Integration Order: ode1, ode2, ode3,ode4,ode5,ode8 ode 14x
ODE: ordinary differential equation; ode1: numerical integration of 1st order.
2.25 Matlab数据类型及基础类
矩阵matrice,浮点数组,整形数组,字符characters,字符串string,逻辑logical
matlab基础类:
Matrix or Array
logical char numeric table(like excel) cell struct
u8/int8 u16/int16 u32/int32 u64/int64 single(float) double
2.26使用Stateflow逻辑模型在图表中定义枚举数据
classdef(Enumeration) APP_Modes <Simulink.IntEnumType
enumeration
Normal(0)
Failure(1)
end
methods (Static=true)
%function retVal = getDefaultVal()
%function retVal = getDescription()
%function retVal = getHeaderFile()
end
在stateflow添加数据(output to simulink),Type: Enum:App_Modes, Expression:APP_Modes.Normal
2.27解决model setting for migration to simplified initialization mode
R2008b 中引入了简化初始化模式以提高仿真结果的一致性。对于没有为条件执行子系统输出端口指定初始条件的模型,此模式尤为重要。
Initialization mode controls how Simulink® handles:
-
Initialization values for conditionally executed subsystems.
-
Initial values for Merge blocks.
-
Discrete-Time Integrator blocks.
-
Subsystem elapsed time.
当使用以上模块时在设置中搜索Underspecified initialization detection,设置为simplified
参考:Simplified Initialization Mode- MATLAB & Simulink- MathWorks 中国
2.28 Model Advisor
1) 打开model advisor,By product,选择VV, simulink verification and validation
2) By Task ,可自选测试配置
2.29 导入/输出array和Bus
array定义Size长度即可,访问使用dataArray[i]
Bus需要点击Edit新建Bus 对象DataBus.data1
2.30 规则
可见状态的数量小于10个,最大子图深度不超过5层
2.31 subchart
将chart封装起来,如果选择Atomic subchart,则可以直接拷贝到library中。
2.32 调用外部C文件
需要在以下两个地方添加对应头文件和源文件,如果文件过多可以放在文件夹中,然后在“Include directories”添加目录
三、汽车发动机启动过程
3.1初识汽车电源
IG: IG电源指钥匙打到ON档接通的电源,也就是发动机起动过程中不断开的电源;这部分用电器一是发动机启动过程中给必要的用电器供电,二是在发动机工作运转的情况下才使用,取自发电机的电源,避免了为蓄电池充电时争电源的可能性。如:仪表电源、制动灯电源、安全气囊电源等;在保证acc供电的基础上,增加了发动机的点火功能。
ACC: ACC电源指钥匙打到ACC档的电源,也就是发动机起动过程中要断开的电源,这部分电器件一般所带的负载较大,且在汽车起动时不必工作,一般有点烟器电源、空调电源、收放机电源、刮水器电源等。
Start: 启动档,主要给发动机启动系统供电,这时一般会切断acc档的电路,已保证发动机顺利启动。
SSB点火开关四个档位,lock(关闭档), acc(收音机档) , on(点火档), start(起动档)。
- 锁车后钥匙会处于lock状态,此时钥匙门不仅锁住方向,同时切断全车电源。
- acc状态是接通汽车部分电器设备的电源,如cd、空调等。 (第一次按下)
- 而start档是发动机启动档位,启动后会自动恢复正常状态也就是on档。(第二次按下)
- 正常行车时钥匙处于on状态,这时全车所有电路都处于工作状态。(IG on)
- ig2和ig2都是属于on档。
电源线路:
- am1、am2是从前舱配电盒进入点火开关的常电
- 当点火开关打到acc档时,am1和acc接通
- 当点火开关打到on档时,am2和ig2接通,am1和acc、ig1接通
- 当钥匙打到start档时,am1和acc,ig1断开,am2和ig2、start接通
注:IG1和IG2在电路中,是运用到日系,韩系车的火线的标记。IG1是大功率用电器的(除了起动机),IG2是发动机控制单元和小用电器的火线,当钥匙打到ON时候,IG1,IG2都通电,当打到ST时候,acc切断,IG2和ST通电,切断IG1的理由是全力以赴的满足启动!IG1和IG2就相当于德系车电路图中的X和15号线。
3.2电源启动状态机
四、毫米波雷达LCAS状态机
五、简单驾驶员决策模型
建立模型如下:
参考:
官网指导:对有限状态机建模- MATLAB & Simulink- MathWorks 中国
haschangedto: 检测数据和表达式值的更改- MATLAB & Simulink- MathWorks 中国