一个任务或者中断服务程序有时候需要和另一个任务交流信息,这个信息传递的过程称为任务间的通信,任务间的通信可以根据两种途径实现:一是通过全局变量,二是通过发布消息。任务要想与中断服务通信只能通过全局变量。
使用全局变量时,每一个任务或者中断服务程序都必须保证其对变量的独占访问。如果有中断服务程序参与,那唯一能保证对共享变量独占访问的方法就是关中断。
消息可以通过消息队列作为中介发布给任务,也可以直接发布给任务,因为μC/OS-III中,每个任务都内嵌有消息队列。当有多个任务子在等待消息的时候,可以使用外部的消息队列;而如果只有一个任务需要对接收的数据进行处理,则应该直接向任务发布消息。
1 消息
一则消息包含几个部分:指向数据的指针,表明数据长度的变量,记录消息发布时刻的时间戳。指针指向的可以是一块数据区或者甚至是一个函数。所以发布方和接收方需要对数据的内容和含义大承约定。
消息的内容必须一致保持可见性(指代表消息的变量必须在接收消息的任务代码范围内有效),因为发布数据采用的是引用传递,即传递的是指针变量而不是对值进行传递(发布的数据不产生拷贝).
1.1 保持数据的可见性
(1)串口收到字符时串口产生中断
(2)串口中断服务程序如下面代码(简略代码),服务程序读取串口收到的字节,并检查是否是包头,如果是则请求存储区进行存储
(3)收到的字符被存入缓存区
(4)当收到数据包结尾的字符时,将缓冲区的地址发布到消息队列,一遍任务可以处理收到的数据包
(5)发布的消息使得数据处理任务变为最高优先级任务,中断服务结束时,调度器调度任务,执行数据处理任务,而不返回被中断的任务,数据处理任务从消息队列取出数据包,OSQPend接收到的有数据的起始地址以及数据的大小及数据发送时的时间戳
(6)数据处理任务处理完数据包,通过调用OSMemPut()释放之前分配的存储块,将其返还给存储分区管理器。
void UART_ISR(void)
{
OS_ERR err;
RxData = Read Byte from UART;
if(RxData == Start of Packet) /*检查是否是数据包的包头*/
{
RxDataPtr = OSMemGet(&UART_MelPool, &err); /*是数据包就申请缓存区*/
RxDataStartPtr = RxDataPtr; /*记录数据包的起始地址*/
/*check error*/
*RxDataPtr++ = RxData; /*保存数据包报头*/
RxDataCtr = 1;
}
if(RxData == end of Packet Byte) /*检查是否是数据包的结尾*/
{
*RxDataPtr++ = RxData; /*保存数据包结尾*/
RxDataCtr++;
OSQPost((OS_Q*) *UART_Q, /*向相应的处理任务发布消息*/
(void*) RxDataStartPtr, /*发布消息的起始地址(这个顺序有些迷)*/
(OS_MSG_SIZE) RxDataCtr, /*发布消息的大小*/
(OS_OPT) OS_OPT_POST_FIFO,
(OS_ERR*) &err);
RxDataPtr = NULL; /*修改数据指针指向NULL,防止数据被修改*/
RxDataCtr = 0;
}
else /*保存收到的字节*/
{
*RxDataPtr++ = RxData;
RxDataCtr++;
}
}
2 消息队列
消息队列是一种由用户程序分配的内核对象,用户可以分配任意数量的消息队列,唯一的显示就是可用的RAM空间。
消息队列的读取采用先进先出(FIFO)的方式。在μC/OS-III中也可以采用后进先出(LIFO)的方式发布消息。当任务或者中断服务程序需要向一个任务发布一条紧急消息时,后进先出的方式(LIFO)非常有用。使用后进先出的方式,发布的消息会绕过所有其他已经位于雄安锡队列中的消息而最先传递给任务。
消息队列中包含一个等待列表,记录了所有正在等待获取消息的任务。当多个任务在一个消息队列中等待时,一旦一则消息被发布到消息队列,等待表中最高优先级的任务将获得该消息;或者发布方也可以向所有等待任务广播该消息。
函数 | 含义 |
---|---|
OSQCreate() | 创建一个消息队列 |
OSQDel() | 删除一个消息队列 |
OSQFlush() | 清空一个消息队列 |
OSQPend() | 等待消息 |
OSQPendAbort() | 取消等待消息队列 |
OSQPost() | 发布一则消息 |
2.1 使用消息队列
范例:使用消息队列确定飞轮转速
(1)程序的目的时测量一个转动飞轮的转速
(2)通过使用传感器探测转轮上的一个小孔来获得转速,实际上,如果要提高精度,转轮上可以有多个等间距的小孔
(3)程序使用一个32位的输入捕获寄存器和一个自由运行的计数器,每当转到小孔位置时,计时器的值就会被记录到输入捕获寄存器中。
(4)当检测到小孔位置时还会产生一个中断。中断服务程序读取输入捕获寄存器的当前值,并减去上一次的捕获值,结果就是飞轮转一圈的时间(假设飞轮上只有一个小孔)。
(5)
(6)计数增量被发送到消息队列
(7)消息发布后,转速测量任务被唤醒,可以设置一个超时时间,来知道飞轮是否停止转动
(8)任务还可以计算平均转速,最高转速,检测是否高于或低于设定的阈值
OS_Q RPM_Q
CPU_INT32U DeltaCounts; /*计数增量*/
CPU_INT32U CurrentCounts; /*当前计数值*/
CPU_INT32U PreviousCounts; /*前一次计数值*/
void main(void)
{
OS_ERR err;
...
OSInit(&err);
...
OSQCreate((OS_Q*) &RPM_Q
(CPU_CHAR*) "My Queue",
(OS_MSG_QTY) 10, /*消息队列的长度*/
(OS_ERR*) &err);
...
OSStart(&err);
}
/*中断服务函数*/
void RPM_ISR(void)
{
OS_ERR err;
Clear the interrupt from the sensor;
CurrentCounts= read the input capture;
DeltaCounts = CurrentCounts-PreviousCounts;
PreviousCounts = CurrentCounts;
OSQPost((OS_Q*) &RPM_Q,
(void*) &DeltaCounts;
(OS_MSG_SIZE) sizeof(DeltaCounts),
(OS_OPT) OS_OPT_POST_FIFO,
(OS_ERR*) &err);
}
/*任务函数*/
void RPM_Task(void* p_arg)
{
CPU_INT32U delta;
OS_ERR err;
OS_SMG_SIZE size;
CPU_TS ts;
DeltaCounts = 0;
PreviousCounts = 0;
CurrentCounts = 0;
while(DEF_ON)
{
delta = (CPU_INT32U)OSQPend((OS_Q*) &RPM_Q,
(OS_TICK) OS_CFG_TICK_RATE_HZ*10,
(OS_OPT) OS_OPT_PEND_BLOCKING,
(OS_MSG_SIZE*) &size,
(CPU_TS) &ts,
(OS_ERR*) &err);
if(err == OS_ERR_TIMEOUT)
RPM = 0;
else
{
if(delta > 0u)
RPM = 60*Reference Frequency.delta;
}
/*响应时间的计算(从发布消息到接收消息的间隔时间)*/
respond time = OS_TS_GET()-ts;
Compute average RPM;
Detect maximum RPM;
Check for overspeed;
Check for underspeed;
}
}
3 任务内建的消息队列
实际上,多个任务等待同一个消息队列的应用很少见。因此,在μC/OS-III中,每一个任务都有其内建的消息队列,用户可以不用通过外部的消息队列而直接向任务发布消息。
函数 | 含义 |
---|---|
OSTaskQPend() | 等待消息 |
OSTaskQPendAbort() | 取消等待消息 |
OSTaskQPost() | 向任务发布一条消息 |
OSTaskQFlush() | 清空任务的消息队列 |
4 双向同步
两个任务可以使用两个消息队列进行同步,与信号量效果类似,区别在于使用消息队列时双方都可以向对方发布消息。任务与中断服务程序之间无法实现双向同步,因为中断服务程序是无法进行等待消息操作的。
在双向同步中,每个消息队列最多只能容纳一则消息,在初始化时都为空,当左边的任务执行到同步点时,它向上面的消息队列发布一则消息,同样,当右边的任务执行到同步点时,它向下方的消息队列发布一则消息并等待上方消息队列中的消息。
5 流量控制
任务间的通信通常涉及到数据的传递,一个任务生产数据,而另一个任务消费数据。然而,数据的处理比较花时间,消费数据的速度也许赶不上生产数据的速度,或者,当有高优先级的任务抢占了处理数据的任务,那么生产者往队列中放数据就有可能溢出,解决该问题的一种办法是在数据传递的过程中加上流量控制。
可以使用一个计数型的信号量,其初始值为允许生产者发布的消息的消息数目,如果消费者最多只能容纳10则消息,则计数型信号量初始值为10。
6 消息队列内部细节
struct os_q { /* Message Queue */
/* ------------------ GENERIC MEMBERS ------------------ */
OS_OBJ_TYPE Type; /* Should be set to OS_OBJ_TYPE_Q */
CPU_CHAR *NamePtr; /* Pointer to Message Queue Name (NUL terminated ASCII) */
OS_PEND_LIST PendList; /* 消息队列挂起表*/
OS_MSG_Q MsgQ; /* 消息列表 */
};
/*消息队列的挂起表*/
struct os_pend_list {
OS_PEND_DATA *HeadPtr; /*等待消息队列优先级最高的任务*/
OS_PEND_DATA *TailPtr; /*等待消息队列优先级最低的任务*/
OS_OBJ_QTY NbrEntries; /*等待列表中等待任务的数量*/
};
/*消息列表*/
struct os_msg_q { /* OS_MSG_Q */
OS_MSG *InPtr; /* 下一则消息插入的位置(在该消息后面)*/
OS_MSG *OutPtr; /* 将要取出的消息位置*/
OS_MSG_QTY NbrEntriesSize; /* 消息队列最多容纳的OS_MSG数目*/
OS_MSG_QTY NbrEntries; /* 消息队列中的当前消息数目*/
OS_MSG_QTY NbrEntriesMax; /* 消息队列中消息数目曾经达到的最大值 */
};