ucos ii学习笔记3 消息队列、信号量集

时间:2020-12-03 20:07:07

这一篇可以说是上一篇的升级版,消息队列是邮箱的升级版,邮箱只能传递一个数据,消息队列可以传递多个数据。信号量集则是多个二值信号量的集合。

消息队列由3个部分组成:事件控制块、消息队列和消息。当把事件控制块成员OSEventType的值置为 OS_EVENT_TYPE_Q时,这个事件控制块描述的就是一个消息队列了。消息队列的数据结构如下图:(图片来源是探索者F4的书,应该不会侵权吧)

ucos ii学习笔记3 消息队列、信号量集ucos ii学习笔记3 消息队列、信号量集ucos ii学习笔记3 消息队列、信号量集

从图中可以看到,事件控制块成员OSEventPtr指向一个叫  队列控制块(OS_Q)  的结构,这个结构管理着一个数组  MsgTbl[ ],该数组中的元素都是一些指向消息的指针。

队列控制块的各参数含义如下表:

ucos ii学习笔记3 消息队列、信号量集

其中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是定义逻辑运算关系的一个常数,具体如下表:

ucos ii学习笔记3 消息队列、信号量集
信号滤波器OSFlagNodeFlags作用为选择性的挑选OSFlagFlags中的部分(或全部)位作为有效信号,信号量集各成员的关系如下图:
ucos ii学习笔记3 消息队列、信号量集
举个简单的例子,假设请求信号量集的任务设置OSFlagNodeFlags的值为0X0F,这只OSFlagNodeWaitType的值为WAIT_SET_ANY,那么只要OSFlagFlags低4位的任何一位为1请求信号量集的任务将得到有效请求,从而执行相关操作。

说完了大致原理,接下来就是与信号量集相关的几个常用函数了:
1.创建信号量集函数OS_FLAG_GRP   *OSFlagCreate(OS_FLAGS flags, INT8U *err);
其中,flags为信号量的初始值(OSFlagFlags的值),函数返回值是该信号量集的标志组的指针,程序根据这个指针对信号量集进行相应的操作。
2.请求信号量集函数OS_FLAGS  OSFlagPend (OS_FLAG_GRP  *pgrp, OS_FLAGS  flags, INT8U         wait_type, INT16U  timeout, INT8U     *perr);
其中,pgrp为所请求的信号量集指针,flags为滤波器(OSFlagNodeFlags),wait_type为逻辑运算类型(OSFlagNodeWaitType),timeout为等待时间,err为错误信息。
3.向信号量集发送信号函数
OS_FLAGS  OSFlagPost (OS_FLAG_GRP  *pgrp, OS_FLAGS   flags, INT8U  opt,  INT8U   *perr);
其中,pgrp为所请求的信号量集指针,flags为要发送的信号,opt为信号有效选项,err为错误信息。
下面照常上个例子过过眼:
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);//全部信号量清零
明显的,这个例程就是用信号量集来传递不同的按键。