对于事件来说,当其被占用时,会导致其他请求该事件的任务因暂时的得不到该事件的服务而处于等待状态。作为功能完善的事件,应该对等待任务具有两方面的管理功能:
一是要对等待事件的所有任务进行记录并排序;
二是应该允许等待任务有一个等待时限。
UCOSII定义了一个INT8U类型的数组OSEventTb1[]作为等待事件的任务的记录表,即等待任务表。位为1则表示为等待任务,0则不是。
为了加快对该表的访问速度,也定义了一个INT8U类型的变量OSEventGrp来表示等待任务表中的等待组。OSEventTb1[]与变量OSEventGrp示意图如下:
为了把事件统一起来,UCOSII用事件控制块ECB的数据结构来描述信号量、消息邮箱、和消息队列等事件。结构如下:
#if (OS_EVENT_EN) && (OS_MAX_EVENTS > 0u)
typedef struct os_event {
INT8U OSEventType; /* 事件的类型 */
INT16U OSEventCnt; /* 信号量计数器 */
void *OSEventPtr; /* 消息或消息队列的指针 */
OS_PRIO OSEventGrp; /* 等待事件的任务组 */
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* 任务等待表 */
} OS_EVENT;
1)事件控制块初始化函数
void OS_EventWaitListInit(
OS_EVENT *pevent //事件控制块的指针
)
void OS_EventTaskWait(
OS_EVENT *pevent //事件控制块的指针
)
3)使一个正在等待的任务进入就绪状态的函数
INT8U OS_EventTaskRdy(
OS_EVENT *pevent //事件控制块的指针
void *msg, //未使用
INT8U msk //清除TCB状态标识掩码
)
4)使一个等待超时的任务进入就绪状态的函数
void OS_EventTO(
OS_EVENT *pevent //事件控制块的指针
)
UCOSII初始化时,系统会在初始化函数OSInit()中按照程序使用事件的总数OS_MAX_EVENTS(在文件OS_CFG.H中定义)创建OS_MAX_EVENTS个空事件控制块并借用成员OSEventPtr作为链接指针。由于链表中所有控制块尚未与具体事件想关联,因此叫空事件控制块链表。以后,每当应用程序创建一个事件时,系统会从链表中取出一个空事件控制块,并对它进行初始化以描述该事件。当删除一个事件时,系统会将该事件的控制块归还给空事件控制块链表。
如果要在任务与与任务之间传递一个数据,那么为了适应不同数据的需要最好在存储器中建立一个数据缓存区,然后以这个缓存区为中介来实现任务间的数据传递。
把数据缓存区指针赋给控制块的成员OSEventPtr,同时使事件控制块成员OSEventType为常数OS_EVENT_TYPE_MBOX,则事件控制块就叫做消息邮箱。消息邮箱通过在两个需要通信的任务之间传递数据缓冲区指针进行通信。消息邮箱数据结构如下图:
1.定义消息邮箱
OS_EVENT * msg_key; //定义按键消息邮箱事件块指针
2.创建消息邮箱
msg_key=OSMboxCreate((void*)0); //创建消息邮箱,定义msg初值为NULL
3请求消息邮箱
//在主任务中请求消息邮箱,切等待时间为10ms,根据消息邮箱中的键值做出不同的动作
void main_task(void *pdata)
{
u32 key=0;
u8 err;
u8 semmask=0;
u8 tcnt=0;
while(1)
{
key=(u32)OSMboxPend(msg_key,10,&err); //请求邮箱函数,请求消息邮箱指针为msg_key,等待时间为100ms
switch(key)
{
case 1://控制DS1
LED1=!LED1;
break;
case 2://发送信号量
semmask=1;
OSSemPost(sem_beep);//发送信号量函数,释放信号量,释放信号量后UART_TASK任务才能执行
OSMutexPost(sem_mutex);//发送互斥型信号量
break;
case 3://清除
LCD_Fill(0,121,lcddev.width,lcddev.height,WHITE);
break;
case 4://校准
OSTaskSuspend(TOUCH_TASK_PRIO); //挂起触摸屏任务
if((tp_dev.touchtype&0X80)==0)TP_Adjust();
OSTaskResume(TOUCH_TASK_PRIO); //解挂
ucos_load_main_ui(); //重新加载主界面
break;
}
if(semmask||sem_beep->OSEventCnt)//需要显示sem,即此时的信号量计数器值不为0
{
LCD_ShowxNum(192,50,sem_beep->OSEventCnt,3,16,0X80);//显示信号量的值
if(sem_beep->OSEventCnt==0)semmask=0; //停止更新
}
if(tcnt==50)//0.5秒更新一次CPU使用率
{
tcnt=0;
POINT_COLOR=BLUE;
LCD_ShowxNum(192,30,OSCPUUsage,3,16,0); //显示CPU使用率
}
tcnt++;
delay_ms(10);
}
}
4.发生消息邮箱
//在按键扫描任务中发生消息邮箱
void key_task(void *pdata)
{
u8 key;
while(1)
{
key=KEY_Scan(0);
if(key)OSMboxPost(msg_key,(void*)key);//向邮箱发送消息函数,把当前KEY值传送到邮箱当中,在main_task中查询该值,以实现KEY任务与MAINR任务之间的通信
delay_ms(10);
}
}
5.查询邮箱的状态
INT8U OSMboxQuery(
OS_EVENT *pevent, //消息邮箱指针
SO_MBOX_DATA *pdata //存放邮箱消息的结构
)
SO_MBOX_DATA 结构如下:
typedef struct
{
void *OSMsg;
INT8U OSEventTb1[OS_EVENT_TBL_SIZE];
INTU8 OSEventGrp;
}SO_MBOX_DATA
6.删除邮箱
OS_EVENT *OSboxDel(
OS_EVENT *pevent, //消息邮箱指针
INT8U opt, //删除选项
INT8U *err //错误信息
)
使用消息队列可以在任务之间传递多条消息,消息队列由三部分组成:事件控制块、消息队列、消息。
当事件控制块成员OSEventType值为OS_EVENT_TYPE_Q时,该事件控制块代表一个消息队列。
消息队列相当于一个共用一个任务等待列表的消息邮箱数组,事件控制块成员OSEventPtr指向一个叫做队列控制块(OS_Q)的结构,该结构管理着一个数组MsgTb1[],该数组中的元素都是指向消息的指针。
消息队列的核心部件为消息指针数组。
可以采用2种方式向指针数组插入消息:先进先出的FIFO方式和后进先出的LIFO方式。当采用FIFO方式时,消息队列将在指针OSQIn指向的位置插入消息指针,而OSQOut指向的消息指针为输出;当采用LIFO方式时,则只使用指针OSQOut。
UCOSII把消息指针数组的基本参数都记录在一个叫做队列控制块的结构中。队列控制块的结构如下:
#if OS_Q_EN > 0u
typedef struct os_q { /* QUEUE CONTROL BLOCK */
struct os_q *OSQPtr; /* Link to next queue control block in list of free blocks */
void **OSQStart; /* Pointer to start of queue data */
void **OSQEnd; /* Pointer to end of queue data */
void **OSQIn; /* Pointer to where next message will be inserted in the Q */
void **OSQOut; /* Pointer to where next message will be extracted from the Q */
INT16U OSQSize; /* Size of queue (maximum number of entries) */
INT16U OSQEntries; /* Current number of entries in the queue */
} OS_Q;
在UCOSII系统初始化时,系统将按文件OS_CFG.H中配置OS_MAX_QS个队列控制块,并用队列控制块中的指针OSQPtr将所有队列控制块链接为链表。由于这时还没有使用它们,因此这个链表叫做空队列控制块列表。
1)定义消息队列
OS_EVENT * q_msg; //定义消息队列控制块
2)创建消息队列
q_msg=OSQCreate(&MsgGrp[0],256); //创建消息队列,缓存区指针数组地址为MsgGrp[0],数组大小为256
3)请求消息队列
//队列消息显示任务:每500ms显示当前消息队列的内容
void qmsgshow_task(void *pdata)
{
u8 *p;
u8 err;
while(1)
{
p=OSQPend(q_msg,0,&err);//请求消息队列,且为无限等待
LCD_ShowString(5,170,240,16,16,p);//显示消息
myfree(SRAMIN,p);
delay_ms(500);
}
}
4)向消息队列发生消息
//软件定时器3的回调函数,100ms溢出一次,自动发送消息到消息队列
void tmr3_callback(OS_TMR *ptmr,void *p_arg)
{
u8* p;
u8 err;
static u8 msg_cnt=0; //msg编号
p=mymalloc(SRAMIN,13); //申请13个字节的内存
if(p)
{
sprintf((char*)p,"ALIENTEK %03d",msg_cnt);
msg_cnt++;
err=OSQPost(q_msg,p); //发送队列,q_msg:消息队列指针。p:待发送消息指针
if(err!=OS_ERR_NONE) //发送失败
{
myfree(SRAMIN,p); //释放内存
OSTmrStop(tmr3,OS_TMR_OPT_NONE,0,&err); //关闭软件定时器3
}
}
}
5)清空消息队列
INT8U OSQFlush(
OS_EVENT *pevent //消息队列指针
)
6)删除消息队列
INT8U OSQDel(
OS_EVENT *pevent //消息队列指针
)
7)查询消息队列
INT8U OSQQuery(
OS_EVENT *pevent //消息队列指针
OS_Q_DATA*pdata //存放状态信息的结构
)
函数中的参数pdata是OS_Q_DATApdata类型的指针。OS_Q_DATApdata的机构如下:
typedef struct struct{
void *OSMsg; /* Pointer to next message to be extracted from queue */
INT16U OSNMsgs; /* Number of messages in message queue */
INT16U OSQSize; /* Size of message queue */
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* List of tasks waiting for event to occur */
OS_PRIO OSEventGrp; /* Group corresponding to tasks waiting for event to occur */
} OS_Q_DATA;
1.消息邮箱是特殊的消息队列,是大小为1的消息队列。
2.消息邮箱是能够在任务之间传递消息指针的数据结构。
3.消息队列是能够在任务之间传递一组消息指针的数据结构。
4.信号量、消息邮箱、消息队列都叫做“事件”,每个事件都有一个用来记录等待事件的任务的表----等待任务表,而任务的等待时限则记录在OSTCBDly中。
5.UCOSII统一用事件控制块来描述各种事件。
6.操作系统中各任务之间的同步与通信是通过各种各样的事件来完成的。
7.重要总结:消息队列的应用有点类似于信号量。
函数需要通过访问事件控制块的成员OSEventPtr指向的队列控制块OS_Q的成员OSQEntries来判断是否有消息可用(有点类似于信号量计数值cnt)。如果有消息可用,则返回OS_Q成员OSQOut指向的消息,同时调整指针OSQOut,使之指向下一条消息并把有效消息数的变量OSQEntries减1,如果无消息可用(也就是OSQEntries=0),则使用调用函数OSQPend()的任务挂起,使之处于等待状态并引发一次任务调度。同样,向队列发送消息后OSQEntries+1.如果希望任务无等待的请求一个消息队列,则可调用函数OSQAccept();函数原型为:void OSQAccept(OS_EVENT *pevent);