Matlab生成dsp程序——官方例程学习(6)
官方链接:官方链接
ccs程序即模型:模型及程序
程序与程序中有我看的时候的中文标注,可能看起来更容易一点。
IPC通信
一、主要目的
这个例程是使用IPC进行2837xD的两个核的通信,CPU1分别启用了四个IPC通道进行数据传输。这里应该使用的是寄存器传输(看到程序后发现这里是重新封装了IPC,还是用的内存共享,而且封装的较为巧妙建议阅读一程序中的**IPCInit()**函数),一次传送数据不超过32位。
通道号 | 传送内容 | 采样时间 |
---|---|---|
IPC0 | 16位整数(0-2^16-1循环)[16 bits] | 1 |
IPC1 | 32位无符号整型数(1234567)[32 bits] | 0.5 |
IPC2 | 1×10矩阵(1 2 3 … 10)[16 bits] | 0.1 |
IPC3 | 单精度浮点数(1.234 2.345 3.456)[32 bits] | 0.01 |
CPU2中对CPU1传送过来的数据进行接收。
IPC0和IPC1传送的数据由IPC0中断和IPC1中断进行接收。
IPC2和IPC3传送的数据由系统轮询进行获取。
因此IPC0和IPC1的接收模块采样时间由中断函数进行决定,在IPC Receive中设置为-1(即继承上一级采样时间)
IPC2和IPC3采样时间则与CPU1的设置完全一样。
二、程序分析
1. CPU1程序
void rt_OneStep(void)
//每0.01s进入一次 采样时间:IPC0-1 IPC1-0.5 IPC2-0.1 IPC3-0.01s
//按照这个逻辑
//IPC3应该是每进入一次中断执行一次
//IPC2 每进入10次执行一次
//IPC1 每进入50次执行一次
//IPC0 每进入100次执行一次
{
boolean_T eventFlags[4];
int_T i;
/* Check base rate for overrun */
if (isRateRunning[0]++) { //1.{0 0 0}变为{1 0 0 0}
IsrOverrun = 1;
isRateRunning[0]--; /* allow future iterations to succeed*/
return;
}
/*
* For a bare-board target (i.e., no operating system), the rates
* that execute this base step are buffered locally to allow for
* overlapping preemption. The generated code includes function
* writeCodeInfoFcn() which sets the rates
* that need to run this time step. The return values are 1 and 0
* for true and false, respectively.
*/
c2837xd_ipc_cpu1_tx_SetEventsForThisBaseStep(eventFlags); //TID[1-3]赋值给eventFlags[1-3]
enableTimer0Interrupt();
c2837xd_ipc_cpu1_tx_step0(); //TD[1-3]自加,TD[1]在0-9循环 TD[2]在0-49循环 TD[3]在0-99循环
/* Get model outputs here */
disableTimer0Interrupt();
isRateRunning[0]--; //{1 0 0}变为{0 0 0 0}
for (i = 1; i < 4; i++)
{
if (eventFlags[i]) //eventFlags[i]不为0进入if //1.{0 2 2 2}
{
if (need2runFlags[i]++) //need2runFlags[i]不为0进入if {0 1 1 1}
{
IsrOverrun = 1;
need2runFlags[i]--; /* allow future iterations to succeed*/
break;
}
}
}
for (i = 1; i < 4; i++)
{
if (isRateRunning[i])
{
/* Yield to higher priority*/
return;
}
if (need2runFlags[i])
{
isRateRunning[i]++; //{0 1 0 0}
enableTimer0Interrupt();
/* Step the model for subrate "i" */
switch (i)
{
case 1 :
c2837xd_ipc_cpu1_tx_step1();
/* Get model outputs here */
break;
case 2 :
c2837xd_ipc_cpu1_tx_step2();
/* Get model outputs here */
break;
case 3 :
c2837xd_ipc_cpu1_tx_step3();
/* Get model outputs here */
break;
default :
break;
}
disableTimer0Interrupt();
need2runFlags[i]--;
isRateRunning[i]--;
}
}
}
以上部分是Timer0中断函数中的主要程序,这个程序实现了采样时间的分配。即
每0.01s进入一次Timer0中断函数 采样时间:IPC0-1 IPC1-0.5 IPC2-0.1 IPC3-0.01s
按照这个逻辑
IPC3应该是每进入一次中断执行一次
IPC2 每进入10次执行一次
IPC1 每进入50次执行一次
IPC0 每进入100次执行一次
明白这一机理需要了解一个结构体,c2837xd_ipc_cpu1_tx_M:
struct tag_RTM_c2837xd_ipc_cpu1_tx_T {
const char_T *errorStatus;
/*
* Timing:
* The following substructure contains information regarding
* the timing information for the model.
*/
struct
{
struct
{
uint8_T TID[4];
} TaskCounters;
} Timing;
};
其中的TID[4]就是任务时间分配的工作,这里IPC0-IPC3一共四个任务,并且四个任务的采样时间都不同,所以TID数组中有四个元素。可以推理,当有更多的不同采样时间时,也会有更多的元素。
-
TID[0]对应IPC3的采样时间(0.01 s)
-
TID[1]对应IPC2的采样时间(0.1 s)
-
TID[2]对应IPC1的采样时间(0.5 s)
-
TID[3]对应IPC0的采样时间(1 s)
试想一下,我要控制IPC2每0.1 s执行一次,是不是只用弄个计数器,一但IPC3执行了十次,那么是不是就代表过去了0.1 s,这时便可以执行IPC2。因此程序中规定:TD[1]在0-9循环,TD[2]在0-49循环,TD[3]在0-99循环。
对应这里顺便提一下,Simulink中的步长设置为自动还是有好处的,因为每个采样时间必须是步长的整数倍。生成的代码也从侧面反应了Simulink中的计算方式吧。
TID中的元素会在每个步长(IPC3)程序中进行自加1。c2837xd_ipc_cpu1_tx_SetEventsForThisBaseStep(eventFlags)这个函数当中会判断TID[x]是否等于0(等于0就代表完成了一次计数周期)。一旦达到了计数周期就会把相应的eventFlags位置置为1。这是通过switch函数进行选择执行函数。
(eg:当TID[1]完成了一次0-9的计数周期,会将eventFlags[1]中的元素置为1,然后通过switch函数执行**step1()**函数)困扰了几天的问题终于解决了。。(遇到逻辑问题可以借助DEBUG或C语言编写程序进行模拟)
这个时序控制的方式比较有意思,看下面这个函数:实现了每0.5 s翻转一次LED的操作。
while(1)
{
LED =1;
DELAY_MS(500);
LED=0;
DELAY_MS(500);
}
换句话说,按照时序的观点,是不是我建立一个定时器。一旦计时到达了0.5 s 我就对LED进行一次操作。这就是生成代码的时序的观点。与我们进行自己写程序时,有所不同。这将是我们在将代码转换成Simulimk模型时,需要注意的事情。
CPU2程序中的程序就是进行中断接收和轮询方式接收了,没什么特别的。
三、生成代码中结构体记录
看程序的时候,一定要对照这些结构体和simulink中的函数参数进行仔细查看。
1)控制模型时序的结构体,实例化对象c2837xd_ipc_cpu1_tx_M
struct tag_RTM_c2837xd_ipc_cpu1_tx_T {
const char_T *errorStatus;
struct
{
struct
{
uint8_T TID[4];
} TaskCounters;
} Timing;
};
//控制模型时序的结构体
//别名:RT_MODEL_c2837xd_ipc_cpu1_tx_T
//实例化对象:c2837xd_ipc_cpu1_tx_M
2)模块信号结构体,实例化对象:c2837xd_ipc_cpu1_tx_B
typedef struct {
uint16_T Output; /* '<S1>/Output' */
} B_c2837xd_ipc_cpu1_tx_T;
//模块信号结构体
//实例化对象:c2837xd_ipc_cpu1_tx_B
3)模块状态结构体,实例化对象:c2837xd_ipc_cpu1_tx_DW
typedef struct {
uint16_T Output_DSTATE; /* '<S1>/Output' */
} DW_c2837xd_ipc_cpu1_tx_T;
4)模块有关参数寄存器,实例化对象:c2837xd_ipc_cpu1_tx_P(这个函数里面的所有内容都和模块有关系。我们看到的Simulink模块也是经过封装,可以直接拖拽使用,这里转为程序时,将每个模块的底层需要重新实现)
struct P_c2837xd_ipc_cpu1_tx_T_ {
uint16_T WrapToZero_Threshold; /* Mask Parameter: WrapToZero_Threshold
//65535 = 2^16-1 * Referenced by: '<S3>/FixPt Switch'*/
//显然和IPC0传送的(0-2^16-1)数有关,这是最大值。
real32_T Vectoroftypesingle_Value[3]; //float 32位
//{1.234F 2.345F 3.456F}
//IPC3传送的单精度数
/* Expression: [single(1.234) single(2.345) single(3.456)]
* Referenced by: '<Root>/Vector of type single'*/
uint32_T uin32constant_Value; //32位
// 1234567U
// IPC1传送的32位无符号整型数
/* Computed Parameter: uin32constant_Value
* Referenced by: '<Root>/uin32 constant'
*/
uint16_T Constant_Value; //一旦 加到2^16-1 时,需要把计数器的值重新修改为0。这个函数就代表这个值。
// 0U
//IPC0传送值的下限
/* Computed Parameter: Constant_Value
* Referenced by: '<S3>/Constant'
*/
uint16_T Vector1to10_Value[10];
// { 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U },
//IPC2传送的值
/* Computed Parameter: Vector1to10_Value
* Referenced by: '<Root>/Vector 1 to 10 '
*/
uint16_T Output_InitialCondition;
// 0U
/* Computed Parameter: Output_InitialCondition
* Referenced by: '<S1>/Output'
*/
uint16_T FixPtConstant_Value;
//1U
/* Computed Parameter: FixPtConstant_Value
* Referenced by: '<S2>/FixPt Constant'
*/
};
上面官方给的注释就是在Simulink主界面的各个元件,这种应该是Subsystem(子函数)也就是封装的底层。
这个IPC传送函数封装的还是很好的,希望有机会能够移植到自己的代码中。