这一篇可以说是上一篇的升级版,消息队列是邮箱的升级版,邮箱只能传递一个数据,消息队列可以传递多个数据。信号量集则是多个二值信号量的集合。
消息队列由3个部分组成:事件控制块、消息队列和消息。当把事件控制块成员OSEventType的值置为 OS_EVENT_TYPE_Q时,这个事件控制块描述的就是一个消息队列了。消息队列的数据结构如下图:(图片来源是探索者F4的书,应该不会侵权吧)
从图中可以看到,事件控制块成员OSEventPtr指向一个叫 队列控制块(OS_Q) 的结构,这个结构管理着一个数组 MsgTbl[ ],该数组中的元素都是一些指向消息的指针。
队列控制块的各参数含义如下表:
其中OSQIn和OSQOut是可移动的指针,当可OSQIn或OSQOut移动到数组末尾,也就是OSQEnd时,可移动指针会被调整回到数组的起始位置OSQStart,也就是说,这个消息指针结构数组是头尾衔接的,形成一个循环队列。
接下来介绍几个常用的函数
1.OS_EVENT *OSQCreate(void * *start, INT16U size);
创建消息队列函数,start为存放消息缓冲区指针数组的地址,size为该数组的大小。创建一个消息队列前需要定义一个指针数组,然后把各个消息数据缓冲区的首地址存入这个数组中。
2.INT8U OSQPost(OS_EVENT *pevent , void *msg); 或 INT8U OSQPostFront(OS_EVENT *pevent, void *msg);
这两个都是 向消息队列发送消息函数,OSQPost是以FIFO(先进先出)的方式存放消息队列,OSQPostFront是按LIFO(后进先出)的方式。参数和 向邮箱发送消息函数 一样,就不多说了,值得说明的是,在创建消息队列时并不会申请好内存空间,因为每个消息占用的内存空间都是不知道,所以需要用户在使用前先申请内存,用mymalloc函数,然后读取完消息队列后要把内存释放。
3.void *OSQPend(OS_EVENT *pevent, INT16U timeout,INT8U *err);
请求消息队列函数,参数和 请求邮箱函数 一样,不多说了。
下面上个例子直观的感受一下:
OS_EVENT * q_msg; //消息队列
void * MsgGrp[256]; //消息队列存储地址,最大支持256个消息
...... //省略一堆东西q_msg=OSQCreate(&MsgGrp[0],256); //创建消息队列......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); } }...... void tmr3_callback(OS_TMR *ptmr,void *p_arg) //软件定时器3的回调函数{ 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); //发送队列 if(err!=OS_ERR_NONE) //发送失败 { myfree(SRAMIN,p); //释放内存 OSTmrStop(tmr3,OS_TMR_OPT_NONE,0,&err); //关闭软件定时器3 } }}
软件定时器等下会讲到,这里先忽视它,主要是消息队列的发送接收过程。就如前面说的,在发送队列前要先申请内存,例子里是 p=mymalloc(SRAMIN,13);这句申请了13字节内存,关于这个函数的详细使用方法以后再说,,先自行百度去~ 。~
至于sprintf函数,作用就是把一堆数据打印到一个地方去,这里是p,这个函数跟printf很像,只是输出地方不同,sprintf常用的就是把整数打印到字符串里,比如:
sprintf(s,"%d",123);
这样就会产生字符串“123”,不用自己转换来转换去的。具体用法就不在这里深究了。回到ucos ii,上面说完了消息队列的,接下来说一个 信号量的升级版 ,信号量集。
信号量集的作用跟拨码开关差不多,就是根据多个信号量的组合作用来决定任务的运行方式。和信号量、消息邮箱、消息队列不同的是,在ucos ii里面不使用事件控制块来描述信号量集,而是使用了一个 标志组结构OS_FLAG_GRP来描述,结构如下:
typedef
{
INT8U OSFlagType; //识别是否为信号量的标志
void *OSFlagWaitList; //指向等待任务链表的指针
OS_FLAGS OSFlagFlags; //所有信号列表
}OS_FLAGS_GRP;
成员OSFlagWaitLIst是一个指针,当一个信号量集被创建后,它就指向了这个信号量集的等待任务链表,链表节点OS_FLAG_NODE的结构如下:
typedef struct
{
void *OSFlagNodeNext; //指向下一个节点的指针
void *OSFlagNodePrev; //指向前一个节点的指针
void *OSFlagNodeTCB; //指向对应任务控制块的指针
void OSFlagNodeFlagGrp; //反向指向信号量集的指针
OS_FLAGS OSFlagNodeFlags; //信号滤波器
INT8U OSFlagNodeWaitType; //定义逻辑运算关系的数据
}OS_FLAG_NODE;
其中,最后一个成员OSFlagNodeWaitType是定义逻辑运算关系的一个常数,具体如下表:OS_FLAG_GRP * flags_key; //按键信号量集
.....
flags_key=OSFlagCreate(0,&err); //创建信号量集
.....
OSFlagPost(flags_key,1<<(key-1),OS_FLAG_SET,&err);//设置对应的信号量为1,key为按键结果
.....
u16 flags;
.....
flags=OSFlagPend(flags_key,0X001F,OS_FLAG_WAIT_SET_ANY,0,&err);//等待信号量
if(flags&0X0001)LCD_ShowString(140,162,240,16,16,"KEY0 DOWN ");
if(flags&0X0002)LCD_ShowString(140,162,240,16,16,"KEY1 DOWN ");
if(flags&0X0004)LCD_ShowString(140,162,240,16,16,"KEY2 DOWN ");
if(flags&0X0008)LCD_ShowString(140,162,240,16,16,"KEY_UP DOWN");
if(flags&0X0010)LCD_ShowString(140,162,240,16,16,"TPAD DOWN ");
delay_ms(50);
OSFlagPost(flags_key,0X001F,OS_FLAG_CLR,&err);//全部信号量清零
明显的,这个例程就是用信号量集来传递不同的按键。