文章总结(帮你们节约时间)
- 消息队列是多任务系统中任务间通信的核心机制,解决了数据传递、同步和解耦的问题
- 队列采用FIFO数据结构,支持多生产者多消费者模式,并提供线程安全的访问机制
- 阻塞机制是队列的精髓,入队和出队操作都可以设置超时时间,实现优雅的任务同步
- 合理的队列设计和参数配置直接影响系统性能,需要根据应用场景进行优化调整
为什么我们需要消息队列?任务间通信的痛点
你有没有想过,在一个繁忙的餐厅里,厨师是如何知道顾客点了什么菜的?服务员又是如何将做好的菜品准确送到对应的餐桌?如果没有一套完善的信息传递机制,整个餐厅就会乱成一锅粥!
在多任务系统中,我们面临着同样的挑战。想象一下,你的ESP32系统中有一个传感器读取任务,一个数据处理任务,还有一个显示任务。传感器任务就像勤劳的采集员,不停地收集温度、湿度数据;数据处理任务像个精明的分析师,对原始数据进行计算和过滤;显示任务则像个贴心的服务员,把最终结果呈现给用户。
但问题来了:这些任务之间如何传递数据呢?
**全局变量?**太危险了!就像在拥挤的市场里大声喊话,不仅容易被其他声音掩盖,还可能被误解。多个任务同时访问全局变量会导致数据竞争,结果不可预测。
**直接函数调用?**这会让任务之间耦合得太紧密,就像把所有人用绳子绑在一起,一个人摔倒,大家都跟着倒霉。
**信号量?**只能传递"有"或"没有"的信息,就像只能点头或摇头,无法表达复杂的内容。
这时候,消息队列就像救世主一样出现了!它就像餐厅里的传菜窗口,有序、安全、高效地在任务之间传递信息。
消息队列解决了多任务系统中的几个核心问题:
数据传递问题:任务可以通过队列传递任意类型的数据,从简单的数字到复杂的结构体,就像传菜窗口可以传递各种菜品一样。
同步问题:发送方和接收方不需要同时在线,队列可以暂存数据,就像传菜窗口可以暂时存放做好的菜品,等服务员来取。
解耦问题:任务之间不需要直接知道对方的存在,只需要知道队列的存在即可,就像厨师不需要认识每个顾客,只需要把菜放到传菜窗口即可。
缓冲问题:当生产速度和消费速度不匹配时,队列可以起到缓冲作用,就像传菜窗口可以临时存放多道菜品。
队列的本质:FIFO数据结构的魅力
队列(Queue)这个词来自于英语中的"排队",这个比喻再恰当不过了!想象一下银行里的排队系统:先来的客户先办理业务,后来的客户在后面排队等候。这就是队列的核心特性——先进先出(First In First Out,FIFO)。
但是,FreeRTOS中的消息队列比银行排队系统要复杂得多,它更像一个智能化的物流仓库:
队列的内部结构:一个精密的数据仓库
FreeRTOS的队列内部结构可以用以下数学模型来描述:
Queue={Items[],Head,Tail,Length,ItemSize,MaxItems}Queue = \{Items[], Head, Tail, Length, ItemSize, MaxItems\}Queue={Items[],Head,Tail,Length,ItemSize,MaxItems}
其中:
- Items[]Items[]Items[]:存储数据的数组,这是队列的"仓库"
- HeadHeadHead:指向队列头部的指针,表示下一个要取出的数据位置
- TailTailTail:指向队列尾部的指针,表示下一个要插入的数据位置
- LengthLengthLength:当前队列中的数据个数
- ItemSizeItemSizeItemSize:每个数据项的大小(字节数)
- MaxItemsMaxItemsMaxItems:队列能容纳的最大数据项数量
队列的状态可以用以下公式表示:
QueueFull=(Length==MaxItems)QueueFull = (Length == MaxItems)QueueFull=(Length==MaxItems)
QueueEmpty=(Length==0)QueueEmpty = (Length == 0)QueueEmpty=(Length==0)
AvailableSpace=MaxItems−LengthAvailableSpace = MaxItems - LengthAvailableSpace=MaxItems−Length
让我们用一个生动的比喻来理解队列的工作原理:
想象队列是一条传送带,传送带上有固定数量的"托盘"(存储位置)。工人A(生产者任务)把产品放在传送带的一端,工人B(消费者任务)从传送带的另一端取走产品。传送带按照固定方向移动,确保先放上去的产品先被取走。
// 队列的基本结构(简化版)
typedef struct {
void* storage; // 数据存储区域
uint32_t itemSize; // 每个数据项的大小
uint32_t maxItems; // 最大容量
uint32_t head; // 头指针
uint32_t tail; // 尾指针
uint32_t itemCount; // 当前数据项数量
SemaphoreHandle_t sendSemaphore; // 发送信号量
SemaphoreHandle_t receiveSemaphore; // 接收信号量
SemaphoreHandle_t mutex; // 互斥锁
} Queue_t;
数据的存储机制:复制还是引用?
这里有一个很多人容易混淆的概念:FreeRTOS的队列存储的是数据的副本,而不是指针!
这就像邮局寄信一样,你把信件交给邮递员时,邮局会把信件内容完整地复制一份进行传递,而不是仅仅传递信件的地址。这样做的好处是:
数据安全性:发送方在发送数据后可以立即修改或释放原始数据,不会影响队列中的副本。
内存管理简单:不需要担心指针指向的内存被意外释放。
线程安全:每个任务操作的都是独立的数据副本,避免了数据竞争。
但这也意味着:
内存开销:需要为每个数据项分配存储空间。
性能影响:大数据量的复制会消耗更多CPU时间。
对于大数据量的传递,通常的做法是传递指针而不是数据本身:
// 传递大数据的推荐方式
typedef struct {
uint8_t* dataPtr; // 指向实际数据的指针
size_t dataSize; // 数据大小
uint32_t dataId; // 数据标识符
} DataPacket_t;
// 而不是直接传递大数组
typedef struct {
uint8_t bigData[4096]; // 这会导致大量的内存复制
} BigDataPacket_t;
多任务访问:并发世界的交通规则
在多任务环境中,队列就像一个繁忙的十字路口,需要有完善的"交通规则"来确保各个任务能够安全、有序地访问队列。
线程安全的实现原理
FreeRTOS的队列是线程安全的,这意味着多个任务可以同时对同一个队列进行操作而不会出现数据损坏。这是如何实现的呢?
答案就在于临界区保护和原子操作!
// 队列操作的临界区保护(简化版本)
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait) {
// 进入临界区,禁用中断
taskENTER_CRITICAL();
// 检查队列是否已满
if (queueIsFull(xQueue)) {
// 退出临界区
taskEXIT_CRITICAL();
// 如果队列满了且设置了等待时间,则阻塞等待
if (xTicksToWait > 0) {
// 阻塞当前任务,等待队列有空间
vTaskSuspend(NULL);
}
return pdFALSE;
}
// 将数据复制到队列中
copyItemToQueue(xQueue, pvItemToQueue);
// 更新队列状态
updateQueueState(xQueue);
// 退出临界区
taskEXIT_CRITICAL();
// 通知等待的接收任务
notifyWaitingTasks(xQueue);
return pdTRUE;
}
这个过程就像银行的VIP服务:当你需要办理业务时,银行会为你提供一个私密的空间,确保在你办理业务的过程中不会被其他客户打扰。
多生产者多消费者模式
FreeRTOS的队列支持多个生产者(发送方)和多个消费者(接收方)同时操作,这就像一个大型的物流中心:
多生产者场景:
- 多个传感器任务同时向数据处理队列发送数据
- 多个网络连接同时向消息处理队列发送数据包
- 多个用户界面元素同时向事件处理队列发送事件
多消费者场景:
- 多个工作线程从任务队列中获取待处理的任务
- 多个显示任务从同一个数据队列中获取显示内容
- 多个日志处理任务从日志队列中获取日志条目
// 多生产者多消费者的实际应用示例
QueueHandle_t dataProcessingQueue;
// 生产者任务1:温度传感器
void temperatureSensorTask(void *parameter) {
SensorData_t tempData;
while(1) {
tempData.type = SENSOR_TEMPERATURE;
tempData.value = readTemperature();
tempData.timestamp = xTaskGetTickCount();
// 发送到处理队列
xQueueSend(dataProcessingQueue, &tempData, pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// 生产者任务2:湿度传感器
void humiditySensorTask(void *parameter) {
SensorData_t humidityData;
while(1) {
humidityData.type = SENSOR_HUMIDITY;
humidityData.value = readHumidity();
humidityData.timestamp = xTaskGetTickCount();
// 发送到同一个处理队列
xQueueSend(dataProcessingQueue, &humidityData, pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(1500));
}
}
// 消费者任务1:数据处理
void dataProcessingTask(void *parameter) {
SensorData_t receivedData;
while(1) {
// 从队列接收数据
if (xQueueReceive(dataProcessingQueue, &receivedData, portMAX_DELAY) == pdTRUE) {
processData(&receivedData);
}
}
}
// 消费者任务2:数据记录
void dataLoggingTask(void *parameter) {
SensorData_t receivedData;
while(1) {
// 从队列接收数据(使用peek,不移除数据)
if (xQueuePeek(dataProcessingQueue, &receivedData, pdMS_TO_TICKS(1000)) == pdTRUE) {
logData(&receivedData);
}
}
}
出队阻塞:优雅等待的艺术
出队阻塞是队列机制中最精妙的设计之一,它解决了一个经典问题:当队列为空时,消费者任务应该怎么办?
传统方式的问题
在没有阻塞机制的系统中,消费者任务只能采用"忙等待"的方式:
// 低效的忙等待方式
void inefficientConsumer(void *parameter) {
DataItem_t item;
while(1) {
if (queueIsEmpty(myQueue)) {
// 队列为空,继续检查
vTaskDelay(pdMS_TO_TICKS(10)); // 短暂延时后再检查
continue;
}
// 队列不为空,取出数据
getItemFromQueue(myQueue, &item);
processItem(&item);
}
}
这种方式就像一个急性子的顾客,明明知道商店还没开门,却每隔几分钟就去推一次门,既浪费自己的时间,也消耗系统资源。
阻塞机制的优雅解决方案
FreeRTOS的出队阻塞机制就像一个智能的门铃系统:当你按下门铃后,你可以安心地坐在椅子上等待,当主人开门时,门铃会自动通知你。
// 优雅的阻塞等待方式
void efficientConsumer(void *parameter) {
DataItem_t item;
while(1) {
// 阻塞等待,直到队列中有数据
if (xQueueReceive(myQueue, &item, portMAX_DELAY) == pdTRUE) {
processItem(&item);
}
}
}
阻塞的数学模型
出队阻塞的行为可以用以下状态转换模型来描述:
TaskState={Runningif QueueLength>0Blockedif QueueLength=0 and Timeout>0Readyif QueueLength=0 and Timeout=0TaskState = \begin{cases} Running & \text{if } QueueLength > 0 \\ Blocked & \text{if } QueueLength = 0 \text{ and } Timeout > 0 \\ Ready & \text{if } QueueLength = 0 \text{ and } Timeout = 0 \end{cases}TaskState=⎩⎨⎧RunningBlockedReadyif QueueLength>0if QueueLength=0 and Timeout>0if QueueLength=0 and Timeout=0
当任务被阻塞时,它会被加入到队列的等待列表中,按照优先级排序:
WaitingList={Task1,Task2,...,Taskn} sorted by priorityWaitingList = \{Task_1, Task_2, ..., Task_n\} \text{ sorted by priority}WaitingList={Task1,Task2,...,Taskn} sorted by priority
当队列中有新数据时,系统会按照以下算法唤醒等待的任务:
// 唤醒等待任务的算法(简化版)
void wakeupWaitingTasks(QueueHandle_t queue) {
if (queue->waitingToReceive.count > 0) {
// 获取优先级最高的等待任务
TaskHandle_t highestPriorityTask = getHighestPriorityWaitingTask(&queue->waitingToReceive);
// 将任务从等待列表移除
removeFromWaitingList(&queue->waitingToReceive, highestPriorityTask);
// 唤醒任务
vTaskResume(highestPriorityTask);
// 如果被唤醒的任务优先级更高,立即进行任务切换
if (getTaskPriority(highestPriorityTask) > getCurrentTaskPriority()) {
taskYIELD();
}
}
}
超时机制:时间就是金钱
超时机制让阻塞变得更加灵活,就像给等待设置了一个闹钟:
// 不同的超时策略
void timeoutExamples(void *parameter) {
DataItem_t item;
while(1) {
// 策略1:无限等待(直到有数据为止)
if (xQueueReceive(myQueue, &item, portMAX_DELAY) == pdTRUE) {
Serial.println("收到数据,无限等待模式");
}
// 策略2:立即返回(不等待)
if (xQueueReceive(myQueue, &item, 0) == pdTRUE) {
Serial.println("收到数据,非阻塞模式");
} else {
Serial.println("队列为空,立即返回");
}
// 策略3:等待指定时间
if (xQueueReceive(myQueue, &item, pdMS_TO_TICKS(1000)) == pdTRUE) {
Serial.println("收到数据,1秒超时模式");
} else {
Serial.println("等待超时,执行其他任务");
doOtherWork();
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
超时时间的选择需要根据应用场景来决定:
实时系统:通常使用较短的超时时间,确保系统响应性
批处理系统:可以使用较长的超时时间,提高处理效率
交互系统:需要平衡响应性和效率,使用中等超时时间
入队阻塞:生产者的耐心等待
入队阻塞处理的是另一个经典问题:当队列满了的时候,生产者任务应该怎么办?
队列满载的困境
想象一个繁忙的停车场,当所有停车位都被占满时,新来的车辆该怎么办?他们有几种选择:
- 直接离开:相当于非阻塞模式,立即返回失败
- 在门口等待:相当于阻塞模式,等待有空位
- 等一会儿就走:相当于超时模式,等待一段时间后放弃
FreeRTOS的入队阻塞机制为生产者提供了这些灵活的选择:
// 不同的入队策略
void producerStrategies(void *parameter) {
DataItem_t item;
while(1) {
// 生成数据
generateData(&item);
// 策略1:非阻塞发送
if (xQueueSend(myQueue, &item, 0) == pdTRUE) {
Serial.println("数据发送成功");
} else {
Serial.println("队列已满,数据丢弃");
handleDataLoss(&item);
}
// 策略2:阻塞发送(等待队列有空间)
if (xQueueSend(myQueue, &item, portMAX_DELAY) == pdTRUE) {
Serial.println("数据发送成功(阻塞模式)");
}
// 策略3:超时发送
if (xQueueSend(myQueue, &item, pdMS_TO_TICKS(500)) == pdTRUE) {
Serial.println("数据发送成功(超时模式)");
} else {
Serial.println("发送超时,执行备用方案");
handleSendTimeout(&item);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
队列满载处理策略
当队列满载时,不同的应用场景需要不同的处理策略:
数据采集系统:
- 高频数据:丢弃旧数据,保留新数据
- 关键数据:阻塞等待,确保数据不丢失
- 普通数据:超时后记录日志,继续采集
网络通信系统:
- 实时数据:立即丢弃过时数据
- 重要消息:重试发送机制
- 批量数据:缓存到文件系统
用户界面系统:
- 用户输入:短时间阻塞,超时后提示用户
- 动画数据:丢弃过时帧,保持流畅性
- 状态更新:覆盖旧状态,保持最新
// 智能的队列满载处理
BaseType_t smartQueueSend(QueueHandle_t queue, void* item, DataPriority_t priority) {
switch(priority) {
case PRIORITY_CRITICAL:
// 关键数据:无限等待
return xQueueSend(queue, item, portMAX_DELAY);
case PRIORITY_HIGH:
// 高优先级:等待较长时间
if (xQueueSend(queue, item, pdMS_TO_TICKS(1000)) != pdTRUE) {
// 如果还是失败,尝试覆盖最旧的数据
if (uxQueueMessagesWaiting(queue) > 0) {
void* oldItem;
xQueueReceive(queue, &oldItem, 0); // 移除最旧的数据
return xQueueSend(queue, item, 0); // 立即发送新数据
}
}
return pdTRUE;
case PRIORITY_NORMAL:
// 普通优先级:短时间等待
return xQueueSend(queue, item, pdMS_TO_TICKS(100));
case PRIORITY_LOW:
// 低优先级:立即返回
return xQueueSend(queue, item, 0);
default:
return pdFALSE;
}
}
流量控制机制
在高速数据流的场景中,入队阻塞还可以作为一种天然的流量控制机制:
// 自适应流量控制
void adaptiveProducer(void *parameter) {
DataItem_t item;
uint32_t successCount = 0;
uint32_t failCount = 0;
TickType_t adaptiveDelay = pdMS_TO_TICKS(10); // 初始延时10ms
while(1) {
generateData(&item);
// 尝试发送数据
if (xQueueSend(myQueue, &item, pdMS_TO_TICKS(100)) == pdTRUE) {
successCount++;
// 发送成功率高,可以加快生产速度
if (successCount > 10 && adaptiveDelay > pdMS_TO_TICKS(1)) {
adaptiveDelay -= pdMS_TO_TICKS(1);
successCount = 0;
}
} else {
failCount++;
// 发送失败率高,需要减慢生产速度
if (failCount > 5) {
adaptiveDelay += pdMS_TO_TICKS(5);
failCount = 0;
}
}
vTaskDelay(adaptiveDelay);
}
}
这种机制就像高速公路上的自适应巡航控制,根据前方交通状况自动调整车速,既保证了效率,又避免了拥堵。
消息队列常用函数详解:程序员的工具箱
FreeRTOS提供了丰富的队列操作函数,就像一个功能齐全的工具箱,每个工具都有其特定的用途和使用场景。
队列创建与删除:生命周期管理
xQueueCreate() - 队列的诞生
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize);
这个函数就像建造一个仓库,你需要指定仓库能存放多少货物(uxQueueLength)以及每件货物的大小(uxItemSize)。
// 创建不同类型的队列
void createQueues() {
// 创建整数队列
QueueHandle_t intQueue = xQueueCreate(10, sizeof(int));
if (intQueue == NULL) {
Serial.println("整数队列创建失败!");
return;
}
// 创建结构体队列
typedef struct {
float temperature;
float humidity;
uint32_t timestamp;
} SensorData_t;
QueueHandle_t sensorQueue = xQueueCreate(5, sizeof(SensorData_t));
if (sensorQueue == NULL) {
Serial.println("传感器队列创建失败!");
return;
}
// 创建指针队列(用于传递大数据)
QueueHandle_t pointerQueue = xQueueCreate(20, sizeof(void*));
if (pointerQueue == NULL) {
Serial.println("指针队列创建失败!");
return;
}
Serial.println("所有队列创建成功!");
}
队列创建时的内存分配计算公式:
MemoryRequired=QueueLength×ItemSize+QueueOverheadMemoryRequired = QueueLength \times ItemSize + QueueOverheadMemoryRequired=QueueLength×ItemSize+QueueOverhead
其中QueueOverhead包括队列控制结构、信号量、互斥锁等开销。
vQueueDelete() - 队列的终结
void vQueueDelete(QueueHandle_t xQueue);
删除队列时需要特别小心,就像拆除建筑物一样,必须确保没有任务正在使用它:
// 安全的队列删除
void safeQueueDelete(QueueHandle_t* queue) {
if (*queue != NULL) {
// 检查队列是否还有数据
UBaseType_t itemsWaiting = uxQueueMessagesWaiting(*queue);
if (itemsWaiting > 0) {
Serial.printf("警告:队列中还有 %u 个未处理的数据项\n", itemsWaiting);
}
// 删除队列
vQueueDelete(*queue);
*queue = NULL;
Serial.println("队列已安全删除");
}
}
数据发送函数:生产者的武器库
xQueueSend() - 标准发送
BaseType_t xQueueSend(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
这是最常用的发送函数,数据会被添加到队列的尾部:
// 标准发送示例
void standardSendExample() {
QueueHandle_t myQueue = xQueueCreate(10, sizeof(int));
int data = 42;
// 立即发送(非阻塞)
if (xQueueSend(myQueue, &data, 0) == pdTRUE) {
Serial.println("数据发送成功");
} else {
Serial.println("队列已满,发送失败");
}
// 等待1秒发送
if (xQueueSend(myQueue, &data, pdMS_TO_TICKS(1000)) == pdTRUE) {
Serial.println("数据发送成功(1秒超时)");
} else {
Serial.println("发送超时");
}
// 无限等待发送
xQueueSend(myQueue, &data, portMAX_DELAY);
Serial.println("数据发送成功(无限等待)");
}
xQueueSendToFront() - 插队发送
BaseType_t xQueueSendToFront(QueueHandle_t xQueue, const void *pvItemToQueue, TickType_t xTicksToWait);
这个函数允许数据"插队",被添加到队列的头部,就像VIP客户可以优先办理业务:
// 紧急数据处理示例
void emergencyDataHandling() {
typedef struct {
uint8_t priority;
char message[50];
} Message_t;
QueueHandle_t messageQueue = xQueueCreate(10, sizeof(Message_t));
Message_t normalMsg = {1, "Normal message"};
Message_t urgentMsg = {9, "URGENT: System error detected!"};
// 发送普通消息
xQueueSend(messageQueue, &normalMsg, 0);
// 发送紧急消息到队列头部
xQueueSendToFront(messageQueue, &urgentMsg, 0);
// 紧急消息会被优先处理
}
xQueueSendFromISR() - 中断中的发送
BaseType_t xQueueSendFromISR(QueueHandle_t xQueue, const void *pvItemToQueue, BaseType_t *pxHigherPriorityTaskWoken);
这是专门为中断服务程序设计的发送函数,就像急救车的专用通道:
// 中断服务程序示例
QueueHandle_t interruptQueue;
void IRAM_ATTR buttonISR() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint32_t buttonState = digitalRead(BUTTON_PIN);
// 在中断中发送数据
xQueueSendFromISR(interruptQueue, &buttonState, &xHigherPriorityTaskWoken);
// 如果有更高优先级的任务被唤醒,立即进行任务切换
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 按键处理任务
void buttonHandlerTask(void *parameter) {
uint32_t buttonState;
while(1) {
if (xQueueReceive(interruptQueue, &buttonState, portMAX_DELAY) == pdTRUE) {
Serial.printf("按键状态变化: %u\n", buttonState);
handleButtonPress(buttonState);
}
}
}
数据接收函数:消费者的工具集
xQueueReceive() - 标准接收
BaseType_t xQueueReceive(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
这是最常用的接收函数,会从队列头部取出数据并将其从队列中移除:
// 标准接收示例
void standardReceiveExample() {
QueueHandle_t myQueue = xQueueCreate(10, sizeof(int));
int receivedData;
// 非阻塞接收
if (xQueueReceive(myQueue, &receivedData, 0) == pdTRUE) {
Serial.printf("接收到数据: %d\n", receivedData);
} else {
Serial.println("队列为空");
}
// 阻塞接收
if (xQueueReceive(myQueue, &receivedData, portMAX_DELAY) == pdTRUE) {
Serial.printf("接收到数据: %d\n", receivedData);
processData(receivedData);
}
}
xQueuePeek() - 窥视数据
BaseType_t xQueuePeek(QueueHandle_t xQueue, void *pvBuffer, TickType_t xTicksToWait);
这个函数允许你"偷看"队列中的数据而不将其移除,就像透过窗户看店里的商品:
// 数据预览和处理
void dataPreviewExample() {
typedef struct {
uint8_t dataType;
float value;
uint32_t timestamp;
} DataPacket_t;
QueueHandle_t dataQueue = xQueueCreate(10, sizeof(DataPacket_t));
DataPacket_t packet;
// 先预览数据,决定是否处理
if (xQueuePeek(dataQueue, &packet, pdMS_TO_TICKS(100)) == pdTRUE) {
Serial.printf("预览数据类型: %u, 值: %.2f\n", packet.dataType, packet.value);
// 根据数据类型决定是否处理
if (packet.dataType == CRITICAL_DATA) {
// 处理关键数据,从队列中移除
xQueueReceive(dataQueue, &packet, 0);
processCriticalData(&packet);
} else if (packet.dataType == NORMAL_DATA) {
// 处理普通数据
xQueueReceive(dataQueue, &packet, 0);
processNormalData(&packet);
}
// 如果是其他类型,数据仍保留在队列中
}
}
xQueueReceiveFromISR() - 中断中的接收
BaseType_t xQueueReceiveFromISR(QueueHandle_t xQueue, void *pvBuffer, BaseType_t *pxHigherPriorityTaskWoken);
这个函数允许在中断服务程序中接收队列数据:
// 定时器中断处理示例
QueueHandle_t timerQueue;
void IRAM_ATTR timerISR() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint32_t timerValue;
// 在中断中接收数据
if (xQueueReceiveFromISR(timerQueue, &timerValue, &xHigherPriorityTaskWoken) == pdTRUE) {
// 处理定时器数据
handleTimerValue(timerValue);
}
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
队列状态查询函数:系统的体检工具
uxQueueMessagesWaiting() - 队列中的数据数量
UBaseType_t uxQueueMessagesWaiting(const QueueHandle_t xQueue);
这个函数告诉你队列中当前有多少个数据项,就像查看仓库的库存:
// 队列状态监控
void queueStatusMonitor() {
QueueHandle_t myQueue = xQueueCreate(10, sizeof(int));
// 检查队列状态
UBaseType_t itemsWaiting = uxQueueMessagesWaiting(myQueue);
UBaseType_t spacesAvailable = uxQueueSpacesAvailable(myQueue);
Serial.printf("队列状态 - 已用: %u, 可用: %u\n", itemsWaiting, spacesAvailable);
// 根据队列状态采取不同策略
if (itemsWaiting == 0) {
Serial.println("队列为空,生产者可以加速生产");
} else if (spacesAvailable == 0) {
Serial.println("队列已满,消费者需要加速处理");
} else if (itemsWaiting > 7) {
Serial.println("队列接近满载,需要注意");
}
}
uxQueueSpacesAvailable() - 队列中的可用空间
UBaseType_t uxQueueSpacesAvailable(const QueueHandle_t xQueue);
这个函数告诉你队列还能容纳多少个数据项:
// 智能的数据发送策略
void intelligentSender(void *parameter) {
QueueHandle_t myQueue = xQueueCreate(10, sizeof(int));
int data = 0;
while(1) {
UBaseType_t availableSpace = uxQueueSpacesAvailable(myQueue);
if (availableSpace > 5) {
// 队列空间充足,可以批量发送
for(int i = 0; i < 3 && availableSpace > 0; i++) {
xQueueSend(myQueue, &data, 0);
data++;
availableSpace--;
}
Serial.println("批量发送完成");
} else if (availableSpace > 0) {
// 队列空间紧张,单个发送
xQueueSend(myQueue, &data, pdMS_TO_TICKS(100));
data++;
Serial.println("单个发送完成");
} else {
// 队列已满,等待空间
Serial.println("队列已满,等待空间");
vTaskDelay(pdMS_TO_TICKS(500));
}
vTaskDelay(pdMS_TO_TICKS(200));
}
}
高级队列函数:专业工具
xQueueOverwrite() - 覆盖发送
BaseType_t xQueueOverwrite(QueueHandle_t xQueue, const void *pvItemToQueue);
这个函数专门用于长度为1的队列,新数据会覆盖旧数据,就像邮箱里的最新邮件会覆盖旧邮件:
// 状态更新队列示例
void statusUpdateExample() {
// 创建长度为1的队列,用于状态更新
QueueHandle_t statusQueue = xQueueCreate(1, sizeof(SystemStatus_t));
SystemStatus_t currentStatus;
// 状态更新任务
while(1) {
updateSystemStatus(¤tStatus);
// 覆盖发送,总是保持最新状态
xQueueOverwrite(statusQueue, ¤tStatus);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
xQueueReset() - 队列重置
BaseType_t xQueueReset(QueueHandle_t xQueue);
这个函数会清空队列中的所有数据,就像清空垃圾桶:
// 队列重置示例
void queueResetExample() {
QueueHandle_t myQueue = xQueueCreate(10, sizeof(int));
// 发送一些数据
for(int i = 0; i < 5; i++) {
xQueueSend(myQueue, &i, 0);
}
Serial.printf("重置前队列中有 %u 个数据\n", uxQueueMessagesWaiting(myQueue));
// 重置队列
xQueueReset(myQueue);
Serial.printf("重置后队列中有 %u 个数据\n", uxQueueMessagesWaiting(myQueue));
}
实战代码:构建一个完整的数据采集与处理系统
理论学得再多,不如动手实践一次!让我们构建一个完整的多传感器数据采集与处理系统,这个系统将展示消息队列在实际项目中的强大威力。
系统架构设计
我们的系统包含以下组件:
- 多个传感器任务:采集温度、湿度、光照、声音等数据
- 数据预处理任务:对原始数据进行滤波和校准
- 数据分析任务:计算统计信息和趋势
- 存储任务:将数据保存到SD卡
- 网络传输任务:将数据上传到云端
- 显示任务:在OLED屏幕上显示实时数据
- 报警任务:检测异常情况并发出警报
系统使用多个队列来连接这些任务:
#include <Arduino.h>
#include <WiFi.h>
#include <DHT.h>
#include <ArduinoJson.h>
#include <SD.h>
#include <SPI.h>
// 硬件引脚定义
#define DHT_PIN 4
#define LIGHT_SENSOR_PIN 34
#define SOUND_SENSOR_PIN 35
#define BUZZER_PIN 2
#define LED_PIN 5
#define SD_CS_PIN 13
// 传感器对象
DHT dht(DHT_PIN, DHT22);
// 数据结构定义
typedef enum {
SENSOR_TEMPERATURE,
SENSOR_HUMIDITY,
SENSOR_LIGHT,
SENSOR_SOUND
} SensorType_t;
typedef struct {
SensorType_t type;
float value;
uint32_t timestamp;
uint8_t sensorId;
uint8_t quality; // 数据质量评分 0-100
} RawSensorData_t;
typedef struct {
SensorType_t type;
float value;
float filteredValue;
float trend; // 变化趋势
uint32_t timestamp;
uint8_t confidence; // 置信度
} ProcessedSensorData_t;
typedef struct {
float avgTemperature;
float avgHumidity;
float avgLight;
float avgSound;
float tempTrend;
float humidityTrend;
uint32_t sampleCount;
uint32_t timestamp;
} AnalysisResult_t;
typedef struct {
uint8_t alertLevel; // 0-3: 信息、警告、严重、紧急
char message[100];
SensorType_t relatedSensor;
float triggerValue;
uint32_t timestamp;
} AlertData_t;
typedef struct {
char filename[50];
void* data;
size_t dataSize;
uint8_t priority;
} StorageRequest_t;
// 队列句柄
QueueHandle_t rawDataQueue;
QueueHandle_t processedDataQueue;
QueueHandle_t analysisQueue;
QueueHandle_t storageQueue;
QueueHandle_t networkQueue;
QueueHandle_t displayQueue;
QueueHandle_t alertQueue;
// 任务句柄
TaskHandle_t temperatureTaskHandle;
TaskHandle_t humidityTaskHandle;
TaskHandle_t lightTaskHandle;
TaskHandle_t soundTaskHandle;
TaskHandle_t preprocessTaskHandle;
TaskHandle_t analysisTaskHandle;
TaskHandle_t storageTaskHandle;
TaskHandle_t networkTaskHandle;
TaskHandle
_t displayTaskHandle;
TaskHandle_t alertTaskHandle;
TaskHandle_t systemMonitorTaskHandle;
// 全局配置
typedef struct {
float tempThresholdHigh;
float tempThresholdLow;
float humidityThresholdHigh;
float lightThresholdLow;
float soundThresholdHigh;
uint32_t samplingInterval;
bool alertEnabled;
bool networkEnabled;
bool storageEnabled;
} SystemConfig_t;
SystemConfig_t systemConfig = {
.tempThresholdHigh = 35.0f,
.tempThresholdLow = 10.0f,
.humidityThresholdHigh = 80.0f,
.lightThresholdLow = 100.0f,
.soundThresholdHigh = 70.0f,
.samplingInterval = 1000,
.alertEnabled = true,
.networkEnabled = true,
.storageEnabled = true
};
// 温度传感器任务
void temperatureSensorTask(void *parameter) {
RawSensorData_t sensorData;
TickType_t xLastWakeTime = xTaskGetTickCount();
uint32_t errorCount = 0;
Serial.println("温度传感器任务启动 - 开始监测环境温度");
while(1) {
float temperature = dht.readTemperature();
if (!isnan(temperature)) {
sensorData.type = SENSOR_TEMPERATURE;
sensorData.value = temperature;
sensorData.timestamp = millis();
sensorData.sensorId = 1;
// 数据质量评估
if (temperature >= -40.0f && temperature <= 80.0f) {
sensorData.quality = 100; // 正常范围
} else if (temperature >= -50.0f && temperature <= 90.0f) {
sensorData.quality = 70; // 可疑范围
} else {
sensorData.quality = 30; // 异常范围
}
// 发送到原始数据队列
if (xQueueSend(rawDataQueue, &sensorData, pdMS_TO_TICKS(100)) == pdTRUE) {
Serial.printf("温度数据采集成功: %.1f°C (质量: %u%%)\n",
temperature, sensorData.quality);
errorCount = 0;
} else {
Serial.println("警告:原始数据队列已满,温度数据丢失!");
}
} else {
errorCount++;
Serial.printf("温度传感器读取失败,连续错误次数: %u\n", errorCount);
if (errorCount > 5) {
// 发送传感器故障警报
AlertData_t alert;
alert.alertLevel = 2; // 严重
strcpy(alert.message, "温度传感器连续读取失败");
alert.relatedSensor = SENSOR_TEMPERATURE;
alert.triggerValue = 0;
alert.timestamp = millis();
xQueueSend(alertQueue, &alert, 0);
errorCount = 0; // 重置计数器,避免重复报警
}
}
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(systemConfig.samplingInterval));
}
}
// 湿度传感器任务
void humiditySensorTask(void *parameter) {
RawSensorData_t sensorData;
TickType_t xLastWakeTime = xTaskGetTickCount();
Serial.println("湿度传感器任务启动 - 开始监测环境湿度");
while(1) {
float humidity = dht.readHumidity();
if (!isnan(humidity)) {
sensorData.type = SENSOR_HUMIDITY;
sensorData.value = humidity;
sensorData.timestamp = millis();
sensorData.sensorId = 2;
// 湿度数据质量评估
if (humidity >= 0.0f && humidity <= 100.0f) {
sensorData.quality = 100;
} else {
sensorData.quality = 0; // 湿度超出物理范围,数据无效
}
if (xQueueSend(rawDataQueue, &sensorData, pdMS_TO_TICKS(100)) == pdTRUE) {
Serial.printf("湿度数据采集成功: %.1f%% (质量: %u%%)\n",
humidity, sensorData.quality);
}
}
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(systemConfig.samplingInterval + 200));
}
}
// 光照传感器任务
void lightSensorTask(void *parameter) {
RawSensorData_t sensorData;
TickType_t xLastWakeTime = xTaskGetTickCount();
Serial.println("光照传感器任务启动 - 开始监测环境光照");
while(1) {
uint16_t lightRaw = analogRead(LIGHT_SENSOR_PIN);
// 将ADC值转换为勒克斯值(简化转换)
float lightLux = (float)lightRaw * 3.3f / 4095.0f * 1000.0f;
sensorData.type = SENSOR_LIGHT;
sensorData.value = lightLux;
sensorData.timestamp = millis();
sensorData.sensorId = 3;
sensorData.quality = 95; // 光照传感器通常比较稳定
if (xQueueSend(rawDataQueue, &sensorData, pdMS_TO_TICKS(50)) == pdTRUE) {
Serial.printf("光照数据采集成功: %.1f lux\n", lightLux);
}
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(systemConfig.samplingInterval + 500));
}
}
// 声音传感器任务
void soundSensorTask(void *parameter) {
RawSensorData_t sensorData;
TickType_t xLastWakeTime = xTaskGetTickCount();
Serial.println("声音传感器任务启动 - 开始监测环境噪音");
while(1) {
// 采样多次求平均值,提高声音测量精度
uint32_t soundSum = 0;
for(int i = 0; i < 10; i++) {
soundSum += analogRead(SOUND_SENSOR_PIN);
vTaskDelay(pdMS_TO_TICKS(5));
}
uint16_t soundAvg = soundSum / 10;
// 简化的分贝转换
float soundDb = (float)soundAvg * 3.3f / 4095.0f * 100.0f;
sensorData.type = SENSOR_SOUND;
sensorData.value = soundDb;
sensorData.timestamp = millis();
sensorData.sensorId = 4;
sensorData.quality = 85; // 声音传感器精度相对较低
if (xQueueSend(rawDataQueue, &sensorData, pdMS_TO_TICKS(50)) == pdTRUE) {
Serial.printf("声音数据采集成功: %.1f dB\n", soundDb);
}
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(systemConfig.samplingInterval + 300));
}
}
// 数据预处理任务
void dataPreprocessTask(void *parameter) {
RawSensorData_t rawData;
ProcessedSensorData_t processedData;
// 滑动平均滤波器
const int FILTER_SIZE = 5;
float tempBuffer[FILTER_SIZE] = {0};
float humidityBuffer[FILTER_SIZE] = {0};
float lightBuffer[FILTER_SIZE] = {0};
float soundBuffer[FILTER_SIZE] = {0};
int bufferIndex = 0;
bool bufferFull = false;
// 历史数据用于趋势分析
float lastTemp = 0, lastHumidity = 0, lastLight = 0, lastSound = 0;
Serial.println("数据预处理任务启动 - 准备对原始数据进行滤波和校准");
while(1) {
if (xQueueReceive(rawDataQueue, &rawData, portMAX_DELAY) == pdTRUE) {
// 数据质量检查
if (rawData.quality < 50) {
Serial.printf("数据质量过低,跳过处理: 传感器%u, 质量%u%%\n",
rawData.sensorId, rawData.quality);
continue;
}
processedData.type = rawData.type;
processedData.value = rawData.value;
processedData.timestamp = rawData.timestamp;
// 根据传感器类型进行不同的处理
switch(rawData.type) {
case SENSOR_TEMPERATURE:
tempBuffer[bufferIndex] = rawData.value;
if (bufferFull) {
float sum = 0;
for(int i = 0; i < FILTER_SIZE; i++) {
sum += tempBuffer[i];
}
processedData.filteredValue = sum / FILTER_SIZE;
processedData.trend = processedData.filteredValue - lastTemp;
lastTemp = processedData.filteredValue;
processedData.confidence = 90;
} else {
processedData.filteredValue = rawData.value;
processedData.trend = 0;
processedData.confidence = 60;
}
break;
case SENSOR_HUMIDITY:
humidityBuffer[bufferIndex] = rawData.value;
if (bufferFull) {
float sum = 0;
for(int i = 0; i < FILTER_SIZE; i++) {
sum += humidityBuffer[i];
}
processedData.filteredValue = sum / FILTER_SIZE;
processedData.trend = processedData.filteredValue - lastHumidity;
lastHumidity = processedData.filteredValue;
processedData.confidence = 85;
} else {
processedData.filteredValue = rawData.value;
processedData.trend = 0;
processedData.confidence = 50;
}
break;
case SENSOR_LIGHT:
lightBuffer[bufferIndex] = rawData.value;
if (bufferFull) {
float sum = 0;
for(int i = 0; i < FILTER_SIZE; i++) {
sum += lightBuffer[i];
}
processedData.filteredValue = sum / FILTER_SIZE;
processedData.trend = processedData.filteredValue - lastLight;
lastLight = processedData.filteredValue;
processedData.confidence = 95;
} else {
processedData.filteredValue = rawData.value;
processedData.trend = 0;
processedData.confidence = 70;
}
break;
case SENSOR_SOUND:
soundBuffer[bufferIndex] = rawData.value;
if (bufferFull) {
float sum = 0;
for(int i = 0; i < FILTER_SIZE; i++) {
sum += soundBuffer[i];
}
processedData.filteredValue = sum / FILTER_SIZE;
processedData.trend = processedData.filteredValue - lastSound;
lastSound = processedData.filteredValue;
processedData.confidence = 75;
} else {
processedData.filteredValue = rawData.value;
processedData.trend = 0;
processedData.confidence = 40;
}
break;
}
// 更新缓冲区索引
bufferIndex = (bufferIndex + 1) % FILTER_SIZE;
if (bufferIndex == 0) {
bufferFull = true;
}
// 发送处理后的数据
if (xQueueSend(processedDataQueue, &processedData, pdMS_TO_TICKS(100)) == pdTRUE) {
Serial.printf("数据预处理完成: 类型%d, 原值%.2f, 滤波值%.2f, 趋势%.2f, 置信度%u%%\n",
processedData.type, processedData.value,
processedData.filteredValue, processedData.trend, processedData.confidence);
}
// 异常检测和报警
if (systemConfig.alertEnabled) {
AlertData_t alert;
bool shouldAlert = false;
switch(processedData.type) {
case SENSOR_TEMPERATURE:
if (processedData.filteredValue > systemConfig.tempThresholdHigh) {
alert.alertLevel = 1;
sprintf(alert.message, "温度过高: %.1f°C", processedData.filteredValue);
shouldAlert = true;
} else if (processedData.filteredValue < systemConfig.tempThresholdLow) {
alert.alertLevel = 1;
sprintf(alert.message, "温度过低: %.1f°C", processedData.filteredValue);
shouldAlert = true;
}
break;
case SENSOR_HUMIDITY:
if (processedData.filteredValue > systemConfig.humidityThresholdHigh) {
alert.alertLevel = 1;
sprintf(alert.message, "湿度过高: %.1f%%", processedData.filteredValue);
shouldAlert = true;
}
break;
case SENSOR_LIGHT:
if (processedData.filteredValue < systemConfig.lightThresholdLow) {
alert.alertLevel = 0;
sprintf(alert.message, "光照不足: %.1f lux", processedData.filteredValue);
shouldAlert = true;
}
break;
case SENSOR_SOUND:
if (processedData.filteredValue > systemConfig.soundThresholdHigh) {
alert.alertLevel = 1;
sprintf(alert.message, "噪音过大: %.1f dB", processedData.filteredValue);
shouldAlert = true;
}
break;
}
if (shouldAlert) {
alert.relatedSensor = processedData.type;
alert.triggerValue = processedData.filteredValue;
alert.timestamp = millis();
xQueueSend(alertQueue, &alert, 0);
}
}
}
}
}
// 数据分析任务
void dataAnalysisTask(void *parameter) {
ProcessedSensorData_t processedData;
AnalysisResult_t analysisResult = {0};
uint32_t tempCount = 0, humidityCount = 0, lightCount = 0, soundCount = 0;
float tempSum = 0, humiditySum = 0, lightSum = 0, soundSum = 0;
uint32_t lastAnalysisTime = 0;
const uint32_t ANALYSIS_INTERVAL = 30000; // 30秒分析一次
Serial.println("数据分析任务启动 - 准备进行统计分析和趋势预测");
while(1) {
if (xQueueReceive(processedDataQueue, &processedData, pdMS_TO_TICKS(1000)) == pdTRUE) {
// 累积数据用于统计分析
switch(processedData.type) {
case SENSOR_TEMPERATURE:
if (processedData.confidence > 70) {
tempSum += processedData.filteredValue;
tempCount++;
analysisResult.tempTrend = processedData.trend;
}
break;
case SENSOR_HUMIDITY:
if (processedData.confidence > 70) {
humiditySum += processedData.filteredValue;
humidityCount++;
analysisResult.humidityTrend = processedData.trend;
}
break;
case SENSOR_LIGHT:
if (processedData.confidence > 70) {
lightSum += processedData.filteredValue;
lightCount++;
}
break;
case SENSOR_SOUND:
if (processedData.confidence > 70) {
soundSum += processedData.filteredValue;
soundCount++;
}
break;
}
// 定期生成分析报告
uint32_t currentTime = millis();
if (currentTime - lastAnalysisTime > ANALYSIS_INTERVAL) {
// 计算平均值
analysisResult.avgTemperature = tempCount > 0 ? tempSum / tempCount : 0;
analysisResult.avgHumidity = humidityCount > 0 ? humiditySum / humidityCount : 0;
analysisResult.avgLight = lightCount > 0 ? lightSum / lightCount : 0;
analysisResult.avgSound = soundCount > 0 ? soundSum / soundCount : 0;
analysisResult.sampleCount = tempCount + humidityCount + lightCount + soundCount;
analysisResult.timestamp = currentTime;
// 发送分析结果
if (xQueueSend(analysisQueue, &analysisResult, pdMS_TO_TICKS(100)) == pdTRUE) {
Serial.println("=== 数据分析报告 ===");
Serial.printf("时间段: %u ms\n", ANALYSIS_INTERVAL);
Serial.printf("平均温度: %.1f°C (趋势: %+.2f)\n",
analysisResult.avgTemperature, analysisResult.tempTrend);
Serial.printf("平均湿度: %.1f%% (趋势: %+.2f)\n",
analysisResult.avgHumidity, analysisResult.humidityTrend);
Serial.printf("平均光照: %.1f lux\n", analysisResult.avgLight);
Serial.printf("平均声音: %.1f dB\n", analysisResult.avgSound);
Serial.printf("总样本数: %u\n", analysisResult.sampleCount);
Serial.println("==================");
}
// 重置统计数据
tempSum = humiditySum = lightSum = soundSum = 0;
tempCount = humidityCount = lightCount = soundCount = 0;
lastAnalysisTime = currentTime;
}
}
}
}
// 存储任务
void storageTask(void *parameter) {
StorageRequest_t storageRequest;
File dataFile;
Serial.println("存储任务启动 - 准备将数据保存到SD卡");
// 初始化SD卡
if (!SD.begin(SD_CS_PIN)) {
Serial.println("SD卡初始化失败!");
// 发送存储系统故障警报
AlertData_t alert;
alert.alertLevel = 2;
strcpy(alert.message, "SD卡初始化失败,无法存储数据");
alert.relatedSensor = SENSOR_TEMPERATURE; // 影响所有传感器
alert.timestamp = millis();
xQueueSend(alertQueue, &alert, 0);
// 任务暂停,等待手动重启
vTaskSuspend(NULL);
}
while(1) {
if (xQueueReceive(storageQueue, &storageRequest, portMAX_DELAY) == pdTRUE) {
// 根据优先级决定存储策略
const char* mode = (storageRequest.priority > 5) ? "a" : "a"; // 都是追加模式
dataFile = SD.open(storageRequest.filename, FILE_APPEND);
if (dataFile) {
// 写入时间戳
dataFile.printf("[%u] ", millis());
// 写入数据
dataFile.write((uint8_t*)storageRequest.data, storageRequest.dataSize);
dataFile.println(); // 换行
dataFile.close();
Serial.printf("数据已保存到文件: %s, 大小: %u bytes\n",
storageRequest.filename, storageRequest.dataSize);
// 释放数据内存
free(storageRequest.data);
} else {
Serial.printf("无法打开文件: %s\n", storageRequest.filename);
free(storageRequest.data);
}
}
}
}
// 网络传输任务
void networkTransmissionTask(void *parameter) {
AnalysisResult_t analysisData;
WiFiClient client;
Serial.println("网络传输任务启动 - 准备连接WiFi和云端服务");
// WiFi连接
WiFi.begin("YourWiFiSSID", "YourWiFiPassword");
int wifiRetries = 0;
while (WiFi.status() != WL_CONNECTED && wifiRetries < 20) {
Serial.print(".");
vTaskDelay(pdMS_TO_TICKS(500));
wifiRetries++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("\nWiFi连接成功!IP地址: %s\n", WiFi.localIP().toString().c_str());
} else {
Serial.println("\nWiFi连接失败,网络功能将被禁用");
systemConfig.networkEnabled = false;
}
while(1) {
if (!systemConfig.networkEnabled) {
vTaskDelay(pdMS_TO_TICKS(10000)); // 网络禁用时,每10秒检查一次
continue;
}
if (xQueueReceive(networkQueue, &analysisData, pdMS_TO_TICKS(5000)) == pdTRUE) {
// 创建JSON数据包
DynamicJsonDocument doc(1024);
doc["timestamp"] = analysisData.timestamp;
doc["device_id"] = "ESP32_Sensor_Node_001";
doc["data"]["temperature"]["avg"] = analysisData.avgTemperature;
doc["data"]["temperature"]["trend"] = analysisResult.tempTrend;
doc["data"]["humidity"]["avg"] = analysisData.avgHumidity;
doc["data"]["humidity"]["trend"] = analysisResult.humidityTrend;
doc["data"]["light"]["avg"] = analysisData.avgLight;
doc["data"]["sound"]["avg"] = analysisData.avgSound;
doc["data"]["sample_count"] = analysisData.sampleCount;
String jsonString;
serializeJson(doc, jsonString);
Serial.println("准备上传数据到云端:");
Serial.println(jsonString);
// 模拟HTTP POST请求(实际项目中需要实现真正的HTTP客户端)
if (WiFi.status() == WL_CONNECTED) {
// 这里应该是真正的HTTP POST代码
// 为了演示,我们只是打印数据
Serial.println("数据上传成功!");
// 如果需要,可以将上传状态保存到存储队列
if (systemConfig.storageEnabled) {
StorageRequest_t* storageReq = (StorageRequest_t*)malloc(sizeof(StorageRequest_t));
strcpy(storageReq->filename, "/upload_log.txt");
storageReq->data = malloc(jsonString.length() + 1);
strcpy((char*)storageReq->data, jsonString.c_str());
storageReq->dataSize = jsonString.length();
storageReq->priority = 3;
xQueueSend(storageQueue, storageReq, 0);
free(storageReq);
}
} else {
Serial.println("WiFi连接丢失,数据上传失败");
// 尝试重连WiFi
WiFi.reconnect();
}
}
}
}
// 显示任务
void displayTask(void *parameter) {
AnalysisResult_t displayData;
uint32_t lastUpdateTime = 0;
const uint32_t UPDATE_INTERVAL = 2000; // 2秒更新一次显示
Serial.println("显示任务启动 - 准备更新OLED显示屏");
// 这里应该初始化OLED显示屏
// display.begin();
while(1) {
uint32_t currentTime = millis();
// 尝试获取最新的分析数据
if (xQueuePeek(displayQueue, &displayData, 0) == pdTRUE) {
if (currentTime - lastUpdateTime > UPDATE_INTERVAL) {
// 更新显示内容
Serial.println("=== OLED显示更新 ===");
Serial.printf("温度: %.1f°C %s\n", displayData.avgTemperature,
displayData.tempTrend > 0.1 ? "↗" : (displayData.tempTrend < -0.1 ? "↘" : "→"));
Serial.printf("湿度: %.1f%% %s\n", displayData.avgHumidity,
displayData.humidityTrend > 0.1 ? "↗" : (displayData.humidityTrend < -0.1 ? "↘" : "→"));
Serial.printf("光照: %.0f lux\n", displayData.avgLight);
Serial.printf("声音: %.0f dB\n", displayData.avgSound);
Serial.printf("样本: %u\n", displayData.sampleCount);
Serial.printf("时间: %u\n", displayData.timestamp);
Serial.println("==================");
lastUpdateTime = currentTime;
// 实际项目中这里会是OLED显示代码
/*
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.printf("Temp: %.1fC %s", displayData.avgTemperature,
displayData.tempTrend > 0.1 ? "UP" : (displayData.tempTrend < -0.1 ? "DN" : "--"));
display.setCursor(0, 10);
display.printf("Humi: %.1f%% %s", displayData.avgHumidity,
displayData.humidityTrend > 0.1 ? "UP" : (displayData.humidityTrend < -0.1 ? "DN" : "--"));
display.setCursor(0, 20);
display.printf("Light: %.0f lux", displayData.avgLight);
display.setCursor(0, 30);
display.printf("Sound: %.0f dB", displayData.avgSound);
display.display();
*/
}
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// 报警任务
void alertTask(void *parameter) {
AlertData_t alertData;
uint32_t lastAlertTime = 0;
const uint32_t ALERT_COOLDOWN = 5000; // 5秒报警冷却时间
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
Serial.println("报警任务启动 - 准备处理系统警报");
while(1) {
if (xQueueReceive(alertQueue, &alertData, portMAX_DELAY) == pdTRUE) {
uint32_t currentTime = millis();
// 报警冷却机制,避免频繁报警
if (currentTime - lastAlertTime < ALERT_COOLDOWN && alertData.alertLevel < 2) {
continue; // 跳过低级别的频繁报警
}
Serial.printf("=== 系统警报 ===\n");
Serial.printf("级别: %u (0-信息, 1-警告, 2-严重, 3-紧急)\n", alertData.alertLevel);
Serial.printf("消息: %s\n", alertData.message);
Serial.printf("相关传感器: %u\n", alertData.relatedSensor);
Serial.printf("触发值: %.2f\n", alertData.triggerValue);
Serial.printf("时间: %u\n", alertData.timestamp);
Serial.println("===============\n");
// 根据警报级别采取不同的行动
switch(alertData.alertLevel) {
case 0: // 信息
// 只记录日志,不做其他操作
break;
case 1: // 警告
// LED闪烁
for(int i = 0; i < 3; i++) {
digitalWrite(LED_PIN, HIGH);
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(LED_PIN, LOW);
vTaskDelay(pdMS_TO_TICKS(200));
}
break;
case 2: // 严重
// LED闪烁 + 短促蜂鸣
for(int i = 0; i < 5; i++) {
digitalWrite(LED_PIN, HIGH);
digitalWrite(BUZZER_PIN, HIGH);
vTaskDelay(pdMS_TO_TICKS(100));
digitalWrite(LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
vTaskDelay(pdMS_TO_TICKS(100));
}
break;
case 3: // 紧急
// 持续报警
for(int i = 0; i < 10; i++) {
digitalWrite(LED_PIN, HIGH);
digitalWrite(BUZZER_PIN, HIGH);
vTaskDelay(pdMS_TO_TICKS(500));
digitalWrite(LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
vTaskDelay(pdMS_TO_TICKS(200));
}
break;
}
// 记录报警到存储系统
if (systemConfig.storageEnabled) {
StorageRequest_t* storageReq = (StorageRequest_t*)malloc(sizeof(StorageRequest_t));
strcpy(storageReq->filename, "/alerts.log");
char* alertLog = (char*)malloc(200);
sprintf(alertLog, "ALERT[%u]: Level=%u, Sensor=%u, Value=%.2f, Message=%s",
alertData.timestamp, alertData.alertLevel, alertData.relatedSensor,
alertData.triggerValue, alertData.message);
storageReq->data = alertLog;
storageReq->dataSize = strlen(alertLog);
storageReq->priority = 8; // 高优先级
xQueueSend(storageQueue, storageReq, 0);
free(storageReq);
}
lastAlertTime = currentTime;
}
}
}
// 系统监控任务
void systemMonitorTask(void *parameter) {
const TickType_t MONITOR_INTERVAL = pdMS_TO_TICKS(10000); // 10秒监控一次
Serial.println("系统监控任务启动 - 开始监控系统健康状态");
while(1) {
Serial.println("\n=== 系统健康监控报告 ===");
// 内存使用情况
size_t freeHeap = ESP.getFreeHeap();
size_t minFreeHeap = ESP.getMinFreeHeap();
Serial.printf("当前空闲堆内存: %u bytes\n", freeHeap);
Serial.printf("历史最小空闲堆内存: %u bytes\n", minFreeHeap);
if (freeHeap < 10240) { // 小于10KB时报警
AlertData_t alert;
alert.alertLevel = 2;
sprintf(alert.message, "系统内存不足: %u bytes", freeHeap);
alert.relatedSensor = SENSOR_TEMPERATURE; // 影响所有传感器
alert.timestamp = millis();
xQueueSend(alertQueue, &alert, 0);
}
// 任务栈使用情况
UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
Serial.printf("当前任务栈剩余: %u bytes\n", stackHighWaterMark * sizeof(StackType_t));
// 队列状态
Serial.printf("原始数据队列: %u/%u\n",
uxQueueMessagesWaiting(rawDataQueue),
uxQueueSpacesAvailable(rawDataQueue) + uxQueueMessagesWaiting(rawDataQueue));
Serial.printf("处理数据队列: %u/%u\n",
uxQueueMessagesWaiting(processedDataQueue),
uxQueueSpacesAvailable(processedDataQueue) + uxQueueMessagesWaiting(processedDataQueue));
Serial.printf("分析队列: %u/%u\n",
uxQueueMessagesWaiting(analysisQueue),
uxQueueSpacesAvailable(analysisQueue) + uxQueueMessagesWaiting(analysisQueue));
Serial.printf("存储队列: %u/%u\n",
uxQueueMessagesWaiting(storageQueue),
uxQueueSpacesAvailable(storageQueue) + uxQueueMessagesWaiting(storageQueue));
Serial.printf("网络队列: %u/%u\n",
uxQueueMessagesWaiting(networkQueue),
uxQueueSpacesAvailable(networkQueue) + uxQueueMessagesWaiting(networkQueue));
Serial.printf("显示队列: %u/%u\n",
uxQueueMessagesWaiting(displayQueue),
uxQueueSpacesAvailable(displayQueue) + uxQueueMessagesWaiting(displayQueue));
Serial.printf("警报队列: %u/%u\n",
uxQueueMessagesWaiting(alertQueue),
uxQueueSpacesAvailable(alertQueue) + uxQueueMessagesWaiting(alertQueue));
// 系统运行时间
uint32_t uptime = millis();
Serial.printf("系统运行时间: %u.%03u 秒\n", uptime / 1000, uptime % 1000);
// WiFi连接状态
if (systemConfig.networkEnabled) {
Serial.printf("WiFi状态: %s\n", WiFi.status() == WL_CONNECTED ? "已连接" : "断开");
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("信号强度: %d dBm\n", WiFi.RSSI());
}
}
Serial.println("========================\n");
vTaskDelay(MONITOR_INTERVAL);
}
}
void setup() {
Serial.begin(115200);
Serial.println("========================================");
Serial.println("ESP32 FreeRTOS 消息队列演示系统启动");
Serial.println("多传感器数据采集与处理系统");
Serial.println("========================================");
// 初始化硬件
dht.begin();
pinMode(LIGHT_SENSOR_PIN, INPUT);
pinMode(SOUND_SENSOR_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
// 创建消息队列
rawDataQueue = xQueueCreate(20, sizeof(RawSensorData_t));
processedDataQueue = xQueueCreate(15, sizeof(ProcessedSensorData_t));
analysisQueue = xQueueCreate(5, sizeof(AnalysisResult_t));
storageQueue = xQueueCreate(10, sizeof(StorageRequest_t));
networkQueue = xQueueCreate(3, sizeof(AnalysisResult_t));
displayQueue = xQueueCreate(2, sizeof(AnalysisResult_t));
alertQueue = xQueueCreate(10, sizeof(AlertData_t));
// 检查队列创建是否成功
if (!rawDataQueue || !processedDataQueue || !analysisQueue ||
!storageQueue || !networkQueue || !displayQueue || !alertQueue) {
Serial.println("错误:队列创建失败!系统无法启动");
while(1) {
digitalWrite(LED_PIN, HIGH);
delay(100);
digitalWrite(LED_PIN, LOW);
delay(100);
}
}
Serial.println("所有消息队列创建成功!");
// 创建传感器任务
xTaskCreate(temperatureSensorTask, "TempSensor", 2048, NULL, 3, &temperatureTaskHandle);
xTaskCreate(humiditySensorTask, "HumiditySensor", 2048, NULL, 3, &humidityTaskHandle);
xTaskCreate(lightSensorTask, "LightSensor", 2048, NULL, 3, &lightTaskHandle);
xTaskCreate(soundSensorTask, "SoundSensor", 2048, NULL, 3, &soundTaskHandle);
// 创建数据处理任务
xTaskCreate(dataPreprocessTask, "DataPreprocess", 3072, NULL, 4, &preprocessTaskHandle);
xTaskCreate(dataAnalysisTask, "DataAnalysis", 3072, NULL, 2, &analysisTaskHandle);
// 创建输出任务
xTaskCreate(storageTask, "Storage", 4096, NULL, 2, &storageTaskHandle);
xTaskCreate(networkTransmissionTask, "Network", 4096, NULL, 1, &networkTaskHandle);
xTaskCreate(displayTask, "Display", 2048, NULL, 2, &displayTaskHandle);
xTaskCreate(alertTask, "Alert", 2048, NULL, 5, &alertTaskHandle);
// 创建系统监控任务
xTaskCreate(systemMonitorTask, "SystemMonitor", 2048, NULL, 1, &systemMonitorTaskHandle);
Serial.println("所有任务创建完成!");
Serial.println("系统开始运行,队列通信已建立");
// 设置数据流向
// 原始数据 -> 预处理 -> 分析 -> 存储/网络/显示
// 同时预处理阶段会产生警报数据
Serial.println("数据流向:");
Serial.println("传感器 -> 原始数据队列 -> 预处理任务 -> 处理数据队列 -> 分析任务");
Serial.println("分析任务 -> 分析队列 -> 存储/网络/显示任务");
Serial.println("预处理任务 -> 警报队列 -> 警报任务");
}
void loop() {
// 在FreeRTOS多任务环境中,主循环通常为空
// 所有功能都由任务来实现
// 可以在这里添加一些简单的状态指示
static uint32_t lastHeartbeat = 0;
uint32_t currentTime = millis();
if (currentTime - lastHeartbeat > 5000) {
Serial.printf("系统心跳 - 运行时间: %u.%03u 秒, 空闲内存: %u bytes\n",
currentTime / 1000, currentTime % 1000, ESP.getFreeHeap());
lastHeartbeat = currentTime;
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
队列性能优化技巧:让数据传输更高效
通过上面的实战代码,我们可以看到消息队列在复杂系统中的强大威力。但是,要让系统达到最佳性能,还需要掌握一些优化技巧:
队列大小的艺术
队列大小的选择就像选择水管的粗细,太细会造成堵塞,太粗会浪费资源:
OptimalQueueSize=ProducerRate×ProcessingTimeConsumerRateOptimalQueueSize = \frac{ProducerRate \times ProcessingTime}{ConsumerRate}OptimalQueueSize=ConsumerRateProducerRate×ProcessingTime
其中:
- ProducerRateProducerRateProducerRate:生产者的数据产生速率
- ProcessingTimeProcessingTimeProcessingTime:数据处理的平均时间
- ConsumerRateConsumerRateConsumerRate:消费者的数据处理速率
// 队列大小计算示例
void calculateOptimalQueueSize() {
// 假设传感器每秒产生10个数据包
float producerRate = 10.0f; // 数据包/秒
// 数据处理平均需要50毫秒
float processingTime = 0.05f; // 秒
// 处理器每秒能处理15个数据包
float consumerRate = 15.0f; // 数据包/秒
// 计算理论队列大小
float theoreticalSize = (producerRate * processingTime) / consumerRate;
// 添加安全余量(通常是理论值的2-3倍)
int optimalSize = (int)(theoreticalSize * 2.5f) + 1;
Serial.printf("理论队列大小: %.2f\n", theoreticalSize);
Serial.printf("推荐队列大小: %d\n", optimalSize);
}
数据结构优化
合理的数据结构设计能显著提升队列性能:
// 优化前:数据结构不紧凑
typedef struct {
float temperature; // 4 bytes
bool isValid; // 1 byte,但实际占用4 bytes(内存对齐)
float humidity; // 4 bytes
uint32_t timestamp; // 4 bytes
char sensorName[20]; // 20 bytes
} UnoptimizedData_t; // 总计:32 bytes
// 优化后:数据结构紧凑
typedef struct {
uint32_t timestamp; // 4 bytes
float temperature; // 4 bytes
float humidity; // 4 bytes
uint8_t sensorId; // 1 byte(用ID代替名称)
uint8_t flags; // 1 byte(包含isValid等标志位)
uint16_t reserved; // 2 bytes(保留,确保4字节对齐)
} OptimizedData_t; // 总计:16 bytes,节省50%内存!
// 标志位操作宏
#define SET_VALID_FLAG(flags) ((flags) |= 0x01)
#define CLEAR_VALID_FLAG(flags) ((flags) &= ~0x01)
#define IS_VALID(flags) ((flags) & 0x01)
批量处理技
批量处理技术
批量处理就像批发采购一样,一次处理多个数据项比逐个处理更高效:
// 批量数据处理示例
#define BATCH_SIZE 5
void batchProcessingExample() {
ProcessedSensorData_t dataBuffer[BATCH_SIZE];
int batchCount = 0;
TickType_t batchTimeout = pdMS_TO_TICKS(1000); // 1秒批处理超时
TickType_t lastBatchTime = xTaskGetTickCount();
while(1) {
ProcessedSensorData_t singleData;
// 尝试接收数据
if (xQueueReceive(processedDataQueue, &singleData, pdMS_TO_TICKS(100)) == pdTRUE) {
dataBuffer[batchCount++] = singleData;
}
// 检查是否需要处理批次
TickType_t currentTime = xTaskGetTickCount();
bool shouldProcessBatch = (batchCount >= BATCH_SIZE) ||
(batchCount > 0 && (currentTime - lastBatchTime) > batchTimeout);
if (shouldProcessBatch) {
Serial.printf("批量处理 %d 个数据项\n", batchCount);
// 批量处理逻辑
for(int i = 0; i < batchCount; i++) {
// 处理单个数据项
processSingleDataItem(&dataBuffer[i]);
}
// 批量写入存储(更高效)
if (systemConfig.storageEnabled) {
batchWriteToStorage(dataBuffer, batchCount);
}
// 重置批次
batchCount = 0;
lastBatchTime = currentTime;
}
}
}
// 批量存储函数
void batchWriteToStorage(ProcessedSensorData_t* dataArray, int count) {
File batchFile = SD.open("/batch_data.csv", FILE_APPEND);
if (batchFile) {
for(int i = 0; i < count; i++) {
batchFile.printf("%u,%.2f,%.2f,%.2f,%u\n",
dataArray[i].timestamp,
dataArray[i].value,
dataArray[i].filteredValue,
dataArray[i].trend,
dataArray[i].confidence);
}
batchFile.close();
Serial.printf("批量写入 %d 条记录到存储\n", count);
}
}
优先级队列实现
有时候我们需要根据数据的重要性来决定处理顺序,这就需要优先级队列:
// 优先级队列实现
typedef struct {
void* data;
size_t dataSize;
uint8_t priority; // 0-255,数值越大优先级越高
uint32_t timestamp;
} PriorityQueueItem_t;
typedef struct {
PriorityQueueItem_t* items;
int maxSize;
int currentSize;
SemaphoreHandle_t mutex;
} PriorityQueue_t;
PriorityQueue_t* createPriorityQueue(int maxSize) {
PriorityQueue_t* pq = (PriorityQueue_t*)malloc(sizeof(PriorityQueue_t));
if (pq) {
pq->items = (PriorityQueueItem_t*)malloc(maxSize * sizeof(PriorityQueueItem_t));
pq->maxSize = maxSize;
pq->currentSize = 0;
pq->mutex = xSemaphoreCreateMutex();
}
return pq;
}
bool priorityQueueSend(PriorityQueue_t* pq, void* data, size_t dataSize, uint8_t priority) {
if (xSemaphoreTake(pq->mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
if (pq->currentSize >= pq->maxSize) {
xSemaphoreGive(pq->mutex);
return false; // 队列已满
}
// 找到插入位置(按优先级排序)
int insertPos = pq->currentSize;
for (int i = 0; i < pq->currentSize; i++) {
if (priority > pq->items[i].priority) {
insertPos = i;
break;
}
}
// 移动后面的元素
for (int i = pq->currentSize; i > insertPos; i--) {
pq->items[i] = pq->items[i-1];
}
// 插入新元素
pq->items[insertPos].data = malloc(dataSize);
memcpy(pq->items[insertPos].data, data, dataSize);
pq->items[insertPos].dataSize = dataSize;
pq->items[insertPos].priority = priority;
pq->items[insertPos].timestamp = millis();
pq->currentSize++;
xSemaphoreGive(pq->mutex);
return true;
}
return false;
}
bool priorityQueueReceive(PriorityQueue_t* pq, void* buffer, size_t bufferSize) {
if (xSemaphoreTake(pq->mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
if (pq->currentSize == 0) {
xSemaphoreGive(pq->mutex);
return false; // 队列为空
}
// 取出优先级最高的元素(索引0)
PriorityQueueItem_t* item = &pq->items[0];
if (item->dataSize <= bufferSize) {
memcpy(buffer, item->data, item->dataSize);
free(item->data);
// 移动剩余元素
for (int i = 0; i < pq->currentSize - 1; i++) {
pq->items[i] = pq->items[i + 1];
}
pq->currentSize--;
xSemaphoreGive(pq->mutex);
return true;
}
xSemaphoreGive(pq->mutex);
}
return false;
}
内存池优化
频繁的内存分配和释放会导致内存碎片,使用内存池可以有效解决这个问题:
// 内存池实现
typedef struct {
void* pool;
size_t blockSize;
int totalBlocks;
int freeBlocks;
uint8_t* freeList;
SemaphoreHandle_t mutex;
} MemoryPool_t;
MemoryPool_t* createMemoryPool(size_t blockSize, int blockCount) {
MemoryPool_t* pool = (MemoryPool_t*)malloc(sizeof(MemoryPool_t));
if (!pool) return NULL;
// 分配内存池
pool->pool = malloc(blockSize * blockCount);
if (!pool->pool) {
free(pool);
return NULL;
}
// 初始化空闲列表
pool->freeList = (uint8_t*)malloc(blockCount);
if (!pool->freeList) {
free(pool->pool);
free(pool);
return NULL;
}
// 标记所有块为可用
for (int i = 0; i < blockCount; i++) {
pool->freeList[i] = 1; // 1表示可用
}
pool->blockSize = blockSize;
pool->totalBlocks = blockCount;
pool->freeBlocks = blockCount;
pool->mutex = xSemaphoreCreateMutex();
return pool;
}
void* memoryPoolAlloc(MemoryPool_t* pool) {
if (xSemaphoreTake(pool->mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
for (int i = 0; i < pool->totalBlocks; i++) {
if (pool->freeList[i] == 1) {
pool->freeList[i] = 0; // 标记为已使用
pool->freeBlocks--;
void* ptr = (uint8_t*)pool->pool + (i * pool->blockSize);
xSemaphoreGive(pool->mutex);
return ptr;
}
}
xSemaphoreGive(pool->mutex);
}
return NULL; // 无可用块
}
void memoryPoolFree(MemoryPool_t* pool, void* ptr) {
if (xSemaphoreTake(pool->mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
// 计算块索引
size_t offset = (uint8_t*)ptr - (uint8_t*)pool->pool;
int blockIndex = offset / pool->blockSize;
if (blockIndex >= 0 && blockIndex < pool->totalBlocks && pool->freeList[blockIndex] == 0) {
pool->freeList[blockIndex] = 1; // 标记为可用
pool->freeBlocks++;
}
xSemaphoreGive(pool->mutex);
}
}
// 在队列操作中使用内存池
MemoryPool_t* sensorDataPool;
void initializeMemoryPools() {
// 为传感器数据创建内存池
sensorDataPool = createMemoryPool(sizeof(RawSensorData_t), 50);
if (!sensorDataPool) {
Serial.println("内存池创建失败!");
} else {
Serial.println("传感器数据内存池创建成功");
}
}
// 使用内存池的传感器任务
void optimizedTemperatureSensorTask(void *parameter) {
TickType_t xLastWakeTime = xTaskGetTickCount();
while(1) {
// 从内存池分配内存
RawSensorData_t* sensorData = (RawSensorData_t*)memoryPoolAlloc(sensorDataPool);
if (sensorData) {
float temperature = dht.readTemperature();
if (!isnan(temperature)) {
sensorData->type = SENSOR_TEMPERATURE;
sensorData->value = temperature;
sensorData->timestamp = millis();
sensorData->sensorId = 1;
sensorData->quality = 100;
// 发送指针而不是数据副本
if (xQueueSend(rawDataQueue, &sensorData, pdMS_TO_TICKS(100)) != pdTRUE) {
// 发送失败,释放内存
memoryPoolFree(sensorDataPool, sensorData);
}
} else {
// 读取失败,释放内存
memoryPoolFree(sensorDataPool, sensorData);
}
} else {
Serial.println("内存池已满,无法分配内存");
}
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
}
}
队列调试技巧:让问题无处遁形
调试队列问题就像侦探破案,需要收集线索、分析证据、找出真相。
队列状态监控工具
// 队列监控结构
typedef struct {
QueueHandle_t queue;
const char* name;
uint32_t sendCount;
uint32_t receiveCount;
uint32_t sendFailures;
uint32_t receiveTimeouts;
uint32_t maxUsage;
TickType_t lastActivity;
} QueueMonitor_t;
QueueMonitor_t queueMonitors[10];
int monitorCount = 0;
void registerQueueMonitor(QueueHandle_t queue, const char* name) {
if (monitorCount < 10) {
queueMonitors[monitorCount].queue = queue;
queueMonitors[monitorCount].name = name;
queueMonitors[monitorCount].sendCount = 0;
queueMonitors[monitorCount].receiveCount = 0;
queueMonitors[monitorCount].sendFailures = 0;
queueMonitors[monitorCount].receiveTimeouts = 0;
queueMonitors[monitorCount].maxUsage = 0;
queueMonitors[monitorCount].lastActivity = xTaskGetTickCount();
monitorCount++;
}
}
// 包装发送函数,添加监控
BaseType_t monitoredQueueSend(QueueHandle_t queue, const void* item, TickType_t timeout) {
// 找到对应的监控器
for (int i = 0; i < monitorCount; i++) {
if (queueMonitors[i].queue == queue) {
BaseType_t result = xQueueSend(queue, item, timeout);
if (result == pdTRUE) {
queueMonitors[i].sendCount++;
queueMonitors[i].lastActivity = xTaskGetTickCount();
// 更新最大使用量
UBaseType_t currentUsage = uxQueueMessagesWaiting(queue);
if (currentUsage > queueMonitors[i].maxUsage) {
queueMonitors[i].maxUsage = currentUsage;
}
} else {
queueMonitors[i].sendFailures++;
}
return result;
}
}
// 没找到监控器,直接调用原函数
return xQueueSend(queue, item, timeout);
}
// 包装接收函数,添加监控
BaseType_t monitoredQueueReceive(QueueHandle_t queue, void* buffer, TickType_t timeout) {
for (int i = 0; i < monitorCount; i++) {
if (queueMonitors[i].queue == queue) {
BaseType_t result = xQueueReceive(queue, buffer, timeout);
if (result == pdTRUE) {
queueMonitors[i].receiveCount++;
queueMonitors[i].lastActivity = xTaskGetTickCount();
} else {
queueMonitors[i].receiveTimeouts++;
}
return result;
}
}
return xQueueReceive(queue, buffer, timeout);
}
// 生成队列监控报告
void generateQueueReport() {
Serial.println("\n=== 队列监控报告 ===");
for (int i = 0; i < monitorCount; i++) {
QueueMonitor_t* monitor = &queueMonitors[i];
UBaseType_t currentUsage = uxQueueMessagesWaiting(monitor->queue);
UBaseType_t totalCapacity = currentUsage + uxQueueSpacesAvailable(monitor->queue);
Serial.printf("队列: %s\n", monitor->name);
Serial.printf(" 当前使用: %u/%u (%.1f%%)\n",
currentUsage, totalCapacity,
(float)currentUsage / totalCapacity * 100);
Serial.printf(" 最大使用: %u/%u (%.1f%%)\n",
monitor->maxUsage, totalCapacity,
(float)monitor->maxUsage / totalCapacity * 100);
Serial.printf(" 发送次数: %u (失败: %u)\n",
monitor->sendCount, monitor->sendFailures);
Serial.printf(" 接收次数: %u (超时: %u)\n",
monitor->receiveCount, monitor->receiveTimeouts);
Serial.printf(" 成功率: 发送%.1f%%, 接收%.1f%%\n",
(float)monitor->sendCount / (monitor->sendCount + monitor->sendFailures) * 100,
(float)monitor->receiveCount / (monitor->receiveCount + monitor->receiveTimeouts) * 100);
Serial.printf(" 最后活动: %u ms前\n",
(xTaskGetTickCount() - monitor->lastActivity) * portTICK_PERIOD_MS);
Serial.println();
}
Serial.println("==================\n");
}
队列死锁检测
// 队列死锁检测器
typedef struct {
TaskHandle_t task;
QueueHandle_t waitingQueue;
TickType_t waitStartTime;
const char* taskName;
} QueueWaiter_t;
QueueWaiter_t queueWaiters[20];
int waiterCount = 0;
SemaphoreHandle_t waiterMutex;
void registerQueueWaiter(TaskHandle_t task, QueueHandle_t queue, const char* taskName) {
if (xSemaphoreTake(waiterMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
if (waiterCount < 20) {
queueWaiters[waiterCount].task = task;
queueWaiters[waiterCount].waitingQueue = queue;
queueWaiters[waiterCount].waitStartTime = xTaskGetTickCount();
queueWaiters[waiterCount].taskName = taskName;
waiterCount++;
}
xSemaphoreGive(waiterMutex);
}
}
void unregisterQueueWaiter(TaskHandle_t task) {
if (xSemaphoreTake(waiterMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
for (int i = 0; i < waiterCount; i++) {
if (queueWaiters[i].task == task) {
// 移除等待者
for (int j = i; j < waiterCount - 1; j++) {
queueWaiters[j] = queueWaiters[j + 1];
}
waiterCount--;
break;
}
}
xSemaphoreGive(waiterMutex);
}
}
void deadlockDetectionTask(void *parameter) {
const TickType_t DEADLOCK_TIMEOUT = pdMS_TO_TICKS(30000); // 30秒超时
while(1) {
TickType_t currentTime = xTaskGetTickCount();
if (xSemaphoreTake(waiterMutex, pdMS_TO_TICKS(100)) == pdTRUE) {
for (int i = 0; i < waiterCount; i++) {
TickType_t waitTime = currentTime - queueWaiters[i].waitStartTime;
if (waitTime > DEADLOCK_TIMEOUT) {
Serial.printf("警告:检测到可能的队列死锁!\n");
Serial.printf("任务: %s\n", queueWaiters[i].taskName);
Serial.printf("等待队列: %p\n", queueWaiters[i].waitingQueue);
Serial.printf("等待时间: %u ms\n", waitTime * portTICK_PERIOD_MS);
// 可以采取恢复措施
// 1. 记录详细信息
// 2. 重启相关任务
// 3. 清空队列
// 4. 重启系统
}
}
xSemaphoreGive(waiterMutex);
}
vTaskDelay(pdMS_TO_TICKS(5000)); // 每5秒检查一次
}
}
队列性能分析
// 队列性能分析器
typedef struct {
uint32_t totalSendTime;
uint32_t totalReceiveTime;
uint32_t sendOperations;
uint32_t receiveOperations;
uint32_t maxSendTime;
uint32_t maxReceiveTime;
uint32_t minSendTime;
uint32_t minReceiveTime;
} QueuePerformance_t;
QueuePerformance_t queuePerformance[10];
BaseType_t timedQueueSend(QueueHandle_t queue, const void* item, TickType_t timeout, int queueIndex) {
TickType_t startTime = xTaskGetTickCount();
BaseType_t result = xQueueSend(queue, item, timeout);
TickType_t endTime = xTaskGetTickCount();
uint32_t operationTime = (endTime - startTime) * portTICK_PERIOD_MS;
if (queueIndex < 10) {
QueuePerformance_t* perf = &queuePerformance[queueIndex];
perf->totalSendTime += operationTime;
perf->sendOperations++;
if (operationTime > perf->maxSendTime) {
perf->maxSendTime = operationTime;
}
if (perf->minSendTime == 0 || operationTime < perf->minSendTime) {
perf->minSendTime = operationTime;
}
}
return result;
}
void generatePerformanceReport() {
Serial.println("\n=== 队列性能分析报告 ===");
for (int i = 0; i < 10; i++) {
QueuePerformance_t* perf = &queuePerformance[i];
if (perf->sendOperations > 0 || perf->receiveOperations > 0) {
Serial.printf("队列 %d:\n", i);
if (perf->sendOperations > 0) {
Serial.printf(" 发送操作: %u 次\n", perf->sendOperations);
Serial.printf(" 平均发送时间: %.2f ms\n",
(float)perf->totalSendTime / perf->sendOperations);
Serial.printf(" 最大发送时间: %u ms\n", perf->maxSendTime);
Serial.printf(" 最小发送时间: %u ms\n", perf->minSendTime);
}
if (perf->receiveOperations > 0) {
Serial.printf(" 接收操作: %u 次\n", perf->receiveOperations);
Serial.printf(" 平均接收时间: %.2f ms\n",
(float)perf->totalReceiveTime / perf->receiveOperations);
Serial.printf(" 最大接收时间: %u ms\n", perf->maxReceiveTime);
Serial.printf(" 最小接收时间: %u ms\n", perf->minReceiveTime);
}
Serial.println();
}
}
Serial.println("========================\n");
}
高级应用场景:队列的创意用法
消息队列不仅仅是简单的数据传递工具,在创意十足的程序员手中,它还能实现许多高级功能。
事件驱动架构
// 事件驱动系统实现
typedef enum {
EVENT_BUTTON_PRESSED,
EVENT_SENSOR_ALARM,
EVENT_NETWORK_CONNECTED,
EVENT_NETWORK_DISCONNECTED,
EVENT_LOW_BATTERY,
EVENT_SYSTEM_ERROR,
EVENT_USER_COMMAND,
EVENT_TIMER_EXPIRED
} EventType_t;
typedef struct {
EventType_t type;
void* data;
size_t dataSize;
uint32_t timestamp;
TaskHandle_t sender;
} SystemEvent_t;
QueueHandle_t eventQueue;
// 事件发布函数
bool publishEvent(EventType_t type, void* data, size_t dataSize) {
SystemEvent_t event;
event.type = type;
event.timestamp = millis();
event.sender = xTaskGetCurrentTaskHandle();
if (data && dataSize > 0) {
event.data = malloc(dataSize);
if (event.data) {
memcpy(event.data, data, dataSize);
event.dataSize = dataSize;
} else {
return false; // 内存分配失败
}
} else {
event.data = NULL;
event.dataSize = 0;
}
BaseType_t result = xQueueSend(eventQueue, &event, pdMS_TO_TICKS(100));
if (result != pdTRUE && event.data) {
free(event.data); // 发送失败,释放内存
}
return result == pdTRUE;
}
// 事件处理任务
void eventHandlerTask(void *parameter) {
SystemEvent_t event;
while(1) {
if (xQueueReceive(eventQueue, &event, portMAX_DELAY) == pdTRUE) {
Serial.printf("处理事件: 类型=%d, 时间=%u, 发送者=%p\n",
event.type, event.timestamp, event.sender);
switch(event.type) {
case EVENT_BUTTON_PRESSED:
handleButtonEvent(&event);
break;
case EVENT_SENSOR_ALARM:
handleSensorAlarm(&event);
break;
case EVENT_NETWORK_CONNECTED:
handleNetworkConnect(&event);
break;
case EVENT_LOW_BATTERY:
handleLowBattery(&event);
break;
default:
Serial.printf("未知事件类型: %d\n", event.type);
break;
}
// 释放事件数据内存
if (event.data) {
free(event.data);
}
}
}
}
任务协调器
// 任务协调器实现
typedef enum {
TASK_STATE_IDLE,
TASK_STATE_RUNNING,
TASK_STATE_WAITING,
TASK_STATE_COMPLETED,
TASK_STATE_FAILED
} TaskState_t;
typedef struct {
uint32_t taskId;
TaskState_t state;
void* inputData;
size_t inputSize;
void* outputData;
size_t outputSize;
uint32_t priority;
uint32_t deadline;
TaskHandle_t assignedWorker;
} WorkItem_t;
QueueHandle_t workQueue;
QueueHandle_t resultQueue;
// 工作分发器
void workDispatcherTask(void *parameter) {
static uint32_t nextTaskId = 1;
while(1) {
// 创建工作项
WorkItem_t workItem;
workItem.taskId = nextTaskId++;
workItem.state = TASK_STATE_IDLE;
workItem.priority = calculatePriority();
workItem.deadline = millis() + 5000; // 5秒截止时间
// 生成输入数据
generateWorkData(&workItem);
// 发送到工作队列
if (xQueueSend(workQueue, &workItem, pdMS_TO_TICKS(1000)) == pdTRUE) {
Serial.printf("工作项 %u 已分发\n", workItem.taskId);
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
// 工作处理器
void workerTask(void *parameter) {
uint32_t workerId = (uint32_t)parameter;
WorkItem_t workItem;
while(1) {
if (xQueueReceive(workQueue, &workItem, portMAX_DELAY) == pdTRUE) {
Serial.printf("工作者 %u 开始处理任务 %u\n", workerId, workItem.taskId);
workItem.state = TASK_STATE_RUNNING;
workItem.assignedWorker = xTaskGetCurrentTaskHandle();
// 检查截止时间
if (millis() > workItem.deadline) {
Serial.printf("任务 %u 已过期\n", workItem.taskId);
workItem.state = TASK_STATE_FAILED;
} else {
// 执行实际工作
bool success = processWorkItem(&workItem);
workItem.state = success ? TASK_STATE_COMPLETED : TASK_STATE_FAILED;
}
// 发送结果
xQueueSend(resultQueue, &workItem, pdMS_TO_TICKS(500));
}
}
}
// 结果收集器
void resultCollectorTask(void *parameter) {
WorkItem_t result;
uint32_t completedTasks = 0;
uint32_t failedTasks = 0;
while(1) {
if (xQueueReceive(resultQueue, &result, portMAX_DELAY) == pdTRUE) {
if (result.state == TASK_STATE_COMPLETED) {
completedTasks++;
Serial.printf("任务 %u 完成,总完成数: %u\n", result.taskId, completedTasks);
} else {
failedTasks++;
Serial.printf("任务 %u 失败,总失败数: %u\n", result.taskId, failedTasks);
}
// 清理资源
if (result.inputData) free(result.inputData);
if (result.outputData) free(result.outputData);
// 定期报告统计信息
if ((completedTasks + failedTasks) % 10 == 0) {
float successRate = (float)completedTasks / (completedTasks + failedTasks) * 100;
Serial.printf("成功率: %.1f%% (%u/%u)\n",
successRate, completedTasks, completedTasks + failedTasks);
}
}
}
}
数据流水线
// 数据流水线实现
typedef struct {
uint32_t id;
uint8_t stage;
void* data;
size_t dataSize;
uint32_t processTime[5]; // 记录每个阶段的处理时间
} PipelineData_t;
QueueHandle_t pipelineQueues[5]; // 5个阶段的队列
// 流水线阶段1:数据预处理
void pipelineStage1Task(void *parameter) {
PipelineData_t item;
while(1) {
if (xQueueReceive(pipelineQueues[0], &item, portMAX_DELAY) == pdTRUE) {
uint32_t startTime = millis();
// 阶段1处理:数据清洗
preprocessData(&item);
item.stage = 1;
item.processTime[0] = millis() - startTime;
// 传递到下一阶段
xQueueSend(pipelineQueues[1], &item, portMAX_DELAY);
}
}
}
// 流水线阶段2:数据变换
void pipelineStage2Task(void *parameter) {
PipelineData_t item;
while(1) {
if (xQueueReceive(pipelineQueues[1], &item, portMAX_DELAY) == pdTRUE) {
uint32_t startTime = millis();
// 阶段2处理:数据变换
transformData(&item);
item.stage = 2;
item.processTime[1] = millis() - startTime;
// 传递到下一阶段
xQueueSend(pipelineQueues[2], &item, portMAX_DELAY);
}
}
}
// 流水线性能监控
void pipelineMonitorTask(void *parameter) {
uint32_t throughputCounter = 0;
uint32_t lastReportTime = millis();
while(1) {
// 监控最后一个队列的输出
UBaseType_t finalQueueSize = uxQueueMessagesWaiting(pipelineQueues[4]);
if (finalQueueSize > 0) {
throughputCounter += finalQueueSize;
}
uint32_t currentTime = millis();
if (currentTime - lastReportTime > 10000) { // 每10秒报告一次
float throughput = (float)throughputCounter / 10.0f; // 每秒处理量
Serial.printf("流水线吞吐量: %.1f 项/秒\n", throughput);
// 报告各阶段队列状态
for (int i = 0; i < 5; i++) {
UBaseType_t queueSize = uxQueueMessagesWaiting(pipelineQueues[i]);
Serial.printf("阶段 %d 队列: %u 项\n", i, queueSize);
}
throughputCounter = 0;
lastReportTime = currentTime;
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}