|
汽车零部件采购、销售通信录 填写你的培训需求,我们帮你找 招募汽车专业培训老师
全文约7000字,你将看到以下内容:
模型的多任务与调度
基于步长的模型
基于调用的模型
完结撒花
获取《autoMBD原创技术文章合集》
点击上面链接免费获取156页文章合集
本期是整个《MBD的Simulink使用技巧系列文章》的完结篇,距离第一篇发布刚好一年的时间,这个系列总计发布了15篇,累计字数超过了5.5w+,最后求一波转发、点赞和关注支持一下
。
本期将介绍模型生成的代码如何实现多任务的调度和执行。
点击以下链接,可以查看MBD的Simulink使用技巧系列的往期所有文章:
MBD的Simulink使用技巧①:Simulink代码生成的基本概念MBD的Simulink使用技巧②:详解代码生成中的模型与代码MBD的Simulink使用技巧③:虚拟子系统与原子子系统的代码生成MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程
MBD的Simulink使用技巧⑤:详解自动代码生成的配置与优化
MBD的Simulink使用技巧⑥:代码生成目标配置工具
MBD的Simulink使用技巧⑦:自动生成代码的集成方法
MBD的Simulink使用技巧⑧:函数原型、传参的控制与修改
MBD的Simulink使用技巧⑧(续):函数接口封装的控制和修改
MBD的Simulink使用技巧⑨:代码数据类型的修改和控制
MBD的Simulink使用技巧⑩:数据存储类的使用方法
MBD的Simulink使用技巧?:详解数据对象与数据字典(上)
MBD的Simulink使用技巧?:详解数据对象与数据字典(中)
MBD的Simulink使用技巧?:详解数据对象与数据字典(下)
特别提示:在本篇文章中使用到的模型等文件,可以在autoMBD资源库的“临时资源分享”文件夹中找到(资源序号为tA22、tA37、tA38、tA39)。资源库链接的获取可以在《autoMBD原创技术文章合集》中找到(见文章开头)。
1 模型的多任务与调度
Simulink模型有两种运行的方式:
基于步长(Rate-based)
基于调用(Function-call based)
基于步长(Step)的模型,以步长为间隔,在每个时间节点上执行模型的计算任务。可以有多个不同的步长,但必须有一个最小的步长,称为基础步长,其他步长必须是基础步长的整数倍。
基于调用的模型,也可以称为基于触发(Trigger)的模型。它需要触发源和触发子系统,触发源支持上升沿、下降沿、上升下降沿和函数调用(Function-call)四种。如下图所示:
触发子系统 - From autoMBD
触发源的四种类型 - From autoMBD
Tips:这里只展示了最简单的触发子系统,Simulink库中还有很多其他类型的触发子系统。
Function-call是一种特殊触发源,可以通过Stateflow、S-function产生,或者使用Function-Call Genertor生成:
Function-Call Genertor - From autoMBD
基于步长和基于调用的模型都可实现多任务(Multi tasks),在同一个模型中也可以同时使用这两种方式。
对于多任务模型,生成代码时需要考虑如何在代码中执行各个任务,以及解决数据传输问题。为了解决多任务的执行问题,Simulink会产生专门负责任务调度(Tasks Scheduling)的代码。
不同的模型(基于步长 or 调用),多任务调度的代码会有所不同,适用的场景也不一样。
为了方便讲解,这里制作了一个思维导图,用于展示Simulink中任务调度和执行的不同方式:
Function-Call Genertor - From autoMBD
基于这个思维导图,下面逐一介绍不同情况下的任务调度的控制和设计。
Tips:正如前面的文章提到的,在应用于嵌入式的代码生成,往往是定步长模型,本文也只讨论定步长模型。
2 基于步长的模型
2.1 单步长(Single-rate)
Tips:本文将“Single-rate”翻译为“单步长”,将“Multirate”翻译为“多步长”,而不是翻译为其本义“速率”,笔者认为这样更容易理解。
单步长是最简单一类场景,即整个模型都运行在同一个步长下。单步长也可以实施多个任务,但总体来说我们还是把它当作单任务系统,或者说多个子任务在同一个线程(Thread)内被计算。
同一步长内的不同模块可以设置不同的优先级。模块的优先级在一定程度上可以影响子任务或子系统执行的先后顺序,从而改变生成的代码中代码的执行顺序。
右键单击某一模块或者子系统,选择“Properties...”,即可看到模块或子系统的优先级设置窗口:
优先级设置 - From autoMBD
这里优先级的设置,不限数字的范围,但需要为整数,值越小,优先级越高。
可以在“DEBUG”标签页下,选择“Information Overlays -> Execution Order”,显示所有模块的执行顺序:
显示执行顺序 - From autoMBD
以我们经常使用的PI控制(资源序号为tA22)为例,其执行顺序如下:
显示执行顺序 - From autoMBD
Tips:上图中,积分的增益运算被放在最后,是因为积分方法为向前欧拉(Forward Euler)方法。
但是,并非所有的模块或子系统都受到优先级的控制,最终决定模块或子系统执行顺序还是Simulink引擎。
一般来说,没有任何关联的两个模块或子系统之间,模块的优先级可以起作用;有先后依赖关系的两个模块或子系统之间不受优先级的控制。
2.2 多步长(Multirate)
2.2.1 多步长单线程
多步长实际上就意味着多任务,因为每一个步长上至少有一个计算任务。
Tips:特别提示,至少有一个最小步长作为基础步长,其他步长设置只能是基础步长的整数倍。
多步长单线程(Multirate Single-Tasking)指的是:所有的任务都会被放到同一个基础步长内被执行,由调度器Rate Scheduler决定哪些计算任务在不同的时刻分别执行,从而实现多个步长的效果。
多步长单线程和单步长是相似的,在同一个线程内执行各个计算任务。只不过多步长的任务分布在不同的步长上面,因此需要额外的调度器来处理多步长。
在默认情况下,多步长模型生成的代码采用的即为多步长单线程方式。
下面以一个多步长的PI控制器(资源序号为tA37)为例:
多步长PI控制器 - From autoMBD
Tips:在多步长模型中,建议打开步长的颜色显示,以便观察不同模块的不同步长。
该控制器在基础PI控制(资源序号为tA22)的基础上,新增了两个滤波器(Filter1和Filter2)。为了方便生成代码的展示,滤波器和PI控制器均设置为原子子系统。
Tips:原子子系统相关介绍请参见《MBD的Simulink使用技巧③:虚拟子系统与原子子系统的代码生成》
滤波器和PI控制器的步长分别设置为:
Filter1,0.0001s,红色,且为基础步长
Filter2,0.0002s,绿色
PI controller,0.001s,蓝色
此时生成代码Step函数如下:
/* Model step function */void autoMBD_example_PI_Multirate_step(void){ /* Outputs for Atomic SubSystem: '<Root>/Filter1' */Filter1();
/* End of Outputs for SubSystem: '<Root>/Filter1' */if (autoMBD_example_PI_Multirate_M->Timing.TaskCounters.TID[1] == 0) { /* Outputs for Atomic SubSystem: '<Root>/Filter2' */Filter2();
/* End of Outputs for SubSystem: '<Root>/Filter2' */ }
/* Sum: '<Root>/Sum2' */ autoMBD_example_PI_Multirate_B.Err = autoMBD_example_PI_Multirate_B.DiscreteTransferFcn - autoMBD_example_PI_Multirate_B.DiscreteTransferFcn1; if (autoMBD_example_PI_Multirate_M->Timing.TaskCounters.TID[2] == 0) { /* Outputs for Atomic SubSystem: '<Root>/PI_Controller' */ PI_Controller();
/* End of Outputs for SubSystem: '<Root>/PI_Controller' */
/* Outport: '<Root>/PI_Ctrl' */ autoMBD_example_PI_Multirate_Y.PI_Ctrl = autoMBD_example_PI_Multirate_B.PI_Ctrl; }
rate_scheduler();}可以看到,Filter1(),Filter2()和PI_Controller()都在Step函数中被执行,执行的条件是:
Timing.TaskCounters.TID[x] == 0
其中TID[x]代表的是Time Idex,即步长的索引号。索引号“0”保留给频率最快的基础步长;索引号越大,表示步长越大。调度器Rate Scheduler会为除基础步长以外的每一个步长设置一个Counter用于计数。调度器Rate Scheduler的代码如下所示:
/* * This function updates active task flag for each subrate. * The function is called at model base rate, hence the * generated code self-manages all its subrates. */static void rate_scheduler(void){/* Compute which subrates run during the next base time step. Subrates * are an integer multiple of the base rate counter. Therefore, the subtask * counter is reset when it reaches its limit (zero means run). */ (autoMBD_example_PI_Multirate_M->Timing.TaskCounters.TID[1])++;if ((autoMBD_example_PI_Multirate_M->Timing.TaskCounters.TID[1]) > 1) {/* Sample time: [0.0002s, 0.0s] */ autoMBD_example_PI_Multirate_M->Timing.TaskCounters.TID[1] = 0; }
(autoMBD_example_PI_Multirate_M->Timing.TaskCounters.TID[2])++;if ((autoMBD_example_PI_Multirate_M->Timing.TaskCounters.TID[2]) > 9) {/* Sample time: [0.001s, 0.0s] */ autoMBD_example_PI_Multirate_M->Timing.TaskCounters.TID[2] = 0; }}
由于其他步长都是基础步长的整数倍,所以很容易通过基础步长计数来判断其他步长的任务执行条件。
多步长单线程和单步长生成的代码,有一个共同的特点:由于是单线程运行,各个任务之间不会发生抢占。
这要求所有计算任务的执行时间总和必须小于基础步长时间,这样才能保证最差的情况下,所有任务都能得到正确的执行。
2.2.2 多步长多线程(Multirate Multitasking)
上述两种方式的多任务都不能发生抢占(Preemption),不同任务的重要程度被一视同仁。
但实际中,有的计算任务就是比其他任务重要,它必须不被阻碍地执行,这就需要多步长多线程方法了。
多步长多线程方法,会为每一个步长下的任务都生成一个Step函数,基础步长为Step0;其余的按照步长大小,由小到大,依次为Step1、Step2、...。
要实现多步长多线程也很简单,在模型求解器配置页面中,勾选“Treat each discrete rate as a separate task”:
多步长多线程求解器设置 - From autoMBD
一般情况下,这样设置后还不能直接编译,可能会报错。以多步长的PI控制器(资源序号为tA37)为例,修改求解器后编译报错:
编译报错 - From autoMBD
这是由于速率不匹配造成的,速率不匹配会造成任务间数据传输问题。可以添加速率过渡模块解决:
速率过渡模块 - From autoMBD
添加过渡模块的模型如下所示:
添加速率过渡模块的PI控制器 - From autoMBD
也可以下求解器配置页面中,勾选“Automatically handle rate transition for data transfer”:
Automatically handle rate transition for data transfer - From autoMBD
这样模型会自动处理速率不匹配的问题,自动添加速率匹配模块:
自动添加的速率匹配模块 - From autoMBD
Tips:点击上图中的灰色小标签,可以将隐含的速率过渡模块显示出来。
本文以后一种为例进行展示,修改后的多步长多线程PI控制器(资源序号为tA38)生成的Step如下:
/* Model step functionfor TID0 */void autoMBD_example_PI_MultirateMultitasking_step0(void) /* Sample time: [0.0001s, 0.0s] */{ /* Update the flag to indicate when data transfers from * Sample time: [0.0001s, 0.0s] to Sample time: [0.0002s, 0.0s] */ (autoMBD_example_PI_Multirate_M->Timing.RateInteraction.TID0_1)++;if ((autoMBD_example_PI_Multirate_M->Timing.RateInteraction.TID0_1) > 1) { autoMBD_example_PI_Multirate_M->Timing.RateInteraction.TID0_1 = 0; }
/* Update the flag to indicate when data transfers from * Sample time: [0.0001s, 0.0s] to Sample time: [0.001s, 0.0s] */ (autoMBD_example_PI_Multirate_M->Timing.RateInteraction.TID0_2)++;if ((autoMBD_example_PI_Multirate_M->Timing.RateInteraction.TID0_2) > 9) { autoMBD_example_PI_Multirate_M->Timing.RateInteraction.TID0_2 = 0; }
/* Outputs for Atomic SubSystem: '<Root>/Filter1' */Filter1();
/* End of Outputs for SubSystem: '<Root>/Filter1' */
/* RateTransition generated from: '<Root>/Filter2' */if (autoMBD_example_PI_Multirate_M->Timing.RateInteraction.TID0_1 == 1) { /* RateTransition generated from: '<Root>/Filter2' */ autoMBD_example_PI_MultirateM_B.TmpRTBAtFilter2Outport1 = autoMBD_example_PI_Multirate_DW.TmpRTBAtFilter2Outport1_Buffer0; }
/* End of RateTransition generated from: '<Root>/Filter2' */
/* Sum: '<Root>/Sum2' */ autoMBD_example_PI_MultirateM_B.Err_d -= autoMBD_example_PI_MultirateM_B.TmpRTBAtFilter2Outport1;
/* RateTransition generated from: '<Root>/Sum2' */if (autoMBD_example_PI_Multirate_M->Timing.RateInteraction.TID0_2 == 1) { autoMBD_example_PI_Multirate_DW.Err_Buffer = autoMBD_example_PI_MultirateM_B.Err_d; }
/* End of RateTransition generated from: '<Root>/Sum2' */}
/* Model step functionfor TID1 */void autoMBD_example_PI_MultirateMultitasking_step1(void) /* Sample time: [0.0002s, 0.0s] */{ /* Outputs for Atomic SubSystem: '<Root>/Filter2' */Filter2();
/* End of Outputs for SubSystem: '<Root>/Filter2' */
/* RateTransition generated from: '<Root>/Filter2' */ autoMBD_example_PI_Multirate_DW.TmpRTBAtFilter2Outport1_Buffer0 = autoMBD_example_PI_MultirateM_B.DiscreteTransferFcn1;}
/* Model step functionfor TID2 */void autoMBD_example_PI_MultirateMultitasking_step2(void) /* Sample time: [0.001s, 0.0s] */{ /* RateTransition generated from: '<Root>/Sum2' */ autoMBD_example_PI_MultirateM_B.Err = autoMBD_example_PI_Multirate_DW.Err_Buffer;
/* Outputs for Atomic SubSystem: '<Root>/PI_Controller' */ PI_Controller();
/* End of Outputs for SubSystem: '<Root>/PI_Controller' */
/* Outport: '<Root>/PI_Ctrl' */ autoMBD_example_PI_MultirateM_Y.PI_Ctrl = autoMBD_example_PI_MultirateM_B.PI_Ctrl;}
可以看到,一个生成了三个Step函数:
void autoMBD_example_PI_MultirateMultitasking_step0(void)
void autoMBD_example_PI_MultirateMultitasking_step1(void)void autoMBD_example_PI_MultirateMultitasking_step2(void)
其中Step0是基础步长(0.0001),除了本步长的计算任务以外,还有Data Flag和Rate Transition相关的处理代码,这些代码是实现任务间数据传输的代码。
由于多步长任务的速度不同,高优先级任务还会发生抢占,必须保证数据的传输是正确的。任务间数据传输,主要有两点需要考虑:
双击速率过渡模块,可以看到它的两个主要的配置参数:
速率过渡模块配置参数 - From autoMBD
勾选第一个选项,可以保证数据不会因为任务抢占而发生变更,即保证数据完整性;勾选第二个,可以保证数据不会因为任务抢占而丢弃,每个数据都会被传输,相应的数据的延时会增大。
具体怎么勾选要根据实际需求来,我的经验是:一般情况下,勾选数据完整性即可;如果始终希望最新的数据被传输,两个都不勾选;如果希望每一个数据都不漏掉、都被传输,两个都勾选。
此外,在生成的voidrt_OneStep(void)函数中展示了如何实现多步长间的任务抢占:
voidrt_OneStep(void){static boolean_T OverrunFlags[3] = { 0, 0, 0 };
static boolean_T eventFlags[3] = { 0, 0, 0 };/* Model has 3 rates */
static int_T taskCounter[3] = { 0, 0, 0 };
int_T i;
/* Disable interrupts here */
/* Check base rate for overrun */if (OverrunFlags[0]) { rtmSetErrorStatus(autoMBD_example_PI_Multirate_M, "Overrun");return; }
OverrunFlags[0] = true;
/* Save FPU context here (if necessary) *//* Re-enable timer or interrupt here */
/* * For a bare-board target (i.e., no operating system), the * following code checks whether any subrate overruns, * and also sets the rates that need to run this time step. */for (i = 1; i < 3; i++) {if (taskCounter == 0) {if (eventFlags) { OverrunFlags[0] = false; OverrunFlags = true;
/* Sampling too fast */ rtmSetErrorStatus(autoMBD_example_PI_Multirate_M, "Overrun");return; }
eventFlags = true; } }
taskCounter[1]++;if (taskCounter[1] == 2) { taskCounter[1]= 0; }
taskCounter[2]++;if (taskCounter[2] == 10) { taskCounter[2]= 0; }
/* Set model inputs associated with base rate here */
/* Step the model for base rate */ autoMBD_example_PI_MultirateMultitasking_step0();
/* Get model outputs here */
/* Indicate task for base rate complete */ OverrunFlags[0] = false;
/* Step the model for any subrate */for (i = 1; i < 3; i++) {/* If task "i" is running, don't run any lower priority task */if (OverrunFlags) {return; }
if (eventFlags) { OverrunFlags = true;
/* Set model inputs associated with subrates here */
/* Step the model for subrate "i" */switch (i) {case1 : autoMBD_example_PI_MultirateMultitasking_step1();
/* Get model outputs here */break;
case2 : autoMBD_example_PI_MultirateMultitasking_step2();
/* Get model outputs here */break;
default :break; }
/* Indicate task complete for sample time "i" */ OverrunFlags = false; eventFlags = false; } }
/* Disable interrupts here *//* Restore FPU context here (if necessary) *//* Enable interrupts here */}
这段代码中,最关键的是三个控制变量:
OverrunFlags
eventFlagstaskCounter
其中:
OverrunFlags用来指示当前步长任务的运行状态,true为正在运行;
eventFlags用来指示任务的执行条件,true会触发任务的执行;taskCounter用来计数,实现多步长,taskCounter = 0会设置eventFlags为true。
任务抢占的基本逻辑是:如果当前步长的OverrunFlags为true,那么所有低优先级的任务不会被执行;只有OverrunFlags为false的时候,低优先级才能被执行。
Tips:代码注释:If task "i" is running, don't run any lower priority task.
这里有一个隐含的条件:在多步长多线程模型中,步长越小,任务优先级越高,基础步长的任务优先级最高,始终会被执行。
任务抢占也只能发生在小步长任务抢占大步长任务。
这里实现的实际上是一个软件多线程,控制线程的行为是通过变量来控制实现的。它只需要一个ISR,即在基础步长的ISR中,周期调用void rt_OneStep(void)即可。
其实也可以使用硬件多线程,开启多个ISR。每个ISR服务一个Step函数,其调用周期与对应的步长匹配即可。
硬件多线程可以提供更加准确的计时时钟,以及硬件的抢占机制,任务的优先级与ISR的优先级相同。
这种方法就不需要执行voidrt_OneStep(void)了。
3 基于调用的模型
3.1 触发源
在思维导图中,基于调用的模型包含两种:周期调用和触发调用。
这两种本质上是一样的,如果触发源是周期产生的,那么就是周期调用,周期由触发源决定;如果触发源是条件产生,或随机产生,即为触发调用。
触发源可以包含在模型中,不过要注意,模型中的触发源都是基于步长的。所以,这种情况就属于基于步长和基于触发的混用。
Tips:基于调用的模型,不太符合仿真的思路(仿真的思路是基于步长、迭代运算),但它符合软件的思路。由于MBD通常就是开发算法软件,所以时常需要将基于两者一起使用。
能产生上升沿、下降沿、Function-call的模块都可以作为触发源,常用的包括:
Step模块
PWM Generator模块Function-Call Generator模块...
触发源也可以来自外部,来自外部的触发源只支持Function-call。要输入外部触发源,在In模块的配置中,勾选“Output function call”即可:
配置In模块输入外部触发源 - From autoMBD
基于调用的模型,其计算任务执行的基础是触发子系统(Trigger Subsystem),模型的运算在触发子系统中实施。
在触发子系统中是没有步长的概念的,所以触发子系统中的模块不能包含步长,只能设置为-1。
触发子系统也是原子子系统,原子子系统的相关代码生成方法同时适用于触发子系统,包括可复用代码的生成等。
Tips:原子子系统相关介绍请参见《MBD的Simulink使用技巧③:虚拟子系统与原子子系统的代码生成》
3.2 完全的调用模型
如果一个模型全部由触发子系统构成,且触发源全部来自于外部,那么该系统就是完全的调用模型,生成的代码不再包含Step函数。
对多步长的PI控制器(资源序号为tA37)进行一些修改,得到完全基于调用的PI控制器模型(资源序号为tA39),如下所示:
完全基于调用的PI控制器模型 - From autoMBD
该模型的两个滤波器和PI控制都由触发子系统实现,输入端口3、4、5都使能了“Output function call”,用于输入外面Function-call信号。
对这个模型生成代码,不会再生成Step函数,只会生成三个触发子系统的执行函数,如下所示:
/* Model step function for TID1 */void CallFilter1(void) /* Explicit Task: CallFilter1 */{ real_T denAccum;
/* RootInportFunctionCallGenerator generated from: '<Root>/CallFilter1' incorporates: * SubSystem: '<Root>/Filter1' */ /* DiscreteTransferFcn: '<S1>/Discrete Transfer Fcn' incorporates: * Inport: '<Root>/Req_Ctrl' */ denAccum = autoMBD_example_PI_MultirateT_U.Req_Ctrl - -0.9048 * autoMBD_example_PI_Multirate_DW.DiscreteTransferFcn_states;
/* DiscreteTransferFcn: '<S1>/Discrete Transfer Fcn' */ autoMBD_example_PI_MultirateT_B.DiscreteTransferFcn = 0.04762 * denAccum +0.04762 * autoMBD_example_PI_Multirate_DW.DiscreteTransferFcn_states;
/* Update for DiscreteTransferFcn: '<S1>/Discrete Transfer Fcn' */ autoMBD_example_PI_Multirate_DW.DiscreteTransferFcn_states = denAccum;
/* End of Outputs for RootInportFunctionCallGenerator generated from: '<Root>/CallFilter1' */}
/* Model step function for TID2 */void CallFilter2(void) /* Explicit Task: CallFilter2 */{ real_T denAccum;
/* RootInportFunctionCallGenerator generated from: '<Root>/CallFilter2' incorporates: * SubSystem: '<Root>/Filter2' */ /* DiscreteTransferFcn: '<S2>/Discrete Transfer Fcn1' incorporates: * Inport: '<Root>/Feedback' */ denAccum = autoMBD_example_PI_MultirateT_U.Feedback - -0.9048 * autoMBD_example_PI_Multirate_DW.DiscreteTransferFcn1_states;
/* DiscreteTransferFcn: '<S2>/Discrete Transfer Fcn1' */ autoMBD_example_PI_MultirateT_B.DiscreteTransferFcn1 = 0.04762 * denAccum +0.04762 * autoMBD_example_PI_Multirate_DW.DiscreteTransferFcn1_states;
/* Update for DiscreteTransferFcn: '<S2>/Discrete Transfer Fcn1' */ autoMBD_example_PI_Multirate_DW.DiscreteTransferFcn1_states = denAccum;
/* End of Outputs for RootInportFunctionCallGenerator generated from: '<Root>/CallFilter2' */}
/* Model step function for TID3 */void CallPIController(void) /* Explicit Task: CallPIController */{ real_T rtb_Err;
/* RootInportFunctionCallGenerator generated from: '<Root>/CallPIController' incorporates: * SubSystem: '<Root>/PI_Controller' */ /* Sum: '<S3>/Sum2' */ rtb_Err = autoMBD_example_PI_MultirateT_B.DiscreteTransferFcn - autoMBD_example_PI_MultirateT_B.DiscreteTransferFcn1;
/* Outport: '<Root>/PI_Ctrl' incorporates: * DiscreteIntegrator: '<S3>/Discrete-Time Integrator' * Gain: '<S3>/Kp' * Sum: '<S3>/Sum1' */ autoMBD_example_PI_MultirateT_Y.PI_Ctrl = 2.0 * rtb_Err + autoMBD_example_PI_Multirate_DW.DiscreteTimeIntegrator_DSTATE;
/* Update for DiscreteIntegrator: '<S3>/Discrete-Time Integrator' incorporates: * Gain: '<S3>/Ki' */ autoMBD_example_PI_Multirate_DW.DiscreteTimeIntegrator_DSTATE += 3.0 * rtb_Err * 0.001;
/* End of Outputs for RootInportFunctionCallGenerator generated from: '<Root>/CallPIController' */}在生成的的ert_main.c中,也没了rt_OneStep函数。用户要使用生成的代码,直接在自己的代码中,或者在ISR中,调用生成的触发子系统执行函数即可。下面的代码是一个示例:void sample_usage_CallFilter1(void){/* * Set task inputs here: */
/* * Call to exported function */ CallFilter1();
/* * Read function outputs here */}
Tips:很多工程师习惯使用完全的调用模型,因为它可以很方便的生成调用函数。
3.3 隐含的步长
前文提到,触发子系统没有步长的概念。但在有的情况下,它会要求隐含地使用步长。
如果触发子系统中执行的任务仅仅只是加减乘除的数学运算,这种情况不包含隐形步长的使用;如果执行的任务,与时间序列有关联,例如积分运算,那么要求隐含地使用步长。
在完全基于调用的PI控制器模型(资源序号为tA39)中,其PI控制器包含一个积分器。由于该积分器由于处于触发子系统中,所以其采用时间设置为-1:
积分器采用时间设置为-1 - From autoMBD
但积分器没有确切的步长/采样时间是无法运算的,所以必须确定其步长的大小。
触发子系统的步长由触发源决定,所以触发源要设置期望的步长。这里的触发源来自外部,所以在In模块中要设置步长:
在In模块中设置步长 - From autoMBD
同时还要把Tigger触发源的采样类型设置为“periodic”:
Trigger采样时间类型periodic - From autoMBD
这样就能确定了触发子系统中积分器的步长了。调用生成的PI控制器代码时,也要按照设定的步长周期调用,才能得到正确的结果。
4 完结撒花
终于,历时一整年的《MBD的Simulink使用技巧》系列文章完结了
。
这个系列文章介绍了大量建模和代码生成的方法技巧,而且是系统化、集中地展示出来,大部分的展示内容都专门搭建了Example模型,更加生动、具体的展示相关方法。
虽然很难100%覆盖所有的方法和技巧,但绝大部分都有所涉及。如果后续看到有其他比较重要的建模方法,也还会进行补充。
这期间我收到了大量的读者反馈,这些反馈是鼓励我完成这些文章的动力源泉:
部分读者反馈 - From autoMBD
接下来我会花一些时间,对全部15篇文章、5.5w字的内容进行整理,将它们都整合到《autoMBD原创技术文章合集》中。
届时,《文章合集》的完整框架就基本完成了,《合集》会包含MBD概述、MDB建模技巧、硬件支持包的使用、MBD电机控制实战等内容,涵盖概念入门、建模技巧和进阶实战,全面和系统地介绍MBD技术。
最终实现的目标是:只看autoMBD的《文章合集》,即可了解和学习绝大部分MBD相关知识。
敬请期待新一版文章合集的发布,欢迎转发、点赞和关注支持,让autoMBD的文章可以帮到更多有需要的人。
如果你觉得文章对你有帮助,欢迎关注,我会持续更新更多原创文章
与我交流讨论
由于新开通的公众号均不支持留言,欢迎给我发私信,我会及时回复的
。
版权归autoMBD所有,转载请注明作者和来源。
|
|