1、消息队列的基本概念
在FreeRTOS中,消息队列(Message Queue)是任务间通讯(IPC)、中断和任务间通讯的核心机制,用于实现数据传递和同步。
FreeRTOS中使用队列数据结构实现任务异步通信方式,具有如下特性:
- 消息支持先进先出、先进后出方式排队,支持异步读写工作方式;
- 读写队列均支持超时机制;
- 可以允许不同长度的任意类型消息;
- 一个任务能够从任意一个消息队列接收和发送消息;
- 多个任务能从同一个消息队列接收和发送消息;
- 当队列使用结束后,可以通过删除队列函数进行删除。
2、消息队列的运作机制
1、消息队列的创建与删除
创建消息队列时,FreeRTOS会先给消息队列分配一块内存空间,大小等于消息队列控制块大小加上(单个消息空间大小与消息队列长度的乘积)。只有当删除该消息队列的时候,这块内存才会被释放掉。
2、消息的发送
任务或者中断服务程序都可以给消息队列发送消息,如果队列未满或允许覆盖入队,FreeRTOS会将消息拷贝到消息队列的队尾,否则,会根据用户指定的阻塞超时时间进行阻塞。当消息队列中可以继续发送信息后,任务将自动有阻塞态转换为就绪态。当等待时间超过指定的阻塞时间,即使队列中还不允许入队,任务也会自动从阻塞态转移为就绪态,此时发送消息的任务或中断程序会收到一个错误码errQUEUE_FULL
。
3、消息的接收
当某个任务试图读取一个消息队列时,同样可以指定阻塞超时时间。超时时间到或可以读出消息后的任务状态都与消息的发送一致。
3、消息队列的阻塞机制
每个对消息队列读写的函数都有阻塞机制。
当我们进行读操作时,根据队列中是否有可读取的消息,任务可以有三种选择:
- 1、不等待消息,继续执行,任务保持运行状态;
- 2、等待指定tick时间,在等待中,任务进入阻塞态。若在等待中,读取到消息,任务进入就绪态;若超时,函数返回错误代码,继续执行其余任务;
- 3、任务死等,一直处于阻塞态,直到读取到消息。
当我们进行写操作时,根据队列中是否有空余消息空间,任务同样有上述三种选择。
4、消息队列的常用函数
使用队列模块的典型操作如下:
- 创建消息队列;
- 写队列操作;
- 读队列操作;
- 删除队列。
中断服务函数有专用的队列操作函数,要注意,在中断服务函数中也不允许有任务延时。
xQueueCreate() ; /* 消息队列动态创建函数 */
xQueueCreateStatic(); /* 消息队列静态创建函数 */
vQueueDelete() ; /* 消息队列删除 */
xQueueSend(); /* 向消息队列尾部发送一个队列消息 */
xQueueSendToBack() ; /* 向消息队列尾部发送一个队列消息 */
xQueueSendFromISR(); /* 中断中用 */
xQueueSendToBackFromISR() ; /* 中断中用 */
xQueueSendToFront() ; /* 将队列消息发送至队列队首 */
xQueueGenericSend(); /* 通用发送队列消息函数 */
xQueueGenericSendFromISR(); /* 中断专用通用发送队列消息函数 */
xQueueReceive(); /* 从队列中读取消息,并删除 */
xQueuePeek() ; /* 从队列中读取消息,不删除 */
xQueueReceiveFromISR();
xQueuePeekFromISR();
xQueueGenericReceive() ;
5、消息队列的应用场景
5.1、数据生产者 - 消费者模型
任务 A(生产者)收集传感器数据,通过队列传递给任务 B(消费者)进行处理。
示例:温度传感器数据采集→数据滤波→LCD 显示。
5.2、异步事件处理
中断服务函数(ISR)将事件信息(如按键触发)放入队列,由任务后续处理,避免 ISR 执行过长。
示例:外部中断检测到按键→队列传递按键值→任务响应 UI 事件。
5.3、任务间同步
任务 A 完成特定操作后,通过队列发送信号通知任务 B 继续执行。
示例:初始化任务完成硬件配置→队列发送 “READY”→控制任务启动。
5.4、资源共享
通过队列传递资源指针,确保同一时间只有一个任务访问共享资源。
示例:多个任务通过队列请求访问 SD 卡,避免竞争。(核心思想:通过消息队列访问共享资源的访问令牌,而非资源本身)
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/* 定义资源令牌(简单整数即可) */
typedef uint8_t ResourceToken_t;
#define SD_CARD_TOKEN 1
/* 创建资源令牌队列(长度1,表示只能有一个任务持有令牌) */
QueueHandle_t xSDCardTokenQueue;
/* 初始化SD卡(示例函数,需根据硬件实现) */
static void prvInitSDCard(void) {
/* 初始化SD卡硬件 */
// ...
}
/* 获取SD卡访问权限(阻塞直到获取成功) */
static BaseType_t xAcquireSDCardAccess(void) {
ResourceToken_t xToken;
return xQueueReceive(xSDCardTokenQueue, &xToken, portMAX_DELAY);
}
/* 释放SD卡访问权限 */
static void vReleaseSDCardAccess(void) {
ResourceToken_t xToken = SD_CARD_TOKEN;
xQueueSend(xSDCardTokenQueue, &xToken, 0);
}
/* 任务1:日志记录(低优先级) */
void vLoggingTask(void *pvParameters) {
for (;;) {
/* 模拟产生日志数据 */
char *pcLogData = pcGenerateLogData();
/* 请求SD卡访问权限 */
if (xAcquireSDCardAccess() == pdTRUE) {
/* 访问SD卡,写入日志 */
vWriteLogToSDCard(pcLogData);
/* 释放SD卡访问权限 */
vReleaseSDCardAccess();
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
/* 任务2:数据存储(高优先级) */
void vDataStorageTask(void *pvParameters) {
for (;;) {
/* 等待数据更新(示例:信号量) */
xSemaphoreTake(xDataReadySemaphore, portMAX_DELAY);
/* 模拟产生需要存储的数据 */
float *pfData = pfGetSensorData();
/* 请求SD卡访问权限 */
if (xAcquireSDCardAccess() == pdTRUE) {
/* 访问SD卡,存储数据 */
vSaveDataToSDCard(pfData);
/* 释放SD卡访问权限 */
vReleaseSDCardAccess();
}
}
}
/* 主函数 */
int main(void) {
/* 初始化硬件 */
prvSetupHardware();
prvInitSDCard();
/* 创建SD卡令牌队列(长度1) */
xSDCardTokenQueue = xQueueCreate(1, sizeof(ResourceToken_t));
/* 初始化令牌(放入队列,表示资源可用) */
ResourceToken_t xToken = SD_CARD_TOKEN;
xQueueSend(xSDCardTokenQueue, &xToken, 0);
/* 创建任务 */
xTaskCreate(vLoggingTask, "Logging", 256, NULL, 1, NULL);
xTaskCreate(vDataStorageTask, "Storage", 256, NULL, 2, NULL);
/* 启动调度器 */
vTaskStartScheduler();
/* 如果程序执行到此,说明发生了错误 */
for (;;);
}