消息队列作为UCOS-Ⅲ中任务间通信重要的一环,虽然底层原理较为复杂,但在使用中我们只需注意他的API调用即可,本文讲述UCOS-Ⅲ的消息队列API如何调用

文章目录

  • UCOS-Ⅲ消息队列
  • 一、消息队列基本概念
  • 二、调用API及变量类型
  • 三、调用实例
  • 注意问题:

UCOS-Ⅲ消息队列

一、消息队列基本概念

消息队列属于队列结构,用于任务与任务、任务与中断进行通信的数据结构,读取的目标消息队列为空的情况下,当前的任务将被阻塞,可以指定阻塞超时时间,超时时间内如果有消息传输过来,则阻塞任务被唤醒,没有消息则在阻塞时间到期后,任务进入就绪态运行,因此消息队列是一种异步通信的方式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ySkZOqYR-1611018728823)(UCOS-Ⅲ:消息队列/20201202231528116.jpg)]
ucos消息队列特色:

  1. 一般先进先出(FIFO),紧急消息后进先出(LIFO)
  2. 支持超时机制
  3. 允许不同长度的消息传递(数据为指针传递)
  4. 一个任务可以从任意一个队列来接收和发送消息
  5. 多个任务也能从同一个队列接收和发送消息
  6. 队列使用完成可以删除

二、调用API及变量类型

  • API:
  • 消息队列创建:(中断中禁止调用)
/* 创建消息队列 queue */
    OSQCreate ((OS_Q         *)&msg_ptr,            //指向消息队列的指针
               (CPU_CHAR     *)"Queue For Test",  	//队列的名字
               (OS_MSG_QTY    )20,               	 //最多可存放消息的数目
               (OS_ERR       *)&err);            	 //返回错误类型

QS_Q为消息队列结构体指针

该函数创建一个消息队列,把消息队列指针相关属性配置好,用于之后消息队列的一系列操作

  • 消息队列删除:(中断中禁止调用)
OS_OBJ_QTY    OSQDel (OS_Q                  *p_q,	//指向消息队列的指针
                       OS_OPT                 opt,	//删除操作选择
                       OS_ERR                *p_err);//返回错误类型

删除操作可选如下:

OS_OPT_DEL_NO_PEND:                           //只在没有任务等待该消息队列的情况下删除队列
OS_OPT_DEL_ALWAYS:                             //删除该消息队列,以及清空该任务的等待列表
  • 清空消息队列消息列表:(中断中禁止调用)
OS_MSG_QTY    OSQFlush                  (OS_Q                  *p_q,//指向消息队列的指针
                                         OS_ERR                *p_err);

该函数用于把消息队列中的消息释放回消息池

  • 等待一个消息队列:
void  *OSQPend (OS_Q         *p_q,       //消息队列指针
                OS_TICK       timeout,   //等待期限(单位:时钟节拍)
                OS_OPT        opt,       //选项
                OS_MSG_SIZE  *p_msg_size,//返回消息大小(单位:字节)
                CPU_TS       *p_ts,      //获取等到消息时的时间戳
                OS_ERR       *p_err)     //返回错误类型

该函数用于取出消息队列中的消息,并将该消息的地址,通过该函数返回。

等待消息的选项有两种如下:

OS_OPT_PEND_BLOCKING-任务会一直阻塞在当前点,不会往下执行,或执行当前任务中的其他任务,直到该任务等待的消息队列中有消息出现时,才退出阻塞,继续执行。

OS_OPT_PEND_NON_BLOCKING-该设置配合等待期限配置,等待期限为0时,任务执行到当前位置,判断有无消息,无消息就直接执行之后的语句,不会停留,若timeout有值,则任务阻塞对应的节拍数目,阻塞器件有消息过来就处理,节拍结束后则继续执行。

等待消息队列的返回错误类型也很重要,用于判断程序的执行结果,我列出重要的返回类型
OS_ERR_NONE 任务成功接收到消息
OS_ERR_TIMEOUT 在NON_BLOCKING状态时 任务没有接收到消息,并且超时返回的错误标志,用于之后的程序语句进行判断,防止误操作!!!

  • 取消当前等待消息队列,一般用于处理错误时,不要经常使用:(中断中禁止调用)
OS_OBJ_QTY  OSQPendAbort (OS_Q    *p_q,    //消息队列
                          OS_OPT   opt,    //选项
                          OS_ERR  *p_err)  //返回错误类型
  • 向消息队列发送一条消息:(消息队列API中 中断中唯一可以调用的)
void  OSQPost (OS_Q         *p_q,      //消息队列指针
               void         *p_void,   //消息指针
               OS_MSG_SIZE   msg_size, //消息大小(单位:字节)
               OS_OPT        opt,      //选项 
               OS_ERR       *p_err)    //返回错误类型

该函数的可用选项如下:

OS_OPT_POST_ALL		给所有等待该信号量任务发信号/没定义则只给一个任务(最高优先级的)发信号;

OS_OPT_POST_FIFO	先进先出消息

OS_OPT_POST_LIFO    后进先出(紧急消息)

OS_OPT_POST_FIFO + OS_OPT_POST_ALL    先进先出消息+发给所有消息队列

OS_OPT_POST_LIFO + OS_OPT_POST_ALL    后进先出消息+发给所有消息队列

OS_OPT_POST_FIFO + OS_OPT_POST_NO_SCHED    先进先出消息+发布后不调度

OS_OPT_POST_LIFO + OS_OPT_POST_NO_SCHED    后进先出消息+发布后不调度

OS_OPT_POST_FIFO + OS_OPT_POST_ALL + OS_OPT_POST_NO_SCHED    先进先出消息+发给所有消息队列+发布后不调度

OS_OPT_POST_LIFO + OS_OPT_POST_ALL + OS_OPT_POST_NO_SCHED    后进先出消息+发给所有消息队列+发布后不调度

三、调用实例

使用正点原子F103精英板基于野火UCOS源码演示:

  • 一对一发送消息问题:
  1. 在启动任务中创建消息队列:
/* 创建消息队列 queue */
    OSQCreate ((OS_Q         *)&queue,            //指向消息队列的指针
               (CPU_CHAR     *)"Queue For Test",  //队列的名字
               (OS_MSG_QTY    )20,                //最多可存放消息的数目
               (OS_ERR       *)&err);             //返回错误类型
  1. 在UCOS内创建两个线程:数据发送线程和数据接收线程;(线程主体如下)

发布消息线程:

/*
*********************************************************************************************************
*                                          POST TASK	发布消息任务
*********************************************************************************************************
*/
static  void  AppTaskPost ( void * p_arg )
{
	OS_ERR      err;
	(void)p_arg;				 
	while (DEF_TRUE) {                                            //任务体
		/* 发布消息到消息队列 queue */
    OSQPost ((OS_Q        *)&queue,                             //消息变量指针
             (void        *)"Fire uC/OS-III",                //要发送的数据的指针,将内存块首地址通过队列“发送出去”
             (OS_MSG_SIZE  )sizeof ( "Fire uC/OS-III" ),     //数据字节大小
             (OS_OPT       )OS_OPT_POST_FIFO, //先进先出和发布给全部任务的形式
             (OS_ERR      *)&err);	                            //返回错误类型
		
		OSTimeDlyHMSM ( 0, 0, 0, 500, OS_OPT_TIME_DLY, & err );     //每隔500ms发送一次				 
	}
}

接收消息线程:

/*
*********************************************************************************************************
*                                          PEND TASK	接收消息任务
*********************************************************************************************************
*/
static  void  AppTaskPend ( void * p_arg )
{
	OS_ERR      msg1_err;
	OS_MSG_SIZE msg_size;
	CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必需该宏,该宏声明和
					//定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
					// SR(临界段关中断只需保存SR),开中断时将该值还原。
	char * pMsg;
	(void)p_arg;		 
	while (DEF_TRUE) {                                       //任务体
		/* 请求消息队列 queue 的消息 */
    pMsg = OSQPend ((OS_Q         *)&queue,                //消息变量指针
                    (OS_TICK       )0,                     //等待时长为无限
                    (OS_OPT        )OS_OPT_PEND_BLOCKING,  //如果没有获取到消息就等待
                    (OS_MSG_SIZE  *)&msg_size,             //获取消息的字节大小
                    (CPU_TS       *)0,                     //获取任务发送时的时间戳
                    (OS_ERR       *)&msg1_err);                 //返回错误
		if ( msg1_err == OS_ERR_NONE )                              //如果接收成功
		{
			OS_CRITICAL_ENTER();                                 //进入临界段
			
			printf ( "\r\n接收消息的长度:%d字节,内容:%s\r\n", msg_size, pMsg );

			OS_CRITICAL_EXIT();
		}
	}
}

发布消息线程每隔500ms发送一次消息,而接收线程则一直等待在阻塞态,直到等待的消息队列有数据,退出等待处理任务。

  • 一个任务同时接收多个消息队列的问题
  1. 在启动任务中创建两个消息队列:
/* 创建消息队列 queue */
OSQCreate ((OS_Q         *)&queue1,            //指向消息队列的指针
           (CPU_CHAR     *)"Queue For Test1",  //队列的名字
           (OS_MSG_QTY    )20,                //最多可存放消息的数目
           (OS_ERR       *)&err);             //返回错误类型
	/* 创建消息队列 queue */
OSQCreate ((OS_Q         *)&queue2,            //指向消息队列的指针
           (CPU_CHAR     *)"Queue For Test2",  //队列的名字
           (OS_MSG_QTY    )20,                //最多可存放消息的数目
           (OS_ERR       *)&err);             //返回错误类型
  1. 创建两个发送任务和一个接收任务:

两个发送任务:

/*
*********************************************************************************************************
*                                          POST TASK	发布消息任务
*********************************************************************************************************
*/
static  void  AppTaskPost1 ( void * p_arg )
{
	OS_ERR      err;
	(void)p_arg;				 
	while (DEF_TRUE) {                                            //任务体
		/* 发布消息到消息队列 queue */
    OSQPost ((OS_Q        *)&queue1,                             //消息变量指针
             (void        *)"Fire uC/OS-III",                //要发送的数据的指针,将内存块首地址通过队列“发送出去”
             (OS_MSG_SIZE  )sizeof ( "Fire uC/OS-III" ),     //数据字节大小
             (OS_OPT       )OS_OPT_POST_FIFO + OS_OPT_POST_ALL, //先进先出和发布给全部任务的形式
             (OS_ERR      *)&err);	                            //返回错误类型
		
		OSTimeDlyHMSM ( 0, 0, 0, 500, OS_OPT_TIME_DLY, & err );     //每隔500ms发送一次				 
	}
}
static  void  AppTaskPost2 ( void * p_arg )
{
	OS_ERR      err;
	(void)p_arg;				 
	while (DEF_TRUE) {                                            //任务体
		/* 发布消息到消息队列 queue */
    OSQPost ((OS_Q        *)&queue2,                             //消息变量指针
             (void        *)"Fire uC/OS-III",                //要发送的数据的指针,将内存块首地址通过队列“发送出去”
             (OS_MSG_SIZE  )sizeof ( "Fire uC/OS-III" ),     //数据字节大小
             (OS_OPT       )OS_OPT_POST_FIFO + OS_OPT_POST_ALL, //先进先出和发布给全部任务的形式
             (OS_ERR      *)&err);	                            //返回错误类型
		
		OSTimeDlyHMSM ( 0, 0, 0, 500, OS_OPT_TIME_DLY, & err );     //每隔500ms发送一次				 
	}
}

一个接收任务:

/*
*********************************************************************************************************
*                                          PEND TASK	接收消息任务
*********************************************************************************************************
*/
static  void  AppTaskPend ( void * p_arg )
{
	OS_ERR      msg1_err,msg2_err;
	OS_MSG_SIZE msg_size;
	CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必需该宏,该宏声明和
					//定义一个局部变量,用于保存关中断前的 CPU 状态寄存器
					// SR(临界段关中断只需保存SR),开中断时将该值还原。
	char * pMsg;
	(void)p_arg;		 
	while (DEF_TRUE) {                                       //任务体
		/* 请求消息队列 queue1 的消息 */
    pMsg = OSQPend ((OS_Q         *)&queue1,                //消息变量指针
                    (OS_TICK       )0,                     //等待时长为无限
                    (OS_OPT        )OS_OPT_PEND_NON_BLOCKING,  //如果没有获取到消息就等待
                    (OS_MSG_SIZE  *)&msg_size,             //获取消息的字节大小
                    (CPU_TS       *)0,                     //获取任务发送时的时间戳
                    (OS_ERR       *)&msg1_err);                 //返回错误
        if ( msg1_err == OS_ERR_NONE )                              //如果接收成功
        {
            //消息队列1的数据处理
            OS_CRITICAL_ENTER();                                 //进入临界段
            printf ( "\r\n接收消息的长度:%d字节,内容:%s\r\n", msg_size, pMsg );
            OS_CRITICAL_EXIT();
        }
        		/* 请求消息队列 queue2 的消息 */
    pMsg = OSQPend ((OS_Q         *)&queue2,                //消息变量指针
                    (OS_TICK       )0,                     //等待时长为无限
                    (OS_OPT        )OS_OPT_PEND_NON_BLOCKING,  //如果没有获取到消息就等待
                    (OS_MSG_SIZE  *)&msg_size,             //获取消息的字节大小
                    (CPU_TS       *)0,                     //获取任务发送时的时间戳
                    (OS_ERR       *)&msg2_err);             //返回错误
		if ( msg2_err == OS_ERR_NONE )                      //如果接收成功
		{
            //消息队列2的数据处理
			OS_CRITICAL_ENTER();                                 //进入临界段
			printf ( "\r\n接收消息的长度:%d字节,内容:%s\r\n", msg_size, pMsg );
			OS_CRITICAL_EXIT();
		}
	}
}

注意问题:

消息队列的使用一对一时代码简单,但要注意多个任务等待同一个消息队列时的优先级分配的问题,以及一个任务等待多个消息队列时函数执行的细节,搭配使用OS_ERR枚举量处理各种情况。