本文讲解用Simulink Coder生成的C代码
架构
首先撇开模型内容,看生成的C代码的框架。为了方便读者测试,我把关键的2个函数组装到了1个程序中,代码如下:
#include <stdio.h>
#include <float.h>
#include <memory.h>
using namespace std;
#define rtmGetFinalTime(rtm) ((rtm)->Timing.tFinal)
#define rtmGetTFinal(rtm) ((rtm)->Timing.tFinal)
#define rtmSetTFinal(rtm, val) ((rtm)->Timing.tFinal = (val))
typedef double time_T;
typedef struct tag_RTM_SingleSubsystem_T RT_MODEL_SingleSubsystem_T;
struct tag_RTM_SingleSubsystem_T
{
const char *errorStatus;
//RTWLogInfo *rtwLogInfo;
/*
* Timing:
* The following substructure contains information regarding
* the timing information for the model.
*/
struct {
time_T taskTime0;
unsigned int clockTick0;
unsigned int clockTickH0;
time_T stepSize0;
time_T tFinal;
bool stopRequestedFlag;
} Timing;
};
static RT_MODEL_SingleSubsystem_T SingleSubsystem_M_;
RT_MODEL_SingleSubsystem_T *const SingleSubsystem_M = &SingleSubsystem_M_;
bool finish_flag;
void SingleSubsystem_step(void)
{
/* signal main to stop simulation */
{ /* Sample time: [0.2s, 0.0s] */
if ((rtmGetTFinal(SingleSubsystem_M)!=-1) &&
!((rtmGetTFinal(SingleSubsystem_M)-SingleSubsystem_M->Timing.taskTime0) >
SingleSubsystem_M->Timing.taskTime0 * (DBL_EPSILON)))
{
//rtmSetErrorStatus(SingleSubsystem_M, "Simulation finished");
finish_flag = true;
printf("finish\n");
}
}
/* Update absolute time for base rate */
/* The "clockTick0" counts the number of times the code of this task has
* been executed. The absolute time is the multiplication of "clockTick0"
* and "Timing.stepSize0". Size of "clockTick0" ensures timer will not
* overflow during the application lifespan selected.
* Timer of this task consists of two 32 bit unsigned integers.
* The two integers represent the low bits Timing.clockTick0 and the high bits
* Timing.clockTickH0. When the low bit overflows to 0, the high bits increment.
*/
if (!(++SingleSubsystem_M->Timing.clockTick0))
{
++SingleSubsystem_M->Timing.clockTickH0;
}
SingleSubsystem_M->Timing.taskTime0 = SingleSubsystem_M->Timing.clockTick0 *
SingleSubsystem_M->Timing.stepSize0 + SingleSubsystem_M->Timing.clockTickH0 *
SingleSubsystem_M->Timing.stepSize0 * 4294967296.0;
}
/* Model initialize function */
void SingleSubsystem_initialize(void)
{
/* Registration code */
/* initialize non-finites */
//rt_InitInfAndNaN(sizeof(real_T));
// initialize real-time model
(void) memset((void *)SingleSubsystem_M, 0,
sizeof(RT_MODEL_SingleSubsystem_T));
rtmSetTFinal(SingleSubsystem_M, 10.0);
SingleSubsystem_M->Timing.stepSize0 = 0.2;
/* Setup for data logging
{
static RTWLogInfo rt_DataLoggingInfo;
rt_DataLoggingInfo.loggingInterval = (NULL);
SingleSubsystem_M->rtwLogInfo = &rt_DataLoggingInfo;
}*/
/* Setup for data logging
{
rtliSetLogXSignalInfo(SingleSubsystem_M->rtwLogInfo, (NULL));
rtliSetLogXSignalPtrs(SingleSubsystem_M->rtwLogInfo, (NULL));
rtliSetLogT(SingleSubsystem_M->rtwLogInfo, "tout");
rtliSetLogX(SingleSubsystem_M->rtwLogInfo, "");
rtliSetLogXFinal(SingleSubsystem_M->rtwLogInfo, "");
rtliSetLogVarNameModifier(SingleSubsystem_M->rtwLogInfo, "rt_");
rtliSetLogFormat(SingleSubsystem_M->rtwLogInfo, 4);
rtliSetLogMaxRows(SingleSubsystem_M->rtwLogInfo, 0);
rtliSetLogDecimation(SingleSubsystem_M->rtwLogInfo, 1);
rtliSetLogY(SingleSubsystem_M->rtwLogInfo, "");
rtliSetLogYSignalInfo(SingleSubsystem_M->rtwLogInfo, (NULL));
rtliSetLogYSignalPtrs(SingleSubsystem_M->rtwLogInfo, (NULL));
} */
/* external inputs
(void)memset(&SingleSubsystem_U, 0, sizeof(ExtU_SingleSubsystem_T));
/* external outputs
(void)memset(&SingleSubsystem_Y, 0, sizeof(ExtY_SingleSubsystem_T));
*/
/* Matfile logging
rt_StartDataLoggingWithStartTime(SingleSubsystem_M->rtwLogInfo, 0.0,
rtmGetTFinal(SingleSubsystem_M), SingleSubsystem_M->Timing.stepSize0,
(&rtmGetErrorStatus(SingleSubsystem_M))); */
}
int main()
{
finish_flag = false;
SingleSubsystem_initialize();
while( !finish_flag )
{
SingleSubsystem_step();
printf( "%d %f\n", SingleSubsystem_M->Timing.clockTick0, SingleSubsystem_M->Timing.taskTime0);
}
return 0;
}
关键的2个函数是SingleSubsystem_step, SingleSubsystem_initialize. 前者是仿真的每一步要执行的函数,后者是初始化函数。以上代码列出并不是直接从Simulink生成的代码中复制过来的,有一点点修改。首先SingleSubsystem_M_ 全局变量,保存仿真过程中的模型整体数据,其实就是为了控制模型的仿真步数。
SingleSubsystem_initialize
这个函数先把SingleSubsystem_M_清零,然后将结束时间设置为10.0,步长为0.2,这2个数据都可以在模型中设置。下面注释了很多内容,推测是把仿真数据输出到文件。
SingleSubsystem_step
首先是个if语句,条件rtmGetTFinal(SingleSubsystem_M)!=-1显得多余。条件
!((rtmGetTFinal(SingleSubsystem_M)-SingleSubsystem_M->Timing.taskTime0) >
SingleSubsystem_M->Timing.taskTime0 * (DBL_EPSILON))
判断仿真过程目前时间taskTime0是否超过结束时间tFinal. 里面有DBL_EPSILON, 是1个很接近0的浮点数,数量级为10^(-16). if语句条件为真时就应该结束仿真。
接下来是
if (!(++SingleSubsystem_M->Timing.clockTick0))
{
++SingleSubsystem_M->Timing.clockTickH0;
}
这个写法不太好,其实这个语句大部分时候只执行++SingleSubsystem_M->Timing.clockTick0. 但是如果让clockTick0为-1或者说unsigned int的最大值时 ,就会执行if中的语句。个人推测这是为了防止执行的步数超过unsigned int的最大值。注意clockTickH0和clockTick0是不同变量。clockTickH0几乎用不到,但是它只要加1,几乎就意味着仿真结束。原因看Step函数中最后的语句,clockTickH0将乘以4294967296.0,1个极大的权重。
这个样例结束时间为10,步长0.2,所以程序会运行51次Step函数(最后1次Step函数中判断仿真时间超过tFinal).
模型内容
根据模型内容,生成代码除了上述架构,还会在Step函数和Initialize函数中增加一些代码。下面是一个模型例子
在我的例子中只有1个或门、内部是与门的子系统和相应的Inport, Outport. 所以在Step函数中会增加如下代码
SingleSubsystem_Y.Out1 = (SingleSubsystem_U.In2 && SingleSubsystem_U.In3);
SingleSubsystem_Y.Out2 = (SingleSubsystem_U.In4 || SingleSubsystem_U.In5);
直到现在模型里面并没有设置采样时间,一切都是用默认参数。那么如果我们修改采样时间,看看生成的代码有何变化。在子系统中
设置In1的采样时间为0.2, Out1的采样时间为0.8,则生成代码会多出一个函数
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).
*/
(SingleSubsystem_M->Timing.TaskCounters.TID[1])++;
if ((SingleSubsystem_M->Timing.TaskCounters.TID[1]) > 3) {/* Sample time: [0.8s, 0.0s] */
SingleSubsystem_M->Timing.TaskCounters.TID[1] = 0;
}
}
这个if语句的3其实就是0.8/0.2 - 1 =3,也就是TID[1]的变化范围为{0,1,2,3}共4个数。rate_scheduler函数会在step函数的末尾执行。TID可以理解为TaskID,如果多增加一些复杂系统,则会增加TID数组的size。而在step函数的开头计算各个模块的值的部分也有所改变,此时不能看成1个简单的与门运算,代码为
SingleSubsystem_B.and = (SingleSubsystem_U.In1 && SingleSubsystem_U.In2);
if (SingleSubsystem_M->Timing.TaskCounters.TID[1] == 0)
{
/* Outport: '<Root>/Out1' */
SingleSubsystem_Y.Out1 = SingleSubsystem_B.and;
}
这里SingleSubsystem_B存储与Subsystem模块相关的信息。由于输出、输入的采样时间不一致,所以与门的值需要在TID[1]==0时赋予输出端。
如果我们把输出端采样时间改为0.3,那么0.3/0.2不是整数,会发生什么?此时生成代码会把步长设置为0.1,相当于取了2, 3的公因数1. 这是在模型参数配置->求解器->定步长中设置auto的情况,如果强行指定一个步长,那么生成的代码只能按照指定的步长来设置。生成代码时会检查模型中的所有采样时间必须为固定步长的整数倍,若不通过检查,则报错。