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

[TOC]

UCOS-Ⅲ消息队列

一、消息队列基本概念

​ 消息队列属于队列结构,用于任务与任务、任务与中断进行通信的数据结构,读取的目标消息队列为空的情况下,当前的任务将被阻塞,可以指定阻塞超时时间,超时时间内如果有消息传输过来,则阻塞任务被唤醒,没有消息则在阻塞时间到期后,任务进入就绪态运行,因此消息队列是一种异步通信的方式:

ucos消息队列特色:

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

二、调用API及变量类型

  • API:

  • 消息队列创建:(中断中禁止调用)

    1
    2
    3
    4
    5
    /* 创建消息队列 queue */
    OSQCreate ((OS_Q *)&msg_ptr, //指向消息队列的指针
    (CPU_CHAR *)"Queue For Test", //队列的名字
    (OS_MSG_QTY )20, //最多可存放消息的数目
    (OS_ERR *)&err); //返回错误类型

    QS_Q为消息队列结构体指针

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

  • 消息队列删除:(中断中禁止调用)

    1
    2
    3
    OS_OBJ_QTY    OSQDel (OS_Q                  *p_q,	//指向消息队列的指针
    OS_OPT opt, //删除操作选择
    OS_ERR *p_err);//返回错误类型

    删除操作可选如下:

    1
    2
    OS_OPT_DEL_NO_PEND:                           //只在没有任务等待该消息队列的情况下删除队列
    OS_OPT_DEL_ALWAYS: //删除该消息队列,以及清空该任务的等待列表
  • 清空消息队列消息列表:(中断中禁止调用)

    1
    2
    OS_MSG_QTY    OSQFlush                  (OS_Q                  *p_q,//指向消息队列的指针
    OS_ERR *p_err);

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

  • 等待一个消息队列:

    1
    2
    3
    4
    5
    6
    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状态时 任务没有接收到消息,并且超时返回的错误标志,用于之后的程序语句进行判断,防止误操作!!!
  • 取消当前等待消息队列,一般用于处理错误时,不要经常使用:(中断中禁止调用)
1
2
3
OS_OBJ_QTY  OSQPendAbort (OS_Q    *p_q,    //消息队列
OS_OPT opt, //选项
OS_ERR *p_err) //返回错误类型
  • 向消息队列发送一条消息:(消息队列API中 中断中唯一可以调用的)

    1
    2
    3
    4
    5
    void  OSQPost (OS_Q         *p_q,      //消息队列指针
    void *p_void, //消息指针
    OS_MSG_SIZE msg_size, //消息大小(单位:字节)
    OS_OPT opt, //选项
    OS_ERR *p_err) //返回错误类型

    该函数的可用选项如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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. 在启动任务中创建消息队列:
1
2
3
4
5
/* 创建消息队列 queue */
OSQCreate ((OS_Q *)&queue, //指向消息队列的指针
(CPU_CHAR *)"Queue For Test", //队列的名字
(OS_MSG_QTY )20, //最多可存放消息的数目
(OS_ERR *)&err); //返回错误类型
  1. 在UCOS内创建两个线程:数据发送线程和数据接收线程;(线程主体如下)

发布消息线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
*********************************************************************************************************
* 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发送一次
}
}

接收消息线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
*********************************************************************************************************
* 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. 在启动任务中创建两个消息队列:
1
2
3
4
5
6
7
8
9
10
	/* 创建消息队列 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. 创建两个发送任务和一个接收任务:

两个发送任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
*********************************************************************************************************
* 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发送一次
}
}

一个接收任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
*********************************************************************************************************
* 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枚举量处理各种情况。

微信公众号,求个关注0.0

wechat