UC/OS-III 消息队列

一、消息队列基本概念讲解

1、消息队列基本概念

队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息***(类似于裸机系统中用于传递数据用的数组)***,实现了任务接收来自其他任务或中断的不固定长度的消息。任务能够从队列里面读取消息,当队列中的消息是空时,读取消息的任务将被阻塞,用户还可以指定阻塞的任务时间 timeout,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息;当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态。消息队列是一种异步的通信方式。通过消息队列服务,任务或中断服务程序可以将消息放入消息队列中。同样,一个或多个任务可以从消息队列中获得消息。uCOS 支持先进先出原则(FIFO)以及后进先出原则(LIFO)两种模式。
uCOS 中使用队列数据结构实现任务异步通信工作,具有如下特性:
(1)、消息支持先进先出方式排队,支持异步读写工作方式(FIFO)。
(2)、消息支持后进先出方式排队,往队首发送消息(LIFO)。
(3)、读消息队列支持超时机制。
(4)、可以允许不同长度的任意类型消息(因为是引用方式传递,无论多大的数据都只是一个指针)。
(5)、一个任务能够从任意一个消息队列接收和发送消息。
(6)、多个任务能够从同一个消息队列接收和发送消息。
(7)、当队列使用结束后,可以通过删除队列函数进行删除。

2、消息池

2.1、消息池概念

在 μCOS-III 中定义了一个数组 OSCfg_MsgPool[OS_CFG_MSG_POOL_SIZE],在系统初始化OSInit() 的时候就将这个大数组的各个元素串成单向链表,组成我们说的消息池,而这些元素我们称之为消息。消息池的大小OS_CFG_MSG_POOL_SIZE 由用户自己定义,该宏定义在 os_cfg_app.h 头文件中。这样子的处理很快,并且共用了资源,系统中所有被创建的队列都可以从消息池中取出消息,挂载到自身的队列上,以表示消息队列拥有消息,当消息使用完毕,则又会被释放回到消息池中,其他队列也可以从中取出消息,这样子的消息资源是能被系统所有的消息队列反复使用。

2.2、消息池初始化

OS_MsgPoolInit() 函数就是用来初始化消息池的,OS_MsgPoolInit() 函数的定义位于os_msg.c 文件中。

2.3、消息队列的运作机制

uCOS 的消息队列控制块由多个元素组成,当消息队列被创建时,编译器会静态为消息队列分配对应的内存空间(因为我们需要自己定义一个消息队列控制块),用于保存消息队列的一些信息如队列的名字,队列可用的最大消息个数,入队指针、出队指针等。在创建成功的时候,这些内存就被占用了,创建队列的时候用户指定队列的最大消息个数,无法再次更改,每个消息空间可以存放任意类型的数据(因为采用引用方式传递,无论什么类型的多大的数据都只是一个指针)。
任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满,uCOS 会将从消息池中取出一个消息,将消息挂载到队列的尾部,消息中的成员变量MsgPtr 指向要发送的消息。如果队列已满,则返回错误代码,入队失败。

2.4、消息队列的阻塞机制

我们使用的消息队列一般不是属于某个任务的队列,在很多时候,我们创建的队列,是每个任务都可以去对他进行读写操作,但是为了保护每个任务对它进行读操作的过程(uCOS 队列的写操作是没有阻塞的),我们必须要有阻塞机制,在某个任务对它读操作的时候,必须保证该任务能正常完成读操作,而不受后来的任务干扰。
任务对消息队列进行读操作的时候有三种阻塞状态:
(1)、不等待,队列没有消息,任务不进入阻塞态,接着干别的事情去
(2)、等待一段时间(用户设置),任务会在等待时间内接收到消息后或者超出等待时间后,退出阻塞态变成就绪态。
(3)、永久等待,任务会一直等待消息,直至完成读取队列消息,退出阻塞态,变成就绪态。
:如有多个任务阻塞在一个消息队列中,那么这些阻塞的任务将按照任务优先级进行排序,优先级高的任务将优先获得队列的访问权。如果发送消息的时候用户选择广播消息,那么在等待中的任务都会收到一样的消息。

2.5、消息队列的应用场景

消息队列可以应用于发送不定长消息的场合,包括任务与任务间的消息交换,队列是uCOS 中任务与任务间、中断与任务间主要的通讯方式,发送到队列的消息是通过引用方式实现的,这意味着队列存储的是数据的地址,我们可以通过这个地址将这个数据读取出来,这样子,无论数据量是多大,其操作时间都是一定的,只是一个指向数据地址指针。

二、消息队列创建步骤

1、定义消息队列

在app.c中定义

OS_Q queue;                             //声明消息队列

2、创建消息队列

我们使用创建消息队列函数 OSQCreate()在app.c文件中的起始任务AppTaskStart ()中创建。

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

三、消息队列相关函数说明

1、创建消息队列函数OSQCreate()

创建消息队列函数,函数位于os_q.c文件中

    void OSQCreate (OS_Q *p_q,             //指向消息队列的指针
                    CPU_CHAR *p_name,      //队列的名字
                    OS_MSG_QTY max_qty,    //最多可存放消息的数目
                    OS_ERR *p_err)         //返回错误类型

2、消息队列删除函数 OSQDel()

队列删除函数是根据队列结构(队列句柄)直接删除的,删除之后这个消息队列的所有信息都会被系统清空,而且不能再次使用这个消息队列了,一般来说,在删除消息队列之前,首先要删除所有可以访问该消息队列的任务。不过,强烈建议不要在运行时删除内核对象。但是需要注意的是,如果某个消息队列没有被定义,那也是无法被删除的。想要使用消息队列删除函数就必须将OS_CFG_Q_DEL_EN 宏定义配置为 1。该宏位于os_cfg.h文件中、函数位于os_q.c文件中。

    OS_OBJ_QTY OSQDel (OS_Q *p_q,        //消息队列指针
                       OS_OPT opt,       //选项,一般用 OS_OPT_DEL_NO_PEND 选项
                       OS_ERR *p_err)    //返回错误类型
opt选项说明
OS_OPT_DEL_NO_PEND仅在没有等待消息的任务时才删除队列
OS_OPT_DEL_ALWAYS总是删除队列,而不管任务是否正在等待消息

3、消息队列发送函数 OSQPost()

任务或者中断服务程序都可以给消息队列发送消息,当发送消息时,如果队列未满,就说明运行信息入队**。uCOS 会从消息池中取出一个消息,挂载到消息队列的末尾(FIFO发送方式),如果是 LIFO 发送方式,则将消息挂载到消息队列的头部,然后将消息中MsgPtr 成员变量指向要发送的消息(此处可以理解为添加要发送的信息到消息(块)中)**,如果系统有任务阻塞在消息队列中,那么在发送了消息队列的时候,会将任务解除阻塞。

/*函数原型  opt 确定所执行发送的类型。后两个选项可以添加到任意一个OS_OPT_POST_FIFO或OS_OPT_POST_LIFO,创建不同的组合*/
    void OSQPost (OS_Q *p_q,               //消息变量指针
                  void *p_void,            //要发送的数据的指针,将内存块首地址通过队列“发送出去”
                  OS_MSG_SIZE msg_size,    //数据字节大小
                  OS_OPT opt,              //先进先出和发布给全部任务的形式
                  OS_ERR *p_err)           //返回错误类型
              
              /*函数实例 发布消息到消息队列 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_OPT_POST_ALL, //先进先出和发布给全部任务的形式
             (OS_ERR *)&err);	                            //返回错误类型
opt选项说明
OS_OPT_POST_FIFO默认采用 FIFO 方式发送
OS_OPT_POST_LIFO采用 LIFO 方式发送消息
OS_OPT_POST_1将消息发布到最高优先级的等待任务
OS_OPT_POST_ALL向所有等待的任务广播消息
OS_OPT_POST_NO_SCHED发送消息但是不进行任务调度

4、 消息队列获取函数 OSQPend()

当任务试图从队列中的获取消息时,用户可以指定一个阻塞超时时间,当且仅当消息队列中有消息的时候,任务才能获取到消息。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列消息有效。当其他任务或中断服务程序往其等待的队列中写入了数据,该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了用户指定的阻塞时间,即使队列中尚无有效消息,任务也会自动从阻塞态转为就绪态。

/*函数原型*/
    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)               //返回错误类型
/*实例 请求消息队列 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       *)&err);                 //返回错误
opt选项
OS_OPT_PEND_BLOCKING没有获取到信号量就等待
OS_OPT_PEND_NON_BLOCKING没有获取到信号量就也不等待

:timeout参数在指定时应该设置为0(永远等待消息)。因为使用OS_OPT_PEND_NON_BLOCKING选项时,超时值与滴答时钟不同步。

四、例程

app.c


#include <includes.h>

OS_Q queue;                             //声明消息队列

/*                                           TCB                                        */
static  OS_TCB   AppTaskStartTCB;      //任务控制块

static  OS_TCB   AppTaskPostTCB;
static  OS_TCB   AppTaskPendTCB;

/*                                  堆栈                                               */
static  CPU_STK  AppTaskStartStk[APP_TASK_START_STK_SIZE];       //任务堆栈

static  CPU_STK  AppTaskPostStk [ APP_TASK_POST_STK_SIZE ];
static  CPU_STK  AppTaskPendStk [ APP_TASK_PEND_STK_SIZE ];

/*                                   函数声明                                         */

static  void  AppTaskStart  (void *p_arg);               //任务函数声明

static  void  AppTaskPost   ( void * p_arg );
static  void  AppTaskPend   ( void * p_arg );

int  main (void)
{
    OS_ERR  err;
    OSInit(&err);                                                           //初始化 uC/OS-III
	  /* 创建起始任务 */
    OSTaskCreate((OS_TCB     *)&AppTaskStartTCB,                            //任务控制块地址
                 (CPU_CHAR   *)"App Task Start",                            //任务名称
                 (OS_TASK_PTR ) AppTaskStart,                               //任务函数
                 (void       *) 0,                                          //传递给任务函数(形参p_arg)的实参
                 (OS_PRIO     ) APP_TASK_START_PRIO,                        //任务的优先级
                 (CPU_STK    *)&AppTaskStartStk[0],                         //任务堆栈的基地址
                 (CPU_STK_SIZE) APP_TASK_START_STK_SIZE / 10,               //任务堆栈空间剩下1/10时限制其增长
                 (CPU_STK_SIZE) APP_TASK_START_STK_SIZE,                    //任务堆栈空间(单位:sizeof(CPU_STK))
                 (OS_MSG_QTY  ) 5u,                                         //任务可接收的最大消息数
                 (OS_TICK     ) 0u,                                         //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
                 (void       *) 0,                                          //任务扩展(0表不扩展)
                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), //任务选项
                 (OS_ERR     *)&err);                                       //返回错误类型

    OSStart(&err);                                                          //启动多任务管理(交由uC/OS-III控制)

}

/*   起始任务  */
static  void  AppTaskStart (void *p_arg)
{
    CPU_INT32U  cpu_clk_freq;
    CPU_INT32U  cnts;
    OS_ERR      err;
    (void)p_arg;
    BSP_Init();                                                 //板级初始化
    CPU_Init();                                                 //初始化 CPU 组件(时间戳、关中断时间测量和主机名)
    cpu_clk_freq = BSP_CPU_ClkFreq();                           //获取 CPU 内核时钟频率(SysTick 工作时钟)
    cnts = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;        //根据用户设定的时钟节拍频率计算 SysTick 定时器的计数值
    OS_CPU_SysTickInit(cnts);                                   //调用 SysTick 初始化函数,设置定时器计数值和启动定时器
    Mem_Init();                                                 //初始化内存管理组件(堆内存池和内存池表)

#if OS_CFG_STAT_TASK_EN > 0u                                    //如果使能(默认使能)了统计任务
    OSStatTaskCPUUsageInit(&err);                               //计算没有应用任务(只有空闲任务)运行时 CPU 的(最大)
#endif                                                          //容量(决定 OS_Stat_IdleCtrMax 的值,为后面计算 CPU 
                                                                //使用率使用)。
    CPU_IntDisMeasMaxCurReset();                                //复位(清零)当前最大关中断时间
		/* 创建消息队列 queue */
    OSQCreate ((OS_Q         *)&queue,            //指向消息队列的指针
               (CPU_CHAR     *)"Queue For Test",  //队列的名字
               (OS_MSG_QTY    )20,                //最多可存放消息的数目
               (OS_ERR       *)&err);             //返回错误类型
					  
		/* 创建 AppTaskPost 任务 */
    OSTaskCreate((OS_TCB     *)&AppTaskPostTCB,                             //任务控制块地址
                 (CPU_CHAR   *)"App Task Post",                             //任务名称
                 (OS_TASK_PTR ) AppTaskPost,                                //任务函数
                 (void       *) 0,                                          //传递给任务函数(形参p_arg)的实参
                 (OS_PRIO     ) APP_TASK_POST_PRIO,                         //任务的优先级
                 (CPU_STK    *)&AppTaskPostStk[0],                          //任务堆栈的基地址
                 (CPU_STK_SIZE) APP_TASK_POST_STK_SIZE / 10,                //任务堆栈空间剩下1/10时限制其增长
                 (CPU_STK_SIZE) APP_TASK_POST_STK_SIZE,                     //任务堆栈空间(单位:sizeof(CPU_STK))
                 (OS_MSG_QTY  ) 5u,                                         //任务可接收的最大消息数
                 (OS_TICK     ) 0u,                                         //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
                 (void       *) 0,                                          //任务扩展(0表不扩展)
                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), //任务选项
                 (OS_ERR     *)&err);                                       //返回错误类型

		/* 创建 AppTaskPend 任务 */
    OSTaskCreate((OS_TCB     *)&AppTaskPendTCB,                             //任务控制块地址
                 (CPU_CHAR   *)"App Task Pend",                             //任务名称
                 (OS_TASK_PTR ) AppTaskPend,                                //任务函数
                 (void       *) 0,                                          //传递给任务函数(形参p_arg)的实参
                 (OS_PRIO     ) APP_TASK_PEND_PRIO,                         //任务的优先级
                 (CPU_STK    *)&AppTaskPendStk[0],                          //任务堆栈的基地址
                 (CPU_STK_SIZE) APP_TASK_PEND_STK_SIZE / 10,                //任务堆栈空间剩下1/10时限制其增长
                 (CPU_STK_SIZE) APP_TASK_PEND_STK_SIZE,                     //任务堆栈空间(单位:sizeof(CPU_STK))
                 (OS_MSG_QTY  ) 5u,                                         //任务可接收的最大消息数
                 (OS_TICK     ) 0u,                                         //任务的时间片节拍数(0表默认值OSCfg_TickRate_Hz/10)
                 (void       *) 0,                                          //任务扩展(0表不扩展)
                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR), //任务选项
                 (OS_ERR     *)&err);                                       //返回错误类型
    
		OSTaskDel ( & AppTaskStartTCB, & err );                     //删除起始任务本身,该任务不再运行
		
		
}

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_OPT_POST_ALL, //先进先出和发布给全部任务的形式
             (OS_ERR      *)&err);	                            //返回错误类型
		
		OSTimeDlyHMSM ( 0, 0, 0, 500, OS_OPT_TIME_DLY, & err );     //每隔500ms发送一次
						 
	}

}

static  void  AppTaskPend ( void * p_arg )
{
	OS_ERR      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       *)&err);                 //返回错误
		
		if ( err == OS_ERR_NONE )                              //如果接收成功
		{
			OS_CRITICAL_ENTER();                                 //进入临界段
			
			printf ( "\r\n接收消息的长度:%d字节,内容:%s\r\n", msg_size, pMsg );

			OS_CRITICAL_EXIT();
			
		}
		
	}
	
}

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值