制块的.OSEventGrp域为非0值时,说明该消息队列的等待任务列表中有任务。这时,调用
OSEventTaskRdy()函数[见6.02节,使一个任务进入就绪状态,OSEventTaskRdy()]从列表中取出最高优先级的任务[L6.23(3)], 并将它置于就绪状态。 然后调用函数OSSched()[L6.23(4)]进行任务的调度。如果上面取出的任务的优先级在整个系统就绪的任务里也是最高的,而且OSQPost()函数不是中断服务子程序调用的,就执行任务切换,该最高优先级任务被执行。否则的话,OSSched()函数直接返回,调用 OSQPost()函数的任务继续执行。
程序清单L6.23向消息队列发送一条消息
INT8UOSQPost(OS_EVENT*pevent,void*msg)
{
OS_Q*pq;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_Q){(1)
OS_EXIT_CRITICAL();
return(OS_ERR_EVENT_TYPE);
}
if(pevent->OSEventGrp){(2)
OSEventTaskRdy(pevent,msg,OS_STAT_Q);(3)
OS_EXIT_CRITICAL();
OSSched();
(4)
return(OS_NO_ERR);
}else{
pq=pevent->OSEventPtr;
if(pq->OSQEntries>=pq->OSQSize){(5)
OS_EXIT_CRITICAL();
return(OS_Q_FULL);
}else{
*pq->OSQIn++=msg;(6)
pq->OSQEntries++;
if(pq->OSQIn==pq->OSQEnd){
pq->OSQIn=pq->OSQStart;
}
OS_EXIT_CRITICAL();
}
return(OS_NO_ERR);
}
}
如果没有任务等待该消息队列中的消息,而且此时消息队列未满[L6.23(5)],指向该消息的指针被插入到消息队列中[L6.23(6)]。这样,下一个调用OSQPend()函数的任务就可以马上得到该消息。注意,如果此时消息队列已满,那么该消息将由于不能插入到消息队列中而丢失。
此外,如果OSQPost()函数是由中断服务子程序调用的,那么即使产生了更高优先级的任务,也不会在调用OSSched()函数时发生任务切换。这个动作一直要等到中断嵌套的最外层中断服务子程序调用OSIntExit()函数时才能进行(见3.09节,μC/OS-II中的中断)。
6.8.4 向消息队列发送一个消息(后进先出LIFO),OSQPostFront()
OSQPostFront()函数和OSQPost()基本上是一样的, 只是在插入新的消息到消息队列中时,使用.OSQOut作为指向下一个插入消息的单元的指针,而不是.OSQIn。程序清单L6.24是它的源代码。值得注意的是,.OSQOut指针指向的是已经插入了消息指针的单元,所以再插入新的消
息指针前,必须先将.OSQOut指针在消息队列中前移一个单元。如果.OSQOut指针指向的当前单
元是队列中的第一个单元[L6.24(1)],这时再前移就会发生越界,需要特别地将该指针指向队
列的末尾[L6.24(2)]。由于.OSQEnd指向的是消息队列中最后一个单元的下一个单元,因此.OSQOut必须被调整到指向队列的有效范围内[L6.24(3)]。因为QSQPend()函数取出的消息是
由OSQPend()函数刚刚插入的,因此OSQPostFront()函数实现了一个LIFO队列。
程序清单L6.24向消息队列发送一条消息(LIFO)
INT8UOSQPostFront(OS_EVENT*pevent,void*msg)
{
OS_Q*pq;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_Q){
OS_EXIT_CRITICAL();
return(OS_ERR_EVENT_TYPE);
}
if(pevent->OSEventGrp){
OSEventTaskRdy(pevent,msg,OS_STAT_Q);
OS_EXIT_CRITICAL();
OSSched();
return(OS_NO_ERR);
}else{
pq=pevent->OSEventPtr;
if(pq->OSQEntries>=pq->OSQSize){
OS_EXIT_CRITICAL();
return(OS_Q_FULL);
}else{
if(pq->OSQOut==pq->OSQStart){(1)
pq->OSQOut=pq->OSQEnd;(2)
}
pq->OSQOut--;(3)
*pq->OSQOut=msg;
pq->OSQEntries++;
OS_EXIT_CRITICAL();
}
return(OS_NO_ERR);
}
}
6.8.5 无等待地从一个消息队列中取得消息,OSQAccept()
如果试图从消息队列中取出一条消息,而此时消息队列又为空时,也可以不让调用任务等待而直接返回调用函数。这个操作可以调用OSQAccept()函数来完成。程序清单L6.25是该函数的源代码。 OSQAccept()函数首先查看pevent指向的事件控制块是否是由OSQCreate()函数建立的[L6.25(1)],然后它检查当前消息队列中是否有消息[L6.25(2)]。如果消息队列中有至少一条消息,那么就从.OSQOut指向的单元中取出消息[L6.25(3)]。OSQAccept()函数的调用函数需要对OSQAccept()返回的指针进行检查。如果该指针是NULL值,说明消息队列是空的,其中没有消息可以[L6.25(4)]。否则的话,说明已经从消息队列中成功地取得了一条消息。当中断服务子程序要从消息队列中取消息时, 必须使用OSQAccept()函数, 而不能使用OSQPend()函数。
程序清单L6.25无等待地从消息队列中取一条消息
void*OSQAccept(OS_EVENT*pevent)
{
void*msg;
OS_Q*pq;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_Q){(1)
OS_EXIT_CRITICAL();
return((void*)0);
}
pq=pevent->OSEventPtr;
if(pq->OSQEntries!=0){(2)
msg=*pq->OSQOut++;(3)
pq->OSQEntries--;
if(pq->OSQOut==pq->OSQEnd){
pq->OSQOut=pq->OSQStart;
}
}else{
msg=(void*)0;(4)
}
OS_EXIT_CRITICAL();
return(msg);
}
6.8.6 清空一个消息队列,OSQFlush()
OSQFlush()函数允许用户删除一个消息队列中的所有消息,重新开始使用。程序清单L6.26是该函数的源代码。和前面的其它函数一样,该函数首先检查pevent指针是否是执行一个消息队列[L6.26(1)],然后将队列的插入指针和取出指针复位,使它们都指向队列起始单元,同时,将队列中的消息数设为0[L6.26(2)]。这里,没有检查该消息队列的等待任务列表是否为空,因为只要该等待任务列表不空,.OSQEntries就一定是0。唯一不同的是,指针.OSQIn和.OSQOut此时可以指向消息队列中的任何单元,不一定是起始单元。
程序清单L6.26清空消息队列
INT8UOSQFlush(OS_EVENT*pevent)
{
OS_Q*pq;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_Q){(1)
OS_EXIT_CRITICAL();
return(OS_ERR_EVENT_TYPE);
}
pq=pevent->OSEventPtr;
pq->OSQIn=pq->OSQStart;(2)
pq->OSQOut=pq->OSQStart;
pq->OSQEntries=0;
OS_EXIT_CRITICAL();
return(OS_NO_ERR);
}
6.8.7 查询一个消息队列的状态,OSQQuery()
OSQQuery()函数使用户可以查询一个消息队列的当前状态。 程序清单L6.27是该函数的源代码。OSQQuery()需要两个参数:一个是指向消息队列的指针pevent。它是在建立一个消息队列时,由OSQCreate()函数返回的;另一个是指向OS_Q_DATA(见uCOS_II.H)数据结构的指针pdata。该结构包含了有关消息队列的信息。在调用OSQQuery()函数之前,必须先定义该数据结构变量。OS_Q_DATA结构包含下面的几个域:
.OSMsg 如果消息队列中有消息,它包含指针.OSQOut所指向的队列单元中的内容。如果队列是空的,.OSMsg包含一个NULL指针。
.OSNMsgs是消息队列中的消息数(.OSQEntries的拷贝)。
.OSQSize 是消息队列的总的容量
.OSEventTbl[]和.OSEventGrp是消息队列的等待任务列表。通过它们,OSQQuery()的调用函数可以得到等待该消息队列中的消息的任务总数。
OSQQuery()函数首先检查pevent指针指向的事件控制块是一个消息队列[L6.27(1)],然后复制等待任务列表[L6.27(2)]。如果消息队列中有消息[L6.27(3)],.OSQOut指向的队列单元中的内容被复制到OS_Q_DATA结构中[L6.27(4)], 否则的话, 就复制一个NULL指针[L6.27(5)]。
最后,复制消息队列中的消息数和消息队列的容量大小[L6.27(6)]。
程序清单L6.27程序消息队列的状态
INT8UOSQQuery(OS_EVENT*pevent,OS_Q_DATA*pdata)
{
OS_Q*pq;
INT8Ui;
INT8U*psrc;
INT8U*pdest;
OS_ENTER_CRITICAL();
if(pevent->OSEventType!=OS_EVENT_TYPE_Q){(1)
OS_EXIT_CRITICAL();
return(OS_ERR_EVENT_TYPE);
}
pdata->OSEventGrp=pevent->OSEventGrp;(2)
psrc=&pevent->OSEventTbl[0];
pdest=&pdata->OSEventTbl[0];
for(i=0;i
*pdest++=*psrc++;
}
pq=(OS_Q*)pevent->OSEventPtr;
if(pq->OSQEntries>0){(3)
pdata->OSMsg=pq->OSQOut;(4)
}else{
pdata->OSMsg=(void*)0;(5)
}
pdata->OSNMsgs=pq->OSQEntries;(6)
pdata->OSQSize=pq->OSQSize;
OS_EXIT_CRITICAL();
return(OS_NO_ERR);
}
6.8.8 使用消息队列读取模拟量的值
在控制系统中,经常要频繁地读取模拟量的值。这时,可以先建立一个定时任务OSTimeDly()[见5.00节,延时一个任务,OSTimeDly()],并且给出希望的抽样周期。然后,如图F6.11所示,让A/D采样的任务从一个消息队列中等待消息。该程序最长的等待时间就是抽样周期。当没有其它任务向该消息队列中发送消息时,A/D采样任务因为等待超时而退出等待状态并进行执行。这就模仿了OSTimeDly()函数的功能。
也许,读者会提出疑问,既然OSTimeDly()函数能完成这项工作,为什么还要使用消息队列呢?这是因为,借助消息队列,我们可以让其它的任务向消息队列发送消息来终止A/D采样任务等待消息,使其马上执行一次A/D采样。此外,我们还可以通过消息队列来通知A/D采样程序具体对哪个通道进行采样,告诉它增加采样频率等等,从而使得我们的应用更智能化。换句话说,我们可以告诉A/D采样程序,“现在马上读取通道3的输入值!”之后,该采样任务将重新开始在消息队列中等待消息,准备开始一次新的扫描过程。
图F6.11读模拟量输入——Figure6.11
6.8.9 使用一个消息队列作为计数信号量
在消息队列初始化时,可以将消息队列中的多个指针设为非NULL值(如void*1),来实现计数信号量的功能。这里,初始化为非NULL值的指针数就是可用的资源数。系统中的任务可以通过OSQPend()来请求“信号量”,然后通过调用OSQPost()来释放“信号量”,如程序清单L6.28。如果系统中只使用了计数信号量和消息队列,使用这种方法可以有效地节省代码空间。
这时将OS_SEM_EN设为0,就可以不使用信号量,而只使用消息队列。值得注意的是,这种方法
为共享资源引入了大量的指针变量。也就是说,为了节省代码空间,牺牲了RAM空间。另外,
对消息队列的操作要比对信号量的操作慢,因此,当用计数信号量同步的信号量很多时,这种
方法的效率是非常低的。
程序清单L6.28使用消息队列作为一个计数信号量
OS_EVENT*QSem;
void*QMsgTbl[N_RESOURCES]
voidmain(void)
{
OSInit();
.
QSem=OSQCreate(&QMsgTbl[0],N_RESOURCES);
for(i=0;i
OSQPost(Qsem,(void*)1);
}
.
.
OSTaskCreate(Task1,..,..,..);
.
.
OSStart();
}
voidTask1(void*pdata)
{
INT8Uerr;
for(;;){
OSQPend(&QSem,0,&err);/*得到对资源的访问权*/
.
./*任务获得信号量,对资源进行访问*/
.
OSMQPost(QSem,(void*)1);/*释放对资源的访问权*/
}
}