FreeRTOS 快速入门(四)之队列


一、队列的特性

1、数据存储

一个队列能保存有限数量的固定大小的数据单元。一个队列能保存单元的最大数量叫做 “长度”。每个队列数据单元的长度与大小是在创建队列时设置的。

队列通常是一个先入先出(FIFO)的缓冲区,即数据在队列末尾(tail)被写入,在队列前部(head)移出。下图展示了数据被写入和移出作为 FIFO 使用的队列。也可以写入队列的前端,并覆盖已位于队列前端的数据。

2、传输数据的两种方法

使用队列传输数据时有两种方法:

  • 拷贝:把数据、把变量的值复制进队列里
  • 引用:把数据、把变量的地址复制进队列里

FreeRTOS 使用拷贝值的方法,这更简单:

  • 局部变量的值可以发送到队列中,后续即使函数退出、局部变量被回收,也不会影响队列中的数据
  • 无需分配 buffer 来保存数据,队列中有 buffer
  • 局部变量可以马上再次使用
  • 发送任务、接收任务解耦:接收任务不需要知道这数据是谁的、也不需要发送任务来释放数据
  • 如果数据实在太大,你还是可以使用队列传输它的地址
  • 队列的空间有 FreeRTOS 内核分配,无需任务操心

3、队列的阻塞访问

只要知道队列的句柄,谁都可以读、写该队列。任务、ISR 都可读、写队列。可以多个任务读写队列。

任务读写队列时,简单地说:如果读写不成功,则阻塞;可以指定超时时间。

某个任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态:还可以指定阻塞的时间。如果队列有数据了,则该阻塞的任务会变为就绪态。如果一直都没有数据,则时间到之后它也会进入就绪态。

既然读取队列的任务个数没有限制,那么当多个任务读取空队列时,这些任务都会进入阻塞状态:有多个任务在等待同一个队列的数据。当队列中有数据时,哪个任务会进入就绪态?

  • 优先级最高的任务
  • 如果大家的优先级相同,那等待时间最久的任务会进入就绪态

跟读队列类似,一个任务要写队列时,如果队列满了,该任务也可以进入阻塞状态:还可以指定阻塞的时间。如果队列有空间了,则该阻塞的任务会变为就绪态。如果一直都没有空间,则时间到之后它也会进入就绪态。

既然写队列的任务个数没有限制,那么当多个任务写"满队列"时,这些任务都会进入阻塞状态:有多个任务在等待同一个队列的空间。当队列中有空间时,哪个任务会进入就绪态?

  • 优先级最高的任务
  • 如果大家的优先级相同,那等待时间最久的任务会进入就绪态

二、多任务访问

队列本身就是对象,任何知道它们存在的任务或 ISR 都可以访问它们。任意数量的任务可以写入同一个队列,任意数量的任务也可以从同一个队列读取。在实践中,队列有多个写入者是非常常见的,但是队列有多个读取者就不那么常见了。

2.1 阻塞队列读取

当任务尝试从队列中读取时,它可以选择指定 “阻塞” 时间。 如果队列已经为空,则这是任务将保持在阻塞状态以等待队列中的数据可用的时间。 当另一个任务或中断将数据放入队列时,处于阻塞状态且等待数据从队列中变为可用的任务将自动移至就绪状态。 如果指定的阻塞时间在数据可用之前到期,则任务也将自动从 “阻塞” 状态移动到 “就绪” 状态。

队列可以有多个读取者,因此单个队列可能会由多个在其上阻塞等待数据的任务。 在这种情况下,只有一个任务在数据可用时将被解除阻塞。 取消阻塞的任务始终是等待数据的最高优先级任务。 如果被阻塞的任务具有相同的优先级,那么等待数据最长的任务将被阻塞。

2.2 阻塞队列写入

与从队列读取数据时一样,任务也可以在向队列写入数据时指定阻塞时间。在这种情况下,如果队列已经满了,则阻塞时间是任务应该保持在阻塞状态以等待队列上可用空间的最长时间。

队列可以有多个写入者,因此对于一个完整的队列,可能有多个任务阻塞在队列上,等待完成发送操作。在这种情况下,当队列上的空间可用时,只有一个任务将被解除阻塞。未阻塞的任务总是等待空间的最高优先级任务。如果阻塞的任务具有相同的优先级,那么等待空间最长的任务将被解除阻塞。

2.3 阻塞多个队列

队列可被分组到集合中,允许任务进入阻塞状态来等待数据在集合的任何队列中变为可用。

三、队列函数

使用队列的流程:创建队列、写队列、读队列、删除队列。

1、创建

队列的创建有两种方法:动态分配内存、静态分配内存:

  • 动态分配内存:xQueueCreate,队列的内存在函数内部动态分配

函数原型如下:

QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
参数 说明
uxQueueLength 队列长度,最多能存放多少个数据(item)
uxItemSize 每个数据(item)的大小:以字节为单位
返回值 非 0:成功,返回句柄,以后使用句柄来操作队列
NULL:失败,因为内存不足
  • 静态分配内存:xQueueCreateStatic,队列的内存要事先分配好

函数原型如下:

QueueHandle_t xQueueCreateStatic(
							UBaseType_t uxQueueLength,
							UBaseType_t uxItemSize,
							uint8_t *pucQueueStorageBuffer,
							StaticQueue_t *pxQueueBuffer
						);
参数 说明
uxQueueLength 队列长度,最多能存放多少个数据(item)
uxItemSize 每个数据(item)的大小:以字节为单位
pucQueueStorageBuffer 如果 uxItemSize 非 0,pucQueueStorageBuffer 必须指向一个 uint8_t 数组,此数组大小至少为"uxQueueLength * uxItemSize"
pxQueueBuffer 必须执行一个 StaticQueue_t 结构体,用来保存队列的数据结构
返回值 非 0:成功,返回句柄,以后使用句柄来操作队列
NULL:失败,因为 pxQueueBuffer 为 NULL

例:

// 示例代码
#define QUEUE_LENGTH 10
#define ITEM_SIZE sizeof(uint32_t)

// xQueueBuffer用来保存队列结构体
StaticQueue_t xQueueBuffer;
// ucQueueStorage 用来保存队列的数据
// 大小为:队列长度 * 数据大小
uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ];

void vATask( void *pvParameters )
{
   
   
	QueueHandle_t xQueue1;
	// 创建队列: 可以容纳QUEUE_LENGTH个数据,每个数据大小是ITEM_SIZE
	xQueue1 = xQueueCreateStatic( QUEUE_LENGTH,
								  ITEM_SIZE,
								  ucQueueStorage,
								  &xQueueBuffer );
}

复位

队列刚被创建时,里面没有数据;使用过程中可以调用 xQueueReset() 把队列恢复为初始状态,此函数原型为:

/* pxQueue : 复位哪个队列;
* 返回值: pdPASS(必定成功)
*/
BaseType_t xQueueReset( QueueHandle_t pxQueue);

2、删除

删除队列的函数为 vQueueDelete() ,只能删除使用动态方法创建的队列,它会释放内存。原型如下:

void vQueueDelete( QueueHandle_t xQueue );

3、写队列

可以把数据写到队列头部,也可以写到尾部,这些函数有两个版本:在任务中使用、在 ISR 中使用。函数原型如下:

/* 等同于xQueueSendToBack
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSend(
				QueueHandle_t xQueue,
				const void *pvItemToQueue,
				TickType_t xTicksToWait
			);

/*
* 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
*/
BaseType_t xQueueSendToBack(
					QueueHandle_t xQueue,
					const void *pvItemToQueue,
					TickType_t xTicksToWait
				);

/*
* 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
*/
BaseType_t xQueueSendToBackFromISR
### FreeRTOS 快速入门指南教程 FreeRTOS 是一款开源的实时操作系统(RTOS),专为嵌入式系统设计。它提供了一组 API 和软件库,帮助开发者实现任务管理、内存管理、中断处理、时间管理等功能,从而开发出高可靠性的实时嵌入式应用程序。为了快速入门并开始使用 FreeRTOS,以下是一些关键步骤和指导。 #### 安装和配置 FreeRTOS 1. **获取 FreeRTOS 源代码** FreeRTOS 的源代码可以从其官方网站下载。下载后,将源代码解压到您的项目目录中。通常,FreeRTOS 的源代码包含多个目录,其中 `FreeRTOS/Source` 目录下包含了核心的 RTOS 源文件,而 `FreeRTOS/Source/include` 目录下包含了头文件 [^3]。 2. **选择开发环境和目标平台** FreeRTOS 支持多种开发环境和目标平台,包括但不限于 ARM Cortex-M 系列、ESP32、AVR、PIC 等。根据您的目标平台选择合适的开发工具链,例如 Keil、IAR、GCC 等。 3. **配置 FreeRTOS** 在 `FreeRTOS/Source/include` 目录下有一个名为 `FreeRTOSConfig.h` 的配置文件。该文件定义了 FreeRTOS 的一些基本配置,例如系统时钟频率、任务调度策略、内存分配方式等。根据您的项目需求调整这些配置 [^2]。 #### 创建任务和调度 1. **创建任务** FreeRTOS 的任务是通过 `xTaskCreate()` 函数创建的。该函数需要传入任务函数、任务名称、堆栈大小、任务参数、任务优先级以及任务句柄等参数。例如: ```c void vTaskFunction(void *pvParameters) { for (;;) { // 任务执行的代码 vTaskDelay(pdMS_TO_TICKS(1000)); // 廷迟 1 秒 } } xTaskCreate(vTaskFunction, "Task1", 200, NULL, 1, NULL); ``` 2. **启动调度器** 在所有任务创建完成后,调用 `vTaskStartScheduler()` 启动调度器。此时,FreeRTOS 将开始调度任务的执行 。 #### 同步与通信机制 FreeRTOS 提供了多种同步与通信机制,例如信号量(Semaphore)、互斥锁(Mutex)、队列(Queue)等。这些机制可以用于任务之间的同步和数据交换。 1. **信号量** 信号量可以通过 `xSemaphoreCreateBinary()` 创建,通过 `xSemaphoreTake()` 和 `xSemaphoreGive()` 进行获取和释放操作。 2. **队列** 队列可以通过 `xQueueCreate()` 创建,并通过 `xQueueSend()` 和 `xQueueReceive()` 进行发送和接收数据。 #### 内存管理 FreeRTOS 提供了多种内存分配策略,开发者可以根据项目需求选择合适的内存管理方式。默认情况下,FreeRTOS 使用静态内存分配,但也可以通过修改 `FreeRTOSConfig.h` 文件中的 `configUSE_MALLOC_LOCK_AND_RELEASE` 配置项来启用动态内存分配 [^2]。 #### 调试与优化 1. **调试工具** FreeRTOS 支持多种调试工具,例如 Tracealyzer、Percepio DevTools 等。这些工具可以帮助开发者分析任务调度、内存使用、中断响应等关键信息。 2. **性能优化** 通过调整任务优先级、优化任务调度策略、减少任务切换次数等方式,可以提高系统的实时性和性能。 #### 进一步学习 为了更深入地了解 FreeRTOS 的高级功能,建议阅读官方文档和相关教程。官方文档提供了详细的 API 说明和示例代码,帮助开发者更好地理解和使用 FreeRTOS [^1]。 --- ###
评论 19
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值