目录
3.xQueueSend()与xQueueSendToBack()
一、消息队列是什么?
在实际的应用中,常常会遇到
一个任务或者中断服务需要和另外一个任务进行“沟通交流”
,这个“沟通交流”的过程
其实就是
消息传递
的过程。在
没有操作系统
的时候两个应用
程序进行消息传递一般使用
全局变量
的方式,但是如果在使
用
操作系统
的应用中用
全局变量来传递消息就会涉及到“资源管理”的问题
。
FreeRTOS
对此提供了一个叫做“
队列
”
的机制来
完成任务与任务、任务与中断之间的消息传递
,由
于队列用来传递消息的,所以也称为消息队列。
此外,任务从队列读数据或者写入数据到队列时,都可能被阻
塞。这个特性使得
任务可以被设计成基于事件驱动
的运行模式,
大大提高了
CPU
的执行效率。
通常队列采用
先进先出
(FIFO)
的存储缓冲机制,也就是
往队列
发送数据的时候
(
也叫入队
)
永远都是发送到队列的尾部,而从
队列
提取数据的时候
(
也叫出队
)
是从队列的头部提取的。但是
也可以使用
LIFO
的存储缓冲,也就是后进先出,
FreeRTOS中的队列也提供了
LIFO
的存储缓冲机制。
![](https://i-blog.csdnimg.cn/blog_migrate/63a728ac120a270808eb805406d48cc1.png)
二、消息队列的使用
1.相关函数
使用原则:先创建后使用、配对使用
1.xQueueCreate()
动态创建一个新的队列并返回可用于访问这个队列的句柄
函数原型:
QueueHandle_t xQueueCreate(
UBaseType_t uxQueueLength, /*
消息个数
*/
UBaseType_t uxItemSize ); /*
每个消息大小,单位字节
*/
返回值:
如果创建成功会返回消息队列的句柄
,如果由于FreeRTOSConfig.h
文件中
heap
大小不足,无法为此消息队列提供所需的
空间会
返回
NULL
。
2.xQueueCreateStatic()
静态方法创建队列,队列所需要的内存由用户自行分配
函数原型:
QueueHandle_t xQueueCreateStatic(
UBaseType_t uxQueueLength,
uxUBaseType_t uxItemSize,
uint8_t* pucQueueStorageBuffer,
StaticQueue_t* pxQueueBuffer)
uxQueueLength
:要创建的队列的队列长度,这里是队列的项目数
uxItemSize
:队列中每个项目(消息)的长度,单位为字节
pucQueueStorageBuffer
:指向队列项目的存储区,也就是消息的存储区,
这个存储区需要用户自行分配。此参数必须指向一个
uint8_t
类型的数组。
这个存储区要大于等于(uxQueueLength*uxItemsSize)
字节
pxQueueBuffer :
此参数指向一个
StaticQueue_t
类型的变量,用来保存队列
结构体
返回值 :
创建成功返回队列句柄
3.xQueueSend()与xQueueSendToBack()
两者等价,用于将数据发送到队列尾
函数原型:
BaseType_t xQueueSendToBack(
xQueueHandle xQueue,
const void * pvItemToQueue,
portTickType xTicksToWait );
参数:
参数
xQueue
:目标队列的句柄。
参数
pvItemToQueue
:入队元素的指针。队列将存储此指针指向的数据的备份。
参数
xTicksToWait
:指定等待队列有空间
可以容纳新元素入队的最长等待(阻塞)时间
,这种情况发生在队列已经满了的时候。如果需要等待,则任务会因为调用这个函
数而进入阻塞状态,直到队列非满而能让这个任务写入数据或者指定的阻塞时间过期,
才会转变为就绪态。
如果此参数使用0,则当队列已经满了的时候,此函数立即返回而不阻塞。使用portMAX_DELAY作为参数将使得等待时间为无限长
,直到队列非满
而能让这个任务写入数据,注意如果要使用
portMAX_DELAY
为参数,
则必须在FreeRTOSConfig.h将INCLUDE_vTaskSuspend定义为1
返回值:
pdPASS
,当元素成功入队时返回
pdPASS
。
errQUEUE_FULL,因为队列满而无法入队时返回errQUEUE_FULL
注:xQueueSendFromISR()是在中断中将数据发送到队列尾,它共有三个参数,前两个参数与xQueueSend()的一样,由于中断中不允许一直等待,第三各参数默认将任务锁住,等待队列中的可用空间,成功将消息入队就解锁,使用方法与后面的xQueueReceiveFromISR()函数一样
运行过程图例:
当任务向消息队列中发送消息时,它首先判断是否有任务在等待消息队列的消息,
有的话,要先给高优先级的任务获得信息
:
如果没有任务在等待消息队列的消息,那么就会再判断消息队列当前是否已满:
![](https://i-blog.csdnimg.cn/blog_migrate/b75b84f1a185b5e1cf9906e0142d90cd.png)
4.xQueueReceive()
接收消息队列中的数据
函数原型:
BaseType_t xQueueReceive(
QueueHandle_t xQueue, /*
消息队列句柄
*/
void *pvBuffer, /*
接收消息队列数据的缓冲地址
*/
TickType_t xTicksToWait /*
等待消息队列有数据的最大等待时间
*/
);
参数:
第
1
个参数:消息队列句柄。
第
2
个参数:从消息队列中复制出数据后所储存的缓冲地址,缓冲
区空间要
大于等于
消息队列创建函数
xQueueCreate
所指定的单个消
息大小,否则取出的数据无法全部存储到缓冲区,从而造成内存溢出。
第
3
个参数:消息队列为空时,等待消息队列有数据的最大等待时
间,单位系统时钟节拍。参数为
0
,那么此函数会立即返回。
返回值:如果接收到消息返回
pdTRUE
,否则返回
pdFALSE
。
运行过程图例:
消息队列中已存在消息,通过内核服务将消息传递给
等待消息的任务中优先级最高的任务,或最先进入等待消息任务列表的任务。
![](https://i-blog.csdnimg.cn/blog_migrate/e1dd233782133dfb528d9292193d820e.png)
如果消息队列为空,则等待消息的任务被放入等待消息的任务列表中,直到有其它任务向消息队列发送消息后才能解除该阻塞状态
或在超时的情况下运行
![](https://i-blog.csdnimg.cn/blog_migrate/fb39ab4d756ea20c187e81d9345ccc19.png)
5.xQueueReceiveFromISR()
中断中信息队列出队函数
函数原型:
BaseType_t xQueueReceive
FromISR
(
xQueueHandle pxQueue,
void *pvBuffer,
portBASE_TYPE *pxTaskWoken
);
pxQueue :发送项目的队列句柄
pvBuffer
:指向缓冲区的指针,将接收的项目被复制进去。
pxTaskWoken
:
任务将锁住,等待队列中的可用空间。如果 xQueueReceiveFromISR 引起一个任务解锁, *pxTaskWoken 将设置为pdTRUE,否则*pxTaskWoken保留不变
返回值 : pdTRUE
:如果项目成功从队列接收。否则为:
pdFALSE
三、代码测试:
1.两个任务间的数据通讯:
//led初始化和按键初始化过程已经省略
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "string.h"
#include "queue.h"
//任务优先级
#define LED0_TASK_PRIO 1
//任务堆栈大小
#define LED0_STK_SIZE 50
//任务句柄
TaskHandle_t LED0Task_Handler;
//任务函数
void led0_task(void *pvParameters);
//任务优先级
#define KEY_TASK_PRIO 4
//任务堆栈大小
#define KEY_STK_SIZE 50
//任务句柄
TaskHandle_t KEYTask_Handler;
//任务函数
void key_task(void *pvParameters);
QueueHandle_t que1;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4(freertos)
delay_init(); //延时函数初始化
LED_Init(); //初始化LED
KEY_Init();
que1 = xQueueCreate(1,sizeof(u8));//动态创建消息队列
//创建LED0任务
xTaskCreate((TaskFunction_t )led0_task, //任务函数
(const char* )"led0_task", //任;务名称
(uint16_t )LED0_STK_SIZE,//任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )LED0_TASK_PRIO,//任务优先级
(TaskHandle_t* )&LED0Task_Handler); //任务句柄
//创建KEY任务
xTaskCreate((TaskFunction_t )key_task,
(const char* )"key_task",
(uint16_t )KEY_STK_SIZE,
(void* )NULL,
(UBaseType_t )KEY_TASK_PRIO,
(TaskHandle_t* )&KEYTask_Handler);
vTaskStartScheduler(); //开启任务调度
/*
调度器并非自动运行的,需要人为启动它。 API函
数vTaskStartScheduler()用于启动调度器,它会创建一个空闲任务、初始化一些
静态变量,最主要的,它会初始化系统节拍定时器并设置好相应的中断,然后启
动第一个任务
*/
}
u8 KEY_scan(void)
{
static u8 key_up = 1;
if(key_up && ( KEY5==0 || KEY6==0))
{
delay_xms(20);
key_up = 0;
if(KEY5==0) return KEY5_PRESS;
else if(KEY6==0) return KEY6_PRESS;
}
else if (KEY5==1&&KEY6==1)
{
key_up=1;
return 0;
}
}
void key_task(void *pvParameters)
{
u8 key;
while(1)
{
key=KEY_scan();
if(key!=0)
xQueueSend(que1,&key,portMAX_DELAY);
vTaskDelay(100);
}
}
void led0_task(void *pvParameters)
{
u8 num;
while(1)
{
xQueueReceive(que1,&num,portMAX_DELAY);
switch(num)
{
case KEY5_PRESS:
LED0=!LED0;
break;
case KEY6_PRESS:
LED1=!LED1;
break;
vTaskDelay(300);
}
}
}
相关功能实现:通过按键扫描返回一个键值,然后在key_task这个任务函数调用xQueueSend()将此键值入队,led0_task此任务调用xQueueReceive()函数接收信息队列中的信息,然后根据此消息做出相应操作
2..任务与中断的数据通讯:
消息队列用于实现18b20采集与定时器中断数码管显示
已省略很多代码,此处主要看在中断中如何接收队列中的数据
main.c中的一个任务函数:
void led0_task(void *pvParameters)
{
unsigned int temperature = 0;
while(1)
{
if(Tens==1)//后续用事件标志组来实现
{
temperature=DS18B20_Get_Temp();
Tens=0;
xQueueSend(que1,&temperature,portMAX_DELAY);//参数1:事件句柄;参数2:要发的内容地址;参数3:等待时间
}
vTaskDelay(300);
}
}
定时器3的中断服务函数:
void TIM3_IRQHandler(void)
{
BaseType_t pxHigherPriorityTaskWoken;
pxHigherPriorityTaskWoken = pdFALSE;
if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET)
{
Digital_Display(temperature,T);
if(T<3)
T++;
else
T=0;
if(counter<500)
counter++;
else
{
counter=0;
Tens=1;
xQueueReceiveFromISR(que1,&temperature,&pxHigherPriorityTaskWoken);//参数1:事件句柄;参数2:将接收到的数据存入该地址;参数3:pdFALSE的地址
}
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
注:注意中断接收消息队列消息函数的第三个参数的设置。