1,队列简介(了解)
队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)
与全局变量的区别
类似全局变量?假设有一个全局变量a = 0,现有两个任务都在写这个变量a
假如 当任务1在进行数据交换前被任务2打断那么任务2运行完a的值多加了1任务1再运行加1则多加了1
全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损
FreeRTOS基于队列, 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、
二值信号量、 递归互斥信号量,因此很有必要深入了解 FreeRTOS 的队列 。
使用队列的情况如下: 读写队列做好了保护,防止多任务同时访问冲突;我们只需要直接调用API函数即可,简单易用!
在队列中可以存储数量有限、大小固定的数据。队列中的每一个数据叫做“队列项目”,队列能够存储“队列项目”的最大数量称为队列的长度
1、队列长度为:5个
2、队列项目大小为:10字节
在创建队列时,就要指定队列长度以及队列项目的大小!
FreeRTOS队列特点:
1、数据入队出队方式:队列通常采用“先进先出”(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取,FreeRTOS中也可以配置为“后进先出”LIFO方式;
2、数据传递方式:FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递, FreeRTOS采用拷贝数据传递,也可以传递指针,所以在传递较大的数据的时候采用指针传递
3、多任务访问:队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息
4、出队、入队阻塞:当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队
若阻塞时间为0 :直接返回不会等待;
若阻塞时间为0~port_MAX_DELAY :等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
若阻塞时间为port_MAX_DELAY :死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;
问题:当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务 在等待同一 个队列的空间。那当队列中有空间时,哪个任务会进入就绪态?
答:
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;
补充知识
union是一种特殊的数据结构,它允许在同一内存位置存储不同类型的数据。在C语言中,union可以包含多个成员,每个成员可以是不同的数据类型。但是,union只能同时使用一个成员。
union的定义方式和结构体类似,使用union关键字,并在花括号内定义成员。例如:
c
union Data {
int i;
float f;
char str[20];
};
上面的代码定义了一个名为Data的union,它有三个成员:一个整型变量i,一个浮点型变量f,和一个字符数组str。
union在内存中的大小等于最大成员的大小。这是因为union的所有成员共享同一块内存,而且只能同时使用一个成员。当修改union的某个成员时,其他成员的值会被覆盖。
union常用于需要在不同数据类型之间进行转换或节省内存空间的情况。但需要注意的是,在使用union时要确保对成员的访问是合法的,避免出现类型错误和未定义行为。
队列结构体整体示意图:
3,队列相关API函数介绍(掌握)
使用队列的主要流程:创建队列 写队列 读队列。
创建队列相关API函数介绍:
动态和静态创建队列之间的区别:队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中分配,而静态创建需要用户自行分配内存。
#define xQueueCreate ( uxQueueLength, uxItemSize ) \
xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE ))
此函数用于使用动态方式创建队列,队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中分配
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 ) /* 递归互斥信号量 */
#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 种写入位置 :
#define queueSEND_TO_BACK ( ( BaseType_t ) 0 ) /* 写入队列尾部 */
#define queueSEND_TO_FRONT ( ( BaseType_t ) 1 ) /* 写入队列头部 */
#define queueOVERWRITE ( ( BaseType_t ) 2 ) /* 覆写队列*/
注意:覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue,TickType_t xTicksToWait,const BaseType_t xCopyPosition );
从队列读取消息API函数:
BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
此函数用于在任务中,从队列中读取消息,并且消息读取成功后,会将消息从队列中移除。
BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )
此函数用于在任务中,从队列中读取消息, 但与函数 xQueueReceive()不同,此函数在成功读取消息后,并不会移除已读取的消息!
#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 );
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t task3_handler;
void task3( void * pvParameters );
QueueHandle_t g_key_queue_handle,g_big_date_queue_handle;
char g_buff[100] = {"我是一个大数组,大大的数组 124214 uhsidhaksjhdklsadhsaklj"};
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
/*********************************创建队列*********************************************************************/
g_key_queue_handle = xQueueCreate( 2, sizeof(uint8_t));
if(g_key_queue_handle!=NULL)
{
printf("key_queue队列创建成功!!\r\n");
}else printf("key_queue队列创建失败!!\r\n");
g_big_date_queue_handle = xQueueCreate( 1, sizeof(char *) );
if(g_big_date_queue_handle!=NULL)
{
printf("big_date_queue队列创建成功!!\r\n");
}else printf("big_date_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 = g_buff; /* buf = &buff[0] */
BaseType_t err;
while(1)
{
key=key_scan(0);
if(key==KEY0_PRES||key==KEY1_PRES)
{
err=xQueueSend(g_key_queue_handle,&key,portMAX_DELAY);
if(err!=pdTRUE)
{
printf("key_queue队列发送失败\r\n");
}
}
else if(key == WKUP_PRES)
{
err = xQueueSend( g_big_date_queue_handle, &buf, portMAX_DELAY );
if(err != pdTRUE)
{
printf("big_date_queue队列发送失败\r\n");
}
}
vTaskDelay(10);
}
}
void task2( void * pvParameters )
{
uint8_t key = 0;
BaseType_t err = 0;
while(1)
{
err = xQueueReceive( g_key_queue_handle,&key,portMAX_DELAY);
if(err != pdTRUE)
{
printf("key_queue队列读取失败\r\n");
}else
{
printf("key_queue读取队列成功,数据:%d\r\n",key);
}
}
}
void task3( void * pvParameters )
{
char * buf;
BaseType_t err = 0;
while(1)
{
err = xQueueReceive( g_big_date_queue_handle,&buf,portMAX_DELAY);
if(err != pdTRUE)
{
printf("big_date_queue队列读取失败\r\n");
}else
{
printf("数据:%s\r\n",buf);
}
}
}
这里注意一个问题 队列项 的读取和写入参数中的第二个参数都是一个指向任意类型的指针所以我们这里key要取地址 ,同理大队列中队列项是大数组的首地址,所以将他的首地址给临时变量buf 再对buf进行取值写入和读取。