消息队列作为UCOS-Ⅲ中任务间通信重要的一环,虽然底层原理较为复杂,但在使用中我们只需注意他的API调用即可,本文讲述UCOS-Ⅲ的消息队列API如何调用
文章目录
- UCOS-Ⅲ消息队列
- 一、消息队列基本概念
- 二、调用API及变量类型
- 三、调用实例
- 注意问题:
UCOS-Ⅲ消息队列
一、消息队列基本概念
消息队列属于队列结构,用于任务与任务、任务与中断进行通信的数据结构,读取的目标消息队列为空的情况下,当前的任务将被阻塞,可以指定阻塞超时时间,超时时间内如果有消息传输过来,则阻塞任务被唤醒,没有消息则在阻塞时间到期后,任务进入就绪态运行,因此消息队列是一种异步通信的方式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ySkZOqYR-1611018728823)(UCOS-Ⅲ:消息队列/20201202231528116.jpg)]
ucos消息队列特色:
- 一般先进先出(FIFO),紧急消息后进先出(LIFO)
- 支持超时机制
- 允许不同长度的消息传递(数据为指针传递)
- 一个任务可以从任意一个队列来接收和发送消息
- 多个任务也能从同一个队列接收和发送消息
- 队列使用完成可以删除
二、调用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源码演示:
- 一对一发送消息问题:
- 在启动任务中创建消息队列:
/* 创建消息队列 queue */
OSQCreate ((OS_Q *)&queue, //指向消息队列的指针
(CPU_CHAR *)"Queue For Test", //队列的名字
(OS_MSG_QTY )20, //最多可存放消息的数目
(OS_ERR *)&err); //返回错误类型
- 在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发送一次消息,而接收线程则一直等待在阻塞态,直到等待的消息队列有数据,退出等待处理任务。
- 一个任务同时接收多个消息队列的问题
- 在启动任务中创建两个消息队列:
/* 创建消息队列 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); //返回错误类型
- 创建两个发送任务和一个接收任务:
两个发送任务:
/*
*********************************************************************************************************
* 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枚举量处理各种情况。