目录
队列
消息队列是一种数据结构,用于在任务与任务之间、中断与任务之间传递消息。每个消息由一个特定大小的数据块组成,可以包含任何类型的数据。数据交流的一种机制(消息传递)
为什么不用全局变量呢???因为 全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损
读写队列做好了保护,防止多任务同时访问冲突;
写队列: 读队列:
xQueueSend( ) xQueueReceive( )
{ {
// 进入临界区(关中断) // 进入临界区(关中断)
写队列实际操作 读队列实际操作
// 退出临界区(开中断) // 退出临界区(开中断)
} }
在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做 “队列项目”,队列能够存储 “队列项目”的最大数量称为队列的长度 。在创建队列时,就要指定队列长度以及队列项目的大小!
FreeRTOS队列特点:
- 数据传递方式:FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递,FreeRTOS 采用拷贝数据传递,也可以传递指针, 所以在传递较大的数据的时候采用指针传递
- 多任务访问: 队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息使用消息 队列可以,防止多任务的访问冲突。
- 存储机制: 消息队列支持FIFO(先存先读)和LIFO(后进先读)两种存储机制,队列通常采用“先进先出”(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取。
- 超时机制:FreeRTOS消息队列支持超时机制,当任务尝试从空队列中接收消息时,可以指定一个等待时间。如果在这段时间内队列中没有消息,则任务可以自动转为就绪态,继续执行其他任务。
- 若阻塞时间为 0 :直接返回不会等待;
- 若阻塞时间为 0~port_MAX_DELAY :等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
- 若阻塞时间为 port_MAX_DELAY :死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;
入队阻塞:
队列满了,此时写不进去数据;
- 将该任务的状态列表项挂载在 pxDelayedTaskList;
- 将该任务的事件列表项挂载在 xTasksWaitingToSend;
出队阻塞:
队列为空,此时读取不了数据;
- 将该任务的状态列表项挂载在 pxDelayedTaskList;
- 将该任务的事件列表项挂载在 xTasksWaitingToReceive;
队列结构体
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; /* 写入上锁计数器 */
当用于队列使用时:
typedef struct QueuePointers
{
int8_t * pcTail; /* 存储区的结束地址 */
int8_t * pcReadFrom; /* 最后一个读取队列的地址 */
} QueuePointers_t;
当用于互斥信号量和递归互斥信号量时 :
typedef struct SemaphoreData
{
TaskHandle_t xMutexHolder; /* 互斥信号量持有者 */
UBaseType_t uxRecursiveCallCount; /* 递归互斥信号量的获取计数器 */
} SemaphoreData_t;
队列操作基本过程
使用队列的主要流程:创建队列 写队列 读队列。
创建队列相关API函数
函数 |
描述 |
xQueueCreate() |
动态方式创建队列 |
xQueueCreateStatic() |
静态方式创建队列 |
动态和静态创建队列之间的区别:队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中分配,而静态创建需要用户自行分配内存。
#define xQueueCreate ( uxQueueLength, uxItemSize )
xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE ))
形参 |
描述 |
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 ) /* 递归互斥信号量 */
往队列写入第一个消息
往队列写入第二个消息
函数 |
描述 |
xQueueSend() |
往队列的尾部写入消息 |
xQueueSendToBack() |
同 xQueueSend() |
xQueueSendToFront() |
往队列的头部写入消息 |
xQueueOverwrite() |
覆写队列消息(只用于队列长度为 1 的情况) |
xQueueSendFromISR() |
在中断中往队列的尾部写入消息 |
xQueueSendToBackFromISR() |
同 xQueueSendFromISR() |
xQueueSendToFrontFromISR() |
在中断中往队列的头部写入消息 |
xQueueOverwriteFromISR() |
在中断中覆写队列消息(只用于队列长度为 1 的情况) |
几个写入函数调用的是同一个函数xQueueGenericSend( ),只是指定了不同的写入位置!
#define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) /* 写入队列尾部 */
#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) /* 写入队列头部 */
#define queueOVERWRITE ( ( BaseType_t ) 2 ) /* 覆写队列*/
//注意:覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用
#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 )
往队列写入消息函数:
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition );
形参 |
描述 |
xQueue |
待写入的队列 |
pvItemToQueue |
待写入消息 |
xTicksToWait |
阻塞超时时间 |
xCopyPosition |
写入的位置 |
返回值 |
描述 |
pdTRUE |
队列写入成功 |
errQUEUE_FULL |
队列写入失败 |
从队列读取第一个消息
从队列读取消息API函数:
函数 |
描述 |
xQueueReceive() |
从队列头部读取消息,并删除消息 |
xQueuePeek() |
从队列头部读取消息 |
xQueueReceiveFromISR() |
在中断中从队列头部读取消息,并删除消息 |
xQueuePeekFromISR() |
在中断中从队列头部读取消息 |
该函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
形参 |
描述 |
xQueue |
待读取的队列 |
pvBuffer |
信息读取缓冲区 |
xTicksToWait |
阻塞超时时间 |
返回值 |
描述 |
pdTRUE |
读取成功 |
pdFALSE |
读取失败 |
此函数用于在任务中,从队列中读取消息, 但与函数 xQueueReceive() 不同,此函数在成功读取消息后,并不会移除已读取的消息!
BaseType_t xQueuePeek( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait )
形参 |
描述 |
xQueue |
待读取的队列 |
pvBuffer |
信息读取缓冲区 |
xTicksToWait |
阻塞超时时间 |
返回值 |
描述 |
pdTRUE |
读取成功 |
pdFALSE |
读取失败 |
队列操作实验
学习 FreeRTOS 的队列相关API函数的使用 ,实现队列的入队和出队操作。
实验设计:
将设计四个任务:start_task、task1、task2、task3
四个任务的功能如下:
- start_task:用来创建其他的3个任务
- task1:当按键key0或key1按下,将键值拷贝到队列key_queue(入队), 当按键key_up按下,将传输大数据,这里拷贝大数据的地址到队列big_date_queue中
- task2:读取队列key_queue中的消息(出队),打印出接收到的键值
- task3:从队列big_date_queue读取大数据地址,通过地址访问大数据
QueueHandle_t key_quene; /* 定义队列 */
QueueHandle_t big_date_quene; /* 定义队列 */
#define QUEUE_LENGTH 1 /* 队列支持的消息个数 */
#define QUEUE_ITEM_SIZE sizeof(uint8_t) /* 队列中每条消息的大小 */
char buff[100] = {"asdasfdsgasdffasdd"};
void freertos_demo(void)
{
/* 创建队列 */
key_quene = xQueueCreate(QUEUE_LENGTH,QUEUE_ITEM_SIZE);
if(key_quene != NULL)
{
printf("key_quene队列创建成功!!\r\n");
}else printf("key_quene队列创建失败!!\r\n");
big_date_quene = xQueueCreate(QUEUE_LENGTH,sizeof(char *));
if(big_date_quene != NULL)
{
printf("big_date_quene队列创建成功!!\r\n");
}else printf("big_date_quene队列创建失败!!\r\n");
xTaskCreate((TaskFunction_t )start_task, /* 任务函数 */
(const char* )"start_task", /* 任务名称 */
(uint16_t )START_STK_SIZE, /* 任务堆栈大小 */
(void* )NULL, /* 传入给任务函数的参数 */
(UBaseType_t )START_TASK_PRIO, /* 任务优先级 */
(TaskHandle_t* )&StartTask_Handler); /* 任务句柄 */
vTaskStartScheduler();
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); /* 进入临界区 */
/* 创建任务1 */
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
/* 创建任务2 */
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
/* 创建任务3 */
xTaskCreate((TaskFunction_t )task3,
(const char* )"task3",
(uint16_t )TASK3_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK3_PRIO,
(TaskHandle_t* )&Task3Task_Handler);
vTaskDelete(StartTask_Handler); /* 删除开始任务 */
taskEXIT_CRITICAL(); /* 退出临界区 */
}
void task1(void *pvParameters)
{
uint8_t key = 0;
BaseType_t err = 0;
char *buf;
buf = buff;
while (1)
{
key = key_scan(0);
if(key == KEY0_PRES || key == KEY1_PRES)
{
err = xQueueSend(key_quene, &key, portMAX_DELAY);
/* 将键值作为消息发送到队列中 */
if(err != pdTRUE)
{
printf("key_quene队列发送失败\r\n");
}else printf("key_quene队列发送成功\r\n");
}
else if(key == WKUP_PRES)
{
err = xQueueSend(big_date_quene, &buf, portMAX_DELAY);
if(err != pdTRUE)
{
printf("big_date_quene队列发送失败\r\n");
}else printf("big_date_quene队列发送成功\r\n");
}
vTaskDelay(500);
}
}
void task2(void *pvParameters)
{
uint8_t queue_recv = 0;
BaseType_t err =0;
while (1)
{
err = xQueueReceive(key_quene, &queue_recv, portMAX_DELAY);
if(err != pdTRUE)
{
printf("接收队列失败\r\n");
}
else
{
printf("key_quene队列的键值是:%u\r\n",queue_recv);
}
vTaskDelay(500);
}
}
void task3(void *pvParameters)
{
BaseType_t err =0;
char * buf;
while (1)
{
err = xQueueReceive(big_date_quene, &buf, portMAX_DELAY);
if(err != pdTRUE)
{
printf("接收队列失败\r\n");
}
else
{
printf("big_date_quene队列的数据是:%s\r\n",buf);
}
}
}