FreeRTOS消息队列
简介
- 队列是为了任务与任务、任务与中断之间的通信而准备的。
- 队列可以在任务与任务、任务与中断之间传递消息。
- 队列中可以存储有限的、大小固定的数据项目。
- 任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。
- 队列所能保存的最大数据项目的数量叫做队列的长度。
- 创建队列时会指定数据项目的大小和队列的长度。
- 由于队列是用来传递消息的所以队列也叫做 消息队列。
- FreeRTOS中的信号量也是通过队列实现的。
队列功能
- 数据存储
- 多任务访问
- 出队阻塞
- 入队阻塞
数据存储
- 队列(queue)通常采用先进先出(FIFO)的存储机制,向队列发送数据时都是发送到队列的尾部,也叫入队。从队列取出数据时都是从队列的头部提取,也叫出队。
- 有需要也可以使用后进先出(LIFO)机制
- FreeRTOS的队列中的数据采用的是值传递,数据是通过赋值的形式传递进队列的,而不是使用指针,这样可以避免使用局部变量时函数结束变量删除造成的传输失败。(缺点:数据拷贝会浪费时间)
多任务访问
- 队列不是属于某个特定任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。
- 队列出现的目的就是为了多任务访问,在多个任务之间传递消息
出队阻塞
- 当任务从一个队列中读取消息时,可以指定一个阻塞时间。确定当任务从队列中读取的消息无效时任务要阻塞多久。
举例:比如使用任务A来处理串口接收到的数据,首先串口接收到的数据会放到队列Q中,任务A从队列Q中读取数据。如果此队列Q是空的,那么A该怎么办?任务A有3种选择:
1.直接返回(阻塞时间为0)
2.等一小会儿(设置阻塞多久,就等多久)
3.死等到有数据(阻塞时间设置为portMAX_DELAY得最大值0xFFFF FFFF)
入队阻塞
- 入队指的是任务向队列中发送数据。
- 如果消息入队时队列中是满的,此时应设置入队阻塞时间
- 入队阻塞时间设置和出队阻塞设置类似。
消息队列的运作机制
- 通过FreeRTOS提供的函数创建消息队列,指定好队列的长度,每个消息的大小。
- 任务或中断都可以给队列发送消息,如果队列未满或者使用覆盖入队,消息将会被拷贝(赋值)到队尾,否则任务会根据设置的阻塞时间进行阻塞。
- 任务或中断都可以从队列读取消息,消息从队头开始被取出,如果队列为空则根据设置的阻塞时间进行阻塞。
- FressRTOS队列还有发送紧急消息的功能,与发送消息几乎一样,只是消息直接被拷贝到对头,保证接受者优先接受到紧急消息。
消息队列的应用场景
- 任务与任务之间消息交换
- 任务与中断之间信息交换
- 发送不定长消息
消息队列的使用
-
使用消息队列首先要创建消息队列句柄,然后对队列的操作都要通过这个队列句柄。
/*---------------------------------------------------------------------------------------------- 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核对象,必须先创建, 创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我们就可以通过这个句柄操作这些内核对象。 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,任务间的事件同步 等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数来完成的 ------------------------------------------------------------------------------------------------*/ QueueHandle_t Test_Queue =NULL;//创建队列句柄
-
消息队列创建函数xQueueCreate()—动态创建函数(通常不使用静态创建,所以不做介绍)
/* uxQueueLength:队列长度 uxItemSize:队列中消息单元的大小,字节为单位 此函数用于创建一个消息队列,创建成功则返回一个可用于访问所创建队列的句柄;创建失败返回NULL。 */ QueueHandle_t xQueueCreate( uxQueueLength, uxItemSize ); //例如:创建一个长度为4,单元大小为1的队列 QueueHandle_t Test_Queue =NULL;//首先创建队列句柄 Test_Queue = xQueueCreate( 4, 1 );//调用队列创建函数 if(Test_Queue!=NULL) printf("队列创建成功\r\n"); else printf("队列创建失败\r\n");
-
消息队列删除函数—创建的队列如果不需要使用了应该删除以释放内存空间。
/*----------------------------------------------------------------------------- 队列删除函数是根据队列句柄直接删除的,删除后队列空间被回收 未创建的队列不能被删除 --------------------------------------------------------------------------------*/ void vQueueDelete( QueueHandle_t xQueue );//队列删除函数原型 vQueueDelete(Test_Queue);//删除队列Test_Queue
-
向队列发送消息函数
-
xQueueSend() ----任务中调用
-
有些版本的FreeRTOS中会看到xQueueSendToBack() ,此函数与xQueueSend()相同,只是为了向后兼容
/*--------------------------------------------------------------------------------------- 此函数用于向队列尾部发送一个队列消息 xQueue:队列句柄 pvItemToQueue:指针,指向要发送到队列尾部的队列消息 TicksToWait:队列满时,阻塞等待的最大超时时间(此参数设置为0,函数立即返回,设置为portMAX_DELAY函数被挂起) 消息发送成功返回pdTRUE,否则返回errQUEUE_FULL ------------------------------------------------------------------------------------------*/ BaseType_t xQueueSend(QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t TicksToWait);//队列发送函数原型 //使用示例 void Send_Task(void * parameter)//队列发送任务 { BaseType_t xReturn = pdPASS;//定义一个创建信息返回值,默认为 pdPASS (必须赋初值) uint32_t send_data1 = 1;//要发送的数据1 uint32_t send_data2 = 2;//要发送的数据2 while(1) { if(Key_Scan(1))//如果按键被按下 { printf("发送消息send_data1\r\n"); xReturn = xQueueSend(Test_Queue,&send_data1,0); if(xReturn==pdPASS) printf("消息send_data1发送成功\r\n"); } if(Key_Scan(2))//如果按键被按下 { printf("发送消息send_data2\r\n"); xReturn = xQueueSend(Test_Queue,&send_data2,0); if(xReturn==pdPASS) printf("消息send_data2发送成功\r\n"); } } }
-
xQueueSendFromISR() -----专用于中断服务程序中向队列中发送消息
/*------------------------------------------------------------------------------- 在中断服务程序中用于向队列尾部发送一个消息 xQueue:队列句柄 pvItemToQueue:指针,指向要发送队列尾部的消息 pxHigherPriorityTaskWoken:如果入队导致某个人物解锁,并且解锁的任务优先级高于当前 被中断的任务,则将*pxHigherPriorityTaskWoken设置为pdTRUE,然后在退出前需要进行一次任务切换 消息发送成功返回 pdTRUE,否则返回 errQUEUE_FULL。 -------------------------------------------------------------------------------*/ BaseType_t xQueueSendFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t pxHigherPriorityTaskWoken); //使用示例 void vBufferISR(void) { char cIn; BaseType_t xHigherPriorityTaskWoken = pdFALSE;//在ISR开始时,不唤醒任务 do{ cIn = portINPUT_BYTE( RX_REGISTER_ADDRESS );//从接受缓冲区获取一个字节数据 xQueueSendFromISR( xRxQueue, &cIn, &xHigherPriorityTaskWoken );//发送这个数据 }while( portINPUT_BYTE( BUFFER_COUNT ) );//循环到接收缓冲区为空 if(xHigherPriorityTaskWoken) taskYIELD_FROM_ISR ();//任务切换 }
-
xQueueSendToFront() ----用于向队列队首发送一个消息(不能在中断中调用)
/*------------------------------------------------------------------------------------ 用于向队列队首发送一个消息 xQueue:队列句柄 pvItemToQueue:指针,指向要发送的数据 TicksToWait:阻塞等待时间 消息发送成功返回 pdTRUE,否则返回 errQUEUE_FULL。 ------------------------------------------------------------------------------------*/ BaseType_t xQueueSendToFront( QueueHandle_t xQueue,const void * pvItemToQueue,TickType_t TicksToWait ); //使用与向队尾发送消息的函数类似
-
xQueueSendToFrontFromISR() ------用于向队列队首发送一个消息(专用在中断中调用)
/*------------------------------------------------------------------------------- 在中断服务程序中用于向队列首部发送一个消息 xQueue:队列句柄 pvItemToQueue:指针,指向要发送队列首部的消息 pxHigherPriorityTaskWoken:如果入队导致某个人物解锁,并且解锁的任务优先级高于当前 被中断的任务,则将*pxHigherPriorityTaskWoken设置为pdTRUE,然后在退出前需要进行一次任务切换 消息发送成功返回 pdTRUE,否则返回 errQUEUE_FULL。 -------------------------------------------------------------------------------*/ BaseType_t xQueueSendToFrontFromISR(QueueHandle_t xQueue,const void *pvItemToQueue,BaseType_t pxHigherPriorityTaskWoken);
-
xQueueReceive() -----用于从队列中接收消息并把消息从队列中删除,接收消息是以拷贝的形式进行的。(不能用于中断函数中)
/*--------------------------------------------------------------------------------------- 此函数用于从队列中接收消息,并把接收的消息从队列中删除 xQueue:队列句柄 pvBuffer:指针,数据要存放的地方 TicksToWait:队列空时,阻塞等待的最大超时时间(此参数设置为0,函数立即返回,设置为portMAX_DELAY函数被挂起) 消息发送成功返回pdTRUE,否则返回errQUEUE_FULL ------------------------------------------------------------------------------------------*/ BaseType_t xQueueReceive(QueueHandle_t xQueue,void *pvBuffer,TickType_t xTicksToWait); //使用示例 void Receive_Task(void* parameter) { BaseType_t xReturn = pdTRUE;//定义一个创建信息返回值,默认为 pdPASS uint32_t r_queue; //定义一个接收消息的变量 while (1) { xReturn = xQueueReceive(Test_Queue, //消息队列的句柄 &r_queue, //发送的消息内容 portMAX_DELAY); //等待时间 一直等 if (pdTRUE== xReturn) printf("本次接收到的数据是: %d\n\n",r_queue); else printf("数据接收出错,错误代码: 0x%lx\n",xReturn); } }
-
xQueuePeek() —从队列中接收消息-并且不删除消息(具体用法同上个函数↑↑↑↑↑↑↑↑↑↑↑)
-
xQueueReceiveFromISR ( ) -----是 xQueueReceive ()的中断版本,用于在中断服务程序中接收
一个队列消息并把消息从队列中删除;/*--------------------------------------------------------------------------------------- 此函数用于从队列中接收消息,并把接收的消息从队列中删除 xQueue:队列句柄 pvBuffer:指针,数据要存放的地方 pxHigherPriorityTaskWoken:任务在往队列投递信息时, 如果队列满, 则任务将阻塞在该队列上。 如果 xQueueReceiveFromISR()到账了一个任 务 解 锁 了 则 将 *pxHigherPriorityTaskWoken 设 置 为pdTRUE , 消息发送成功返回pdTRUE,否则返回errQUEUE_FULL ------------------------------------------------------------------------------------------*/ BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue,void *pvBuffer,BaseType_t *pxHigherPriorityTaskWoken);
-
xQueuePeekFromISR() ------从队列中接收消息-并且不删除消息(同上个函数一样只用于中断中)
-
消息队列使用至于事项
- 使用 xQueueSend()、 xQueueSendFromISR()、 xQueueReceive()等这些函数之前应先
创建需消息队列,并根据队列句柄进行操作。 - 队列读取采用的是先进先出( FIFO)模式,会先读取先存储在队列中的数据。当
然也 FreeRTOS 也支持后进先出(LIFO)模式,那么读取的时候就会读取到后进
队列的数据。 - 在获取队列中的消息时候,我们必须要定义一个存储读取数据的地方,并且该数
据区域大小不小于消息大小,否则,很可能引发地址非法的错误。 - 无论是发送或者是接收消息都是以拷贝的方式进行, 如果消息过于庞大,可以将
消息的地址作为消息进行发送、接收。 - 队列是具有自己独立权限的内核对象,并不属于任何任务。所有任务都可以向同
一队列写入和读出。一个队列由多任务或中断写入是经常的事,但由多个任务读
出倒是用的比较少。