FreeRTOS内核:详解队列管理FIFO
- 1. 背景
- 2. Queue相关API
- 2.1 xQueueCreate():创建
- 2.2 xQueueSend():发送
- 2.3 xQueueReceive():接收
- 2.4 vQueueDelete():删除
- 2.5 xQueuePeek() :不删除的方式从FIFO读数据,读完不改变FIFO
- 2.6 uxQueueMessagesWaiting():返回FIFO中当前有效数据单元个数。
- 2.7 xQueueReset():用于重置队列。
- 3. FreeRTOS FIFO的原理
- 读FIFO时task状态:
- 写FIFO时task状态:
- 关于优先级:
- 4. 代码示例
- 5. 总结
- 博主热门文章推荐:
- 附:GPT4 output:
(注:本文部分文案由ChatGPT辅助生成,但内容均经过Howie审核和优化,放心使用。)
1. 背景
当我们在嵌入式系统中使用FreeRTOS时,队列(Queue)是一种非常重要的数据结构,它可以帮助我们实现多个任务之间的通信。
在FreeRTOS中,队列的实现是基于FIFO(First-In-First-Out,先进先出)的原则,这意味着先入队列的数据将会先被出队列。
下面,我们将深入介绍FreeRTOS队列管理的细节和用法。
FreeRTOS队列是一种可以在任务之间传递数据的数据结构。队列可以存储多个数据项,每个数据项可以是任何类型的数据,例如整数和结构体。队列的实现是基于FIFO原则
2. Queue相关API
2.1 xQueueCreate():创建
在FreeRTOS中,我们可以使用xQueueCreate()函数来创建一个队列。该函数的原型如下所示:
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
创建队列后返回一个句柄以便于对该队列进行引用, 其中,uxQueueLength参数是队列中可以存储的数据项的最大数量,uxItemSize参数是每个数据项的大小(以字节为单位)。
创建时,FreeRTOS内核从堆空间Heap分配FIFO内存空间,如果没有足够空间函数返回NULL
例如,如果我们想要创建一个可以存储10个整数的队列,可以使用以下代码:
QueueHandle_t xQueue = xQueueCreate(10, sizeof(int));
.
2.2 xQueueSend():发送
在FreeRTOS中,我们可以使用xQueueSend()函数向队列中发送数据。该函数的原型如下所示:
BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);
其中,xQueue参数是要发送数据的队列句柄,pvItemToQueue参数是要发送的数据的指针,xTicksToWait参数是等待数据被发送的最大时间(以FreeRTOS时钟节拍为单位)。
xQueueSend() == xQueneSendToBack()将数据发送到队列尾
中断中,要使用xQueueSendToBackFromISR()安全版本的API
例如,如果我们想要将整数10发送到队列中,可以使用以下代码:
int data = 10;
BaseType_t result = xQueueSend(xQueue, &data, 100);
在上面的代码中,我们将整数10发送到队列中,并等待100个FreeRTOS时钟节拍来等待数据被发送。该函数的返回值是一个BaseType_t类型的值,用于指示数据是否成功发送到队列中。
.
2.3 xQueueReceive():接收
在FreeRTOS中,我们可以使用xQueueReceive()函数从队列中接收数据。该函数的原型如下所示:
BaseType_t xQueueReceive(QueueHandle_t xQueue, void * pvBuffer, TickType_t xTicksToWait);
其中,xQueue参数是要接收数据的队列句柄,pvBuffer参数是一个指向缓冲区的指针,用于存储接收到的数据,xTicksToWait参数是等待数据被接收的最大时间(以FreeRTOS时钟节拍为单位)。
XQueueReceive()用于从FIFO中读取数据,读取完的单元数据同时会自动从FIFO中删除
例如,如果我们想要从队列中接收一个整数,可以使用以下代码:
int data;
BaseType_t result = xQueueReceive(xQueue, &data, 100);
在上面的代码中,我们从队列中接收一个整数,并等待100个FreeRTOS时钟节拍来等待数据被接收。该函数的返回值是一个BaseType_t类型的值,用于指示数据是否成功接收到了缓冲区中。
.
2.4 vQueueDelete():删除
在FreeRTOS中,我们可以使用vQueueDelete()函数来删除一个队列。该函数的原型如下所示:
void vQueueDelete(QueueHandle_t xQueue);
其中,xQueue参数是要删除的队列句柄。例如,如果我们想要删除上面创建的队列,可以使用以下代码:
vQueueDelete(xQueue);
.
2.5 xQueuePeek() :不删除的方式从FIFO读数据,读完不改变FIFO
同样中断中使用xQueueReceiveFromISR()
.
2.6 uxQueueMessagesWaiting():返回FIFO中当前有效数据单元个数。
.
2.7 xQueueReset():用于重置队列。
3. FreeRTOS FIFO的原理
FreeRTOS中所有的通讯与同步机制都是基于消息队列实现
- 队列就是链表
- 队列两个关键字:深度和每个单元的大小。其中深度即 队列可保存的最大单元数
FIFO是具有自己独立权限的内核对象,并不属于Task的资源,所有任务都可以向同一个FIFO队列写入和读出。
读FIFO时task状态:
Task读取队列可设置一个阻塞超时时间,
1、 正常情况下,在设置的超时时间内,如果FIFO为空,则task保持阻塞,当FIFO有数据写入(如其他Task或中断写FIFO),这时该Task将自动由阻塞态转为就绪态。
2、 当等待的时间超过了设定的超时时间,即使队列为空,Task也会自动转为就绪态。
如果多个任务读取FIFO,则根据优先级以及等待时间来决定哪一个Task被解除阻塞
写FIFO时task状态:
当FIFO已满时,写队列的Task进入阻塞态以等待FIFO空间有效,同样可以设置阻塞超时时间。
关于优先级:
如果读队列Task优先级高 -> 队列一直是空的,反之写队列优先级高 -> 队列一直是满的
一旦读队列Task从队列中读走一个数据单元,某个写队列Task就会立即抢占读队列任务,把刚读走的位置重新写入,之后便又转入阻塞态以等待队列空间有效。
如果读队列task的优先级最低,则只有在所有写队列task都进入阻塞态时,读队列task才执行,而写队列只有在队列满时才会进入阻塞态,所以读队列task执行时队列一定是满的。
4. 代码示例
下面是一个简单的代码示例,演示了如何在FreeRTOS中使用FIFO队列:
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#define QUEUE_LENGTH 5
#define QUEUE_ITEM_SIZE sizeof(int)
QueueHandle_t queue;
void producer_task(void *pvParameters)
{
int i;
for (i = 0; i < 10; i++) {
printf("Producer: Sending %d to queue\n", i);
xQueueSend(queue, &i, 0);
}
vTaskDelete(NULL);
}
void consumer_task(void *pvParameters)
{
int item;
while (1) {
xQueueReceive(queue, &item, portMAX_DELAY);
printf("Consumer: Received %d from queue\n", item);
}
}
int main(void)
{
queue = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE);
xTaskCreate(producer_task, "Producer", 1000, NULL, 1, NULL);
xTaskCreate(consumer_task, "Consumer", 1000, NULL, 1, NULL);
vTaskStartScheduler();
return 0;
}
在该示例中,我们创建了一个FIFO队列,并创建了一个生产者任务和一个消费者任务。生产者任务会向队列中发送数据,消费者任务会从队列中接收数据。通过这种方式,我们可以测试FIFO队列的工作原理。
5. 总结
FreeRTOS提供了非常方便和高效的FIFO队列管理方法,它可以帮助开发人员更好地管理和传输数据。在实际应用中,我们可以根据具体的需求选择不同的队列管理方法,并根据变化规律编写相应的代码来实现数据的存储和传输。
博主热门文章推荐:
一篇读懂系列:
- 一篇读懂无线充电技术(附方案选型及原理分析)
- 一篇读懂:Android/iOS手机如何通过音频接口(耳机孔)与外设通信
- 一篇读懂:Android手机如何通过USB接口与外设通信(附原理分析及方案选型)
LoRa Mesh系列:
- LoRa学习:LoRa关键参数(扩频因子,编码率,带宽)的设定及解释
- LoRa学习:信道占用检测原理(CAD)
- LoRa/FSK 无线频谱波形分析(频谱分析仪测试LoRa/FSK带宽、功率、频率误差等)
网络安全系列:
- ATECC508A芯片开发笔记(一):初识加密芯片
- SHA/HMAC/AES-CBC/CTR 算法执行效率及RAM消耗 测试结果
- 常见加密/签名/哈希算法性能比较 (多平台 AES/DES, DH, ECDSA, RSA等)
- AES加解密效率测试(纯软件AES128/256)–以嵌入式Cortex-M0与M3 平台为例
嵌入式开发系列:
- 嵌入式学习中较好的练手项目和课题整理(附代码资料、学习视频和嵌入式学习规划)
- IAR调试使用技巧汇总:数据断点、CallStack、设置堆栈、查看栈使用和栈深度、Memory、Set Next Statement等
- Linux内核编译配置(Menuconfig)、制作文件系统 详细步骤
- Android底层调用C代码(JNI实现)
- 树莓派到手第一步:上电启动、安装中文字体、虚拟键盘、开启SSH等
- Android/Linux设备有线&无线 双网共存(同时上内、外网)
AI / 机器学习系列:
- AI: 机器学习必须懂的几个术语:Lable、Feature、Model…
- AI:卷积神经网络CNN 解决过拟合的方法 (Overcome Overfitting)
- AI: 什么是机器学习的数据清洗(Data Cleaning)
- AI: 机器学习的模型是如何训练的?(在试错中学习)
- 数据可视化:TensorboardX安装及使用(安装测试+实例演示)
附:GPT4 output: