FreeRTOS队列实战:血氧监测系统设计
在 FreeRTOS 中,队列是实现任务间通信的核心机制,它提供了一种线程安全的数据传递方式。下面通过一个医疗器械中 血氧饱和度监测与报警 ,来详细了解队列的使用方法。系统需要持续采集血氧数据,并在数据异常时触发报警。可以设计两个任务:一个数据采集任务和一个数据处理与报警任务,它们通过队列进行通信。
1. 定义数据结构与创建队列
首先,定义一个结构体来封装需要传输的数据,这使得单次通信可以传递多个相关变量,逻辑上更清晰。
#include "FreeRTOS.h"
#include "queue.h"
#include "task.h"// 定义血氧数据结构体
typedef struct {uint8_t spo2; // 血氧饱和度uint16_t heartRate; // 心率TickType_t timestamp; // 时间戳
} OxyData_t;// 队列参数
#define QUEUE_LENGTH 10 // 队列最多能容纳10条消息
#define QUEUE_ITEM_SIZE sizeof(OxyData_t) // 每条消息的大小为结构体的大小// 声明全局队列句柄
QueueHandle_t xOxyQueue;void app_main(void) {// 动态创建队列xOxyQueue = xQueueCreate(QUEUE_LENGTH, QUEUE_ITEM_SIZE);if (xOxyQueue == NULL) {// 队列创建失败,可能是内存不足,需要错误处理log("错误:队列创建失败!\n");return;}// 创建任务xTaskCreate(data_acquisition_task, "DataAcq", 2048, NULL, 2, NULL);//数据采集xTaskCreate(data_processing_task, "DataProc", 2048, NULL, 1, NULL); // 数据处理,优先级可低于或等于采集任务vTaskStartScheduler(); // 启动FreeRTOS调度器
}2. 数据采集任务
这个任务模拟周期性地从传感器读取数据,并将数据发送到队列中。
void data_acquisition_task(void *pvParameters) {OxyData_t currentData = {0};const TickType_t xBlockTime = pdMS_TO_TICKS(1000); // 发送阻塞时间1秒for (;;) {// 模拟从传感器读取数据currentData.spo2 = simulate_spo2_read();currentData.heartRate = simulate_heart_rate_read();currentData.timestamp = xTaskGetTickCount(); // 获取当前系统时钟// 将数据发送到队列尾部// 如果队列满,任务将阻塞最多1秒(xBlockTime)BaseType_t xStatus = xQueueSend(xOxyQueue, ¤tData, xBlockTime);if (xStatus != pdPASS) {// 发送失败,可能是队列在1秒内一直处于满的状态log("警告:数据包未能送入队列!\n");} else {log("数据已发送: SpO2=%d%%, HR=%d\n", currentData.spo2, currentData.heartRate);}vTaskDelay(pdMS_TO_TICKS(500)); // 每500ms采集一次}
}3. 数据处理与报警任务
这个任务从队列中接收数据,进行检查并决定是否报警。
void data_processing_task(void *pvParameters) {OxyData_t receivedData;BaseType_t xStatus;for (;;) {// 从队列头部接收数据// 使用 portMAX_DELAY,表示如果队列为空则无限期阻塞,直到有数据到来xStatus = xQueueReceive(xOxyQueue, &receivedData, portMAX_DELAY);if (xStatus == pdPASS) {// 成功接收到数据,进行判断if (receivedData.spo2 < 90 || receivedData.heartRate < 50 || receivedData.heartRate > 160) {// 数据异常,触发报警trigger_alarm(receivedData.spo2, receivedData.heartRate);log("!!!报警!!! SpO2: %d%%, Heart Rate: %d\n", receivedData.spo2, receivedData.heartRate);} else {// 数据正常,记录或显示display_data(receivedData.spo2, receivedData.heartRate);log("数据正常: SpO2=%d%%, HR=%d\n", receivedData.spo2, receivedData.heartRate);}}// 由于使用了portMAX_DELAY,xStatus不是pdPASS的情况不会发生}
}4.核心机制与注意事项
阻塞机制:
xQueueSend和xQueueReceive的第三个参数xTicksToWait实现了阻塞。这对系统稳定性至关重要,它能有效管理任务状态,避免任务空转消耗CPU资源。采集任务中设置1秒超时,防止在队列满时发送任务无限期阻塞,影响数据采集的周期性。
处理任务中使用
portMAX_DELAY,确保没有数据时自动进入阻塞态,让出CPU,有数据时能立刻被唤醒。
线程安全:队列操作本身是线程安全的。这意味着多个任务(或多个中断服务程序)同时访问同一个队列时,内部机制会保证数据不会损坏。
数据拷贝而非传递指针:队列通过值传递(拷贝数据本身),而不是传递指针。这确保了接收任务获得的是数据的一个完整副本,避免了发送任务在发送后修改数据而导致接收方数据错乱的风险。
中断服务程序中的使用:如果数据采集是由硬件中断触发的,需要在中断服务程序中将数据发送到队列,此时必须使用中断专用函数
xQueueSendFromISR(),其内部进行了去除了阻塞等不适合在中断中执行的操作。
