1 队列简介
队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)。
1)使用全局变量
假设有一个全局变量 a = 0,现有两个任务都在写这个变量a
全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损。
2)使用队列
FreeRTOS基于队列,实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、递归互斥信号量,因此需要深入了解FreeRTOS的队列。
读写队列做好了保护(通过关中断和开中断),防止多任务同时访问冲突,只需要直接调用API函数即可。
在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度。
在创建队列时,就要指定队列长度以及队列项目的大小
队列特点:
1)数据入队出队方式:队列通常采用“先进先出”(FIFO)的数据缓存机制,即先入队的数据会先从队列中被读取,FreeRTS中也可以配置为“后进先出”LIFO方式。
2)数据传递方式:FreeRTOS中队列采用实际值传递,即数据拷贝到队列中进行传递,FreeRTOS采用拷贝数据,也可以传递指针,所以在传递较大的数据的时候采用指针传递
3)多任务访问:队列不属于某个任务,任何任务和中断都可以向队列发送、读取消息
4)出队、入队阻塞:当任务向一个队列发送消息时,假设此时当队列已满无法入队,可以指定一个阻塞时间:
A 若阻塞时间为0:直接返回不会等待;
B 若阻塞时间为0~port_MAX_DELAY :等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
C 若阻塞时间为port_MAX_DELAY :死等,一直等到可以入队为止。出队阻塞和入队阻塞类似。
入队阻塞:
队列满了,此时写不进去数据:
1)将该任务的任务状态列表挂载在pxDelayedTaskList;
2)将该任务的事件列表项挂载在xTaskWaitingToSend;
出队阻塞:
列队为空,此时读取不了数据:
1)将该任务的任务状态列表项挂载在pxDelayedTaskList;
2)将任务的事件列表项挂载在xTaskWaitingToReceive;
问题:当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务在等待同一个队列的空间,那当队列中有空间时,哪个任务会进入就绪态?
1)优先级最高的任务
2)如果大家的优先级相同,那等待时间最久的任务会进入就绪态
队列操作基本过程:
2 队列结构体介绍
typedef struct QueueDefinition
{
int8_t * pcHead /* 存储区域的起始地址 */
int8_t * pcWriteTo; /* 下一个写入的位置 */
union
{
QueuePointers_t xQueue;
SemaphoreData_t xSemaphore;
} u ;
List_t xTasksWaitingToSend; /* 等待发送列表 */
List_t xTasksWaitingToReceive; /* 等待接收列表 */
volatile UBaseType_t uxMessagesWaiting; /* 非空闲队列项目的数量 */
UBaseType_t uxLength; /* 队列长度 */
UBaseType_t uxItemSize; /* 队列项目的大小 */
volatile int8_t cRxLock; /* 读取上锁计数器 */
volatile int8_t cTxLock; /* 写入上锁计数器 */
/* 其他的一些条件编译 */
} xQUEUE;
当用于队列使用时:
typedef struct QueuePointers
{
int8_t * pcTail; /* 存储区的结束地址 */
int8_t * pcReadFrom; /* 最后一个读取队列的地址 */
} QueuePointers_t;
当用于互斥信号量和递归互斥信号量时:
typedef struct SemaphoreData
{
TaskHandle_t xMutexHolder; /* 互斥信号量持有者 */
UBaseType_t uxRecursiveCallCount; /* 递归互斥信号量的获取计数器 */
} SemaphoreData_t;
3 队列相关API函数介绍
使用队列的主要流程:创建队列——>写队列——>读队列
创建队列相关API函数介绍:
函数 | 描述 |
xQueueCreate() | 动态方式创建队列 |
xQueueCreateStatic() | 静态方式创建队列 |
动态和静态创建队列之间的区别:队列所需的内存空间由FReeeRTOS从FreeRTOS管理的堆中分配,而静态创建需要用户自行分配内存。
1)动态方式创建队列
#define xQueueCreate ( uxQueueLength, uxItemSize )
xQueueGenericCreate( (uxQueueLength ), (uxItemSize), (queueQUEUE_TYPE_BASE))
此函数用于使用动态方式创建队列,队列所需的内存空间由FreeRTOS从FreeRTOS管理的堆中分配。
形参 | 描述 |
uxQueueLength | 队列长度 |
uxItemSize | 队列项目的大小 |
返回值 | 描述 |
NULL | 队列创建失败 |
其他值 | 队列创建成功,返回队列句柄 |
FreeRTOS基于队列实现了多种功能,每一种功能对应一种队列类型,队列类型的queue.h文件中有定义:
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) /* 队列 */
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U ) /* 队列集 */
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U ) /* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U ) /* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) /* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U ) /* 递归互斥信号量
2)往队列写入消息API函数:
函数 | 描述 |
xQueueSend() | 往队列的尾部写入消息 |
xQueueSendToBack() | 同xQueueSend() |
xQueueSendToFront() | 往队列的头部写入消息 |
xQueueOverwrite() | 覆写队列消息(只用于队列长度为1 的情况) |
xQueueSendFromISR() | 在中断中往队列的尾部写入消息 |
xQueueSendToBackFromISR() | 同xQueueSendFromISR() |
xQueueSendToFrontISR() | 在中断中往队列的头部写入消息 |
xQueueOverwriteFromISR() | 在中断中覆写写队列消息(只用于队列长度为1 的情况) |
以下几个写入函数调用的是同一个函数xQueueGenericsend(),只是指定了不同的写入位置:
#define xQueueSend(xQueue, pvItemToQueue, xTicksToWait)
xQueueGenericSend((xQueue), (pvItemToQueue), (xTicksToWait),queueSEND_TO_BACK)
#define xQueueSendToBack(xQueue,pvItemToQueue,xTicksToWait)
xQueueGenericSend((xQueue), (pvItemToQueue), (xTicksToWait), queueSEND_TO_BACK)
#define xQueueSendToFront(xQueue,pvItemToQueue,xTicksToWait)
xQueueGenericSend((xQueue), (pvItemToQueue), (xTicksToWait), queueSEND_TO_FRONT)
#define xQueueOverwrite( xQueue, pvItemToQueue )
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )
/*队列一共有3种写入位置*/
/*注意:覆写方式写入队列,只有在队列的队列长度为1时,才能够使用*/
#define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) /* 写入队列尾部 */
#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) /* 写入队列头部 */
#define queueOVERWRITE ( ( BaseType_t ) 2 ) /* 覆写队列*/
/*往队列写入消息函数入口参数解析*/
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition );
//xQueue 为待写入的队列
//pvItemToQueue 为待写入的消息
//xTicksToWait 为阻塞超时时间
//xCopyPosition 为写入的位置
//返回值为pdTURE 表示队列写入成功,errQUEUE_FULL表示队列写入失败
3)从队列读取消息API函数:
函数 | 描述 |
xQueueReceive() | 从队列头部读取消息,并删除消息 |
xQueuePeek() | 从队列头部读取消息 |
xQueueReceiveFromISR() | 在中断中从队列头部读取消息,并删除消息 |
xQueuePeekFromISR() | 在中断中从队列头部读取消息 |
BaseType_t xQueueReceive(QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
//xQueue为待读取的队列
//pvBuffer为信息读取缓冲区
//xTickToWait 为阻塞超时时间
//返回值为pdTRUE为读取成功。pdFALSE为读取失败
BaseType_t xQueuePeek(QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait)
//xQueue为待读取的队列
//pvBuffer为信息读取缓冲区
//xTickToWait为阻塞超时时间
//返回值为pdTRUE为读取成功。pdFALSE为读取失败
4 队列操作实验
实验设计:设计4个任务:start_task、task1、task2、task3
四个任务的功能如下:
start_task:用来创建task1、task2、task3
task1:当按键key0或key1按下,将键值拷贝到队列key_queue(入队)
当按键key_up按下,将传输大数据,这里拷贝大数据的地址到队列big_data_queue中
task2:读取队列key_queue中的消息(出队),打印出接收到的键值
task3:从队列big_data_queue读取大数据地址,通过地址访问大数据
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t task3_handler;
void task3( void * pvParameters );
QueueHandle_t key_queue; //小数据句柄
QueueHandle_t big_data_queue; //大数据句柄
char buffer[100] = {"这是一个大数组!!!!!!!"};
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{ /*队列的创建*/
key_queue = xQueueCreate( 2, sizeof(uint8_t) );
if(key_queue != NULL)
{
printf("key_queue队列创建成功!!\r\n");
}
else
printf("key_queue队列创建失败!!\r\n");
big_data_queue = xQueueCreate( 1, sizeof(char *) );
if(big_data_queue != NULL)
{
printf("big_data_queue队列创建成功!!\r\n");
}
else
printf("big_data_queue队列创建失败!!\r\n");
xTaskCreate((TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
xTaskCreate((TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
xTaskCreate((TaskFunction_t ) task3,
(char * ) "task3",
(configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK3_PRIO,
(TaskHandle_t * ) &task3_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/* 任务一,实现入队 */
void task1( void * pvParameters )
{
uint8_t key = 0;
char* buf;
//buf = buffer; /*buf = &buffer[0];*/
BaseType_t err = 0 ;
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES || key == KEY1_PRES)
{
err = xQueueSend( key_queue, &key, portMAX_DELAY );
if(err != pdTRUE)
{
printf("key_queue队列写入失败!\r\n");
}
else if(key = WKUP_PRES)
{
err = xQueueSend(big_data_queue,&buf,portMAX_DELAY);
if(err != pdTRUE)
{
printf("big_data_queue队列写入失败!\r\n");
}
}
}
vTaskDelay(10);
}
}
/* 任务二,实现小数据出队*/
void task2( void * pvParameters )
{
uint8_t key = 0;
BaseType_t err = 0;
while(1)
{
err = xQueueReceive( key_queue,&key,portMAX_DELAY);
if(err != pdTRUE)
{
printf("key_queue队列出队失败!\r\n");
}else
{
printf("读取队列成功,数据:%d\r\n",key);
}
}
}
//可以加延时函数也可以不加延时函数,因为task2的优先级高于task1,但是由于key_queue中没有数据可以出队
//因此task2会进入阻塞态
/* 任务三,实现小数据出队*/
void task3( void * pvParameters )
{
BaseType_t err = 0;
char *buf;
while(1)
{
err = xQueueReceive( big_data_queue,&buf,portMAX_DELAY);
if (err != pdTRUE)
{
printf("big_data_queue队列读取失败\r\n");
}else
{
printf("big_data_queue队列读取成功,数据:%s\r\n",&buffer);
}
}
}
//如果task1没有按按键的话,task3和task2是读取不到数据的,因为此时创建的队列是没有数据的