LICENCE: 本文根据《JMAAB V4.01》部分章节进行翻译和整理,文中只是针对MBD开发方式描述了模型架构的纲要和观念。由于Simulink提供了多种可以满足需求的模块及建模方法,所以很难进行详尽的描述说明,翻译和整理由Tomato一人完成,欢迎探讨,转载时需要保留本段文字
所有的系统都可以使用Simulink或Stateflow进行建模。
如果使用Stateflow进行建模,Simulink仅仅作为信号的输入输出和仿真,在Stateflow中可以使用多种公式和方法来替代simulink进行处理。如果只用Simulink,可以通过使用Switch-Case块的方法来替代Stateflow实现复杂的状态变量。
因此,使用Simulink或Stateflow对于特定部分的建模,是主观依据开发人员对于哪种表达方法更为理解。应该根据整体的团队水平来实现如上选择和建模。
在大多数情况下,Stateflow的RAM效率要比Simulink差。因此,Simulink在使用简单公式的计算中具有优势。除此之外,Simulink在使用简单的触发和切换系统中,对于状态变量的使用也更具有优势。当使用Stateflow所建的模型可以用Simulink进行替代建模时,需要考虑如下因素进而做出决定:
在一些情况下,Stateflow能够比Simulink进行更接近于C语言的表达方法,但是这样的模型没有很好的外观状态,并不是很容易理解。在这些情况下,使用S-Function会更有利。
Stateflow 可以计算特定的状态安排,或进行for-loop的计算,在这些层面上要比Simulink更有效率,但是近些年来,使用Matlab语言进行如上计算,也变得更有效率一些。
当使用Simulink进行建模时,如果处理如下所述的状态,则可通过使用Stateflow来改善可读性。
举个例子,在触发器电路中,不同的输出对应于同一个输入。而且,状态变量的值被限定在0和1。 然而,在输入输出都为0或1的情况下,仍可以有无限多的状态分类。此外,在状态进入和执行两个动作间没有区别。换句话说,上诉几条中仅仅符合第一条,所以这时候应该使用Simulink进行建模。
关于采用Simulink还是Stateflow进行建模可以和几个人进行沟通协商,并最终取决于需要解决的实际问题。 Stateflow中是采用状态转换还是 流程图函数也需要进行考虑和决定,例如需要 定夺 使用 状态的转换和条件分支来 替代 使用状态的流程图函数。真值表也被分类为使用条件分支当中的方法。
此外,当使用Stateflow进行如上设计时,需要依据经典的模型样式进行设计,以便于能够更好的生成嵌入式代码。
Stateflow支持生成HDL代码。在实现HDL编码器时,应使用Mealy和Moore模式。此外,当需要对内部泄漏进行保护时, Moore 模式更适合。
需要注意,本指南不是针对HDL代码生成。
如下示例了模型分层中分割和布局的观念,可作为开发中的参考。这并不是一个明确的规则,但这是一个建模的基础方法。
搭建层的方法
层概念
名称 | 层概念 | 层目的 |
---|---|---|
顶层 | 功能层 | 大块功能部分 |
调度层 | 执行时间的表达(采样、顺序) | |
底层 | 子功能层 | 功能的细节表达部分 |
控制流层 | 根据处理和执行顺序划分(input → judgment → output,等) | |
选择层 | (select output with Merge block) 切换相应被激活的子系统并且执行 | |
数据流层 | 用于不可分离计算的层 |
对于顶层划分,主要有如下三个方法
简单控制模型
表现为功能层和调度层在同一层中,在这样的模型里,功能 = 执行单元。
例子: 控制模型只有一个采样周期,并且各功能模块都按照执行顺序进行排列。
复杂控制模型 α
调度层放在最顶层
这样会使手写代码更容易集成,但是功能模块被分割,导致模型可读性降低。
复杂控制模型 β
功能层放在最顶层,并且调度层在各功能模块下进行构建。
对于执行单元,“一个独立功能”的子系统是不必要的。因此,各子系统不一定全都设为原子子系统。
(对于如上列出的 type β, 将功能层子系统设为虚拟子系统更合适一些。如果将他们设为原子子系统,则可能会出现代数环。)
使用注释,功能概述必须在图层上描述或包含在子系统概述中,并显示为注释。
需要设置周期间隔和优先级及顺序。
设置多个周期间隔需要注意
在不同周期的连接系统中,有可能发生信号变量会在快周期任务中被调用,此时在慢周期中该信号还没有被计算出来。当不同周期进行连接时,需要一块固定的RAM区。因此,需要在顶层为每个不同周期的任务进行进行时间分割,保证底层中没有不同周期的模块进行连接。
设置优先级
在设计多个不同功能的系统时,优先级设定十分重要。建议尽量根据连接的顺序来使系统自动确定运行顺序。
对于顺序优先级,如下项需要进行设定:不同周期的优先级,和同周期的优先级。
周期间隔和优先级的设定方法:
如下描述的方法可大体上分为2种类型。
几个条件下存在的模式,例如配置单速率或多速率,原子子系统设置,是否使用模型引用等。这其中启用哪个功能都会直接影响到生成的C代码,所以需要根据项目的不同情况进行综合考量。
受影响的典型因素如下:
进行如上考量后,将会对使用的样式进行调整。
控制层是用于表示所有输入处理、中间处理、和输出处理的层。模块和子系统的排列十分重要。将多个混合的小功能分组,最后排列成3个大组,包括:输入处理,中间过程处理,输出处理。这三个大组构成了控制层的概念基础。与数据流层相似的排列方法是,都使用水平线代表模块运行顺序和方向,与其不同的是控制流层大多是由复合模块和子系统构成的。
在控制流程,同一水平线上的根据箭头所指方向进行顺序处理,同一垂直方向的代表有相同的优先级。
可以使用Area工具进行框选并标注几个模块的部分
控制层可以和具有功能的模块共存。
这些模块放置在子功能层和数据流层之间的区域。
当模块的数量特别多,且都在数据流层进行使用,这时可以使用功能单元打包的方式进行控制层的整合。这样会使模型更容易理解,同时提高了模型的可维护性。
即使仅由模块构成,不存在子系统和模块的混合体,如果模型的水平方向结构能够划分成 输入/中间过程/输出 处理,这样也可以称作为控制层。
选择层可以垂直或水平方向构建。(There is no significance to which orientation is chosen)
选择层是和控制层进行混合而成。
下图红色框内,根据条件只有一个子系统能够运行,所以这被称作为选择层。这层模型虽然能够被划分成 initial processing/intermediate processing (conditional control flow)/output processing
这三个处理模块组,但是称为控制层并不恰当,因为在控制层,水平方向代表不同的处理,并且具有相同优先级的并行处理在垂直方向上进行排列。而在选择层,垂直方向上的并不一定是并行处理,而是每次仅选择其中一个进行运行。
例子:
数据流层是在选择层/控制层之下的一层。
当仅表现为一个功能,并作为输入/中间过程/输出处理的具体实现,且不能再进行划分,则这样的一层被称作为数据流层。举个例子,一个持续计算不能被分割的系统。除一些特殊情况下,数据流层不允许出现子系统。
特殊情况:如下情况允许在数据流层出现子系统。
简单数据流层 例子
复杂数据流层 例子
当遇到输入处理和中间过程处理不能够清晰进行划分的时候,就如上面的例子所示,展开为数据流层。
当同时计算一个信号的前端反馈和后端反馈时,数据流层会变得复杂。甚至此种情况会有很多模块在同一层, 但是为了清晰的表达计算,仍旧不能在数据流层进行子系统的创建。当能够通过划分模型进行整合的时候,应该被打包成控制层而不是数据流层。
在真实的嵌入式系统中运行,需要Simulink模型生成嵌入式C代码。这有很大一部分受到Simulink配置项的影响,根据Simulink针对相关功能进行何种程度的建模,以及如何嵌入和真实系统的时间设定。
如果嵌入式系统里使用的任务与Simulink模型里调度的任务不同,这将产生很严重的影响。
本文中,并不对AUTOSAR标准进行解释,而会说明AUTOSAR的概念。用户不必完全符合AUTOSAR标准,但必须了解它,并作为建模中的参考。
当设计一个控制模型的时候,必须要遵循AUTOSAR 软件平台的概念,并要审查所设计的模型是归类到应用软件还是基础软件。
如果模型混合了应用和基础软件,需要在设计阶段就进行划分。
AUTOSAR 软件平台概念
AUTOSAR软件平台如下图所示
举个例子,设计引擎控制模型的时候,不建立以中断执行任务方式的模型,而是在应用软件层计算所有气缸共享的变量。例如,计算当前排放状态或目标力矩。当不规则中断从基础软件区发生,通过RTE传递到应用层时,计算结果能够被调用,实际的驱动器也能够被激活。AUTOSTAR的概念使得基础软件区的通用计算能够尽可能简单的放到应用层。
当Simulink模型全部构建好之后, 建议在中断服务区添加尽量简单的计算。中断服务程序的简单可以减少其所占时间,这样能保证系统调用任务时间间隔的准确性。如果可以,中断中不要包含标准的PID计算,推荐放置仅执行设定动作的函数。当然,不能缺少必要的计算。举个例子,对于错误诊断,即使是复杂计算,在该执行的时刻还是必须要诊断出结果的。
对于那些比中断服务程序慢的部分,和接收指令要比执行速度快的部分,不应该给出直接执行的代码,而是要让目标值或阶段值在下一个命令到来之前,通过线性插值的方法传递出去。
RCP(Rapid Control Prototyping)快速控制原型,是指使用模型或类模型自动生成原型代码的方式进行开发。Simulink生成的代码不仅仅是原型,更接近于整体的解决方案,这种解决方案一般称之为MBD(Mode based Software Development)基于模型程序开发。
使用RCP等设备建模类似于AUTOSAR中软件更新的概念。当然,生成的代码不符合AUTOSAR规范。例如,RCP的I/O软件允许供应商提供的S函数进行链接,并且允许用户设置应用程序区域。应用程序中用户自定义函数和S函数能够在Simulink中直接连接,不需要考虑对RAM等因素的影响。
生成的C代码在实时操作系统上运行,Simulink 里I/O相关会生成在不同的源代码文件中,实时操作部分和作为中断处理的部分会自然分离。用户不必考虑那些平台;厂商创建的 I/O S-function会在需要的时候运行,并且对于 应用程序建模,不用考虑I/O处理的内容和时间。
具有这种软件结构的真实的控制模型/软件,会有更多的优势。由于RCP能够集中精力开发应用程序,而不必考虑软件结构,他们会自然的选择AUTOSAR 平台。换言之,如果你的产品不满足AUTOSAR 标准,并且在RCP上使用AUTOSAR的概念进行开发,你必须自定义生成的代码,并且从MBD开发成果的共享中分离出来。
嵌入式软件调度器设计中,有单任务和多任务的概念。
对于单任务,假定基础周期为2ms,当2ms、8ms、10ms在系统中存在时,这些时间片都是基于2ms的计时器进行创建的。每2ms的执行顺序为:8ms的任务下每4个2ms任务后执行一次,10ms就是每5个2ms。需要注意的是,要使用低频率的任务片来处理复杂的运算,并且2ms、8ms、10ms都是由相同的2ms进行周期计算。因为所有的运算都要在2ms内完成,以便保证嵌入式软件的实时性,在这种情况下8ms和10ms的任务被分割成了几段,以保证所有2ms的计算量基本持平。在这种方法下,可以通过分区来减少每个周期的计算量,并且使得CPU负载平均分配。
基于如上原因,10ms的任务片被分成如下几个部分。
Fundamental frequency | Offset |
---|---|
10ms | 0ms |
10ms | 2ms |
10ms | 4ms |
10ms | 6ms |
10ms | 8ms |
同理可知,8ms的任务片被分割成了4部分。
当然,绝对的平均分配是不现实的,某些运算功能不能够被分配到所有周期,但重要的是报保证CPU的平均一致的负载率
如何设置任务的运行频率
设置Tasking mode for periodic sample times 为 Single Tasking
然后在子系统的“Sample Time”中输入 “sampling period, offset” 的值。可以指定运行周期的子系统为 原子子系统。
多任务为使用实时操作系统的情况下,系统支持多任务周期设定。如之前介绍单任务中所述,在单任务系统中,平衡CPU负载率不是自动的,需要进行巧妙的设定。而在多任务系统中,CPU会根据当前状态进行自动计算,并且不需要进行特殊的任务分配和设定。系统会从优先级高的任务开始计算并给出结果,任务的优先级由开发人员进行设定。大多数情况下,需要快速执行的任务被分配高的优先级。
在周期内完成计算是十分重要的,包括慢任务,和当高优先级任务执行计算且CPU 释放时,执行下一个优先级任务的计算。高优先级任务会打断低优先级任务,并会先执行完高优先级的计算。
如果子系统B有20ms的运行周期,使用了10ms运行周期的子系统A的输出,当B还在进行计算的时候A可能会有不同的输出。如果在运算过程中发生了值得改变,子系统B可能会运算出一个错误的结果。举个例子,在B中如果存在首次计算值与A的输出比较的运算,且最终输出依据比较的结果,此时有可能发生在B计算过程中A的值发生改变,导致最终输出不正确的情况。为了避免此种情况发生,如果任务主体发生改变,从A得到的输出值需要在被B使用之前进行固定。换言之,即便在B运算过程中A的输出值发生改变,因为调用了不同的RAM区,最终的计算结果也不受影响。
当在Simulink中创建模型并且在Simulink中连接了具有不同运行周期的子系统时,Simulink会自动保留所需的RAM。
当然,如果不同运行周期的输入值获得是通过 手写代码集成的方式,则相应嵌入式软件工程师需要进行相应RAM区保留的设置。在AUTOSAR中的RTW层,就是在接收和发送出口方面都定义了了不同的RAM。
单任务
2ms循环内信号值相同,但是需要注意不同的2ms运算值和上一个2ms的值可能不同。如果2-1 、 2-2 使用了F 1中的信号A,则 2-1 、 2-2 会使用不同2ms任务计算出的值,此处要进行影响识别。
多任务
对于多任务,不能够指定使用计算结果的时间点。在多任务系统中,无论是否为不同运行周期,总是需要给信号变量和传递变量分配不同的RAM。
在执行任务的新计算之前,所有的值都会一次性复制一遍。