将生成的独立可执行程序部署到目标硬件
默认情况下,为嵌入式编码器®软件生成独立的不需要外部实时执行器或操作系统的可执行程序。一个独立的程序只需要很少的修改就可以适应目标硬件。独立程序架构支持用单个或多个采样率执行模型。金宝app
生成独立程序
生成一个独立的程序:
选择模型配置参数生成一个示例主程序。这使目标操作系统菜单。
从目标操作系统菜单中,选择
BareBoardExample
。生成的代码。
根据这些因素,多速率模型会生成不同的代码:
模型是在单任务模式还是多任务模式下执行。
是否正在生成可重用代码。
这些因素会影响生成代码中使用的调度算法,在某些情况下还会影响模型入口点函数的API。下面几节将讨论这些变体。
独立程序组件
独立程序的核心是主循环。在每次迭代中,主循环执行一个后台任务或空任务,并检查终止条件。
主循环由计时器周期性地中断。这个函数rt_OneStep
要么作为计时器中断服务例程(ISR)安装,要么在每个时钟步从计时器ISR调用。
执行驱动程序,rt_OneStep
,序列调用
功能。的操作模型
_steprt_OneStep
根据生成模型是单速率还是多速率而不同。在单速率模型中,rt_OneStep
只需调用
函数。在多速率模型中,模型
_steprt_OneStep
根据它们运行的速率对块的执行进行优先级排序和调度。
主程序
操作概述
下面的伪代码显示了一个主程序的执行。
main(){初始化(包括安装rt_OneStep作为实时时钟的中断服务例程)初始化和启动定时器硬件启用中断While(不是Error)和(time < final time)后台任务EndWhile禁用中断(禁用rt_OneStep执行)完成任何后台任务Shutdown}
伪代码是用于驱动模型的控制程序的设计。主程序仅部分实现了该设计。你必须根据你的规格修改它。
修改主程序指南
本节描述您应该在主程序模块的生产版本中进行的最小修改,以实现您的控制程序。
调用
。模型
_initialize初始化特定于目标的数据结构和硬件,如adc或dac。
安装
rt_OneStep
作为一个定时ISR。初始化定时器硬件。
启用定时器中断并启动定时器。
请注意
rtModel
是否处于有效状态
已被调用。定时器中断的服务不应该开始,直到模型
_initialize
已被调用。模型
_initialize可选地,在主循环中插入后台任务调用。
主循环终止时(如适用):
禁用定时器中断。
执行特定于目标的清理,例如将dac归零。
检测和处理错误。请注意,即使您的程序被设计为无限期运行,您也可能需要处理严重的错误条件,例如定时器中断溢出。
您可以使用宏
rtmGetErrorStatus
和rtmSetErrorStatus
检测错误并发出错误信号。
rt_OneStep和调度注意事项
操作概述
的操作rt_OneStep
取决于
无论你的模型是单费率还是多费率。在单速率模型中,模型中所有块的采样时间和模型的固定步长是相同的。样本时间和步长不满足这些条件的模型称为多速率模型。
您的模型的求解器模式(
SingleTasking
与多任务处理
)
嵌入式实时系统目标文件允许的求解器模式总结了单速率模型和多速率模型允许的求解器模式。请注意,这仅适用于单费率模型SingleTasking
允许使用求解器模式。
嵌入式实时系统目标文件允许的求解器模式
模式 | 单频 | 多重速率的 |
---|---|---|
|
允许 |
允许 |
|
不允许 |
允许 |
|
允许 (默认为 |
允许 (默认为 |
生成的代码rt_OneStep
(以及相关的定时数据结构和支持功能)是根据模型中的速率数量和求解器模式量身定制的。金宝app下面几节将讨论每种可能的情况。
单速率单任务操作
对于单速率模型,唯一有效的求解器模式是SingleTasking
。这些模型以“单速率”运行。
的设计rt_OneStep
在单一费率计划中。
rt_OneStep(){检查中断溢出或其他错误启用“rt_OneStep”(计时器)中断Model_Step()——时间步结合输出,日志记录,更新}
对于单费率情况,生成的
函数是模型
_step
无效模型_step(空白)
单频rt_OneStep
是用来执行的
在一个时钟周期内。为了执行这个时间限制,模型
_steprt_OneStep
维护并检查计时器超时标志。在进入时,计时器中断被禁用,直到检查了溢出标志和其他错误条件。如果超限标志是空的,rt_OneStep
设置标志,并在启用定时器中断的情况下继续。
只有在成功返回时,溢出标志才会被清除
。因此,如果模型
_steprt_OneStep
在完成之前是否被重新中断
,则通过溢出标志检测重新中断。模型
_step
Reinterruption的rt_OneStep
计时器是一个错误条件。如果检测到此条件rt_OneStep
发出错误信号并立即返回。(请注意,如果您想以不同的方式处理条件,可以更改此行为。)
的设计注意rt_OneStep
假设之前禁用了中断rt_OneStep
被称为。rt_OneStep
在检查中断溢出标志之前,应该是不可中断的。
多速率多任务操作
在多速率多任务系统中,代码生成使用优先级,抢占式多任务方案来执行模型中的不同采样率。
的设计rt_OneStep
在多速率多任务程序中。
rt_OneStep(){检查基本速率中断溢出启用“rt_OneStep”中断确定需要运行该时间步骤的速率Model_Step0()——运行基本速率时间步骤代码对于N=1:NumTasks-1——遍历子速率任务如果(子速率任务N被调度)检查子速率中断溢出Model_StepN()——运行子速率时间步码EndIf EndFor}
任务标识符。具有不同采样率的块的执行被分解为任务。每个以给定采样率执行的块被分配一个任务标识符(tid
),它将其与以该速度执行的任务相关联。哪里有NumTasks
系统中的任务,任务标识符的取值范围是0..NumTasks
1。
基本速率和子速率任务的优先级。任务按速率降序排列。的基础概率任务是系统中运行速度最快的任务(即硬件时钟速率)。基本速率任务具有最高优先级(tid
下一个最快的任务(tid
1)具有下一个最高优先级,依此类推直到最慢,优先级最低的任务(tid
NumTasks
1)。
以基本速率的倍数运行的较慢的任务被调用subrate任务。
速率分组和速率特定的model_step函数。在单速率模型中,块输出计算在单个函数中执行,
。对于多速率、多任务模型,代码生成器尝试使用不同的策略。这个策略叫做率分组。费率分组产生单独的模型
_step
模型中基本速率任务和每个子速率任务的函数。这些函数的命名约定是模型
_step
模型_stepN
在哪里
是任务标识符。例如,对于一个名为N
my_model
即有三个速率,生成以下函数:
无效my_model_step0(无效);无效my_model_step1(无效);无效my_model_step2(无效);
每一个
函数执行块共享模型
_stepN
tid
;换句话说,就是在任务内执行的块代码N
被分组到关联的N
函数。模型
_stepN
调度model_stepN执行。每一次时钟滴答作响,rt_OneStep
维护调度计数器和事件标志对于每个子任务。计数器实现为taskCounter
上索引的数组tid
。事件标志被实现为索引的数组tid
。
子速率的调度计数器和任务标志由rt_OneStep
。调度计数器基本上是时钟速率分配器,它计算与每个子速率任务相关的采样周期。交换数据的一对任务以更快的速度维护交互标志。任务交互标志表示计划运行快速和慢速任务。
事件标志指示是否计划执行给定的任务。rt_OneStep
基于任务计数器维护事件标志,该任务计数器由模型的主程序模块中的代码维护。当计数器指示任务的采样周期已经过时,主代码将为该任务设置事件标志。
每次调用时,rt_OneStep
更新其调度数据结构并逐步执行基本速率任务(rt_OneStep
调用
因为基础速率任务必须在每个时钟步上执行)。然后,模型
_step0rt_OneStep
中的调度标志进行迭代tid
秩序,无条件呼唤
对于已设置标志的任何任务。任务按优先级顺序执行。模型
_stepN
抢占。的设计注意rt_OneStep
假设之前禁用了中断rt_OneStep
被称为。rt_OneStep
在基本速率中断溢出标志被检查之前,应该是不可中断的(参见上面的伪代码)。
使用的事件标志数组和循环变量rt_OneStep
作为本地(堆栈)变量存储。因此,rt_OneStep
是可重入的。如果rt_OneStep
重中断时,高优先级的任务抢占低优先级的任务。从中断返回后,较低优先级的任务按先前计划的顺序恢复。
被检测。多重速率的rt_OneStep
还维护定时器超时标志数组。rt_OneStep
通过与单速率相同的逻辑检测每个任务的定时器超时rt_OneStep
。
请注意
如果您已经开发了多速率s函数,或者如果您使用自定义的静态主程序模块,请参见速率分组遵从性和兼容性问题有关如何调整代码以实现速率分组兼容性的信息。这种调整可以让您的多速率、多任务模型生成更高效的代码。
多速率单任务操作
在多速率单任务程序中,根据定义,模型中的采样时间必须是模型固定步长的整数倍。
在多速率单任务程序中,块以不同的速率执行,但在相同的任务标识符下。的操作rt_OneStep
在本例中,是多速率多任务操作的简化版本。没有使用速率分组。唯一的任务是基本速率任务。因此,只有一个
函数生成:模型
_step
无效模型_step(空白)
每一次时钟滴答作响,rt_OneStep
检查溢出标志并调用
。多速率单任务程序的调度函数为模型
_steprate_scheduler
(而不是rate_monotonic_scheduler
)。调度程序维护每个时钟刻度上的调度计数器。模型中每个采样率都有一个计数器。的数组中实现计数器(以tid为索引)时机
内部结构rtModel
。
计数器是时钟速率分配器,计算与每个子任务相关的采样周期。当计数器指示给定速率的采样周期已经过时,rate_scheduler
清除计数器。此条件表明以该速率运行的块应该在下次调用时执行
,它负责检查柜台。模型
_step
rt_OneStep修改指导
rt_OneStep
不需要大量修改。唯一需要的修改是在检查了溢出标志和错误条件之后重新启用中断。如果适用,你也应该
保存并恢复进入和退出时的FPU上下文
rt_OneStep
。在调用之前设置与基本比率相关的模型输入
。模型
_step0获取调用后与基本速率关联的模型输出
。模型
_step0请注意
如果你修改
rt_OneStep
要在每个基本速率模型步骤之后从连续输出端口读取值,请参阅下面的相关警告指南。在多速率、多任务模型中,在调用之前设置与子速率相关联的模型输入
在子循环中。模型
_stepN
在多速率、多任务模型中,在调用后获取与子节点相关联的模型输出
在子循环中。模型
_stepN
注释rt_OneStep
指示添加代码的位置。
在多重速率的rt_OneStep
,您可以通过展开来提高性能为
和而
循环。
此外,您可以选择修改溢出行为,以便在错误恢复完成后继续执行。
同时也要遵守以下警示准则:
您不应该修改计数器、事件标志或其他计时数据结构的设置方式
rt_OneStep
中调用的函数rt_OneStep
。的rt_OneStep
定时数据结构(包括rtModel
)和逻辑对于生成的程序的操作至关重要。如果您定制了主程序模块,以便在每个基本速率模型步骤之后读取模型输出,请注意选择模型配置参数金宝app支持:连续时间和单个输出/更新功能一起可以导致输出值读取
主要
对于连续输出端口,与模型记录数据中的相应输出值略有不同。这是因为,虽然记录的数据是主要时间步骤的输出快照,但从主要
基础速率模型后的步长可能反映了中间的小时间步长。要消除差异,可以将生成的输出和更新函数分开(清除单个输出/更新功能参数)或在连续输出端口前放置零阶保持块。如果在每次调用模型步骤函数之前没有设置模型输入,则可能观察到模拟结果与生成代码中记录的MAT文件结果之间的不匹配。在生成的示例主程序中,下面的注释显示了设置输入和用代码步进模型的位置:
/*设置模型输入*/ /*步进模型*/
如果您的模型应用信号重用,并且您正在使用
MatFileLogging
要将仿真结果与生成的代码进行比较,请修改rt_OneStep
按照这些注释的指示,在每个时间步中编写模型输入。或者,你可以选择SIL或PIL方法核查。
静态主程序模块
概述
在大多数情况下,部署生成代码的最简单策略是使用生成一个示例主程序选项生成ert_main.c
或. cpp
模块(见生成独立程序)。
然而,如果你把生成一个示例主程序选项关闭后,您可以使用静态主模块作为开发嵌入式应用程序的示例或模板。由MathWorks提供的静态主模块®包括:
——金宝app支持matlabroot
/ rtw / c / src /共同/rt_main.c那种一次性的功能
代码接口打包。
——金宝app支持matlabroot
/ rtw / c / src /共同/rt_malloc_main.c可重用的功能
代码接口打包。必须选择模型配置参数使用动态内存分配模型初始化并设置参数将根级I/O作为来部分模型数据结构
。
——金宝app支持matlabroot
/ rtw / c / src /共同/rt_cppclass_main.cppc++类
代码接口打包。
静态主模块不是生成代码的一部分;它是作为您自定义修改的基础,并用于模拟。如果您现有的应用程序依赖于静态ert_main.c
(在R2012b之前的版本中开发),rt_main.c
,rt_malloc_main.c
,或rt_cppclass_main.cpp
,您可能需要继续使用静态主程序模块。
在使用静态主模块开发应用程序时,应该将该模块复制到工作文件夹中,并在进行修改之前对其进行重命名。例如,您可以重命名rt_main.c
来
。此外,您必须修改模板makefile或工具链设置,以便构建过程创建相应的对象文件,例如模型
_rt_main.c
(在UNIX®,模型
_rt_main.obj
),在构建文件夹中。模型
_rt_main.o
静态主模块包含
rt_OneStep
定时器中断服务程序(ISR)。rt_OneStep
调用
在模型的一个时钟周期内执行处理。模型
_step骨骼
主要
函数。提供,主要
仅在模拟中有用。你必须修改主要
用于实时中断驱动的执行。
对于单速率模型,操作rt_OneStep
静态main模块中的main函数与自动生成版本中的main函数本质上是相同的将生成的独立可执行程序部署到目标硬件。然而,对于多速率、多任务模型,静态代码和生成的代码略有不同。下一节将描述这种情况。
速率分组和静态主程序
基于ERT目标的目标有时使用静态主模块,而不允许使用生成一个示例主程序选择。这样做是因为目标特定的修改已经添加到静态主模块中,如果重新生成主程序,这些修改将不会被保留。
静态主模块可能使用速率分组兼容,也可能不使用速率分组兼容
功能。如果你的主模块是基于静态的模型
_stepN
rt_main.c
,rt_malloc_main.c
,或rt_cppclass_main.cpp
模块,它不使用特定于速率的
函数调用。它用的是老式的样式模型
_stepN
函数,传入一个任务标识符:模型
_step
无效模型_step (int_T tid);
缺省情况下,当生成一个示例主程序选项关闭时,ERT目标将生成
多速率、多任务模型的“包装器”。包装器的目的是为特定于速率的接口模型
_step
函数到旧式调用。包装器代码分派给模型
_stepN
打电话模型
_stepN
开关
语句,如下例所示:
void mymodel_step(int_T tid) /*采样时间:*/ {switch(tid) {case 0: mymodel_step0();打破;案例1:mymodel_step1();打破;案例2:mymodel_step2();打破;默认值:break;}}
下面的伪代码展示了如何实现rt_OneStep
调用
由静态主程序实现了多速率、多任务的模式。模型
_step
rt_OneStep(){检查基本速率中断溢出启用“rt_OneStep”中断确定需要运行该时间步骤的速率ModelStep(tid=0)——基本速率时间步骤对于N=1:NumTasks-1——遍历子速率任务检查子速率中断溢出如果(子速率任务N被调度)ModelStep(tid=N)——子速率时间步骤EndIf EndFor}
您可以使用TLC变量RateBasedStepFcn
指定只生成基于速率的步骤函数,而不生成包装器函数。如果您的目标调用速率分组兼容
功能直接设置模型
_stepN
RateBasedStepFcn
来1
。在这种情况下,不会生成包装器函数。
你应该设置RateBasedStepFcn
之前%包括“codegenentry.tlc”
语句在系统目标文件中。或者,您可以设置RateBasedStepFcn
在你的target_settings.tlc
文件。
修改静态主程序
与生成的ert_main.c
或. cpp
,您应该对主循环和rt_OneStep
。看到修改主程序指南和rt_OneStep修改指导。
此外,您应该替换rt_OneStep
使用后台任务调用或空语句在主循环中调用。
您可能需要做的其他修改是
如果适用,请按照代码中的注释来添加读取/写入模型I/O和保存/恢复FPU上下文的代码。
请注意
如果你修改
rt_main.c
,rt_malloc_main.c
,或rt_cppclass_main.cpp
若要在每个基本速率模型步骤之后从连续输出端口读取值,请参阅中的相关警告指南rt_OneStep修改指导。当生成一个示例主程序选项是关闭的,
rtmodel.h
在主模块和生成的模型代码之间提供一个接口。如果创建自己的静态主程序模块,通常会包含rtmodel.h
。的生成
rtmodel.h
,并包括
直接在主模块中。抑制一代模型
。hrtmodel.h
,在系统目标文件中使用以下语句:%assign AutoBuildProcedure = 0
如果您已经清除了需要终止功能选项,删除或注释掉产品版本中的以下内容
rt_main.c
,rt_malloc_main.c
,或rt_cppclass_main.cpp
:的
#如果TERMFCN……
编译时错误检查呼唤
MODEL_TERMINATE
为
rt_main.c
(不适用于rt_cppclass_main.cpp
)如果有的话不要结合输出和更新功能,明确机型配置参数单个输出/更新功能的生产版本中进行以下更改rt_main.c
:替换对
MODEL_STEP
呼唤着MODEL_OUTPUT
和MODEL_UPDATE
。删除
#如果ONESTEPFCN……
错误检查。
静态
rt_main.c
模块不支持金宝app可重用的功能
代码接口打包。以下错误检查将引发编译时错误,如果可重用的功能
非法使用代码接口打包。#如果MULTI_INSTANCE_CODE = = 1
修改Static Main以分配和访问模型实例数据
如果您使用的是静态主程序模块,并且您的模型配置为可重用的功能
代码接口封装,但模型配置参数使用动态内存分配模型初始化则必须由调用主代码静态或动态地分配模型实例数据。指向各个模型数据结构(如Block IO、DWork和Parameters)的指针必须在顶层实时模型数据结构中设置。
为了支金宝app持主要修改,构建过程根据模型的数据需求,生成以下实时模型(RTM)宏的子集
。模型
。h
RTM宏语法 | 描述 |
---|---|
rtmGetBlockIO (rtm) |
获取块I/O数据结构 |
val rtmSetBlockIO (rtm) |
设置块I/O数据结构 |
rtmGetContStates (rtm) |
获得连续状态数据结构 |
val rtmSetContStates (rtm) |
设置连续状态数据结构 |
rtmGetDefaultParam (rtm) |
获取默认参数数据结构 |
val rtmSetDefaultParam (rtm) |
设置默认参数数据结构 |
rtmGetPrevZCSigState (rtm) |
获取之前的过零信号状态数据结构 |
val rtmSetPrevZCSigState (rtm) |
设置之前的过零信号状态数据结构 |
rtmGetRootDWork (rtm) |
获取DWork数据结构 |
val rtmSetRootDWork (rtm) |
设置DWork数据结构 |
rtmGetU (rtm) |
获取根输入数据结构(当根输入作为模型数据结构的一部分传递时) |
val rtmSetU (rtm) |
设置根输入数据结构(当根输入作为模型数据结构的一部分传递时) |
rtmGetY (rtm) |
获取根输出数据结构(当根输出作为模型数据结构的一部分传递时) |
val rtmSetY (rtm) |
设置根输出数据结构(当根输出作为模型数据结构的一部分传递时) |
在静态主程序中使用这些宏来访问RTM数据结构中的各个模型数据结构。例如,假设示例模型rtwdemo_reusable
配置为可重用的功能
代码接口打包;使用动态内存分配模型初始化清除,将根级I/O作为设置为个人观点
,优化窗格中选择删除根级I/O零初始化清除。构建模型会生成以下模型数据结构和模型入口点rtwdemo_reusable.h
:
/*块状态(自动存储)系统'<根>' */ typedef struct {real_T Delay_DSTATE;/* '/Delay' */} D_Work;/*参数(auto storage) */ struct Parameters_ {real_T k1;/*变量:k1 *引用:' /Gain' */};/*模型入口点函数*/ extern void rtwdemo_reusable_initialize(RT_MODEL *const rtM, real_T *rtU_In1, real_T *rtU_In2, real_T *rtY_Out1);extern void rtwdemo_reusable_step(RT_MODEL *const rtM, real_T rtU_In1, real_T rtU_In2, real_T *rtY_Out1);
另外,如果模型配置参数生成一个示例主程序为模型未选择的,rtwdemo_reusable.h
包含RTM宏的定义rtmGetDefaultParam
,rtmsetDefaultParam
,rtmGetRootDWork
,rtmSetRootDWork
。
此外,作为参考,生成的rtmodel.h
文件包含一个具有初始值的示例参数定义(非执行代码):
#if 0 /*具有初始值的示例参数数据定义*/ static参数rtP ={2.0 /*变量:k1 *引用:'/Gain' */};/*可修改的参数*/ #endif
在静态主文件的定义部分,您可以使用以下代码来静态地分配实时模型数据结构和参数rtwdemo_reusable
模型:
static RT_MODEL rtM_;static RT_MODEL *const rtM = &rtM_;/*实时模型*/静态参数rtP ={2.0 /*变量:k1 *引用:'/Gain' */};/*可修改参数*/ static D_Work rtDWork/*可观察状态*/ /* '<根>/In1' */ static real_T rtU_In1;/* ' /In2' */ static real_T rtU_In2;/* ' /Out1' */ static real_T rtY_Out1;
在主函数的主体中,可以使用以下RTM宏调用来设置实时模型数据结构中的模型参数和DWork数据:
int_T main(int_T argc, const char *argv[]){…/*将模型数据打包到RTM */ rtmSetDefaultParam(RTM, &rtP);&rtDWork rtmSetRootDWork (rtM);*/ rtwdemo_reusable_initialize(rtM, &rtU_In1, &rtU_In2, &rtY_Out1);…}
遵循类似的方法来设置模型数据的多个实例,其中每个实例的实时模型数据结构都有自己的数据。特别是参数结构(rtP
)应该初始化为所需的值,或者静态地作为rtP
数据定义或在运行时。
速率分组遵从性和兼容性问题
主程序兼容性
当生成一个示例主程序选项关闭,代码生成产生稍微不同的速率分组代码,以便与旧的静态兼容ert_main.c
模块。看到速率分组和静态主程序获取详细信息。
使你的s -函数率分组兼容
内置的仿真软件金宝app®模块以及DSP System Toolbox™模块都符合生成速率分组代码的要求。然而,用户编写的多速率内联s函数可能不兼容速率分组。不兼容的块生成效率较低的代码,但在其他方面与速率分组兼容。为了充分利用速率分组的效率,您的多速率内联s函数必须升级为完全兼容速率分组。您应该升级TLC s函数实现,如本节所述。
使用不兼容的多速率块来生成速率分组代码会生成死代码。这会导致两个问题:
降低代码效率。
编译时发出的警告消息。当死代码在初始化之前引用临时变量时,就会产生此类警告。由于死代码不会运行,因此此问题不会影响生成代码的运行时行为。
要使s函数速率分组兼容,可以使用以下TLC函数来生成ModelOutputs
和ModelUpdate
代码,分别为:
UpdateForTID(block, system, tid)
下面的代码清单演示了不使用速率分组(清单1)和使用速率分组(清单2)的输出计算的生成。
的
tid
参数是任务标识符(0 . . NumTasks-1
)。只有代码由
tid
传递给OutputsForTID
是生成的。的如果(% < LibIsSFcnSampleHit (portName) >)
中没有使用TestOutputsForTID
。在生成费率分组代码时,
OutputsForTID
和/或UpdateForTID
在代码生成期间调用。在生成非速率分组代码时,输出
和/或更新
被称为。在速率分组兼容的代码中,顶级
输出
和/或更新
函数调用OutputsForTID
和/或UpdateForTID
各比率的功能(tid
)参与了这一事件。返回的代码OutputsForTID
和/或UpdateForTID
必须由相应的守护吗tid
警卫:如果(% < LibIsSFcnSampleHit (portName) >)
如清单2所示。
清单1:没有速率分组的输出代码生成
% % multirate_blk。薄层色谱%implements "multirate_blk" "C" %% Function: mdlOutputs ===================================================== %% Abstract: %% %% Compute the two outputs (input signal decimated by the %% specified parameter). The decimation is handled by sample times. %% The decimation is only performed if the block is enabled. %% Each port has a different rate. %% %% Note, the usage of the enable should really be protected such that %% each task has its own enable state. In this example, the enable %% occurs immediately which may or may not be the expected behavior. %% %function Outputs(block, system) Output /* %Block: % */ %assign enable = LibBlockInputSignal(0, "", "", 0) { int_T *enabled = &% ; %if LibGetSFcnTIDType("InputPortIdx0") == "continuous" %% Only check the enable signal on a major time step. if (% && ... % ) { *enabled = (% > 0.0); } %else if (% ) { *enabled = (% > 0.0); } %endif if (*enabled) { %assign signal = LibBlockInputSignal(1, "", "", 0) if (% ) { %assign y = LibBlockOutputSignal(0, "", "", 0) % = % ; } if (% ) { %assign y = LibBlockOutputSignal(1, "", "", 0) % = % ; } } } %endfunction %% [EOF] sfun_multirate.tlc
清单2:使用速率分组生成输出代码
% % example_multirateblk。薄层色谱%implements "example_multirateblk" "C" %% Function: mdlOutputs ===================================================== %% Abstract: %% %% Compute the two outputs (the input signal decimated by the %% specified parameter). The decimation is handled by sample times. %% The decimation is only performed if the block is enabled. %% All ports have different sample rate. %% %% Note: the usage of the enable should really be protected such that %% each task has its own enable state. In this example, the enable %% occurs immediately which may or may not be the expected behavior. %% %function Outputs(block, system) Output %assign portIdxName = ["InputPortIdx0","OutputPortIdx0","OutputPortIdx1"] %assign portTID = [%, ... % , ... % ] %foreach i = 3 %assign portName = portIdxName[i] %assign tid = portTID[i] if (% ) { % } %endforeach %endfunction %function OutputsForTID(block, system, tid) Output /* % Block: % */ %assign enable = LibBlockInputSignal(0, "", "", 0) %assign enabled = LibBlockIWork(0, "", "", 0) %assign signal = LibBlockInputSignal(1, "", "", 0) %switch(tid) %case LibGetGlobalTIDFromLocalSFcnTID("InputPortIdx0") %if LibGetSFcnTIDType("InputPortIdx0") == "continuous" %% Only check the enable signal on a major time step. if (% ) { % = (% > 0.0); } %else % = (% > 0.0); %endif %break %case LibGetGlobalTIDFromLocalSFcnTID("OutputPortIdx0") if (% ) { %assign y = LibBlockOutputSignal(0, "", "", 0) % = % ; } %break %case LibGetGlobalTIDFromLocalSFcnTID("OutputPortIdx1") if (% ) { %assign y = LibBlockOutputSignal(1, "", "", 0) % = % ; } %break %default %% error it out %endswitch %endfunction %% [EOF] sfun_multirate.tlc
生成从字面内存地址解引用数据的代码
这个示例展示了如何通过解引用指定的内存地址来生成读取信号值的代码。使用这种技术,您可以生成与硬件填充的内存交互的控制算法(例如,在微控制器中存储模数转换器输出的内存)。
在本例中,您将生成一个算法,该算法从地址处的16位内存块获取输入数据0 x8675309
。假设硬件设备只异步地填充地址的后10位。该算法必须将该地址视为只读(常量
),易挥发的(挥发性
)数据,忽略地址的上6位。
生成的代码可以通过定义一个解引用的宏来访问数据0 x8675309
并掩盖了不必要的部分:
#define A2D_INPUT ((*(volatile const uint16_T *)0x8675309)&0x03FF)
要配置一个模型来生成定义和使用这个宏的代码:
创建示例模型。
创建包以包含数据类和存储类的定义。
使用自定义存储类设计器创建存储类。
定义一个类来存储存储类的属性设置。
编写目标语言编译器(TLC)代码,为存储类发出正确的C代码。
为导入数据定义一个类。
将包加载到嵌入式编码器字典中。
配置模型根级导入以使用存储类。
生成并检查代码。
有关显示如何使用自定义存储类设计器而无需编写TLC代码的示例,请参见创建并应用存储类。
作为编写TLC代码的替代方法,您可以使用内存部分来生成包含pragmas的代码。根据您的构建工具链,您可以使用pragma来指定用于存储全局变量的文字内存地址。有关内存部分的更多信息,请参见通过插入pragma在内存中放置控制数据和函数。
宏语法的派生
在本例中,您配置生成的代码来定义和使用解引用宏。要确定宏的正确语法,首先要记录目标地址。
0 x8675309
将地址转换为指向16位整数的指针。使用Simuli金宝appnk编码器数据类型名称uint16_T
。
(uint16_T *) 0 x8675309
添加存储类型限定符常量
因为生成的代码不能写入地址。添加挥发性
因为硬件可以在任意时间填充地址。
(volatile const uint16_T *)0x8675309
取消对地址的引用。
*(volatile const uint16_T *)0x8675309
在解引用操作之后,应用掩码只保留硬件填充的10位。使用显式括号来控制操作的顺序。
(*(volatile const uint16_T *)0x8675309)&0x03FF
作为一种安全的编码实践,将整个结构包裹在另一层括号中。
((*(volatile const uint16_T *)0x8675309)&0x03FF)
创建示例模型
创建示例模型ex_memmap_simple
。
对于import块,将输出数据类型设置为uint16
。将信号命名为A2D_INPUT
。的轮廓尺寸
块和信号线表示硬件填充的数据。
对于Gain块,将输出数据类型设置为双
。
创建包以包含数据类和存储类的定义
在当前文件夹中,创建一个名为+ MemoryMap
。文件夹定义了一个名为MemoryMap
。
要使包可以在当前文件夹之外使用,可以添加包含+ MemoryMap
文件夹到MATLAB路径。
创建存储类
生成定义和读取的代码A2D_INPUT
作为一个宏,您必须创建一个可以应用于模型根级导入的存储类。稍后,编写TLC代码来补充存储类。
以高级模式打开自定义存储类设计器。若要设计通过自定义TLC代码操作的存储类,必须使用高级模式。
cscdesigner (“MemoryMap”,“先进”);
在“自定义存储类设计器”中,单击新。新的自定义存储类,NewCSC_1
,出现在存储类定义列表中。
将存储类重命名为MemoryMappedAddress
。
为MemoryMappedAddress
,在…一般选项卡,设置:
类型来
其他
。存储类可以通过稍后编写的自定义TLC代码进行操作。数据范围来
出口
。对于使用此自定义存储类的数据项,Simulink Coder生成定义(例如,金宝app#定义
定义宏的语句)。数据初始化来
没有一个
。金宝appSimulink编码器不生成初始化数据项的代码。使用此设置是因为此自定义存储类表示只读数据。你没有选择宏
因为自定义存储类设计器不允许使用宏
用于信号数据。定义文件来
指定
(保留文本框为空)。对于在生成的代码中消耗内存的数据项,定义文件指定了。c
分配内存的源文件。这个存储类产生一个宏,它不需要内存。通常,头文件(。h
),而不是。c
文件,定义宏。设置定义文件来指定
而不是具体的实例
防止存储类的用户不必要地指定定义文件。头文件来
具体的实例
。要控制宏定义的文件位置,存储类的用户必须为使用此存储类的每个数据项指定一个头文件。老板来
指定
(保留文本框为空)。老板仅应用于消耗内存的数据项。
设置完成后,单击应用和保存。
现在,当您将存储类应用于数据项(如import)时三机一体
时,可以指定一个头文件来包含生成的宏定义。您还不能为数据项指定内存地址。要启用内存地址的指定,请创建一个可与MemoryMappedAddress
存储类。
定义类以存储存储类的属性设置
定义一个MATLAB类来存储使用存储类的数据项的附加信息。在这种情况下,附加信息是内存地址。
在MemoryMap
包(+ MemoryMap
文件夹),创建一个名为@MemoryMapAttribs
。
在@MemoryMapAttribs
文件夹,创建一个名为MemoryMapAttribs
。该文件定义了一个从内置类派生的类金宝app仿真软件。CustomStorageClassAttributes
。
classdefMemoryMapAttribs < 金宝appSimulink。CustomStorageClassAttributes属性PropertyType =“字符”) MemoryAddress =”;结束结束
稍后,您将这个MATLAB类与MemoryMappedAddress
存储类。然后,当您将存储类应用于数据项时,您可以指定内存地址。
编写TLC代码,发出正确的C代码
编写使用存储类属性的TLC代码,例如HeaderFile
和MemoryAddress
,为每个数据项生成正确的C代码。
在+ MemoryMap
文件夹,创建一个名为薄层色谱
。
导航到新文件夹。
检查内置模板TLC文件,TEMPLATE_v1.tlc
。
编辑(fullfile (matlabroot,…“工具箱”,“环球套票”,“目标”,“是”,“csc_templates”,“TEMPLATE_v1.tlc”))
保存的副本TEMPLATE_v1.tlc
在薄层色谱
文件夹中。重命名副本memory_map_csc.tlc
。
在memory_map_csc.tlc
,找到控制生成c代码数据声明的部分。
LibDefaultCustomStorageDeclare是默认的声明函数,%%声明一个全局变量,其标识符是数据的名称。%返回“走读生% < LibDefaultCustomStorageDeclare(记录)>“% % %% ==========================================================================
的声明
例(%的情况
)构造一个返回值(%返回
),代码生成器将其发送到您为每个数据项指定的头文件中。控件中的返回值可以控制声明每个数据项的C代码声明
的情况。
替换现有的%的情况
内容用这个新代码,它指定了一个不同的返回值:
在TLC代码中,“记录”是一个数据项(例如,一条信号线)。'LibGetRecordIdentifier'返回数据项的名称。%assign id = LibGetRecordIdentifier(record) %assign dt = LibGetRecordCompositeDataTypeName(record) %%数据项的CoderInfo属性存储了一个%%的Simulink。金宝appCoderInfo’对象,它存储代码生成设置%%,例如您为项指定的存储类或自定义存储类%%。%% 'ci'变量现在存储'Simulink '。金宝appCoderInfo”对象。%%默认情况下,Simulink的“CustomAttributes”属性。金宝appCoderInfo' %%对象存储一个'Simulink '。金宝appCustomStorageClassAttributes”对象。此嵌套对象存储专门的代码生成设置%%,例如为数据项%%指定的头文件和定义文件。%% %% 'MemoryMap'包从'Simulink.CustomStorageClassAttributes'中派生了一个新类%% ' memoryapa金宝appttributes '。新类增加了一个名为“MemoryAddress”的属性。此TLC代码通过%%获取'MemoryAddress'属性的值来确定数据项的内存地址。%assign ca = ci.Object.ObjectProperties.CustomAttributes %assign address = ca. object . objectproperties . memoryaddress %assign width = LibGetDataWidth(record) %%此TLC代码基于TLC变量的值,如'address'和'dt',使用正确的C语法构造完整的宏。 %% This TLC code also asserts that the data item must be a scalar. %if width == 1 %assign macro = ... "#define %((*(volatile const % *)%) & 0x03FF)" %else %error( "Non scalars are not supported yet." ) %endif %return "% " %%break %% ==========================================================================
新的TLC代码使用内置的、文档化的TLC函数,例如LibGetRecordIdentifier
,以及其他TLC命令和操作来访问数据项的信息。临时变量,例如dt
和地址
存储这些信息。TLC代码通过展开变量,使用正确的C语法构造完整的宏,并将宏存储在变量中宏
。
在同一个文件中,找到控制数据定义生成的部分。
LibDefaultCustomStorageDefine是默认的定义函数,用于定义%%一个全局变量,其标识符为数据的名称。如果%%数据是一个参数,则定义也静态初始化为其标称值的%%(如MATLAB中设置的)。%返回“% < LibDefaultCustomStorageDefine(记录)>“% % %% ==========================================================================
的定义
Case派生一个返回值,该返回值由代码生成器发送到。c
文件,它定义消耗内存的数据项。
替换现有的%的情况
内容与这个新的内容:
%例“定义”%返回" % %打破 %% ==========================================================================
MemoryMappedAddress
在生成的代码中生成一个宏,因此可以使用声明
案例而不是定义
实例来构造和发出宏。为了防止定义
如果发出重复的宏定义,新的TLC代码将返回一个空字符串。
找到控制初始化数据的代码生成的部分。
LibDefaultCustomStorageInitialize是默认的initialization %%函数,用于将全局变量的标量元素初始化为0。%返回LibDefaultCustomStorageInitialize(记录,idx雷姆)% % %% ==========================================================================
的初始化
Case生成初始化数据项的代码(例如,在模型_initialize
功能)。
替换现有的%的情况
内容与这个新的内容:
%例“初始化”%返回" % %打破 %% ==========================================================================
MemoryMappedAddress
生成一个宏,因此生成的代码不能尝试初始化宏的值。新的TLC代码返回一个空字符串。
完成存储类的定义
新的MATLAB类,MemoryMapAttribs
,可以使您的存储类的用户MemoryMappedAddress
,为每个数据项指定一个内存地址。要允许此规范,请关联MemoryMapAttribs
与MemoryMappedAddress
。要根据您为每个数据项指定的信息生成正确的C代码,请关联自定义TLC文件,memory_map_csc.tlc
,MemoryMappedAddress
。
导航到包含控件的文件夹+ MemoryMap
文件夹中。
再次打开自定义存储类设计器。
cscdesigner (“MemoryMap”,“先进”);
为MemoryMappedAddress
,在…其他属性选项卡,设置:
TLC文件名来
memory_map_csc.tlc
。CSC属性类来
MemoryMap。MemoryMapAttribs
。
点击应用和保存。
定义信号数据类
要将存储类应用于模型中的数据元素,在MemoryMap
,您必须创建一个派生自的MATLAB类金宝app仿真软件。信号
。当你在模型中配置信号时,你选择这个新的数据类而不是默认的类,金宝app仿真软件。信号
。
在MemoryMap
包,创建一个名为@Signal
。
在@Signal
文件夹,创建一个名为Signal.m
。
classdef信号< Simul金宝appink。信号方法函数setupCoderInfo(this)使用elocalcustomstorageclass (this)“MemoryMap”);返回;结束结束结束
该文件定义了一个名为MemoryMap。信号
。类定义覆盖setupCoderInfo
方法,其中金宝app仿真软件。信号
类已经实现。的对象MemoryMap。信号
类中使用自定义存储类MemoryMap
类中的存储类(而不是金宝app
包)。在模型中配置信号时,选择MemoryMap。信号
类中,您可以选择新的自定义存储类,MemoryMappedAddress
。
加载包到嵌入式编码器字典
导航到包含示例模型的文件夹并打开模型。
打开嵌入式编码器应用程序。
打开嵌入式编码器字典。在C代码选项卡上,选择代码接口>嵌入式编码器字典。
在“嵌入式编码器字典”中,单击管理包。
在“管理包”对话框中单击刷新。刷新完成后,选择该包MemoryMap
。点击负载。
关闭嵌入式编码器字典。
配置根级导入使用存储类
在嵌入式编码器app,使用代码映射编辑器和属性检查器来配置导入三机一体
来使用您定义的存储类。
打开Code Mappings编辑器。在C代码选项卡上,选择代码接口>单个元素代码映射。
在“代码映射编辑器”中,在港口选项卡,选择import三机一体
。为设置存储类三机一体
来MemoryMappedAddress
。
在属性检查器中,在代码,设置HeaderFile财产memory_mapped_addresses.h
和MemoryAddress财产0 x8675309
。
保存模型。
生成和检查代码
从模型生成代码。
###启动ex_memmap_simple的构建过程###成功完成ex_memmap_simple的构建过程构建摘要构建了顶级模型目标:模型动作重建原因================================================================================================= ex_memmap_simple生成和编译的代码代码生成信息文件不存在。构建1个模型中的1个(0个已经更新的模型)构建持续时间:0h 0m 18.874s
检查生成的头文件memory_mapped_addresses.h
。该文件定义了宏A2D_INPUT
,对应模型中的信号线。
/*使用自定义存储类MemoryMappedAddress声明数据*/ #define A2D_INPUT ((*(volatile const uint16_T*)0x8675309) & 0x03FF)
检查生成的文件ex_memmap_simple.c
。生成的算法代码(对应于增益块)计算模型输出,而无。着干活
通过……A2D_INPUT
。
/*模型步骤函数*/无效步骤(无效){/*输出:'<根>/Out1'合并:*增益:'<根>/增益' *输入:'<根>/In1' */ rtY。Out1 = 42.0 * (real_T)A2D_INPUT;}