文章总结(帮你们节约时间)
- 信号量和互斥量是ESP32多任务编程的核心同步机制,掌握它们就像学会了多线程世界的"交通规则"
- 计数信号量适合资源池管理,二进制信号量用于任务同步,互斥量专门保护共享资源
- 优先级反转和死锁是两大常见陷阱,但通过优先级继承和合理设计完全可以避免
- Arduino IDE下的FreeRTOS API使用简单,但理解底层原理才能写出高效稳定的代码
你有没有遇到过这样的情况:ESP32项目中多个任务同时访问串口,结果打印出来的信息乱七八糟,像是外星人发来的密码?或者两个任务都在等待对方释放资源,最后谁也动不了,整个系统就这样"死"在那里?
这就是多任务编程中最经典的问题!就像十字路口没有红绿灯,车辆横冲直撞,不出事故才怪呢。而信号量(Semaphore)和互斥量(Mutex)就是我们的"交通信号灯",它们负责协调各个任务的执行顺序,确保系统井然有序。
ESP32作为一颗双核处理器,天生就具备多任务处理能力。当你在Arduino IDE中使用xTaskCreate()
创建多个任务时,这些任务会并发执行,共享CPU时间片。但是,当多个任务需要访问同一个资源时,问题就来了!
想象一下,你有一个全局变量int counter = 0
,两个任务都要对它进行递增操作。任务A读取counter的值(比如是100),正准备加1,这时任务B也读取了counter的值(还是100),然后任务A写入101,任务B也写入101。结果本来应该是102的counter,最终只变成了101!这就是典型的竞态条件(Race Condition)。
// 这是一个有问题的代码示例
int counter = 0;
void taskA(void *parameter) {
for(;;) {
counter++; // 危险!非原子操作
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void taskB(void *parameter) {
for(;;) {
counter++; // 危险!非原子操作
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
这就是为什么我们需要信号量和互斥量的原因!它们就像是代码世界的"门禁系统",确保在任何时刻只有被授权的任务才能访问关键资源。
基础概念:信号量和互斥量的本质差异
在深入学习之前,我们必须搞清楚信号量和互斥量的本质区别。很多初学者经常把它们搞混,就像把"借钱"和"排队"搞混一样。
**信号量(Semaphore)**就像是一个"令牌桶"。想象一下网吧的上机卡,网吧总共有20台电脑,就发放20张上机卡。顾客想上网就必须先拿到卡,用完后归还。如果卡都被拿光了,后来的顾客就得等待。信号量的值表示可用资源的数量,它可以是任意非负整数。
**互斥量(Mutex)**则更像是一把"独占锁"。想象一下公共厕所的门锁,同一时间只能有一个人使用,其他人必须在外面等待。互斥量只有两种状态:锁定或未锁定,专门用于保护临界区。
让我们用一个生动的比喻来理解:
-
信号量像是停车场的车位。停车场有100个车位,就相当于计数为100的信号量。每来一辆车,可用车位减1;每走一辆车,可用车位加1。当车位满了,新来的车就得等待。
-
互斥量像是单人卫生间的门锁。不管外面排了多少人,里面同时只能有一个人。这个人用完出来后,下一个人才能进去。
从技术角度来看,二者的区别在于:
- 语义不同:信号量用于资源计数和任务同步,互斥量专门用于互斥访问
- 所有权不同:信号量没有所有权概念,任何任务都可以释放;互斥量有所有权,只有获取者才能释放
- 优先级继承:互斥量支持优先级继承,信号量不支持
- 递归性:互斥量可以递归获取,信号量不行
在Arduino ESP32开发中,FreeRTOS提供了丰富的API来操作这些同步原语:
// 信号量相关API
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
SemaphoreHandle_t xSemaphoreCreateBinary(void);
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
// 互斥量相关API
SemaphoreHandle_t xSemaphoreCreateMutex(void);
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex(void);
BaseType_t xSemaphoreTakeRecursive(SemaphoreHandle_t xMutex, TickType_t xTicksToWait);
BaseType_t xSemaphoreGiveRecursive(SemaphoreHandle_t xMutex);
信号量深度解析:从计数到二进制的奇妙世界
计数信号量:资源池的智能管理者
计数信号量就像是一个智能的资源分配器。它维护着一个内部计数器,表示当前可用资源的数量。每当一个任务需要资源时,计数器减1;当任务释放资源时,计数器加1。
让我们来看一个实际的例子:假设你的ESP32项目需要管理3个SPI设备,但只有一条SPI总线。这时候计数信号量就派上用场了!
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// 创建一个计数信号量,最大计数为3,初始计数为3
SemaphoreHandle_t spiSemaphore;
void setup() {
Serial.begin(115200);
// 创建计数信号量:最多3个任务可以同时使用SPI资源
spiSemaphore = xSemaphoreCreateCounting(3, 3);
if (spiSemaphore == NULL) {
Serial.println("信号量创建失败!");
return;
}
// 创建多个任务来模拟SPI设备访问
xTaskCreate(spiTask, "SPI_Task_1", 2048, (void*)1, 1, NULL);
xTaskCreate(spiTask, "SPI_Task_2", 2048, (void*)2, 1, NULL);
xTaskCreate(spiTask, "SPI_Task_3", 2048, (void*)3, 1, NULL);
xTaskCreate(spiTask, "SPI_Task_4", 2048, (void*)4, 1, NULL);
xTaskCreate(spiTask, "SPI_Task_5", 2048, (void*)5, 1, NULL);
Serial.println("所有任务创建完成!");
}
void spiTask(void *parameter) {
int taskId = (int)parameter;
for(;;) {
// 尝试获取SPI资源,最多等待1秒
if (xSemaphoreTake(spiSemaphore, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.printf("任务%d获得SPI资源,开始传输数据...\n", taskId);
// 模拟SPI数据传输(耗时操作)
vTaskDelay(2000 / portTICK_PERIOD_MS);
Serial.printf("任务%d完成SPI传输,释放资源\n", taskId);
// 释放SPI资源
xSemaphoreGive(spiSemaphore);
} else {
Serial.printf("任务%d等待SPI资源超时!\n", taskId);
}
// 等待一段时间再次尝试
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
void loop() {
// 主循环可以做其他事情
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
运行这个程序,你会看到类似这样的输出:
所有任务创建完成!
任务1获得SPI资源,开始传输数据...
任务2获得SPI资源,开始传输数据...
任务3获得SPI资源,开始传输数据...
任务4等待SPI资源超时!
任务5等待SPI资源超时!
任务1完成SPI传输,释放资源
任务2完成SPI传输,释放资源
任务3完成SPI传输,释放资源
任务4获得SPI资源,开始传输数据...
任务5获得SPI资源,开始传输数据...
看到了吗?前3个任务同时获得了SPI资源,而任务4和5必须等待。这就是计数信号量的威力!它精确地控制了同时访问资源的任务数量。
但是,你可能会问:为什么不直接用一个全局变量来计数呢?答案是:原子性!信号量的获取和释放操作是原子的,不会被任务切换打断。而普通的变量操作可能会被中断,导致竞态条件。
二进制信号量:简单而强大的同步工具
二进制信号量就像是一个简化版的计数信号量,它的计数值只能是0或1。你可以把它想象成一个"开关"或者"标志位"。
二进制信号量最常用的场景是任务同步。比如,你希望任务A完成某个操作后,任务B才能开始执行。这时候二进制信号量就是最佳选择!
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
SemaphoreHandle_t binarySemaphore;
void setup() {
Serial.begin(115200);
// 创建二进制信号量
binarySemaphore = xSemaphoreCreateBinary();
if (binarySemaphore == NULL) {
Serial.println("二进制信号量创建失败!");
return;
}
// 创建生产者和消费者任务
xTaskCreate(producerTask, "Producer", 2048, NULL, 2, NULL);
xTaskCreate(consumerTask, "Consumer", 2048, NULL, 1, NULL);
Serial.println("生产者-消费者任务创建完成!");
}
void producerTask(void *parameter) {
int dataCounter = 0;
for(;;) {
// 模拟数据生产过程
dataCounter++;
Serial.printf("生产者:正在生产数据 #%d...\n", dataCounter);
vTaskDelay(2000 / portTICK_PERIOD_MS);
Serial.printf("生产者:数据 #%d 生产完成!通知消费者\n", dataCounter);
// 通知消费者数据已准备好
xSemaphoreGive(binarySemaphore);
// 等待一段时间再生产下一个数据
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
void consumerTask(void *parameter) {
for(;;) {
Serial.println("消费者:等待数据...");
// 等待生产者的通知
if (xSemaphoreTake(binarySemaphore, portMAX_DELAY) == pdTRUE) {
Serial.println("消费者:收到通知,开始处理数据...");
// 模拟数据处理过程
vTaskDelay(1500 / portTICK_PERIOD_MS);
Serial.println("消费者:数据处理完成!\n");
}
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
这个例子展示了二进制信号量在任务同步中的应用。生产者任务生产数据后,通过xSemaphoreGive()
通知消费者;消费者任务通过xSemaphoreTake()
等待通知。
你会看到类似这样的输出:
生产者-消费者任务创建完成!
消费者:等待数据...
生产者:正在生产数据 #1...
生产者:数据 #1 生产完成!通知消费者
消费者:收到通知,开始处理数据...
消费者:数据处理完成!
消费者:等待数据...
生产者:正在生产数据 #2...
生产者:数据 #2 生产完成!通知消费者
消费者:收到通知,开始处理数据...
消费者:数据处理完成!
信号量的创建与配置策略
在ESP32的Arduino环境中,创建信号量有几种不同的方式,每种方式都有其特定的用途:
// 1. 创建计数信号量
SemaphoreHandle_t xSemaphoreCreateCounting(
UBaseType_t uxMaxCount, // 最大计数值
UBaseType_t uxInitialCount // 初始计数值
);
// 2. 创建二进制信号量
SemaphoreHandle_t xSemaphoreCreateBinary(void);
// 3. 使用静态内存创建信号量(高级用法)
SemaphoreHandle_t xSemaphoreCreateCountingStatic(
UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t *pxSemaphoreBuffer
);
创建策略的选择原则:
- 资源池管理:使用计数信号量,最大计数等于资源数量
- 任务同步:使用二进制信号量,初始状态通常为空(计数为0)
- 事件通知:使用二进制信号量,可以替代传统的标志位
- 内存受限环境:考虑使用静态创建方式,避免堆内存碎片
让我们看一个更复杂的例子,展示如何在一个实际的IoT项目中使用信号量:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "WiFi.h"
// 信号量句柄
SemaphoreHandle_t wifiSemaphore; // WiFi连接状态信号量
SemaphoreHandle_t sensorSemaphore; // 传感器数据信号量
SemaphoreHandle_t uartSemaphore; // 串口访问信号量
// 全局变量
float temperature = 0.0;
float humidity = 0.0;
bool wifiConnected = false;
void setup() {
Serial.begin(115200);
// 创建各种信号量
wifiSemaphore = xSemaphoreCreateBinary();
sensorSemaphore = xSemaphoreCreateBinary();
uartSemaphore = xSemaphoreCreateMutex(); // 串口需要互斥访问
if (wifiSemaphore == NULL || sensorSemaphore == NULL || uartSemaphore == NULL) {
Serial.println("信号量创建失败!");
return;
}
// 创建各种任务
xTaskCreate(wifiTask, "WiFi_Task", 4096, NULL, 3, NULL);
xTaskCreate(sensorTask, "Sensor_Task", 2048, NULL, 2, NULL);
xTaskCreate(dataUploadTask, "Upload_Task", 4096, NULL, 1, NULL);
xTaskCreate(displayTask, "Display_Task", 2048, NULL, 1, NULL);
Serial.println("IoT系统启动完成!");
}
void wifiTask(void *parameter) {
WiFi.begin("YourWiFiSSID", "YourPassword");
for(;;) {
if (WiFi.status() == WL_CONNECTED && !wifiConnected) {
wifiConnected = true;
safePrint("WiFi连接成功!");
xSemaphoreGive(wifiSemaphore); // 通知其他任务WiFi已连接
} else if (WiFi.status() != WL_CONNECTED && wifiConnected) {
wifiConnected = false;
safePrint("WiFi连接断开!");
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
void sensorTask(void *parameter) {
for(;;) {
// 模拟读取传感器数据
temperature = random(200, 350) / 10.0; // 20.0 - 35.0°C
humidity = random(300, 800) / 10.0; // 30.0 - 80.0%
safePrint("传感器数据更新:温度=" + String(temperature) + "°C, 湿度=" + String(humidity) + "%");
// 通知其他任务数据已更新
xSemaphoreGive(sensorSemaphore);
vTaskDelay(10000 / portTICK_PERIOD_MS); // 每10秒更新一次
}
}
void dataUploadTask(void *parameter) {
for(;;) {
// 等待传感器数据更新
if (xSemaphoreTake(sensorSemaphore, portMAX_DELAY) == pdTRUE) {
// 等待WiFi连接
if (xSemaphoreTake(wifiSemaphore, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
safePrint("开始上传数据到云端...");
// 模拟数据上传过程
vTaskDelay(2000 / portTICK_PERIOD_MS);
safePrint("数据上传完成!");
// 重新释放WiFi信号量,让其他任务也能使用
xSemaphoreGive(wifiSemaphore);
} else {
safePrint("WiFi未连接,跳过数据上传");
}
}
}
}
void displayTask(void *parameter) {
for(;;) {
safePrint("=== 系统状态 ===");
safePrint("WiFi状态: " + String(wifiConnected ? "已连接" : "未连接"));
safePrint("当前温度: " + String(temperature) + "°C");
safePrint("当前湿度: " + String(humidity) + "%");
safePrint("================\n");
vTaskDelay(15000 / portTICK_PERIOD_MS); // 每15秒显示一次状态
}
}
// 安全的串口打印函数
void safePrint(String message) {
if (xSemaphoreTake(uartSemaphore, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.println("[" + String(millis()) + "] " + message);
xSemaphoreGive(uartSemaphore);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
这个例子展示了一个完整的IoT系统中信号量的使用:
- WiFi信号量:用于通知其他任务WiFi连接状态
- 传感器信号量:用于通知数据更新
- 串口互斥量:保护串口访问,避免输出混乱
信号量的获取与释放机制
信号量的获取和释放是多任务编程的核心操作。理解这些操作的内部机制,对于编写高效的并发程序至关重要。
获取信号量(Take)的过程:
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore, // 信号量句柄
TickType_t xTicksToWait // 等待时间(时钟节拍数)
);
当任务调用xSemaphoreTake()
时,FreeRTOS会执行以下步骤:
- 检查信号量计数:如果计数大于0,立即减1并返回成功
- 计数为0时的处理:如果计数为0且等待时间为0,立即返回失败
- 进入等待状态:如果计数为0且等待时间大于0,任务进入阻塞状态
- 超时处理:如果在指定时间内没有获取到信号量,返回超时错误
释放信号量(Give)的过程:
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
释放信号量的过程相对简单:
- 增加计数:将信号量计数加1(如果未达到最大值)
- 唤醒等待任务:如果有任务在等待此信号量,唤醒优先级最高的任务
- 任务切换:如果被唤醒的任务优先级更高,可能发生任务切换
让我们通过一个详细的例子来观察这个过程:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
SemaphoreHandle_t testSemaphore;
void setup() {
Serial.begin(115200);
// 创建一个初始计数为0的二进制信号量
testSemaphore = xSemaphoreCreateBinary();
// 创建多个等待任务
xTaskCreate(waitingTask, "Waiter_1", 2048, (void*)1, 1, NULL);
xTaskCreate(waitingTask, "Waiter_2", 2048, (void*)2, 1, NULL);
xTaskCreate(waitingTask, "Waiter_3", 2048, (void*)3, 1, NULL);
// 创建信号量释放任务
xTaskCreate(signalingTask, "Signaler", 2048, NULL, 2, NULL);
Serial.println("信号量测试开始!");
}
void waitingTask(void *parameter) {
int taskId = (int)parameter;
for(;;) {
Serial.printf("任务%d:开始等待信号量...\n", taskId);
TickType_t startTime = xTaskGetTickCount();
// 尝试获取信号量,最多等待5秒
if (xSemaphoreTake(testSemaphore, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
TickType_t endTime = xTaskGetTickCount();
TickType_t waitTime = endTime - startTime;
Serial.printf("任务%d:成功获取信号量!等待时间:%d ms\n",
taskId, waitTime * portTICK_PERIOD_MS);
// 模拟使用资源
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.printf("任务%d:使用完毕,准备下次等待\n", taskId);
} else {
Serial.printf("任务%d:等待信号量超时!\n", taskId);
}
// 等待一段时间再次尝试
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void signalingTask(void *parameter) {
for(;;) {
Serial.println("信号发送者:等待3秒后释放信号量...");
vTaskDelay(3000 / portTICK_PERIOD_MS);
Serial.println("信号发送者:释放信号量!");
xSemaphoreGive(testSemaphore);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
运行这个程序,你会看到类似这样的输出:
信号量测试开始!
任务1:开始等待信号量...
任务2:开始等待信号量...
任务3:开始等待信号量...
信号发送者:等待3秒后释放信号量...
信号发送者:释放信号量!
任务1:成功获取信号量!等待时间:3000 ms
任务1:使用完毕,准备下次等待
任务1:开始等待信号量...
信号发送者:等待3秒后释放信号量...
信号发送者:释放信号量!
任务2:成功获取信号量!等待时间:8000 ms
任务2:使用完毕,准备下次等待
从输出可以看出,每次只有一个任务能够获取到二进制信号量,其他任务必须等待。这就是信号量的"排队"机制!
等待时间的艺术:
等待时间的设置是一门艺术,需要在响应性和资源利用率之间找到平衡:
// 不同的等待策略
xSemaphoreTake(semaphore, 0); // 立即返回,不等待
xSemaphoreTake(semaphore, 100 / portTICK_PERIOD_MS); // 等待100ms
xSemaphoreTake(semaphore, portMAX_DELAY); // 永久等待
- 立即返回(0):适用于非关键操作,失败了可以稍后重试
- 有限等待:适用于有实时性要求的场景,避免任务长时间阻塞
- 永久等待:适用于必须获取资源才能继续的场景,但要小心死锁
优先级反转问题的深入剖析
优先级反转是多任务系统中一个非常经典的问题,它就像是"小鬼当家"——低优先级任务居然能够阻塞高优先级任务!
什么是优先级反转?
想象这样一个场景:
- 任务H(高优先级)需要访问某个共享资源
- 任务L(低优先级)正在使用这个资源
- 任务M(中等优先级)开始运行,抢占了任务L的CPU时间
结果就是:任务H被任务L阻塞,而任务L又被任务M抢占,导致任务H实际上被中等优先级的任务M间接阻塞了!这就是优先级反转。
让我们用代码来重现这个经典问题:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
SemaphoreHandle_t resourceSemaphore;
void setup() {
Serial.begin(115200);
// 创建一个互斥量来保护共享资源
resourceSemaphore = xSemaphoreCreateMutex();
// 创建三个不同优先级的任务
xTaskCreate(lowPriorityTask, "Low_Priority", 2048, NULL, 1, NULL);
xTaskCreate(mediumPriorityTask, "Medium_Priority", 2048, NULL, 2, NULL);
xTaskCreate(highPriorityTask, "High_Priority", 2048, NULL, 3, NULL);
Serial.println("优先级反转演示开始!");
}
void lowPriorityTask(void *parameter) {
for(;;) {
Serial.println("低优先级任务:尝试获取资源...");
if (xSemaphoreTake(resourceSemaphore, portMAX_DELAY) == pdTRUE) {
Serial.println("低优先级任务:获取资源成功,开始长时间操作...");
// 模拟长时间的资源使用
for (int i = 0; i < 10; i++) {
Serial.printf("低优先级任务:正在使用资源... %d/10\n", i + 1);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
Serial.println("低优先级任务:释放资源");
xSemaphoreGive(resourceSemaphore);
}
vTaskDelay(10000 / portTICK_PERIOD_MS);
}
}
void mediumPriorityTask(void *parameter) {
vTaskDelay(2000 / portTICK_PERIOD_MS); // 延迟启动
for(;;) {
Serial.println("中等优先级任务:开始CPU密集型操作...");
// 模拟CPU密集型操作(不使用共享资源)
for (int i = 0; i < 20; i++) {
Serial.printf("中等优先级任务:计算中... %d/20\n", i + 1);
vTaskDelay(200 / portTICK_PERIOD_MS);
}
Serial.println("中等优先级任务:操作完成");
vTaskDelay(8000 / portTICK_PERIOD_MS);
}
}
void highPriorityTask(void *parameter) {
vTaskDelay(3000 / portTICK_PERIOD_MS); // 延迟启动,让低优先级任务先获取资源
for(;;) {
Serial.println("高优先级任务:急需访问共享资源!");
TickType_t startTime = xTaskGetTickCount();
if (xSemaphoreTake(resourceSemaphore, portMAX_DELAY) == pdTRUE) {
TickType_t endTime = xTaskGetTickCount();
TickType_t waitTime = endTime - startTime;
Serial.printf("高优先级任务:终于获取到资源!等待时间:%d ms\n",
waitTime * portTICK_PERIOD_MS);
// 高优先级任务通常需要快速完成
Serial.println("高优先级任务:快速使用资源");
vTaskDelay(100 / portTICK_PERIOD_MS);
Serial.println("高优先级任务:释放资源");
xSemaphoreGive(resourceSemaphore);
}
vTaskDelay(15000 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
运行这个程序,你会看到一个有趣的现象:高优先级任务明明应该优先执行,但却被迫等待低优先级任务释放资源。而在等待期间,中等优先级任务却能够抢占CPU,进一步延长了高优先级任务的等待时间!
优先级继承:解决方案的精髓
FreeRTOS提供了一个优雅的解决方案:优先级继承(Priority Inheritance)。当高优先级任务被低优先级任务持有的互斥量阻塞时,低优先级任务会临时继承高优先级任务的优先级,直到释放互斥量。
这就像是"临时提拔"——为了让高优先级任务能够尽快获得资源,系统临时提升了低优先级任务的优先级,让它能够抢占中等优先级任务的CPU时间,尽快完成工作并释放资源。
// 使用支持优先级继承的互斥量
SemaphoreHandle_t priorityInheritanceMutex;
void setup() {
Serial.begin(115200);
// 创建支持优先级继承的互斥量
priorityInheritanceMutex = xSemaphoreCreateMutex();
// 注意:FreeRTOS的互斥量默认支持优先级继承
// 这与普通的二进制信号量不同!
xTaskCreate(demonstratePriorityInheritance, "Demo_Task", 2048, NULL, 1, NULL);
Serial.println("优先级继承演示");
}
void demonstratePriorityInheritance(void *parameter) {
for(;;) {
Serial.println("=== 优先级继承机制说明 ===");
Serial.println("1. 互斥量自动支持优先级继承");
Serial.println("2. 当高优先级任务被阻塞时,持有互斥量的低优先级任务会临时提升优先级");
Serial.println("3. 这样可以防止中等优先级任务无限期地抢占CPU");
Serial.println("4. 互斥量释放后,任务优先级恢复正常");
Serial.println("=============================\n");
vTaskDelay(10000 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
信号量 vs 互斥量在优先级反转方面的区别:
这是一个很多人容易搞混的地方:
- 二进制信号量:不支持优先级继承,可能发生优先级反转
- 互斥量:自动支持优先级继承,有效防止优先级反转
所以,当你需要保护共享资源时,应该使用互斥量而不是二进制信号量!
互斥量完全指南:独占资源的守护神
互斥量的工作原理与内存模型
互斥量(Mutex)这个名字来源于"Mutual Exclusion"(互相排斥),它就像是一把智能锁,确保同一时间只有一个任务能够访问被保护的资源。
与信号量不同,互斥量有一个重要的概念:所有权(Ownership)。只有获取了互斥量的任务才能释放它,这就像是"谁借的钱谁还"一样简单明了。
互斥量的内存结构:
在ESP32的FreeRTOS实现中,互斥量实际上是一个特殊的信号量,但它包含了额外的信息:
typedef struct {
UBaseType_t uxRecursiveCallCount; // 递归调用计数
void *pxMutexHolder; // 持有者任务句柄
UBaseType_t uxBasePriority; // 持有者的原始优先级
// ... 其他内部字段
} MutexControlBlock_t;
这个结构让互斥量能够:
- 跟踪哪个任务持有了互斥量
- 实现优先级继承机制
- 支持递归获取(对于递归互斥量)
- 防止错误的释放操作
让我们通过一个详细的例子来理解互斥量的工作原理:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
SemaphoreHandle_t sharedResourceMutex;
int sharedCounter = 0;
void setup() {
Serial.begin(115200);
// 创建互斥量
sharedResourceMutex = xSemaphoreCreateMutex();
if (sharedResourceMutex == NULL) {
Serial.println("互斥量创建失败!");
return;
}
// 创建多个任务来竞争共享资源
xTaskCreate(incrementTask, "Increment_1", 2048, (void*)"任务A", 2, NULL);
xTaskCreate(incrementTask, "Increment_2", 2048, (void*)"任务B", 2, NULL);
xTaskCreate(incrementTask, "Increment_3", 2048, (void*)"任务C", 2, NULL);
xTaskCreate(monitorTask, "Monitor", 2048, NULL, 1, NULL);
Serial.println("互斥量演示开始!");
}
void incrementTask(void *parameter) {
const char* taskName = (const char*)parameter;
for(;;) {
Serial.printf("%s:尝试获取互斥量...\n", taskName);
// 获取互斥量,保护共享资源
if (xSemaphoreTake(sharedResourceMutex, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.printf("%s:成功获取互斥量,开始修改共享资源\n", taskName);
// 临界区开始 - 只有一个任务能执行这里的代码
int oldValue = sharedCounter;
// 模拟复杂的操作过程
Serial.printf("%s:读取当前值 = %d\n", taskName, oldValue);
vTaskDelay(1000 / portTICK_PERIOD_MS); // 模拟处理时间
sharedCounter = oldValue + 1;
Serial.printf("%s:更新后的值 = %d\n", taskName, sharedCounter);
vTaskDelay(500 / portTICK_PERIOD_MS); // 模拟额外处理
// 临界区结束
Serial.printf("%s:释放互斥量\n", taskName);
xSemaphoreGive(sharedResourceMutex);
} else {
Serial.printf("%s:获取互斥量超时!\n", taskName);
}
// 等待一段时间再次尝试
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void monitorTask(void *parameter) {
for(;;) {
Serial.printf("监控任务:当前共享计数器值 = %d\n", sharedCounter);
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
运行这个程序,你会看到类似这样的输出:
互斥量演示开始!
任务A:尝试获取互斥量...
任务B:尝试获取互斥量...
任务C:尝试获取互斥量...
任务A:成功获取互斥量,开始修改共享资源
任务A:读取当前值 = 0
监控任务:当前共享计数器值 = 0
任务A:更新后的值 = 1
任务A:释放互斥量
任务B:成功获取互斥量,开始修改共享资源
任务B:读取当前值 = 1
任务B:更新后的值 = 2
任务B:释放互斥量
监控任务:当前共享计数器值 = 2
从输出可以看出,即使有多个任务同时竞争,互斥量确保了每次只有一个任务能够修改共享资源,避免了数据竞争。
递归互斥量:解决重入问题的利器
有时候,我们会遇到这样的情况:一个任务已经获取了互斥量,但在执行过程中又需要再次获取同一个互斥量。如果使用普通的互斥量,这会导致死锁!
递归互斥量就是为了解决这个问题而设计的。它允许同一个任务多次获取同一个互斥量,只要获取和释放的次数匹配即可。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
SemaphoreHandle_t recursiveMutex;
void setup() {
Serial.begin(115200);
// 创建递归互斥量
recursiveMutex = xSemaphoreCreateRecursiveMutex();
if (recursiveMutex == NULL) {
Serial.println("递归互斥量创建失败!");
return;
}
xTaskCreate(recursiveTask, "Recursive_Task", 2048, NULL, 1, NULL);
Serial.println("递归互斥量演示开始!");
}
```cpp
void recursiveTask(void *parameter) {
for(;;) {
Serial.println("开始递归互斥量演示...");
// 调用需要互斥量保护的函数
processDataLevel1();
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
void processDataLevel1() {
Serial.println("Level 1: 尝试获取递归互斥量...");
if (xSemaphoreTakeRecursive(recursiveMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.println("Level 1: 成功获取互斥量");
// 模拟一些处理
vTaskDelay(500 / portTICK_PERIOD_MS);
// 调用另一个也需要同一个互斥量的函数
processDataLevel2();
Serial.println("Level 1: 释放互斥量");
xSemaphoreGiveRecursive(recursiveMutex);
} else {
Serial.println("Level 1: 获取互斥量失败!");
}
}
void processDataLevel2() {
Serial.println("Level 2: 尝试获取递归互斥量...");
if (xSemaphoreTakeRecursive(recursiveMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.println("Level 2: 成功获取互斥量(递归)");
// 模拟更多处理
vTaskDelay(300 / portTICK_PERIOD_MS);
// 甚至可以再次递归
processDataLevel3();
Serial.println("Level 2: 释放互斥量");
xSemaphoreGiveRecursive(recursiveMutex);
} else {
Serial.println("Level 2: 获取互斥量失败!");
}
}
void processDataLevel3() {
Serial.println("Level 3: 尝试获取递归互斥量...");
if (xSemaphoreTakeRecursive(recursiveMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.println("Level 3: 成功获取互斥量(再次递归)");
// 最终的处理逻辑
Serial.println("Level 3: 执行核心业务逻辑");
vTaskDelay(200 / portTICK_PERIOD_MS);
Serial.println("Level 3: 释放互斥量");
xSemaphoreGiveRecursive(recursiveMutex);
} else {
Serial.println("Level 3: 获取互斥量失败!");
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
这个例子展示了递归互斥量的强大之处:同一个任务可以多次获取同一个互斥量,只要确保获取和释放的次数匹配即可。
递归互斥量的内部计数机制:
递归互斥量内部维护一个计数器,记录被同一个任务获取的次数:
第1次获取:计数器 = 1
第2次获取:计数器 = 2
第3次获取:计数器 = 3
...
第1次释放:计数器 = 2
第2次释放:计数器 = 1
第3次释放:计数器 = 0(互斥量真正释放)
使用递归互斥量的经典场景:
- 函数调用链:函数A调用函数B,函数B调用函数C,它们都需要同一个互斥量
- 面向对象编程:类的公有方法调用私有方法,都需要保护同一个资源
- 状态机实现:不同状态处理函数可能相互调用,需要保护状态变量
互斥量的超时机制与错误处理
在实际项目中,合理的超时机制和错误处理是确保系统稳定性的关键。没有人愿意看到自己的ESP32因为一个任务无限期等待而整个系统"卡死"。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
SemaphoreHandle_t criticalResourceMutex;
int criticalResource = 0;
void setup() {
Serial.begin(115200);
criticalResourceMutex = xSemaphoreCreateMutex();
// 创建不同超时策略的任务
xTaskCreate(impatientTask, "Impatient", 2048, NULL, 2, NULL);
xTaskCreate(patientTask, "Patient", 2048, NULL, 1, NULL);
xTaskCreate(resourceHogTask, "ResourceHog", 2048, NULL, 1, NULL);
Serial.println("超时机制演示开始!");
}
void impatientTask(void *parameter) {
for(;;) {
Serial.println("急性子任务:我等不了太久!");
// 只等待500ms
if (xSemaphoreTake(criticalResourceMutex, 500 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.println("急性子任务:太好了,立即获取到资源!");
criticalResource += 10;
Serial.printf("急性子任务:快速处理,资源值 = %d\n", criticalResource);
vTaskDelay(200 / portTICK_PERIOD_MS); // 快速处理
xSemaphoreGive(criticalResourceMutex);
Serial.println("急性子任务:快速释放资源");
} else {
Serial.println("急性子任务:等不及了,去做别的事情!");
// 执行备用方案
Serial.println("急性子任务:执行备用处理逻辑");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
void patientTask(void *parameter) {
for(;;) {
Serial.println("耐心任务:我可以等待较长时间");
// 等待5秒
TickType_t startTime = xTaskGetTickCount();
if (xSemaphoreTake(criticalResourceMutex, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
TickType_t waitTime = xTaskGetTickCount() - startTime;
Serial.printf("耐心任务:等待了 %d ms 后获取到资源\n",
waitTime * portTICK_PERIOD_MS);
criticalResource += 50;
Serial.printf("耐心任务:仔细处理,资源值 = %d\n", criticalResource);
vTaskDelay(1000 / portTICK_PERIOD_MS); // 仔细处理
xSemaphoreGive(criticalResourceMutex);
Serial.println("耐心任务:处理完成,释放资源");
} else {
Serial.println("耐心任务:连我都等超时了,肯定有问题!");
// 记录错误或采取恢复措施
logError("MUTEX_TIMEOUT", "耐心任务等待互斥量超时");
}
vTaskDelay(4000 / portTICK_PERIOD_MS);
}
}
void resourceHogTask(void *parameter) {
vTaskDelay(2000 / portTICK_PERIOD_MS); // 延迟启动
for(;;) {
Serial.println("资源占用者:我要长时间使用资源!");
if (xSemaphoreTake(criticalResourceMutex, portMAX_DELAY) == pdTRUE) {
Serial.println("资源占用者:开始长时间占用资源...");
// 模拟长时间的复杂操作
for (int i = 0; i < 10; i++) {
criticalResource += 1;
Serial.printf("资源占用者:处理步骤 %d/10,资源值 = %d\n",
i + 1, criticalResource);
vTaskDelay(800 / portTICK_PERIOD_MS);
}
Serial.println("资源占用者:终于完成了,释放资源");
xSemaphoreGive(criticalResourceMutex);
}
vTaskDelay(15000 / portTICK_PERIOD_MS); // 长时间休息
}
}
void logError(const char* errorCode, const char* description) {
Serial.printf("错误日志 [%s]: %s (时间: %d ms)\n",
errorCode, description, millis());
// 在实际项目中,这里可以:
// 1. 写入EEPROM或SD卡
// 2. 发送到远程服务器
// 3. 触发看门狗重启
// 4. 激活备用系统
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
超时策略的选择指南:
- 立即返回(0):用于非关键操作,失败后有备用方案
- 短超时(100-1000ms):用于实时性要求高的操作
- 中等超时(1-5秒):用于重要但不紧急的操作
- 长超时(5-30秒):用于关键操作,但需要防止死锁
- 永久等待(portMAX_DELAY):只在确定不会死锁时使用
错误处理的最佳实践:
// 错误处理模板
BaseType_t mutexResult = xSemaphoreTake(myMutex, timeoutTicks);
switch (mutexResult) {
case pdTRUE:
// 成功获取互斥量
// 执行临界区代码
xSemaphoreGive(myMutex);
break;
case pdFALSE:
// 获取失败(超时)
Serial.println("警告:获取互斥量超时");
// 选择处理策略:
// 1. 执行备用方案
// 2. 记录错误日志
// 3. 通知用户
// 4. 重试操作
handleMutexTimeout();
break;
default:
// 不应该到达这里
Serial.println("错误:未知的互斥量返回值");
break;
}
死锁问题的识别与预防
死锁是多任务编程中最令人头疼的问题之一。想象两个人在狭窄的桥上相遇,都不肯让路,结果谁也过不去。在程序中,死锁就是两个或多个任务相互等待对方释放资源,导致所有任务都无法继续执行。
经典的死锁场景:
// 这是一个会导致死锁的危险代码示例!
SemaphoreHandle_t mutexA;
SemaphoreHandle_t mutexB;
void task1(void *parameter) {
for(;;) {
// 任务1:先获取A,再获取B
xSemaphoreTake(mutexA, portMAX_DELAY);
Serial.println("任务1:获取到互斥量A");
vTaskDelay(100 / portTICK_PERIOD_MS); // 模拟处理时间
xSemaphoreTake(mutexB, portMAX_DELAY);
Serial.println("任务1:获取到互斥量B");
// 执行需要两个资源的操作
Serial.println("任务1:使用资源A和B");
xSemaphoreGive(mutexB);
xSemaphoreGive(mutexA);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void task2(void *parameter) {
for(;;) {
// 任务2:先获取B,再获取A(顺序相反!)
xSemaphoreTake(mutexB, portMAX_DELAY);
Serial.println("任务2:获取到互斥量B");
vTaskDelay(100 / portTICK_PERIOD_MS); // 模拟处理时间
xSemaphoreTake(mutexA, portMAX_DELAY); // 死锁发生在这里!
Serial.println("任务2:获取到互斥量A");
// 这里的代码永远不会执行
Serial.println("任务2:使用资源A和B");
xSemaphoreGive(mutexA);
xSemaphoreGive(mutexB);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
死锁预防的黄金法则:
- 资源排序法:所有任务都按相同顺序获取多个互斥量
- 超时机制:避免使用
portMAX_DELAY
,总是设置合理的超时时间 - 资源分层:将资源分为不同层次,只能从低层向高层获取
- 银行家算法:在获取资源前检查是否会导致死锁
让我们看一个正确的实现:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
SemaphoreHandle_t mutexA;
SemaphoreHandle_t mutexB;
void setup() {
Serial.begin(115200);
mutexA = xSemaphoreCreateMutex();
mutexB = xSemaphoreCreateMutex();
xTaskCreate(safeTask1, "Safe_Task_1", 2048, NULL, 1, NULL);
xTaskCreate(safeTask2, "Safe_Task_2", 2048, NULL, 1, NULL);
xTaskCreate(deadlockDetector, "Deadlock_Detector", 2048, NULL, 3, NULL);
Serial.println("死锁预防演示开始!");
}
// 安全的任务实现:使用资源排序法
bool acquireMultipleMutexes(SemaphoreHandle_t first, SemaphoreHandle_t second,
const char* taskName, TickType_t timeout) {
Serial.printf("%s:尝试获取第一个互斥量...\n", taskName);
if (xSemaphoreTake(first, timeout) == pdTRUE) {
Serial.printf("%s:获取第一个互斥量成功\n", taskName);
Serial.printf("%s:尝试获取第二个互斥量...\n", taskName);
if (xSemaphoreTake(second, timeout) == pdTRUE) {
Serial.printf("%s:获取第二个互斥量成功\n", taskName);
return true;
} else {
Serial.printf("%s:获取第二个互斥量超时,释放第一个\n", taskName);
xSemaphoreGive(first);
return false;
}
} else {
Serial.printf("%s:获取第一个互斥量超时\n", taskName);
return false;
}
}
void releaseMultipleMutexes(SemaphoreHandle_t first, SemaphoreHandle_t second,
const char* taskName) {
Serial.printf("%s:释放第二个互斥量\n", taskName);
xSemaphoreGive(second);
Serial.printf("%s:释放第一个互斥量\n", taskName);
xSemaphoreGive(first);
}
void safeTask1(void *parameter) {
for(;;) {
Serial.println("安全任务1:开始工作");
// 统一顺序:先A后B
if (acquireMultipleMutexes(mutexA, mutexB, "安全任务1", 2000 / portTICK_PERIOD_MS)) {
Serial.println("安全任务1:同时使用资源A和B");
vTaskDelay(1000 / portTICK_PERIOD_MS);
releaseMultipleMutexes(mutexA, mutexB, "安全任务1");
} else {
Serial.println("安全任务1:无法获取所需资源,执行备用方案");
}
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
void safeTask2(void *parameter) {
for(;;) {
Serial.println("安全任务2:开始工作");
// 同样的顺序:先A后B(避免死锁)
if (acquireMultipleMutexes(mutexA, mutexB, "安全任务2", 2000 / portTICK_PERIOD_MS)) {
Serial.println("安全任务2:同时使用资源A和B");
vTaskDelay(800 / portTICK_PERIOD_MS);
releaseMultipleMutexes(mutexA, mutexB, "安全任务2");
} else {
Serial.println("安全任务2:无法获取所需资源,执行备用方案");
}
vTaskDelay(4000 / portTICK_PERIOD_MS);
}
}
void deadlockDetector(void *parameter) {
TickType_t lastActivityTime = xTaskGetTickCount();
for(;;) {
TickType_t currentTime = xTaskGetTickCount();
TickType_t elapsedTime = currentTime - lastActivityTime;
if (elapsedTime > (10000 / portTICK_PERIOD_MS)) {
Serial.println("死锁检测器:警告!系统可能发生死锁");
Serial.printf("死锁检测器:已经 %d 秒没有活动\n",
elapsedTime * portTICK_PERIOD_MS / 1000);
// 在实际项目中,这里可以:
// 1. 重启系统
// 2. 记录错误日志
// 3. 发送警报
// 4. 尝试恢复
}
lastActivityTime = currentTime;
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
死锁检测的高级技巧:
// 死锁检测和恢复机制
class DeadlockDetector {
private:
struct TaskInfo {
TaskHandle_t handle;
TickType_t lastActiveTime;
const char* name;
};
TaskInfo monitoredTasks[10];
int taskCount;
public:
void addTask(TaskHandle_t task, const char* name) {
if (taskCount < 10) {
monitoredTasks[taskCount].handle = task;
monitoredTasks[taskCount].name = name;
monitoredTasks[taskCount].lastActiveTime = xTaskGetTickCount();
taskCount++;
}
}
void updateTaskActivity(TaskHandle_t task) {
for (int i = 0; i < taskCount; i++) {
if (monitoredTasks[i].handle == task) {
monitoredTasks[i].lastActiveTime = xTaskGetTickCount();
break;
}
}
}
bool checkForDeadlock(TickType_t timeoutMs) {
TickType_t currentTime = xTaskGetTickCount();
TickType_t timeoutTicks = timeoutMs / portTICK_PERIOD_MS;
for (int i = 0; i < taskCount; i++) {
if (currentTime - monitoredTasks[i].lastActiveTime > timeoutTicks) {
Serial.printf("检测到可能的死锁:任务 %s 已经 %d ms 没有活动\n",
monitoredTasks[i].name,
(currentTime - monitoredTasks[i].lastActiveTime) * portTICK_PERIOD_MS);
return true;
}
}
return false;
}
};
优先级继承算法的实现细节
优先级继承是FreeRTOS互斥量的一个重要特性,它能够有效防止优先级反转问题。让我们深入了解这个算法的工作原理。
优先级继承的工作流程:
- 高优先级任务被阻塞:当高优先级任务尝试获取被低优先级任务持有的互斥量时
- 临时提升优先级:低优先级任务临时继承高优先级任务的优先级
- 抢占中等优先级任务:提升后的低优先级任务能够抢占中等优先级任务的CPU时间
- 优先级恢复:当互斥量被释放时,任务优先级恢复到原始值
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
SemaphoreHandle_t priorityInheritanceMutex;
void setup() {
Serial.begin(115200);
priorityInheritanceMutex = xSemaphoreCreateMutex();
// 创建不同优先级的任务来演示优先级继承
xTaskCreate(lowPriorityTask, "Low_Priority", 2048, NULL, 1, NULL);
xTaskCreate(mediumPriorityTask, "Medium_Priority", 2048, NULL, 2, NULL);
xTaskCreate(highPriorityTask, "High_Priority", 2048, NULL, 3, NULL);
xTaskCreate(priorityMonitor, "Priority_Monitor", 2048, NULL, 4, NULL);
Serial.println("优先级继承演示开始!");
}
void lowPriorityTask(void *parameter) {
for(;;) {
Serial.println("低优先级任务:开始执行");
if (xSemaphoreTake(priorityInheritanceMutex, portMAX_DELAY) == pdTRUE) {
Serial.println("低优先级任务:获取到互斥量");
Serial.printf("低优先级任务:当前优先级 = %d\n",
uxTaskPriorityGet(NULL));
// 长时间持有互斥量
for (int i = 0; i < 10; i++) {
Serial.printf("低优先级任务:工作中... %d/10\n", i + 1);
vTaskDelay(500 / portTICK_PERIOD_MS);
// 检查当前优先级是否发生了变化
UBaseType_t currentPriority = uxTaskPriorityGet(NULL);
Serial.printf("低优先级任务:当前优先级 = %d\n", currentPriority);
}
Serial.println("低优先级任务:释放互斥量");
xSemaphoreGive(priorityInheritanceMutex);
Serial.printf("低优先级任务:释放后优先级 = %d\n",
uxTaskPriorityGet(NULL));
}
vTaskDelay(15000 / portTICK_PERIOD_MS);
}
}
void mediumPriorityTask(void *parameter) {
vTaskDelay(1000 / portTICK_PERIOD_MS); // 延迟启动
for(;;) {
Serial.println("中等优先级任务:开始CPU密集型工作");
for (int i = 0; i < 15; i++) {
Serial.printf("中等优先级任务:计算中... %d/15\n", i + 1);
// 模拟CPU密集型操作
volatile int dummy = 0;
for (int j = 0; j < 100000; j++) {
dummy++;
}
vTaskDelay(300 / portTICK_PERIOD_MS);
}
Serial.println("中等优先级任务:工作完成");
vTaskDelay(10000 / portTICK_PERIOD_MS);
}
}
void highPriorityTask(void *parameter) {
vTaskDelay(2000 / portTICK_PERIOD_MS); // 延迟启动,让低优先级任务先获取互斥量
for(;;) {
Serial.println("高优先级任务:紧急!需要立即访问共享资源");
TickType_t startTime = xTaskGetTickCount();
if (xSemaphoreTake(priorityInheritanceMutex, portMAX_DELAY) == pdTRUE) {
TickType_t endTime = xTaskGetTickCount();
TickType_t waitTime = endTime - startTime;
Serial.printf("高优先级任务:等待了 %d ms 后获取到互斥量\n",
waitTime * portTICK_PERIOD_MS);
Serial.println("高优先级任务:快速处理紧急事务");
vTaskDelay(200 / portTICK_PERIOD_MS);
Serial.println("高优先级任务:释放互斥量");
xSemaphoreGive(priorityInheritanceMutex);
}
vTaskDelay(20000 / portTICK_PERIOD_MS);
}
}
void priorityMonitor(void *parameter) {
TaskHandle_t lowTask = xTaskGetHandle("Low_Priority");
TaskHandle_t mediumTask = xTaskGetHandle("Medium_Priority");
TaskHandle_t highTask = xTaskGetHandle("High_Priority");
for(;;) {
Serial.println("=== 优先级监控 ===");
if (lowTask != NULL) {
Serial.printf("低优先级任务当前优先级: %d\n",
uxTaskPriorityGet(lowTask));
}
if (mediumTask != NULL) {
Serial.printf("中等优先级任务当前优先级: %d\n",
uxTaskPriorityGet(mediumTask));
}
if (highTask != NULL) {
Serial.printf("高优先级任务当前优先级: %d\n",
uxTaskPriorityGet(highTask));
}
Serial.println("==================\n");
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
优先级继承的数学模型:
优先级继承可以用以下公式表示:
Peffective=max(Poriginal,Pinherited)P_{effective} = \max(P_{original}, P_{inherited})Peffective=max(Poriginal,Pinherited)
其中:
- PeffectiveP_{effective}Peffective 是任务的有效优先级
- PoriginalP_{original}Poriginal 是任务的原始优先级
- PinheritedP_{inherited}Pinherited 是继承的优先级
优先级继承链:
在复杂的系统中,优先级继承可能形成链式传递:
任务A (优先级1) 等待 互斥量X (被任务B持有)
任务B (优先级2) 等待 互斥量Y (被任务C持有)
任务C (优先级3)
结果:
任务C 继承优先级1
任务B 继承优先级1
任务A 保持优先级1
这种链式继承确保了高优先级任务的需求能够传播到整个依赖链中。
实战演练:经典应用场景的代码实现
理论知识再丰富,不如一个实际的项目来得实在!让我们通过几个经典的应用场景,看看信号量和互斥量在真实项目中是如何发挥作用的。
生产者-消费者模式的优雅实现
生产者-消费者模式是多任务编程中最经典的模式之一。想象一个智能工厂:生产线不断生产产品,仓库负责存储,销售部门负责销售。如果生产太快,仓库会爆满;如果生产太慢,销售部门就没货可卖。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
// 缓冲区配置
#define BUFFER_SIZE 10
#define MAX_PRODUCERS 3
#define MAX_CONSUMERS 2
// 同步原语
SemaphoreHandle_t emptySlots; // 空槽位信号量
SemaphoreHandle_t fullSlots; // 满槽位信号量
SemaphoreHandle_t bufferMutex; // 缓冲区互斥量
// 共享缓冲区
typedef struct {
int data;
int producerId;
unsigned long timestamp;
} Product;
Product buffer[BUFFER_SIZE];
int bufferIn = 0; // 生产者写入位置
int bufferOut = 0; // 消费者读取位置
void setup() {
Serial.begin(115200);
// 创建同步原语
emptySlots = xSemaphoreCreateCounting(BUFFER_SIZE, BUFFER_SIZE); // 初始全空
fullSlots = xSemaphoreCreateCounting(BUFFER_SIZE, 0); // 初始无产品
bufferMutex = xSemaphoreCreateMutex();
if (emptySlots == NULL || fullSlots == NULL || bufferMutex == NULL) {
Serial.println("信号量创建失败!");
return;
}
// 创建生产者任务
for (int i = 0; i < MAX_PRODUCERS; i++) {
char taskName[20];
sprintf(taskName, "Producer_%d", i + 1);
xTaskCreate(producerTask, taskName, 2048, (void*)(i + 1), 2, NULL);
}
// 创建消费者任务
for (int i = 0; i < MAX_CONSUMERS; i++) {
char taskName[20];
sprintf(taskName, "Consumer_%d", i + 1);
xTaskCreate(consumerTask, taskName, 2048, (void*)(i + 1), 1, NULL);
}
// 创建监控任务
xTaskCreate(monitorTask, "Monitor", 2048, NULL, 3, NULL);
Serial.println("生产者-消费者系统启动!");
}
void producerTask(void *parameter) {
int producerId = (int)parameter;
int productCounter = 0;
for(;;) {
// 生产产品
productCounter++;
Product newProduct = {
.data = productCounter,
.producerId = producerId,
.timestamp = millis()
};
Serial.printf("生产者%d:生产了产品#%d\n", producerId, productCounter);
// 等待空槽位
if (xSemaphoreTake(emptySlots, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
// 获取缓冲区访问权限
if (xSemaphoreTake(bufferMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
// 临界区:写入缓冲区
buffer[bufferIn] = newProduct;
Serial.printf("生产者%d:将产品#%d放入缓冲区位置%d\n",
producerId, productCounter, bufferIn);
bufferIn = (bufferIn + 1) % BUFFER_SIZE;
xSemaphoreGive(bufferMutex);
// 通知消费者有新产品
xSemaphoreGive(fullSlots);
} else {
Serial.printf("生产者%d:获取缓冲区访问权限超时\n", producerId);
// 归还空槽位
xSemaphoreGive(emptySlots);
}
} else {
Serial.printf("生产者%d:缓冲区已满,等待超时\n", producerId);
}
// 模拟生产时间
vTaskDelay((1000 + random(2000)) / portTICK_PERIOD_MS);
}
}
void consumerTask(void *parameter) {
int consumerId = (int)parameter;
for(;;) {
// 等待产品
if (xSemaphoreTake(fullSlots, 3000 / portTICK_PERIOD_MS) == pdTRUE) {
// 获取缓冲区访问权限
if (xSemaphoreTake(bufferMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
// 临界区:从缓冲区读取
Product product = buffer[bufferOut];
Serial.printf("消费者%d:从位置%d取出产品#%d (生产者%d生产)\n",
consumerId, bufferOut, product.data, product.producerId);
bufferOut = (bufferOut + 1) % BUFFER_SIZE;
xSemaphoreGive(bufferMutex);
// 通知生产者有空槽位
xSemaphoreGive(emptySlots);
// 模拟消费处理时间
Serial.printf("消费者%d:正在处理产品#%d...\n", consumerId, product.data);
vTaskDelay((500 + random(1000)) / portTICK_PERIOD_MS);
Serial.printf("消费者%d:产品#%d处理完成\n", consumerId, product.data);
} else {
Serial.printf("消费者%d:获取缓冲区访问权限超时\n", consumerId);
// 归还产品槽位
xSemaphoreGive(fullSlots);
}
} else {
Serial.printf("消费者%d:没有产品可消费\n", consumerId);
}
// 消费间隔
vTaskDelay((800 + random(1200)) / portTICK_PERIOD_MS);
}
}
void monitorTask(void *parameter) {
for(;;) {
// 获取当前缓冲区状态
UBaseType_t emptyCount = uxSemaphoreGetCount(emptySlots);
UBaseType_t fullCount = uxSemaphoreGetCount(fullSlots);
Serial.println("=== 缓冲区状态监控 ===");
Serial.printf("空槽位数量: %d/%d\n", emptyCount, BUFFER_SIZE);
Serial.printf("产品数量: %d/%d\n", fullCount, BUFFER_SIZE);
Serial.printf("缓冲区利用率: %.1f%%\n", (float)fullCount / BUFFER_SIZE * 100);
if (fullCount == 0) {
Serial.println("警告:缓冲区为空,消费者可能饥饿!");
} else if (emptyCount == 0) {
Serial.println("警告:缓冲区已满,生产者可能阻塞!");
}
Serial.println("========================\n");
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
这个实现展示了生产者-消费者模式的精髓:
- 计数信号量管理缓冲区容量
- 互斥量保护共享缓冲区
- 监控任务实时显示系统状态
多任务访问串口的同步控制
在ESP32项目中,多个任务都需要向串口输出信息是很常见的情况。如果没有同步控制,输出的信息会混乱不堪,就像多个人同时说话一样。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
SemaphoreHandle_t serialMutex;
SemaphoreHandle_t logSemaphore;
// 日志级别枚举
typedef enum {
LOG_DEBUG = 0,
LOG_INFO = 1,
LOG_WARNING = 2,
LOG_ERROR = 3
} LogLevel;
// 日志消息结构
typedef struct {
LogLevel level;
char message[128];
char taskName[16];
unsigned long timestamp;
} LogMessage;
// 日志缓冲区
#define LOG_BUFFER_SIZE 20
LogMessage logBuffer[LOG_BUFFER_SIZE];
int logBufferIn = 0;
int logBufferOut = 0;
int logBufferCount = 0;
void setup() {
Serial.begin(115200);
// 创建串口互斥量
serialMutex = xSemaphoreCreateMutex();
logSemaphore = xSemaphoreCreateBinary();
if (serialMutex == NULL || logSemaphore == NULL) {
Serial.println("互斥量创建失败!");
return;
}
// 创建日志处理任务
xTaskCreate(logProcessorTask, "Log_Processor", 2048, NULL, 3, NULL);
// 创建多个业务任务
xTaskCreate(sensorTask, "Sensor_Task", 2048, NULL, 2, NULL);
xTaskCreate(networkTask, "Network_Task", 2048, NULL, 2, NULL);
xTaskCreate(storageTask, "Storage_Task", 2048, NULL, 1, NULL);
xTaskCreate(displayTask, "Display_Task", 2048, NULL, 1, NULL);
safePrint("多任务串口同步系统启动!", LOG_INFO);
}
// 安全的串口打印函数
void safePrint(const char* message, LogLevel level) {
const char* levelStrings[] = {"DEBUG", "INFO", "WARN", "ERROR"};
const char* taskName = pcTaskGetTaskName(NULL);
if (xSemaphoreTake(serialMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.printf("[%8lu] [%s] [%s] %s\n",
millis(), levelStrings[level], taskName, message);
xSemaphoreGive(serialMutex);
} else {
// 如果无法获取互斥量,将消息加入缓冲区
addLogToBuffer(message, level, taskName);
}
}
// 添加日志到缓冲区
void addLogToBuffer(const char* message, LogLevel level, const char* taskName) {
if (logBufferCount < LOG_BUFFER_SIZE) {
LogMessage* logMsg = &logBuffer[logBufferIn];
logMsg->level = level;
logMsg->timestamp = millis();
strncpy(logMsg->message, message, sizeof(logMsg->message) - 1);
strncpy(logMsg->taskName, taskName, sizeof(logMsg->taskName) - 1);
logBufferIn = (logBufferIn + 1) % LOG_BUFFER_SIZE;
logBufferCount++;
// 通知日志处理任务
xSemaphoreGive(logSemaphore);
}
}
// 日志处理任务
void logProcessorTask(void *parameter) {
const char* levelStrings[] = {"DEBUG", "INFO", "WARN", "ERROR"};
for(;;) {
// 等待日志消息
if (xSemaphoreTake(logSemaphore, portMAX_DELAY) == pdTRUE) {
if (logBufferCount > 0) {
LogMessage* logMsg = &logBuffer[logBufferOut];
// 获取串口访问权限
if (xSemaphoreTake(serialMutex, 2000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.printf("[%8lu] [%s] [%s] %s (从缓冲区)\n",
logMsg->timestamp, levelStrings[logMsg->level],
logMsg->taskName, logMsg->message);
xSemaphoreGive(serialMutex);
logBufferOut = (logBufferOut + 1) % LOG_BUFFER_SIZE;
logBufferCount--;
}
}
}
}
}
void sensorTask(void *parameter) {
float temperature = 25.0;
float humidity = 60.0;
for(;;) {
// 模拟传感器读取
temperature += (random(-10, 11) / 10.0);
humidity += (random(-5, 6) / 10.0);
char message[64];
sprintf(message, "温度: %.1f°C, 湿度: %.1f%%", temperature, humidity);
safePrint(message, LOG_INFO);
if (temperature > 35.0) {
safePrint("温度过高警告!", LOG_WARNING);
}
if (humidity < 30.0 || humidity > 80.0) {
safePrint("湿度异常!", LOG_WARNING);
}
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
void networkTask(void *parameter) {
bool connected = false;
int connectionAttempts = 0;
for(;;) {
if (!connected) {
connectionAttempts++;
char message[64];
sprintf(message, "尝试连接网络... (第%d次)", connectionAttempts);
safePrint(message, LOG_INFO);
```cpp
// 模拟网络连接
vTaskDelay(2000 / portTICK_PERIOD_MS);
if (random(0, 100) < 70) { // 70% 成功率
connected = true;
safePrint("网络连接成功!", LOG_INFO);
connectionAttempts = 0;
} else {
safePrint("网络连接失败,稍后重试", LOG_ERROR);
if (connectionAttempts >= 5) {
safePrint("网络连接多次失败,可能存在问题", LOG_ERROR);
connectionAttempts = 0;
}
}
} else {
// 已连接,模拟数据传输
if (random(0, 100) < 10) { // 10% 断开概率
connected = false;
safePrint("网络连接断开!", LOG_WARNING);
} else {
safePrint("数据上传成功", LOG_DEBUG);
}
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
void storageTask(void *parameter) {
int fileCount = 0;
for(;;) {
// 模拟文件操作
fileCount++;
char message[64];
sprintf(message, "保存文件 data_%d.txt", fileCount);
safePrint(message, LOG_INFO);
// 模拟写入时间
vTaskDelay(1500 / portTICK_PERIOD_MS);
if (random(0, 100) < 5) { // 5% 失败率
safePrint("文件写入失败,磁盘空间不足?", LOG_ERROR);
} else {
safePrint("文件保存成功", LOG_DEBUG);
}
vTaskDelay(8000 / portTICK_PERIOD_MS);
}
}
void displayTask(void *parameter) {
for(;;) {
safePrint("更新显示内容", LOG_DEBUG);
// 模拟显示更新
vTaskDelay(500 / portTICK_PERIOD_MS);
if (random(0, 100) < 15) { // 15% 概率显示特殊信息
safePrint("用户交互检测", LOG_INFO);
}
vTaskDelay(4000 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
SPI总线的多设备管理
SPI总线是ESP32中常用的通信接口,但它是半双工的,同一时间只能与一个设备通信。这就需要用信号量来协调多个任务对SPI总线的访问。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "SPI.h"
// SPI设备信息结构
typedef struct {
int csPin;
int clockSpeed;
int dataMode;
const char* deviceName;
} SPIDevice;
// SPI总线管理
SemaphoreHandle_t spiBusMutex;
SemaphoreHandle_t spiResultSemaphore;
// 定义SPI设备
SPIDevice devices[] = {
{5, 1000000, SPI_MODE0, "温度传感器"},
{17, 2000000, SPI_MODE1, "加速度计"},
{16, 500000, SPI_MODE0, "SD卡"},
{4, 8000000, SPI_MODE2, "显示屏"}
};
#define DEVICE_COUNT (sizeof(devices) / sizeof(devices[0]))
// 共享结果缓冲区
typedef struct {
int deviceId;
uint8_t data[32];
int dataLength;
bool success;
unsigned long timestamp;
} SPIResult;
SPIResult spiResults[10];
int resultIndex = 0;
void setup() {
Serial.begin(115200);
// 初始化SPI
SPI.begin();
// 创建SPI总线互斥量
spiBusMutex = xSemaphoreCreateMutex();
spiResultSemaphore = xSemaphoreCreateBinary();
if (spiBusMutex == NULL || spiResultSemaphore == NULL) {
Serial.println("SPI互斥量创建失败!");
return;
}
// 初始化片选引脚
for (int i = 0; i < DEVICE_COUNT; i++) {
pinMode(devices[i].csPin, OUTPUT);
digitalWrite(devices[i].csPin, HIGH);
}
// 创建设备访问任务
for (int i = 0; i < DEVICE_COUNT; i++) {
char taskName[20];
sprintf(taskName, "SPI_Device_%d", i);
xTaskCreate(spiDeviceTask, taskName, 2048, (void*)i, 2, NULL);
}
// 创建结果处理任务
xTaskCreate(spiResultProcessor, "SPI_Result_Processor", 2048, NULL, 1, NULL);
Serial.println("SPI多设备管理系统启动!");
}
void spiDeviceTask(void *parameter) {
int deviceId = (int)parameter;
SPIDevice* device = &devices[deviceId];
for(;;) {
Serial.printf("设备%d (%s):请求SPI总线访问\n", deviceId, device->deviceName);
// 请求SPI总线访问权限
if (xSemaphoreTake(spiBusMutex, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.printf("设备%d (%s):获得SPI总线访问权限\n", deviceId, device->deviceName);
// 配置SPI参数
SPI.beginTransaction(SPISettings(device->clockSpeed, MSBFIRST, device->dataMode));
// 选择设备
digitalWrite(device->csPin, LOW);
// 执行SPI通信
bool success = performSPITransaction(deviceId);
// 释放设备
digitalWrite(device->csPin, HIGH);
SPI.endTransaction();
Serial.printf("设备%d (%s):SPI通信%s\n",
deviceId, device->deviceName, success ? "成功" : "失败");
// 释放SPI总线
xSemaphoreGive(spiBusMutex);
} else {
Serial.printf("设备%d (%s):获取SPI总线超时!\n", deviceId, device->deviceName);
}
// 不同设备有不同的访问频率
int delayTime = 2000 + (deviceId * 1000);
vTaskDelay(delayTime / portTICK_PERIOD_MS);
}
}
bool performSPITransaction(int deviceId) {
SPIDevice* device = &devices[deviceId];
uint8_t txData[4] = {0x00, 0x01, 0x02, 0x03};
uint8_t rxData[4] = {0};
Serial.printf("设备%d (%s):开始SPI数据传输...\n", deviceId, device->deviceName);
// 模拟不同设备的通信协议
switch (deviceId) {
case 0: // 温度传感器
txData[0] = 0xD0; // 读取温度寄存器
break;
case 1: // 加速度计
txData[0] = 0x80 | 0x32; // 读取X轴数据
break;
case 2: // SD卡
txData[0] = 0x58; // CMD24 (写块)
break;
case 3: // 显示屏
txData[0] = 0x2A; // 设置列地址
break;
}
// 执行SPI传输
for (int i = 0; i < 4; i++) {
rxData[i] = SPI.transfer(txData[i]);
delayMicroseconds(10); // 设备间延迟
}
// 模拟传输时间
vTaskDelay(100 / portTICK_PERIOD_MS);
// 保存结果
SPIResult* result = &spiResults[resultIndex];
result->deviceId = deviceId;
result->dataLength = 4;
memcpy(result->data, rxData, 4);
result->success = (random(0, 100) < 90); // 90% 成功率
result->timestamp = millis();
resultIndex = (resultIndex + 1) % 10;
// 通知结果处理任务
xSemaphoreGive(spiResultSemaphore);
return result->success;
}
void spiResultProcessor(void *parameter) {
for(;;) {
// 等待SPI结果
if (xSemaphoreTake(spiResultSemaphore, portMAX_DELAY) == pdTRUE) {
// 处理最新的结果
int currentIndex = (resultIndex - 1 + 10) % 10;
SPIResult* result = &spiResults[currentIndex];
Serial.printf("结果处理器:设备%d (%s) 的数据处理\n",
result->deviceId, devices[result->deviceId].deviceName);
if (result->success) {
Serial.printf("接收数据: ");
for (int i = 0; i < result->dataLength; i++) {
Serial.printf("0x%02X ", result->data[i]);
}
Serial.println();
// 根据设备类型处理数据
processDeviceData(result);
} else {
Serial.printf("设备%d 数据传输失败\n", result->deviceId);
}
}
}
}
void processDeviceData(SPIResult* result) {
switch (result->deviceId) {
case 0: // 温度传感器
{
float temperature = (result->data[0] << 8 | result->data[1]) / 10.0;
Serial.printf("温度: %.1f°C\n", temperature);
}
break;
case 1: // 加速度计
{
int16_t accelX = (result->data[0] << 8 | result->data[1]);
Serial.printf("加速度X: %d\n", accelX);
}
break;
case 2: // SD卡
Serial.println("SD卡写入操作完成");
break;
case 3: // 显示屏
Serial.println("显示屏更新完成");
break;
}
}
void loop() {
// 主循环可以执行其他任务
static int counter = 0;
counter++;
if (counter % 10 == 0) {
Serial.printf("主循环运行中... (计数: %d)\n", counter);
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
WiFi连接状态的线程安全通知
WiFi连接状态的变化需要通知多个任务,这是一个典型的"一对多"通知场景。我们可以使用信号量来实现这种通知机制。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "WiFi.h"
// WiFi状态管理
typedef enum {
WIFI_DISCONNECTED = 0,
WIFI_CONNECTING = 1,
WIFI_CONNECTED = 2,
WIFI_FAILED = 3
} WiFiStatus;
// 同步原语
SemaphoreHandle_t wifiStatusMutex;
SemaphoreHandle_t wifiConnectedSemaphore;
SemaphoreHandle_t wifiDisconnectedSemaphore;
// 全局状态
WiFiStatus currentWiFiStatus = WIFI_DISCONNECTED;
char currentSSID[32] = "";
int connectionAttempts = 0;
unsigned long lastConnectionTime = 0;
// WiFi配置
const char* ssid = "YourWiFiSSID";
const char* password = "YourWiFiPassword";
void setup() {
Serial.begin(115200);
// 创建同步原语
wifiStatusMutex = xSemaphoreCreateMutex();
wifiConnectedSemaphore = xSemaphoreCreateBinary();
wifiDisconnectedSemaphore = xSemaphoreCreateBinary();
if (wifiStatusMutex == NULL || wifiConnectedSemaphore == NULL ||
wifiDisconnectedSemaphore == NULL) {
Serial.println("WiFi同步原语创建失败!");
return;
}
// 创建WiFi管理任务
xTaskCreate(wifiManagerTask, "WiFi_Manager", 4096, NULL, 3, NULL);
// 创建依赖WiFi的业务任务
xTaskCreate(webServerTask, "Web_Server", 4096, NULL, 2, NULL);
xTaskCreate(mqttClientTask, "MQTT_Client", 4096, NULL, 2, NULL);
xTaskCreate(ntpSyncTask, "NTP_Sync", 2048, NULL, 1, NULL);
xTaskCreate(wifiStatusMonitor, "WiFi_Monitor", 2048, NULL, 1, NULL);
Serial.println("WiFi连接状态管理系统启动!");
}
void wifiManagerTask(void *parameter) {
WiFi.mode(WIFI_STA);
for(;;) {
WiFiStatus currentStatus = getWiFiStatus();
switch (currentStatus) {
case WIFI_DISCONNECTED:
Serial.println("WiFi管理器:开始连接WiFi...");
setWiFiStatus(WIFI_CONNECTING);
WiFi.begin(ssid, password);
connectionAttempts++;
// 等待连接结果
{
int timeout = 20; // 20秒超时
while (timeout > 0 && WiFi.status() != WL_CONNECTED) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
timeout--;
Serial.printf("WiFi管理器:连接中... (%d秒)\n", 20 - timeout);
}
if (WiFi.status() == WL_CONNECTED) {
Serial.printf("WiFi管理器:连接成功!IP: %s\n", WiFi.localIP().toString().c_str());
setWiFiStatus(WIFI_CONNECTED);
strcpy(currentSSID, ssid);
lastConnectionTime = millis();
connectionAttempts = 0;
// 通知所有等待连接的任务
notifyWiFiConnected();
} else {
Serial.printf("WiFi管理器:连接失败 (第%d次尝试)\n", connectionAttempts);
setWiFiStatus(WIFI_FAILED);
if (connectionAttempts >= 5) {
Serial.println("WiFi管理器:多次连接失败,等待更长时间后重试");
vTaskDelay(30000 / portTICK_PERIOD_MS);
connectionAttempts = 0;
}
}
}
break;
case WIFI_CONNECTING:
// 等待连接完成
vTaskDelay(1000 / portTICK_PERIOD_MS);
break;
case WIFI_CONNECTED:
// 检查连接状态
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi管理器:检测到连接断开");
setWiFiStatus(WIFI_DISCONNECTED);
// 通知所有依赖WiFi的任务
notifyWiFiDisconnected();
} else {
// 连接正常,定期检查
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
break;
case WIFI_FAILED:
Serial.println("WiFi管理器:等待后重新尝试连接");
vTaskDelay(10000 / portTICK_PERIOD_MS);
setWiFiStatus(WIFI_DISCONNECTED);
break;
}
}
}
WiFiStatus getWiFiStatus() {
WiFiStatus status;
if (xSemaphoreTake(wifiStatusMutex, 100 / portTICK_PERIOD_MS) == pdTRUE) {
status = currentWiFiStatus;
xSemaphoreGive(wifiStatusMutex);
} else {
status = WIFI_DISCONNECTED; // 默认值
}
return status;
}
void setWiFiStatus(WiFiStatus newStatus) {
if (xSemaphoreTake(wifiStatusMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
currentWiFiStatus = newStatus;
xSemaphoreGive(wifiStatusMutex);
}
}
void notifyWiFiConnected() {
// 通知所有等待WiFi连接的任务
// 注意:二进制信号量只能通知一个任务,所以我们需要多次调用
xSemaphoreGive(wifiConnectedSemaphore);
xSemaphoreGive(wifiConnectedSemaphore);
xSemaphoreGive(wifiConnectedSemaphore);
}
void notifyWiFiDisconnected() {
// 通知所有需要知道断开连接的任务
xSemaphoreGive(wifiDisconnectedSemaphore);
xSemaphoreGive(wifiDisconnectedSemaphore);
xSemaphoreGive(wifiDisconnectedSemaphore);
}
void webServerTask(void *parameter) {
bool serverRunning = false;
for(;;) {
if (!serverRunning) {
Serial.println("Web服务器:等待WiFi连接...");
// 等待WiFi连接
if (xSemaphoreTake(wifiConnectedSemaphore, portMAX_DELAY) == pdTRUE) {
Serial.println("Web服务器:WiFi已连接,启动HTTP服务器");
// 模拟启动Web服务器
serverRunning = true;
Serial.printf("Web服务器:服务器运行在 http://%s/\n",
WiFi.localIP().toString().c_str());
}
} else {
// 检查WiFi状态
if (getWiFiStatus() != WIFI_CONNECTED) {
Serial.println("Web服务器:WiFi断开,停止服务器");
serverRunning = false;
} else {
// 模拟处理HTTP请求
Serial.println("Web服务器:处理HTTP请求");
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void mqttClientTask(void *parameter) {
bool mqttConnected = false;
for(;;) {
if (!mqttConnected) {
Serial.println("MQTT客户端:等待WiFi连接...");
// 等待WiFi连接
if (xSemaphoreTake(wifiConnectedSemaphore, portMAX_DELAY) == pdTRUE) {
Serial.println("MQTT客户端:WiFi已连接,连接MQTT服务器");
// 模拟MQTT连接
vTaskDelay(2000 / portTICK_PERIOD_MS);
if (random(0, 100) < 80) { // 80% 成功率
mqttConnected = true;
Serial.println("MQTT客户端:连接成功");
} else {
Serial.println("MQTT客户端:连接失败,稍后重试");
}
}
} else {
// 检查连接状态
if (getWiFiStatus() != WIFI_CONNECTED) {
Serial.println("MQTT客户端:WiFi断开,断开MQTT连接");
mqttConnected = false;
} else {
// 模拟MQTT消息处理
Serial.println("MQTT客户端:发送心跳消息");
vTaskDelay(10000 / portTICK_PERIOD_MS);
}
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void ntpSyncTask(void *parameter) {
bool timeSynced = false;
for(;;) {
if (!timeSynced) {
Serial.println("NTP同步:等待WiFi连接...");
// 等待WiFi连接
if (xSemaphoreTake(wifiConnectedSemaphore, portMAX_DELAY) == pdTRUE) {
Serial.println("NTP同步:WiFi已连接,开始时间同步");
// 模拟NTP同步
vTaskDelay(3000 / portTICK_PERIOD_MS);
timeSynced = true;
Serial.println("NTP同步:时间同步成功");
}
} else {
// 定期重新同步
if (getWiFiStatus() == WIFI_CONNECTED) {
Serial.println("NTP同步:定期时间同步");
vTaskDelay(1000 / portTICK_PERIOD_MS);
} else {
timeSynced = false;
Serial.println("NTP同步:WiFi断开,时间同步失效");
}
}
vTaskDelay(60000 / portTICK_PERIOD_MS); // 每分钟检查一次
}
}
void wifiStatusMonitor(void *parameter) {
for(;;) {
WiFiStatus status = getWiFiStatus();
const char* statusStrings[] = {"断开", "连接中", "已连接", "失败"};
Serial.println("=== WiFi状态监控 ===");
Serial.printf("状态: %s\n", statusStrings[status]);
if (status == WIFI_CONNECTED) {
Serial.printf("SSID: %s\n", currentSSID);
Serial.printf("IP地址: %s\n", WiFi.localIP().toString().c_str());
Serial.printf("信号强度: %d dBm\n", WiFi.RSSI());
Serial.printf("连接时长: %lu 秒\n", (millis() - lastConnectionTime) / 1000);
} else if (status == WIFI_FAILED) {
Serial.printf("连接尝试次数: %d\n", connectionAttempts);
}
Serial.println("===================\n");
vTaskDelay(15000 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
传感器数据的缓冲区管理
在IoT项目中,传感器数据的采集和处理往往有不同的时间要求。采集需要实时性,处理可能需要更多时间。这时候就需要一个缓冲区来平衡两者的速度差异。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
// 传感器数据结构
typedef struct {
float temperature;
float humidity;
float pressure;
float lightLevel;
unsigned long timestamp;
int sensorId;
} SensorData;
// 环形缓冲区
#define BUFFER_SIZE 50
SensorData sensorBuffer[BUFFER_SIZE];
int bufferHead = 0;
int bufferTail = 0;
int bufferCount = 0;
// 同步原语
SemaphoreHandle_t bufferMutex;
SemaphoreHandle_t dataAvailableSemaphore;
SemaphoreHandle_t bufferSpaceSemaphore;
// 统计信息
typedef struct {
unsigned long totalSamples;
unsigned long droppedSamples;
unsigned long processedSamples;
float averageProcessingTime;
} BufferStats;
BufferStats bufferStats = {0};
void setup() {
Serial.begin(115200);
// 创建同步原语
bufferMutex = xSemaphoreCreateMutex();
dataAvailableSemaphore = xSemaphoreCreateCounting(BUFFER_SIZE, 0);
bufferSpaceSemaphore = xSemaphoreCreateCounting(BUFFER_SIZE, BUFFER_SIZE);
if (bufferMutex == NULL || dataAvailableSemaphore == NULL ||
bufferSpaceSemaphore == NULL) {
Serial.println("传感器缓冲区同步原语创建失败!");
return;
}
// 创建传感器采集任务
xTaskCreate(temperatureSensorTask, "Temp_Sensor", 2048, (void*)1, 3, NULL);
xTaskCreate(humiditySensorTask, "Humidity_Sensor", 2048, (void*)2, 3, NULL);
xTaskCreate(pressureSensorTask, "Pressure_Sensor", 2048, (void*)3, 3, NULL);
xTaskCreate(lightSensorTask, "Light_Sensor", 2048, (void*)4, 3, NULL);
// 创建数据处理任务
xTaskCreate(dataProcessorTask, "Data_Processor", 4096, NULL, 2, NULL);
xTaskCreate(dataAnalyzerTask, "Data_Analyzer", 4096, NULL, 1, NULL);
// 创建统计监控任务
xTaskCreate(bufferStatsTask, "Buffer_Stats", 2048, NULL, 1, NULL);
Serial.println("传感器数据缓冲区管理系统启动!");
}
bool addSensorData(SensorData* data) {
// 尝试获取缓冲区空间
if (xSemaphoreTake(bufferSpaceSemaphore, 100 / portTICK_PERIOD_MS) == pdTRUE) {
// 获取缓冲区访问权限
if (xSemaphoreTake(bufferMutex, 200 / portTICK_PERIOD_MS) == pdTRUE) {
// 添加数据到缓冲区
sensorBuffer[bufferHead] = *data;
bufferHead = (bufferHead + 1) % BUFFER_SIZE;
bufferCount++;
bufferStats.totalSamples++;
xSemaphoreGive(bufferMutex);
// 通知数据可用
xSemaphoreGive(dataAvailableSemaphore);
return true;
} else {
// 无法获取互斥量,归还空间信号量
xSemaphoreGive(bufferSpaceSemaphore);
return false;
}
} else {
// 缓冲区已满,丢弃数据
bufferStats.droppedSamples++;
return false;
}
}
bool getSensorData(SensorData* data) {
// 等待数据可用
if (xSemaphoreTake(dataAvailableSemaphore, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
// 获取缓冲区访问权限
if (xSemaphoreTake(bufferMutex, 200 / portTICK_PERIOD_MS) == pdTRUE) {
// 从缓冲区取出数据
*data = sensorBuffer[bufferTail];
bufferTail = (bufferTail + 1) % BUFFER_SIZE;
bufferCount--;
xSemaphoreGive(bufferMutex);
// 释放缓冲区空间
xSemaphoreGive(bufferSpaceSemaphore);
return true;
} else {
// 无法获取互斥量,归还数据信号量
xSemaphoreGive(dataAvailableSemaphore);
return false;
}
}
return false;
}
void temperatureSensorTask(void *parameter) {
int sensorId = (int)parameter;
float temperature = 25.0;
for(;;) {
// 模拟温度传感器读取
temperature += (random(-20, 21) / 100.0); // ±0.2°C 变化
SensorData data = {
.temperature = temperature,
.humidity = 0,
.pressure = 0,
.lightLevel = 0,
.timestamp = millis(),
.sensorId = sensorId
};
if (addSensorData(&data)) {
Serial.printf("温度传感器:添加数据 %.2f°C\n", temperature);
} else {
Serial.println("温度传感器:缓冲区满,数据丢失!");
}
vTaskDelay(2000 / portTICK_PERIOD_MS); // 每2秒采集一次
}
}
void humiditySensorTask(void *parameter) {
int sensorId = (int)parameter;
float humidity = 60.0;
for(;;) {
// 模拟湿度传感器读取
humidity += (random(-50, 51) / 100.0); // ±0.5% 变化
if (humidity < 0) humidity = 0;
if (humidity > 100) humidity = 100;
SensorData data = {
.temperature = 0,
.humidity = humidity,
.pressure = 0,
.lightLevel = 0,
.timestamp = millis(),
.sensorId = sensorId
};
if (addSensorData(&data)) {
Serial.printf("湿度传感器:添加数据 %.1f%%\n", humidity);
} else {
Serial.println("湿度传感器:缓冲区满,数据丢失!");
}
vTaskDelay(3000 / portTICK_PERIOD_MS); // 每3秒采集一次
}
}
void pressureSensorTask(void *parameter) {
int sensorId = (int)parameter;
float pressure = 1013.25; // 标准大气压
for(;;) {
// 模拟气压传感器读取
pressure += (random(-100, 101) / 100.0); // ±1 hPa 变化
SensorData data = {
.temperature = 0,
.humidity = 0,
.pressure = pressure,
.lightLevel = 0,
.timestamp = millis(),
.sensorId = sensorId
};
if (addSensorData(&data)) {
Serial.printf("气压传感器:添加数据 %.2f hPa\n", pressure);
} else {
Serial.println("气压传感器:缓冲区满,数据丢失!");
}
vTaskDelay(5000 / portTICK_PERIOD_MS); // 每5秒采集一次
}
}
void lightSensorTask(void *parameter) {
int sensorId = (int)parameter;
float lightLevel = 500.0; // 室内光照强度
for(;;) {
// 模拟光照传感器读取
lightLevel += (random(-100, 101) / 10.0); // ±10 lux 变化
if (lightLevel < 0) lightLevel = 0;
SensorData data = {
.temperature = 0,
.humidity = 0,
.pressure = 0,
.lightLevel = lightLevel,
.timestamp = millis(),
.sensorId = sensorId
};
if (addSensorData(&data)) {
Serial.printf("光照传感器:添加数据 %.1f lux\n", lightLevel);
} else {
Serial.println("光照传感器:缓冲区满,数据丢失!");
}
vTaskDelay(4000 / portTICK_PERIOD_MS); // 每4秒采集一次
}
}
void dataProcessorTask(void *parameter) {
SensorData data;
for(;;) {
if (getSensorData(&data)) {
unsigned long startTime = millis();
Serial.printf("数据处理器:处理传感器%d的数据 (时间戳: %lu)\n",
data.sensorId, data.timestamp);
// 根据传感器类型处理数据
switch (data.sensorId) {
case 1: // 温度传感器
Serial.printf(" 温度: %.2f°C", data.temperature);
if (data.temperature > 35.0) {
Serial.print(" [高温警告]");
}
Serial.println();
break;
case 2: // 湿度传感器
Serial.printf(" 湿度: %.1f%%", data.humidity);
if (data.humidity > 80.0) {
Serial.print(" [高湿警告]");
}
Serial.println();
break;
case 3: // 气压传感器
Serial.printf(" 气压: %.2f hPa", data.pressure);
if (data.pressure < 1000.0) {
Serial.print(" [低压警告]");
}
Serial.println();
break;
case 4: // 光照传感器
Serial.printf(" 光照: %.1f lux", data.lightLevel);
if (data.lightLevel < 100.0) {
Serial.print(" [光照不足]");
}
Serial.println();
break;
}
// 模拟数据处理时间
vTaskDelay(500 / portTICK_PERIOD_MS);
unsigned long processingTime = millis() - startTime;
bufferStats.processedSamples++;
bufferStats.averageProcessingTime =
(bufferStats.averageProcessingTime * (bufferStats.processedSamples - 1) +
processingTime) / bufferStats.processedSamples;
}
}
}
void dataAnalyzerTask(void *parameter) {
SensorData data;
float tempSum = 0, humiditySum = 0, pressureSum = 0, lightSum = 0;
int tempCount = 0, humidityCount = 0, pressureCount = 0, lightCount = 0;
for(;;) {
if (getSensorData(&data)) {
Serial.printf("数据分析器:分析传感器%d的数据\n", data.sensorId);
// 累积统计数据
switch (data.sensorId) {
case 1: // 温度
tempSum += data.temperature;
tempCount++;
break;
case 2: // 湿度
humiditySum += data.humidity;
humidityCount++;
break;
case 3: // 气压
pressureSum += data.pressure;
pressureCount++;
break;
case 4: // 光照
lightSum += data.lightLevel;
lightCount++;
break;
}
// 每处理10个数据输出一次统计
if ((tempCount + humidityCount + pressureCount + lightCount) % 10 == 0) {
Serial.println("=== 数据分析统计 ===");
if (tempCount > 0) {
Serial.printf("平均温度: %.2f°C (%d个样本)\n",
tempSum / tempCount, tempCount);
}
if (humidityCount > 0) {
Serial.printf("平均湿度: %.1f%% (%d个样本)\n",
humiditySum / humidityCount, humidityCount);
}
if (pressureCount > 0) {
Serial.printf("平均气压: %.2f hPa (%d个样本)\n",
pressureSum / pressureCount, pressureCount);
}
if (lightCount > 0) {
Serial.printf("平均光照: %.1f lux (%d个样本)\n",
lightSum / lightCount, lightCount);
}
Serial.println("===================\n");
}
// 模拟更复杂的分析处理
vTaskDelay(800 / portTICK_PERIOD_MS);
}
}
}
void bufferStatsTask(void *parameter) {
for(;;) {
Serial.println("=== 缓冲区统计信息 ===");
Serial.printf("缓冲区使用率: %d/%d (%.1f%%)\n",
bufferCount, BUFFER_SIZE,
(float)bufferCount / BUFFER_SIZE * 100);
Serial.printf("总采样数: %lu\n", bufferStats.totalSamples);
Serial.printf("已处理数: %lu\n", bufferStats.processedSamples);
Serial.printf("丢失数: %lu\n", bufferStats.droppedSamples);
if (bufferStats.totalSamples > 0) {
Serial.printf("丢失率: %.2f%%\n",
(float)bufferStats.droppedSamples / bufferStats.totalSamples * 100);
}
Serial.printf("平均处理时间: %.1f ms\n", bufferStats.averageProcessingTime);
Serial.println("========================\n");
vTaskDelay(20000 / portTICK_PERIOD_MS); // 每20秒输出一次统计
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
性能优化:让你的ESP32飞起来
性能优化就像是给汽车调教引擎,合适的配置能让你的ESP32发挥出最佳性能。在多任务环境中,信号量和互斥量的使用策略直接影响系统的整体性能。
信号量vs互斥量的性能对比分析
首先,我们需要理解不同同步机制的性能特点。就像选择交通工具一样,不同的场景需要不同的选择。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// 性能测试配置
#define TEST_ITERATIONS 10000
#define TEST_TASKS 4
// 测试用的同步原语
SemaphoreHandle_t binarySemaphore;
SemaphoreHandle_t mutex;
SemaphoreHandle_t recursiveMutex;
// 性能统计结构
typedef struct {
unsigned long totalTime;
unsigned long minTime;
unsigned long maxTime;
unsigned long averageTime;
const char* testName;
} PerformanceStats;
void setup() {
Serial.begin(115200);
// 创建测试用的同步原语
binarySemaphore = xSemaphoreCreateBinary();
mutex = xSemaphoreCreateMutex();
recursiveMutex = xSemaphoreCreateRecursiveMutex();
// 初始化二进制信号量
xSemaphoreGive(binarySemaphore);
Serial.println("ESP32 同步机制性能测试开始!");
// 运行各种性能测试
runPerformanceTests();
}
void runPerformanceTests() {
Serial.println("=== 性能测试结果 ===\n");
// 测试二进制信号量
PerformanceStats binaryStats = testBinarySemaphore();
printPerformanceStats(&binaryStats);
// 测试互斥量
PerformanceStats mutexStats = testMutex();
printPerformanceStats(&mutexStats);
// 测试递归互斥量
PerformanceStats recursiveStats = testRecursiveMutex();
printPerformanceStats(&recursiveStats);
// 测试无同步(基准测试)
PerformanceStats noSyncStats = testNoSynchronization();
printPerformanceStats(&noSyncStats);
// 性能对比分析
performanceComparison(&binaryStats, &mutexStats, &recursiveStats, &noSyncStats);
}
PerformanceStats testBinarySemaphore() {
PerformanceStats stats = {0, ULONG_MAX, 0, 0, "二进制信号量"};
Serial.println("测试二进制信号量性能...");
for (int i = 0; i < TEST_ITERATIONS; i++) {
unsigned long startTime = micros();
// 获取和释放二进制信号量
xSemaphoreTake(binarySemaphore, portMAX_DELAY);
xSemaphoreGive(binarySemaphore);
unsigned long elapsedTime = micros() - startTime;
stats.totalTime += elapsedTime;
if (elapsedTime < stats.minTime) stats.minTime = elapsedTime;
if (elapsedTime > stats.maxTime) stats.maxTime = elapsedTime;
}
stats.averageTime = stats.totalTime / TEST_ITERATIONS;
return stats;
}
PerformanceStats testMutex() {
PerformanceStats stats = {0, ULONG_MAX, 0, 0, "互斥量"};
Serial.println("测试互斥量性能...");
for (int i = 0; i < TEST_ITERATIONS; i++) {
unsigned long startTime = micros();
// 获取和释放互斥量
xSemaphoreTake(mutex, portMAX_DELAY);
xSemaphoreGive(mutex);
unsigned long elapsedTime = micros() - startTime;
stats.totalTime += elapsedTime;
if (elapsedTime < stats.minTime) stats.minTime = elapsedTime;
if (elapsedTime > stats.maxTime) stats.maxTime = elapsedTime;
}
```cpp
stats.averageTime = stats.totalTime / TEST_ITERATIONS;
return stats;
}
PerformanceStats testRecursiveMutex() {
PerformanceStats stats = {0, ULONG_MAX, 0, 0, "递归互斥量"};
Serial.println("测试递归互斥量性能...");
for (int i = 0; i < TEST_ITERATIONS; i++) {
unsigned long startTime = micros();
// 获取和释放递归互斥量
xSemaphoreTakeRecursive(recursiveMutex, portMAX_DELAY);
xSemaphoreGiveRecursive(recursiveMutex);
unsigned long elapsedTime = micros() - startTime;
stats.totalTime += elapsedTime;
if (elapsedTime < stats.minTime) stats.minTime = elapsedTime;
if (elapsedTime > stats.maxTime) stats.maxTime = elapsedTime;
}
stats.averageTime = stats.totalTime / TEST_ITERATIONS;
return stats;
}
PerformanceStats testNoSynchronization() {
PerformanceStats stats = {0, ULONG_MAX, 0, 0, "无同步(基准)"};
volatile int dummyVariable = 0;
Serial.println("测试无同步基准性能...");
for (int i = 0; i < TEST_ITERATIONS; i++) {
unsigned long startTime = micros();
// 简单的变量操作作为基准
dummyVariable++;
dummyVariable--;
unsigned long elapsedTime = micros() - startTime;
stats.totalTime += elapsedTime;
if (elapsedTime < stats.minTime) stats.minTime = elapsedTime;
if (elapsedTime > stats.maxTime) stats.maxTime = elapsedTime;
}
stats.averageTime = stats.totalTime / TEST_ITERATIONS;
return stats;
}
void printPerformanceStats(PerformanceStats* stats) {
Serial.printf("=== %s ===\n", stats->testName);
Serial.printf("平均时间: %lu 微秒\n", stats->averageTime);
Serial.printf("最小时间: %lu 微秒\n", stats->minTime);
Serial.printf("最大时间: %lu 微秒\n", stats->maxTime);
Serial.printf("总时间: %lu 微秒\n", stats->totalTime);
Serial.println();
}
void performanceComparison(PerformanceStats* binary, PerformanceStats* mutex,
PerformanceStats* recursive, PerformanceStats* noSync) {
Serial.println("=== 性能对比分析 ===");
Serial.printf("基准性能(无同步): %lu 微秒\n", noSync->averageTime);
float binaryOverhead = (float)(binary->averageTime - noSync->averageTime) / noSync->averageTime * 100;
float mutexOverhead = (float)(mutex->averageTime - noSync->averageTime) / noSync->averageTime * 100;
float recursiveOverhead = (float)(recursive->averageTime - noSync->averageTime) / noSync->averageTime * 100;
Serial.printf("二进制信号量开销: %.1f%%\n", binaryOverhead);
Serial.printf("互斥量开销: %.1f%%\n", mutexOverhead);
Serial.printf("递归互斥量开销: %.1f%%\n", recursiveOverhead);
Serial.println("\n性能排序(从快到慢):");
if (binary->averageTime <= mutex->averageTime && binary->averageTime <= recursive->averageTime) {
Serial.println("1. 二进制信号量");
if (mutex->averageTime <= recursive->averageTime) {
Serial.println("2. 互斥量");
Serial.println("3. 递归互斥量");
} else {
Serial.println("2. 递归互斥量");
Serial.println("3. 互斥量");
}
} else if (mutex->averageTime <= binary->averageTime && mutex->averageTime <= recursive->averageTime) {
Serial.println("1. 互斥量");
if (binary->averageTime <= recursive->averageTime) {
Serial.println("2. 二进制信号量");
Serial.println("3. 递归互斥量");
} else {
Serial.println("2. 递归互斥量");
Serial.println("3. 二进制信号量");
}
} else {
Serial.println("1. 递归互斥量");
if (binary->averageTime <= mutex->averageTime) {
Serial.println("2. 二进制信号量");
Serial.println("3. 互斥量");
} else {
Serial.println("2. 互斥量");
Serial.println("3. 二进制信号量");
}
}
Serial.println("\n推荐使用场景:");
Serial.println("• 任务同步 → 二进制信号量");
Serial.println("• 资源保护 → 互斥量");
Serial.println("• 递归调用 → 递归互斥量");
Serial.println("• 资源计数 → 计数信号量");
Serial.println("========================\n");
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
内存占用与执行效率的权衡
在ESP32这样的嵌入式系统中,内存是珍贵的资源。我们需要在功能和内存占用之间找到平衡点。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// 内存使用分析工具
void analyzeMemoryUsage() {
Serial.println("=== ESP32 内存使用分析 ===");
// 获取堆内存信息
size_t freeHeap = esp_get_free_heap_size();
size_t minFreeHeap = esp_get_minimum_free_heap_size();
size_t totalHeap = ESP.getHeapSize();
Serial.printf("总堆内存: %d 字节\n", totalHeap);
Serial.printf("当前可用堆内存: %d 字节\n", freeHeap);
Serial.printf("历史最小可用堆内存: %d 字节\n", minFreeHeap);
Serial.printf("堆内存使用率: %.1f%%\n",
(float)(totalHeap - freeHeap) / totalHeap * 100);
// 获取任务栈信息
UBaseType_t taskCount = uxTaskGetNumberOfTasks();
Serial.printf("当前任务数量: %d\n", taskCount);
// 分析不同同步原语的内存占用
analyzeSynchronizationMemory();
Serial.println("=============================\n");
}
void analyzeSynchronizationMemory() {
Serial.println("\n--- 同步原语内存占用分析 ---");
size_t heapBefore = esp_get_free_heap_size();
// 创建各种同步原语
SemaphoreHandle_t testBinary = xSemaphoreCreateBinary();
size_t heapAfterBinary = esp_get_free_heap_size();
SemaphoreHandle_t testMutex = xSemaphoreCreateMutex();
size_t heapAfterMutex = esp_get_free_heap_size();
SemaphoreHandle_t testRecursive = xSemaphoreCreateRecursiveMutex();
size_t heapAfterRecursive = esp_get_free_heap_size();
SemaphoreHandle_t testCounting = xSemaphoreCreateCounting(10, 0);
size_t heapAfterCounting = esp_get_free_heap_size();
// 计算内存占用
Serial.printf("二进制信号量: %d 字节\n", heapBefore - heapAfterBinary);
Serial.printf("互斥量: %d 字节\n", heapAfterBinary - heapAfterMutex);
Serial.printf("递归互斥量: %d 字节\n", heapAfterMutex - heapAfterRecursive);
Serial.printf("计数信号量: %d 字节\n", heapAfterRecursive - heapAfterCounting);
// 清理资源
vSemaphoreDelete(testBinary);
vSemaphoreDelete(testMutex);
vSemaphoreDelete(testRecursive);
vSemaphoreDelete(testCounting);
Serial.println("-----------------------------");
}
// 内存优化策略演示
void demonstrateMemoryOptimization() {
Serial.println("=== 内存优化策略演示 ===");
// 策略1:使用静态分配
demonstrateStaticAllocation();
// 策略2:合理的任务栈大小
demonstrateStackOptimization();
// 策略3:同步原语的复用
demonstrateSynchronizationReuse();
Serial.println("========================\n");
}
void demonstrateStaticAllocation() {
Serial.println("策略1:静态内存分配");
size_t heapBefore = esp_get_free_heap_size();
// 动态分配(传统方式)
SemaphoreHandle_t dynamicSemaphore = xSemaphoreCreateBinary();
size_t heapAfterDynamic = esp_get_free_heap_size();
// 静态分配
StaticSemaphore_t staticSemaphoreBuffer;
SemaphoreHandle_t staticSemaphore = xSemaphoreCreateBinaryStatic(&staticSemaphoreBuffer);
size_t heapAfterStatic = esp_get_free_heap_size();
Serial.printf("动态分配堆内存消耗: %d 字节\n", heapBefore - heapAfterDynamic);
Serial.printf("静态分配堆内存消耗: %d 字节\n", heapAfterDynamic - heapAfterStatic);
Serial.println("静态分配的优势:");
Serial.println("• 不消耗堆内存");
Serial.println("• 避免内存碎片");
Serial.println("• 分配时间确定");
Serial.println("• 适合内存受限的应用\n");
// 清理资源
vSemaphoreDelete(dynamicSemaphore);
vSemaphoreDelete(staticSemaphore);
}
void demonstrateStackOptimization() {
Serial.println("策略2:任务栈大小优化");
Serial.println("不同任务类型的推荐栈大小:");
Serial.println("• 简单控制任务: 1024-2048 字节");
Serial.println("• 数据处理任务: 2048-4096 字节");
Serial.println("• 网络通信任务: 4096-8192 字节");
Serial.println("• 复杂算法任务: 8192+ 字节");
Serial.println("\n栈使用监控示例:");
// 创建一个测试任务来演示栈使用监控
xTaskCreate(stackMonitorTask, "Stack_Monitor", 2048, NULL, 1, NULL);
Serial.println("(栈监控任务已创建,将定期报告栈使用情况)\n");
}
void stackMonitorTask(void *parameter) {
for(;;) {
// 获取当前任务的栈使用情况
UBaseType_t stackHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
Serial.printf("栈监控:剩余栈空间 %d 字节\n", stackHighWaterMark * sizeof(StackType_t));
if (stackHighWaterMark < 200) {
Serial.println("警告:栈空间不足!");
}
vTaskDelay(30000 / portTICK_PERIOD_MS); // 每30秒检查一次
}
}
void demonstrateSynchronizationReuse() {
Serial.println("策略3:同步原语复用");
Serial.println("复用策略:");
Serial.println("• 全局串口互斥量:所有任务共享一个");
Serial.println("• 资源池信号量:按资源类型分组");
Serial.println("• 状态通知:使用任务通知替代信号量");
Serial.println("• 临时同步:使用栈上的局部变量");
// 演示任务通知的使用(替代二进制信号量)
demonstrateTaskNotification();
}
void demonstrateTaskNotification() {
Serial.println("\n任务通知示例(替代二进制信号量):");
// 创建演示任务
TaskHandle_t notificationTask;
xTaskCreate(taskNotificationDemo, "Notification_Demo", 2048, NULL, 1, ¬ificationTask);
// 等待一段时间后发送通知
vTaskDelay(2000 / portTICK_PERIOD_MS);
Serial.println("发送任务通知...");
xTaskNotifyGive(notificationTask);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println("任务通知的优势:");
Serial.println("• 不需要额外的内存分配");
Serial.println("• 执行速度更快");
Serial.println("• 每个任务内置支持");
Serial.println("• 适合简单的通知场景\n");
}
void taskNotificationDemo(void *parameter) {
Serial.println("任务通知演示:等待通知...");
// 等待任务通知(类似于等待信号量)
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
Serial.println("任务通知演示:收到通知!");
// 任务完成后删除自己
vTaskDelete(NULL);
}
void setup() {
Serial.begin(115200);
Serial.println("ESP32 性能优化演示启动!");
// 分析初始内存状态
analyzeMemoryUsage();
// 运行性能测试
runPerformanceTests();
// 演示内存优化策略
demonstrateMemoryOptimization();
// 创建内存监控任务
xTaskCreate(memoryMonitorTask, "Memory_Monitor", 2048, NULL, 1, NULL);
}
void memoryMonitorTask(void *parameter) {
for(;;) {
analyzeMemoryUsage();
vTaskDelay(60000 / portTICK_PERIOD_MS); // 每分钟检查一次
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
任务切换开销的量化分析
任务切换是多任务系统的核心机制,但它也带来了开销。理解这些开销有助于我们优化系统性能。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// 任务切换分析工具
SemaphoreHandle_t switchTestSemaphore;
volatile unsigned long switchStartTime;
volatile unsigned long switchEndTime;
volatile int switchCount = 0;
void setup() {
Serial.begin(115200);
switchTestSemaphore = xSemaphoreCreateBinary();
Serial.println("任务切换开销分析开始!");
// 创建任务切换测试任务
xTaskCreate(taskSwitchProducer, "Switch_Producer", 2048, NULL, 2, NULL);
xTaskCreate(taskSwitchConsumer, "Switch_Consumer", 2048, NULL, 1, NULL);
// 创建性能监控任务
xTaskCreate(performanceMonitor, "Performance_Monitor", 2048, NULL, 3, NULL);
}
void taskSwitchProducer(void *parameter) {
for(;;) {
// 记录开始时间
switchStartTime = micros();
// 释放信号量,这会导致任务切换
xSemaphoreGive(switchTestSemaphore);
// 等待消费者完成
vTaskDelay(10 / portTICK_PERIOD_MS);
switchCount++;
if (switchCount >= 1000) {
// 重置计数器
switchCount = 0;
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
}
void taskSwitchConsumer(void *parameter) {
for(;;) {
// 等待信号量
if (xSemaphoreTake(switchTestSemaphore, portMAX_DELAY) == pdTRUE) {
// 记录结束时间
switchEndTime = micros();
// 计算任务切换时间
unsigned long switchTime = switchEndTime - switchStartTime;
// 每100次切换报告一次
if (switchCount % 100 == 0) {
Serial.printf("任务切换 #%d: %lu 微秒\n", switchCount, switchTime);
}
}
}
}
void performanceMonitor(void *parameter) {
TickType_t lastWakeTime = xTaskGetTickCount();
for(;;) {
// 获取系统统计信息
TaskStatus_t *taskStatusArray;
UBaseType_t taskCount = uxTaskGetNumberOfTasks();
// 分配内存存储任务状态
taskStatusArray = pvPortMalloc(taskCount * sizeof(TaskStatus_t));
if (taskStatusArray != NULL) {
// 获取任务状态信息
UBaseType_t actualTaskCount = uxTaskGetSystemState(taskStatusArray, taskCount, NULL);
Serial.println("=== 任务性能统计 ===");
Serial.printf("任务数量: %d\n", actualTaskCount);
for (UBaseType_t i = 0; i < actualTaskCount; i++) {
Serial.printf("任务: %s\n", taskStatusArray[i].pcTaskName);
Serial.printf(" 状态: %s\n", getTaskStateName(taskStatusArray[i].eCurrentState));
Serial.printf(" 优先级: %d\n", taskStatusArray[i].uxCurrentPriority);
Serial.printf(" 栈剩余: %d 字节\n",
taskStatusArray[i].usStackHighWaterMark * sizeof(StackType_t));
}
Serial.println("===================\n");
// 释放内存
vPortFree(taskStatusArray);
}
// 每10秒更新一次
vTaskDelayUntil(&lastWakeTime, 10000 / portTICK_PERIOD_MS);
}
}
const char* getTaskStateName(eTaskState state) {
switch (state) {
case eRunning: return "运行中";
case eReady: return "就绪";
case eBlocked: return "阻塞";
case eSuspended: return "挂起";
case eDeleted: return "已删除";
default: return "未知";
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
实时性要求下的最佳选择策略
在实时系统中,可预测性比平均性能更重要。我们需要确保关键任务能够在规定时间内完成。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// 实时性分析配置
#define CRITICAL_TASK_PERIOD_MS 50 // 关键任务周期50ms
#define NORMAL_TASK_PERIOD_MS 200 // 普通任务周期200ms
#define BACKGROUND_TASK_PERIOD_MS 1000 // 后台任务周期1000ms
// 同步原语
SemaphoreHandle_t criticalResourceMutex;
SemaphoreHandle_t normalResourceMutex;
SemaphoreHandle_t realtimeNotification;
// 性能统计
typedef struct {
unsigned long maxExecutionTime;
unsigned long minExecutionTime;
unsigned long totalExecutionTime;
unsigned long executionCount;
unsigned long deadlineMisses;
} RealtimeStats;
RealtimeStats criticalTaskStats = {0, ULONG_MAX, 0, 0, 0};
RealtimeStats normalTaskStats = {0, ULONG_MAX, 0, 0, 0};
void setup() {
Serial.begin(115200);
// 创建同步原语
criticalResourceMutex = xSemaphoreCreateMutex();
normalResourceMutex = xSemaphoreCreateMutex();
realtimeNotification = xSemaphoreCreateBinary();
Serial.println("实时性能分析系统启动!");
// 创建不同优先级的任务
xTaskCreate(criticalRealtimeTask, "Critical_RT", 2048, NULL, 5, NULL); // 最高优先级
xTaskCreate(normalRealtimeTask, "Normal_RT", 2048, NULL, 3, NULL); // 中等优先级
xTaskCreate(backgroundTask, "Background", 2048, NULL, 1, NULL); // 低优先级
xTaskCreate(realtimeMonitor, "RT_Monitor", 2048, NULL, 4, NULL); // 监控任务
// 启动实时性测试
xTaskCreate(realtimeTestController, "RT_Controller", 2048, NULL, 2, NULL);
}
void criticalRealtimeTask(void *parameter) {
TickType_t lastWakeTime = xTaskGetTickCount();
for(;;) {
unsigned long startTime = micros();
// 关键实时任务的工作
if (xSemaphoreTake(criticalResourceMutex, 5 / portTICK_PERIOD_MS) == pdTRUE) {
// 模拟关键操作
performCriticalOperation();
xSemaphoreGive(criticalResourceMutex);
unsigned long executionTime = micros() - startTime;
updateRealtimeStats(&criticalTaskStats, executionTime, CRITICAL_TASK_PERIOD_MS * 1000);
} else {
// 无法获取资源,记录为错过截止时间
criticalTaskStats.deadlineMisses++;
Serial.println("关键任务:无法获取资源,错过截止时间!");
}
// 严格按周期执行
vTaskDelayUntil(&lastWakeTime, CRITICAL_TASK_PERIOD_MS / portTICK_PERIOD_MS);
}
}
void normalRealtimeTask(void *parameter) {
TickType_t lastWakeTime = xTaskGetTickCount();
for(;;) {
unsigned long startTime = micros();
// 普通实时任务的工作
if (xSemaphoreTake(normalResourceMutex, 20 / portTICK_PERIOD_MS) == pdTRUE) {
// 模拟普通操作
performNormalOperation();
xSemaphoreGive(normalResourceMutex);
unsigned long executionTime = micros() - startTime;
updateRealtimeStats(&normalTaskStats, executionTime, NORMAL_TASK_PERIOD_MS * 1000);
} else {
// 记录错过截止时间
normalTaskStats.deadlineMisses++;
Serial.println("普通任务:无法获取资源,错过截止时间!");
}
// 按周期执行
vTaskDelayUntil(&lastWakeTime, NORMAL_TASK_PERIOD_MS / portTICK_PERIOD_MS);
}
}
void backgroundTask(void *parameter) {
for(;;) {
Serial.println("后台任务:执行低优先级操作");
// 模拟后台处理
for (int i = 0; i < 1000; i++) {
// 可中断的循环
if (i % 100 == 0) {
vTaskDelay(1 / portTICK_PERIOD_MS); // 让出CPU
}
}
vTaskDelay(BACKGROUND_TASK_PERIOD_MS / portTICK_PERIOD_MS);
}
}
void performCriticalOperation() {
// 模拟关键操作(必须在5ms内完成)
volatile int calculation = 0;
for (int i = 0; i < 10000; i++) {
calculation += i;
}
}
void performNormalOperation() {
// 模拟普通操作(必须在20ms内完成)
volatile int calculation = 0;
for (int i = 0; i < 50000; i++) {
calculation += i;
}
}
void updateRealtimeStats(RealtimeStats* stats, unsigned long executionTime, unsigned long deadline) {
stats->executionCount++;
stats->totalExecutionTime += executionTime;
if (executionTime > stats->maxExecutionTime) {
stats->maxExecutionTime = executionTime;
}
if (executionTime < stats->minExecutionTime) {
stats->minExecutionTime = executionTime;
}
// 检查是否错过截止时间
if (executionTime > deadline) {
stats->deadlineMisses++;
}
}
void realtimeMonitor(void *parameter) {
for(;;) {
Serial.println("=== 实时性能监控报告 ===");
// 关键任务统计
Serial.println("关键任务统计:");
if (criticalTaskStats.executionCount > 0) {
Serial.printf(" 执行次数: %lu\n", criticalTaskStats.executionCount);
Serial.printf(" 平均执行时间: %lu 微秒\n",
criticalTaskStats.totalExecutionTime / criticalTaskStats.executionCount);
Serial.printf(" 最大执行时间: %lu 微秒\n", criticalTaskStats.maxExecutionTime);
Serial.printf(" 最小执行时间: %lu 微秒\n", criticalTaskStats.minExecutionTime);
Serial.printf(" 错过截止时间: %lu 次\n", criticalTaskStats.deadlineMisses);
Serial.printf(" 实时性成功率: %.2f%%\n",
(float)(criticalTaskStats.executionCount - criticalTaskStats.deadlineMisses) /
criticalTaskStats.executionCount * 100);
}
Serial.println();
// 普通任务统计
Serial.println("普通任务统计:");
if (normalTaskStats.executionCount > 0) {
Serial.printf(" 执行次数: %lu\n", normalTaskStats.executionCount);
Serial.printf(" 平均执行时间: %lu 微秒\n",
normalTaskStats.totalExecutionTime / normalTaskStats.executionCount);
Serial.printf(" 最大执行时间: %lu 微秒\n", normalTaskStats.maxExecutionTime);
Serial.printf(" 最小执行时间: %lu 微秒\n", normalTaskStats.minExecutionTime);
Serial.printf(" 错过截止时间: %lu 次\n", normalTaskStats.deadlineMisses);
Serial.printf(" 实时性成功率: %.2f%%\n",
(float)(normalTaskStats.executionCount - normalTaskStats.deadlineMisses) /
normalTaskStats.executionCount * 100);
}
Serial.println("========================\n");
vTaskDelay(15000 / portTICK_PERIOD_MS); // 每15秒报告一次
}
}
void realtimeTestController(void *parameter) {
for(;;) {
Serial.println("实时性测试控制器:开始压力测试");
// 创建额外的干扰任务来测试实时性
TaskHandle_t interferenceTask;
xTaskCreate(interferenceTask_func, "Interference", 2048, NULL, 2, &interferenceTask);
// 运行10秒压力测试
vTaskDelay(10000 / portTICK_PERIOD_MS);
// 停止干扰任务
vTaskDelete(interferenceTask);
Serial.println("实时性测试控制器:压力测试结束");
// 等待30秒后再次测试
vTaskDelay(30000 / portTICK_PERIOD_MS);
}
}
void interferenceTask_func(void *parameter) {
for(;;) {
// 创建CPU负载来测试实时性
volatile int calculation = 0;
for (int i = 0; i < 100000; i++) {
calculation += i * i;
}
vTaskDelay(1 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
调试技巧:当信号量和互斥量不按套路出牌
调试多任务程序就像是当侦探,你需要从蛛丝马迹中找出问题的根源。当信号量和互斥量出现问题时,症状往往很明显,但原因却可能隐藏得很深。
常见错误模式的识别与解决
让我们来看看那些让程序员头疼的经典问题!
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// 调试工具配置
#define DEBUG_ENABLE 1
#define DEBUG_PRINT(fmt, ...) do { if(DEBUG_ENABLE) Serial.printf("[DEBUG] " fmt "\n", ##__VA_ARGS__); } while(0)
#define ERROR_PRINT(fmt, ...) Serial.printf("[ERROR] " fmt "\n", ##__VA_ARGS__)
#define WARN_PRINT(fmt, ...) Serial.printf("[WARN] " fmt "\n", ##__VA_ARGS__)
// 错误检测和记录
typedef struct {
const char* errorType;
const char* taskName;
unsigned long timestamp;
const char* description;
} ErrorRecord;
#define MAX_ERROR_RECORDS 20
ErrorRecord errorLog[MAX_ERROR_RECORDS];
int errorLogIndex = 0;
// 测试用的同步原语
SemaphoreHandle_t problematicMutex;
SemaphoreHandle_t debugSemaphore;
void setup() {
Serial.begin(115200);
problematicMutex = xSemaphoreCreateMutex();
debugSemaphore = xSemaphoreCreateBinary();
Serial.println("多任务调试技巧演示开始!");
// 演示各种常见错误
demonstrateCommonErrors();
}
void demonstrateCommonErrors() {
Serial.println("=== 常见错误模式演示 ===\n");
// 错误1:忘记释放互斥量
Serial.println("错误1:忘记释放互斥量");
xTaskCreate(forgetfulTask, "Forgetful", 2048, NULL, 2, NULL);
xTaskCreate(waitingTask, "Waiting", 2048, NULL, 1, NULL);
vTaskDelay(5000 / portTICK_PERIOD_MS);
// 错误2:错误的任务释放信号量
Serial.println("\n错误2:错误的任务释放信号量");
xTaskCreate(wrongReleaseTask, "WrongRelease", 2048, NULL, 2, NULL);
vTaskDelay(3000 / portTICK_PERIOD_MS);
// 错误3:在中断中使用错误的API
Serial.println("\n错误3:在中断中使用错误的API");
demonstrateInterruptError();
vTaskDelay(2000 / portTICK_PERIOD_MS);
// 错误4:优先级反转
Serial.println("\n错误4:优先级反转");
demonstratePriorityInversion();
vTaskDelay(5000 / portTICK_PERIOD_MS);
// 错误5:竞态条件
Serial.println("\n错误5:竞态条件");
demonstrateRaceCondition();
// 创建错误监控任务
xTaskCreate(errorMonitorTask, "Error_Monitor", 2048, NULL, 3, NULL);
}
// 错误1:忘记释放互斥量
void forgetfulTask(void *parameter) {
DEBUG_PRINT("健忘任务:开始执行");
if (xSemaphoreTake(problematicMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
DEBUG_PRINT("健忘任务:获取到互斥量");
// 模拟一些工作
vTaskDelay(2000 / portTICK_PERIOD_MS);
// 故意"忘记"释放互斥量
ERROR_PRINT("健忘任务:哎呀,忘记释放互斥量了!");
recordError("MUTEX_LEAK", "Forgetful", "忘记释放互斥量");
// 在实际代码中,这里应该有 xSemaphoreGive(problematicMutex);
}
// 任务结束但没有释放互斥量
vTaskDelete(NULL);
}
void waitingTask(void *parameter) {
vTaskDelay(1000 / portTICK_PERIOD_MS); // 等待健忘任务先获取互斥量
DEBUG_PRINT("等待任务:尝试获取互斥量...");
if (xSemaphoreTake(problematicMutex, 3000 / portTICK_PERIOD_MS) == pdTRUE) {
DEBUG_PRINT("等待任务:获取到互斥量");
xSemaphoreGive(problematicMutex);
} else {
ERROR_PRINT("等待任务:获取互斥量超时!可能发生了互斥量泄漏");
recordError("MUTEX_TIMEOUT", "Waiting", "获取互斥量超时");
}
vTaskDelete(NULL);
}
// 错误2:错误的任务释放信号量
void wrongReleaseTask(void *parameter) {
DEBUG_PRINT("错误释放任务:开始执行");
// 错误:尝试释放一个没有获取的信号量
if (xSemaphoreGive(debugSemaphore) == pdTRUE) {
DEBUG_PRINT("错误释放任务:释放信号量成功");
} else {
ERROR_PRINT("错误释放任务:释放信号量失败");
recordError("WRONG_RELEASE", "WrongRelease", "释放未获取的信号量");
}
vTaskDelete(NULL);
}
// 错误3:在中断中使用错误的API
void demonstrateInterruptError() {
DEBUG_PRINT("演示中断中的错误API使用");
// 注意:这只是演示,实际的中断处理需要更复杂的设置
Serial.println("在中断服务程序中应该使用:");
Serial.println("• xSemaphoreGiveFromISR() 而不是 xSemaphoreGive()");
Serial.println("• xSemaphoreTakeFromISR() 而不是 xSemaphoreTake()");
Serial.println("• 避免在ISR中使用会阻塞的API");
recordError("ISR_API_ERROR", "ISR", "在中断中使用了错误的API");
}
// 错误4:优先级反转演示
void demonstratePriorityInversion() {
DEBUG_PRINT("演示优先级反转问题");
// 创建一个普通信号量(不支持优先级继承)
SemaphoreHandle_t normalSemaphore = xSemaphoreCreateBinary();
xSemaphoreGive(normalSemaphore);
// 创建不同优先级的任务
xTaskCreate(lowPriorityTask, "LowPriority", 2048, (void*)normalSemaphore, 1, NULL);
xTaskCreate(mediumPriorityTask, "MediumPriority", 2048, NULL, 2, NULL);
xTaskCreate(highPriorityTask, "HighPriority", 2048, (void*)normalSemaphore, 3, NULL);
Serial.println("优先级反转问题:");
Serial.println("• 高优先级任务被低优先级任务阻塞");
Serial.println("• 中优先级任务抢占了低优先级任务的CPU时间");
Serial.println("• 导致高优先级任务等待时间过长");
recordError("PRIORITY_INVERSION", "System", "检测到优先级反转");
}
void lowPriorityTask(void *parameter) {
SemaphoreHandle_t semaphore = (SemaphoreHandle_t)parameter;
DEBUG_PRINT("低优先级任务:获取信号量");
if (xSemaphoreTake(semaphore, portMAX_DELAY) == pdTRUE) {
DEBUG_PRINT("低优先级任务:开始长时间工作");
// 长时间工作
for (int i = 0; i < 10; i++) {
DEBUG_PRINT("低优先级任务:工作中... %d/10", i + 1);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
DEBUG_PRINT("低优先级任务:释放信号量");
xSemaphoreGive(semaphore);
}
vTaskDelete(NULL);
}
void mediumPriorityTask(void *parameter) {
vTaskDelay(1000 / portTICK_PERIOD_MS); // 延迟启动
DEBUG_PRINT("中优先级任务:开始CPU密集型工作");
for (int i = 0; i < 8; i++) {
DEBUG_PRINT("中优先级任务:计算中... %d/8", i + 1);
vTaskDelay(300 / portTICK_PERIOD_MS);
}
DEBUG_PRINT("中优先级任务:工作完成");
vTaskDelete(NULL);
}
void highPriorityTask(void *parameter) {
SemaphoreHandle_t semaphore = (SemaphoreHandle_t)parameter;
vTaskDelay(2000 / portTICK_PERIOD_MS); // 延迟启动
DEBUG_PRINT("高优先级任务:紧急需要信号量!");
unsigned long startTime = millis();
if (xSemaphoreTake(semaphore, portMAX_DELAY) == pdTRUE) {
unsigned long waitTime = millis() - startTime;
if (waitTime > 1000) { // 如果等待超过1秒
ERROR_PRINT("高优先级任务:等待时间过长 (%lu ms),可能发生优先级反转", waitTime);
recordError("PRIORITY_INVERSION", "HighPriority", "等待时间异常");
}
DEBUG_PRINT("高优先级任务:获取到信号量,等待了 %lu ms", waitTime);
xSemaphoreGive(semaphore);
}
vTaskDelete(NULL);
}
// 错误5:竞态条件演示
volatile int sharedCounter = 0;
SemaphoreHandle_t counterMutex;
void demonstrateRaceCondition() {
DEBUG_PRINT("演示竞态条件问题");
counterMutex = xSemaphoreCreateMutex();
sharedCounter = 0;
// 创建多个任务同时修改共享变量
xTaskCreate(unsafeCounterTask, "UnsafeCounter1", 2048, NULL, 1, NULL);
xTaskCreate(unsafeCounterTask, "UnsafeCounter2", 2048, NULL, 1, NULL);
xTaskCreate(safeCounterTask, "SafeCounter", 2048, NULL, 1, NULL);
xTaskCreate(counterMonitorTask, "CounterMonitor", 2048, NULL, 2, NULL);
Serial.println("竞态条件问题:");
Serial.println("• 多个任务同时访问共享资源");
Serial.println("• 没有适当的同步机制保护");
Serial.println("• 导致数据不一致");
recordError("RACE_CONDITION", "System", "检测到竞态条件");
}
void unsafeCounterTask(void *parameter) {
for (int i = 0; i < 1000; i++) {
// 不安全的操作:没有互斥量保护
int temp = sharedCounter;
vTaskDelay(1 / portTICK_PERIOD_MS); // 模拟处理时间
sharedCounter = temp + 1;
if (i % 100 == 0) {
DEBUG_PRINT("%s:不安全地增加计数器到 %d", pcTaskGetTaskName(NULL), sharedCounter);
}
}
vTaskDelete(NULL);
}
void safeCounterTask(void *parameter) {
for (int i = 0; i < 1000; i++) {
// 安全的操作:使用互斥量保护
if (xSemaphoreTake(counterMutex, portMAX_DELAY) == pdTRUE) {
int temp = sharedCounter;
vTaskDelay(1 / portTICK_PERIOD_MS); // 模拟处理时间
sharedCounter = temp + 1;
if (i % 100 == 0) {
DEBUG_PRINT("%s:安全地增加计数器到 %d", pcTaskGetTaskName(NULL), sharedCounter);
}
xSemaphoreGive(counterMutex);
}
}
```cpp
vTaskDelete(NULL);
}
void counterMonitorTask(void *parameter) {
int lastCounter = 0;
int unexpectedChanges = 0;
for(;;) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
int currentCounter = sharedCounter;
int change = currentCounter - lastCounter;
DEBUG_PRINT("计数器监控:当前值 %d,变化 %d", currentCounter, change);
// 检测异常变化(理论上每秒应该增加约300,但由于竞态条件可能会更少)
if (change < 200 && change > 0) {
unexpectedChanges++;
WARN_PRINT("计数器监控:检测到异常变化,可能存在竞态条件");
if (unexpectedChanges > 3) {
ERROR_PRINT("计数器监控:多次检测到异常,确认存在竞态条件");
recordError("RACE_CONDITION", "CounterMonitor", "共享变量访问异常");
}
}
lastCounter = currentCounter;
}
}
// 错误记录函数
void recordError(const char* errorType, const char* taskName, const char* description) {
ErrorRecord* record = &errorLog[errorLogIndex];
record->errorType = errorType;
record->taskName = taskName;
record->timestamp = millis();
record->description = description;
errorLogIndex = (errorLogIndex + 1) % MAX_ERROR_RECORDS;
ERROR_PRINT("记录错误:%s - %s - %s", errorType, taskName, description);
}
// 错误监控任务
void errorMonitorTask(void *parameter) {
for(;;) {
Serial.println("=== 错误监控报告 ===");
// 统计错误类型
int mutexErrors = 0;
int priorityErrors = 0;
int raceConditionErrors = 0;
int otherErrors = 0;
for (int i = 0; i < MAX_ERROR_RECORDS; i++) {
if (errorLog[i].errorType != NULL) {
if (strcmp(errorLog[i].errorType, "MUTEX_LEAK") == 0 ||
strcmp(errorLog[i].errorType, "MUTEX_TIMEOUT") == 0) {
mutexErrors++;
} else if (strcmp(errorLog[i].errorType, "PRIORITY_INVERSION") == 0) {
priorityErrors++;
} else if (strcmp(errorLog[i].errorType, "RACE_CONDITION") == 0) {
raceConditionErrors++;
} else {
otherErrors++;
}
}
}
Serial.printf("互斥量相关错误: %d\n", mutexErrors);
Serial.printf("优先级反转错误: %d\n", priorityErrors);
Serial.printf("竞态条件错误: %d\n", raceConditionErrors);
Serial.printf("其他错误: %d\n", otherErrors);
// 显示最近的错误
Serial.println("\n最近的错误记录:");
for (int i = 0; i < 5; i++) {
int index = (errorLogIndex - 1 - i + MAX_ERROR_RECORDS) % MAX_ERROR_RECORDS;
if (errorLog[index].errorType != NULL) {
Serial.printf(" [%lu] %s - %s: %s\n",
errorLog[index].timestamp,
errorLog[index].errorType,
errorLog[index].taskName,
errorLog[index].description);
}
}
Serial.println("===================\n");
vTaskDelay(30000 / portTICK_PERIOD_MS); // 每30秒报告一次
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
使用ESP-IDF调试工具追踪同步问题
ESP-IDF提供了强大的调试工具来帮助我们追踪同步问题。让我们看看如何使用这些工具。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_task_wdt.h"
// 调试标签
static const char* TAG = "SYNC_DEBUG";
// 高级调试配置
#define ENABLE_TASK_TRACE 1
#define ENABLE_MUTEX_TRACE 1
#define ENABLE_DEADLOCK_DETECTION 1
// 调试用的同步原语
SemaphoreHandle_t debugMutexA;
SemaphoreHandle_t debugMutexB;
SemaphoreHandle_t traceSemaphore;
// 任务跟踪信息
typedef struct {
TaskHandle_t handle;
const char* name;
unsigned long lastActiveTime;
SemaphoreHandle_t heldMutex;
SemaphoreHandle_t waitingMutex;
} TaskTraceInfo;
#define MAX_TRACED_TASKS 10
TaskTraceInfo tracedTasks[MAX_TRACED_TASKS];
int tracedTaskCount = 0;
void setup() {
Serial.begin(115200);
// 配置ESP日志级别
esp_log_level_set("*", ESP_LOG_INFO);
esp_log_level_set(TAG, ESP_LOG_DEBUG);
// 创建调试用的同步原语
debugMutexA = xSemaphoreCreateMutex();
debugMutexB = xSemaphoreCreateMutex();
traceSemaphore = xSemaphoreCreateBinary();
ESP_LOGI(TAG, "ESP-IDF调试工具演示开始");
// 启用看门狗
esp_task_wdt_init(30, true); // 30秒超时
// 创建调试演示任务
xTaskCreate(debugTask1, "DebugTask1", 2048, NULL, 2, NULL);
xTaskCreate(debugTask2, "DebugTask2", 2048, NULL, 2, NULL);
xTaskCreate(deadlockDetectionTask, "DeadlockDetector", 2048, NULL, 3, NULL);
xTaskCreate(taskTraceTask, "TaskTracer", 2048, NULL, 1, NULL);
// 演示各种调试技巧
demonstrateDebuggingTechniques();
}
void demonstrateDebuggingTechniques() {
ESP_LOGI(TAG, "=== ESP-IDF调试技巧演示 ===");
// 1. 使用ESP_LOG进行结构化日志
demonstrateStructuredLogging();
// 2. 使用任务看门狗检测死锁
demonstrateWatchdogUsage();
// 3. 使用堆栈跟踪
demonstrateStackTrace();
// 4. 使用内存调试
demonstrateMemoryDebugging();
}
void demonstrateStructuredLogging() {
ESP_LOGI(TAG, "=== 结构化日志演示 ===");
// 不同级别的日志
ESP_LOGD(TAG, "调试信息:系统初始化完成");
ESP_LOGI(TAG, "信息:开始执行主要逻辑");
ESP_LOGW(TAG, "警告:检测到潜在问题");
ESP_LOGE(TAG, "错误:发生了严重错误");
// 带参数的日志
int taskCount = uxTaskGetNumberOfTasks();
size_t freeHeap = esp_get_free_heap_size();
ESP_LOGI(TAG, "系统状态 - 任务数: %d, 可用堆内存: %d 字节", taskCount, freeHeap);
// 条件日志
if (freeHeap < 10000) {
ESP_LOGW(TAG, "内存不足警告:仅剩 %d 字节", freeHeap);
}
}
void demonstrateWatchdogUsage() {
ESP_LOGI(TAG, "=== 看门狗使用演示 ===");
// 为当前任务添加看门狗
esp_task_wdt_add(NULL);
ESP_LOGI(TAG, "看门狗已启用,任务必须定期喂狗");
// 模拟正常工作
for (int i = 0; i < 5; i++) {
ESP_LOGI(TAG, "正常工作中... %d/5", i + 1);
vTaskDelay(2000 / portTICK_PERIOD_MS);
// 喂狗
esp_task_wdt_reset();
}
ESP_LOGI(TAG, "看门狗演示完成");
// 移除看门狗
esp_task_wdt_delete(NULL);
}
void demonstrateStackTrace() {
ESP_LOGI(TAG, "=== 堆栈跟踪演示 ===");
// 获取当前任务信息
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
char* taskName = pcTaskGetTaskName(currentTask);
UBaseType_t stackRemaining = uxTaskGetStackHighWaterMark(currentTask);
ESP_LOGI(TAG, "当前任务: %s", taskName);
ESP_LOGI(TAG, "剩余栈空间: %d 字节", stackRemaining * sizeof(StackType_t));
if (stackRemaining < 100) {
ESP_LOGE(TAG, "栈空间不足!可能发生栈溢出");
// 在实际应用中,这里可以触发堆栈转储
// esp_backtrace_print(100);
}
}
void demonstrateMemoryDebugging() {
ESP_LOGI(TAG, "=== 内存调试演示 ===");
// 获取详细的内存信息
multi_heap_info_t info;
heap_caps_get_info(&info, MALLOC_CAP_DEFAULT);
ESP_LOGI(TAG, "堆内存统计:");
ESP_LOGI(TAG, " 总大小: %d 字节", info.total_free_bytes + info.total_allocated_bytes);
ESP_LOGI(TAG, " 已分配: %d 字节", info.total_allocated_bytes);
ESP_LOGI(TAG, " 可用: %d 字节", info.total_free_bytes);
ESP_LOGI(TAG, " 最大块: %d 字节", info.largest_free_block);
ESP_LOGI(TAG, " 分配次数: %d", info.allocated_blocks);
ESP_LOGI(TAG, " 空闲块数: %d", info.free_blocks);
// 检测内存泄漏
static size_t lastFreeHeap = 0;
size_t currentFreeHeap = esp_get_free_heap_size();
if (lastFreeHeap > 0) {
int memoryChange = currentFreeHeap - lastFreeHeap;
if (memoryChange < -1000) { // 内存减少超过1KB
ESP_LOGW(TAG, "检测到可能的内存泄漏:内存减少 %d 字节", -memoryChange);
}
}
lastFreeHeap = currentFreeHeap;
}
void debugTask1(void *parameter) {
addTaskToTrace(xTaskGetCurrentTaskHandle(), "DebugTask1");
for(;;) {
ESP_LOGD(TAG, "DebugTask1: 开始执行");
updateTaskTrace(xTaskGetCurrentTaskHandle(), NULL, NULL);
// 尝试获取互斥量A
ESP_LOGD(TAG, "DebugTask1: 尝试获取互斥量A");
updateTaskTrace(xTaskGetCurrentTaskHandle(), NULL, debugMutexA);
if (xSemaphoreTake(debugMutexA, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
ESP_LOGD(TAG, "DebugTask1: 获取互斥量A成功");
updateTaskTrace(xTaskGetCurrentTaskHandle(), debugMutexA, NULL);
// 持有互斥量A时尝试获取互斥量B
vTaskDelay(1000 / portTICK_PERIOD_MS);
ESP_LOGD(TAG, "DebugTask1: 尝试获取互斥量B");
updateTaskTrace(xTaskGetCurrentTaskHandle(), debugMutexA, debugMutexB);
if (xSemaphoreTake(debugMutexB, 2000 / portTICK_PERIOD_MS) == pdTRUE) {
ESP_LOGD(TAG, "DebugTask1: 获取互斥量B成功");
// 同时持有两个互斥量
vTaskDelay(500 / portTICK_PERIOD_MS);
ESP_LOGD(TAG, "DebugTask1: 释放互斥量B");
xSemaphoreGive(debugMutexB);
} else {
ESP_LOGW(TAG, "DebugTask1: 获取互斥量B超时");
}
ESP_LOGD(TAG, "DebugTask1: 释放互斥量A");
xSemaphoreGive(debugMutexA);
updateTaskTrace(xTaskGetCurrentTaskHandle(), NULL, NULL);
} else {
ESP_LOGW(TAG, "DebugTask1: 获取互斥量A超时");
}
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
void debugTask2(void *parameter) {
addTaskToTrace(xTaskGetCurrentTaskHandle(), "DebugTask2");
vTaskDelay(1500 / portTICK_PERIOD_MS); // 错开启动时间
for(;;) {
ESP_LOGD(TAG, "DebugTask2: 开始执行");
updateTaskTrace(xTaskGetCurrentTaskHandle(), NULL, NULL);
// 尝试获取互斥量B
ESP_LOGD(TAG, "DebugTask2: 尝试获取互斥量B");
updateTaskTrace(xTaskGetCurrentTaskHandle(), NULL, debugMutexB);
if (xSemaphoreTake(debugMutexB, 5000 / portTICK_PERIOD_MS) == pdTRUE) {
ESP_LOGD(TAG, "DebugTask2: 获取互斥量B成功");
updateTaskTrace(xTaskGetCurrentTaskHandle(), debugMutexB, NULL);
// 持有互斥量B时尝试获取互斥量A
vTaskDelay(1000 / portTICK_PERIOD_MS);
ESP_LOGD(TAG, "DebugTask2: 尝试获取互斥量A");
updateTaskTrace(xTaskGetCurrentTaskHandle(), debugMutexB, debugMutexA);
if (xSemaphoreTake(debugMutexA, 2000 / portTICK_PERIOD_MS) == pdTRUE) {
ESP_LOGD(TAG, "DebugTask2: 获取互斥量A成功");
// 同时持有两个互斥量
vTaskDelay(500 / portTICK_PERIOD_MS);
ESP_LOGD(TAG, "DebugTask2: 释放互斥量A");
xSemaphoreGive(debugMutexA);
} else {
ESP_LOGW(TAG, "DebugTask2: 获取互斥量A超时");
}
ESP_LOGD(TAG, "DebugTask2: 释放互斥量B");
xSemaphoreGive(debugMutexB);
updateTaskTrace(xTaskGetCurrentTaskHandle(), NULL, NULL);
} else {
ESP_LOGW(TAG, "DebugTask2: 获取互斥量B超时");
}
vTaskDelay(3000 / portTICK_PERIOD_MS);
}
}
// 任务跟踪函数
void addTaskToTrace(TaskHandle_t handle, const char* name) {
if (tracedTaskCount < MAX_TRACED_TASKS) {
tracedTasks[tracedTaskCount].handle = handle;
tracedTasks[tracedTaskCount].name = name;
tracedTasks[tracedTaskCount].lastActiveTime = millis();
tracedTasks[tracedTaskCount].heldMutex = NULL;
tracedTasks[tracedTaskCount].waitingMutex = NULL;
tracedTaskCount++;
ESP_LOGI(TAG, "添加任务到跟踪列表: %s", name);
}
}
void updateTaskTrace(TaskHandle_t handle, SemaphoreHandle_t held, SemaphoreHandle_t waiting) {
for (int i = 0; i < tracedTaskCount; i++) {
if (tracedTasks[i].handle == handle) {
tracedTasks[i].lastActiveTime = millis();
tracedTasks[i].heldMutex = held;
tracedTasks[i].waitingMutex = waiting;
break;
}
}
}
void deadlockDetectionTask(void *parameter) {
for(;;) {
ESP_LOGI(TAG, "=== 死锁检测报告 ===");
bool potentialDeadlock = false;
// 检查所有跟踪的任务
for (int i = 0; i < tracedTaskCount; i++) {
TaskTraceInfo* task = &tracedTasks[i];
ESP_LOGI(TAG, "任务 %s:", task->name);
ESP_LOGI(TAG, " 最后活动: %lu ms 前", millis() - task->lastActiveTime);
ESP_LOGI(TAG, " 持有互斥量: %p", task->heldMutex);
ESP_LOGI(TAG, " 等待互斥量: %p", task->waitingMutex);
// 检查是否长时间无活动
if (millis() - task->lastActiveTime > 10000) {
ESP_LOGW(TAG, " 警告:任务可能被阻塞");
potentialDeadlock = true;
}
// 检查循环等待
if (task->heldMutex && task->waitingMutex) {
for (int j = 0; j < tracedTaskCount; j++) {
if (i != j && tracedTasks[j].heldMutex == task->waitingMutex &&
tracedTasks[j].waitingMutex == task->heldMutex) {
ESP_LOGE(TAG, " 检测到潜在死锁:%s 和 %s 相互等待",
task->name, tracedTasks[j].name);
potentialDeadlock = true;
}
}
}
}
if (potentialDeadlock) {
ESP_LOGE(TAG, "系统可能存在死锁!");
// 在实际应用中,这里可以:
// 1. 触发系统重启
// 2. 记录详细的调试信息
// 3. 发送警报
// 4. 尝试自动恢复
} else {
ESP_LOGI(TAG, "未检测到死锁");
}
ESP_LOGI(TAG, "========================");
vTaskDelay(15000 / portTICK_PERIOD_MS);
}
}
void taskTraceTask(void *parameter) {
for(;;) {
ESP_LOGI(TAG, "=== 任务跟踪报告 ===");
// 获取系统中所有任务的信息
UBaseType_t taskCount = uxTaskGetNumberOfTasks();
TaskStatus_t* taskStatusArray = (TaskStatus_t*)pvPortMalloc(taskCount * sizeof(TaskStatus_t));
if (taskStatusArray != NULL) {
UBaseType_t actualCount = uxTaskGetSystemState(taskStatusArray, taskCount, NULL);
for (UBaseType_t i = 0; i < actualCount; i++) {
TaskStatus_t* task = &taskStatusArray[i];
ESP_LOGI(TAG, "任务: %s", task->pcTaskName);
ESP_LOGI(TAG, " 状态: %d", task->eCurrentState);
ESP_LOGI(TAG, " 优先级: %d", task->uxCurrentPriority);
ESP_LOGI(TAG, " 栈剩余: %d", task->usStackHighWaterMark);
ESP_LOGI(TAG, " 任务号: %d", task->xTaskNumber);
}
vPortFree(taskStatusArray);
}
ESP_LOGI(TAG, "========================");
vTaskDelay(20000 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
死锁检测的自动化方案
死锁检测是一个复杂的问题,但我们可以通过一些自动化的方案来及早发现和处理死锁。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/timers.h"
// 死锁检测系统配置
#define MAX_MONITORED_MUTEXES 10
#define MAX_MONITORED_TASKS 20
#define DEADLOCK_TIMEOUT_MS 5000
#define WATCHDOG_INTERVAL_MS 1000
// 互斥量监控信息
typedef struct {
SemaphoreHandle_t mutex;
const char* name;
TaskHandle_t holder;
unsigned long acquireTime;
bool isActive;
} MutexMonitorInfo;
// 任务监控信息
typedef struct {
TaskHandle_t handle;
const char* name;
SemaphoreHandle_t waitingFor;
unsigned long waitStartTime;
unsigned long lastHeartbeat;
bool isActive;
} TaskMonitorInfo;
// 死锁检测系统
typedef struct {
MutexMonitorInfo mutexes[MAX_MONITORED_MUTEXES];
TaskMonitorInfo tasks[MAX_MONITORED_TASKS];
int mutexCount;
int taskCount;
TimerHandle_t watchdogTimer;
bool systemEnabled;
} DeadlockDetector;
DeadlockDetector detector = {0};
// 死锁检测API
void deadlockDetector_init();
void deadlockDetector_registerMutex(SemaphoreHandle_t mutex, const char* name);
void deadlockDetector_registerTask(TaskHandle_t task, const char* name);
void deadlockDetector_mutexTaken(SemaphoreHandle_t mutex, TaskHandle_t task);
void deadlockDetector_mutexGiven(SemaphoreHandle_t mutex);
void deadlockDetector_taskWaiting(TaskHandle_t task, SemaphoreHandle_t mutex);
void deadlockDetector_taskResumed(TaskHandle_t task);
void deadlockDetector_heartbeat(TaskHandle_t task);
// 测试用的互斥量和任务
SemaphoreHandle_t testMutexA, testMutexB, testMutexC;
void setup() {
Serial.begin(115200);
// 初始化死锁检测系统
deadlockDetector_init();
// 创建测试互斥量
testMutexA = xSemaphoreCreateMutex();
testMutexB = xSemaphoreCreateMutex();
testMutexC = xSemaphoreCreateMutex();
// 注册互斥量到检测系统
deadlockDetector_registerMutex(testMutexA, "TestMutexA");
deadlockDetector_registerMutex(testMutexB, "TestMutexB");
deadlockDetector_registerMutex(testMutexC, "TestMutexC");
// 创建测试任务
TaskHandle_t task1, task2, task3;
xTaskCreate(deadlockTestTask1, "DeadlockTest1", 2048, NULL, 2, &task1);
xTaskCreate(deadlockTestTask2, "DeadlockTest2", 2048, NULL, 2, &task2);
xTaskCreate(deadlockTestTask3, "DeadlockTest3", 2048, NULL, 2, &task3);
// 注册任务到检测系统
deadlockDetector_registerTask(task1, "DeadlockTest1");
deadlockDetector_registerTask(task2, "DeadlockTest2");
deadlockDetector_registerTask(task3, "DeadlockTest3");
Serial.println("死锁检测系统启动完成!");
}
void deadlockDetector_init() {
detector.mutexCount = 0;
detector.taskCount = 0;
detector.systemEnabled = true;
// 创建看门狗定时器
detector.watchdogTimer = xTimerCreate(
"DeadlockWatchdog",
WATCHDOG_INTERVAL_MS / portTICK_PERIOD_MS,
pdTRUE, // 自动重载
NULL,
deadlockWatchdogCallback
);
if (detector.watchdogTimer != NULL) {
xTimerStart(detector.watchdogTimer, 0);
Serial.println("死锁检测器:初始化完成");
} else {
Serial.println("死锁检测器:初始化失败");
}
}
void deadlockDetector_registerMutex(SemaphoreHandle_t mutex, const char* name) {
if (detector.mutexCount < MAX_MONITORED_MUTEXES) {
MutexMonitorInfo* info = &detector.mutexes[detector.mutexCount];
info->mutex = mutex;
info->name = name;
info->holder = NULL;
info->acquireTime = 0;
info->isActive = true;
detector.mutexCount++;
Serial.printf("死锁检测器:注册互斥量 %s\n", name);
}
}
void deadlockDetector_registerTask(TaskHandle_t task, const char* name) {
if (detector.taskCount < MAX_MONITORED_TASKS) {
TaskMonitorInfo* info = &detector.tasks[detector.taskCount];
info->handle = task;
info->name = name;
info->waitingFor = NULL;
info->waitStartTime = 0;
info->lastHeartbeat = millis();
info->isActive = true;
detector.taskCount++;
Serial.printf("死锁检测器:注册任务 %s\n", name);
}
}
void deadlockDetector_mutexTaken(SemaphoreHandle_t mutex, TaskHandle_t task) {
for (int i = 0; i < detector.mutexCount; i++) {
if (detector.mutexes[i].mutex == mutex) {
detector.mutexes[i].holder = task;
detector.mutexes[i].acquireTime = millis();
break;
}
}
// 更新任务状态
for (int i = 0; i < detector.taskCount; i++) {
if (detector.tasks[i].handle == task) {
detector.tasks[i].waitingFor = NULL;
detector.tasks[i].waitStartTime = 0;
break;
}
}
}
void deadlockDetector_mutexGiven(SemaphoreHandle_t mutex) {
for (int i = 0; i < detector.mutexCount; i++) {
if (detector.mutexes[i].mutex == mutex) {
detector.mutexes[i].holder = NULL;
detector.mutexes[i].acquireTime = 0;
break;
}
}
}
void deadlockDetector_taskWaiting(TaskHandle_t task, SemaphoreHandle_t mutex) {
for (int i = 0; i < detector.taskCount; i++) {
if (detector.tasks[i].handle == task) {
detector.tasks[i].waitingFor = mutex;
detector.tasks[i].waitStartTime = millis();
break;
}
}
}
void deadlockDetector_taskResumed(TaskHandle_t task) {
for (int i = 0; i < detector.taskCount; i++) {
if (detector.tasks[i].handle == task) {
detector.tasks[i].waitingFor = NULL;
detector.tasks[i].waitStartTime = 0;
break;
}
}
}
void deadlockDetector_heartbeat(TaskHandle_t task) {
for (int i = 0; i < detector.taskCount; i++) {
if (detector.tasks[i].handle == task) {
detector.tasks[i].lastHeartbeat = millis();
break;
}
}
}
void deadlockWatchdogCallback(TimerHandle_t xTimer) {
if (!detector.systemEnabled) return;
unsigned long currentTime = millis();
bool deadlockDetected = false;
// 检查长时间等待
for (int i = 0; i < detector.taskCount; i++) {
TaskMonitorInfo* task = &detector.tasks[i];
if (task->waitingFor != NULL &&
(currentTime - task->waitStartTime) > DEADLOCK_TIMEOUT_MS) {
Serial.printf("死锁检测器:任务 %s 长时间等待互斥量\n", task->name);
deadlockDetected = true;
}
// 检查心跳超时
if ((currentTime - task->lastHeartbeat) > (DEADLOCK_TIMEOUT_MS * 2)) {
Serial.printf("死锁检测器:任务 %s 心跳超时\n", task->name);
deadlockDetected = true;
}
}
// 检查循环等待
if (detectCircularWait()) {
Serial.println("死锁检测器:检测到循环等待");
deadlockDetected = true;
}
if (deadlockDetected) {
handleDeadlock();
}
}
bool detectCircularWait() {
// 构建等待图并检测循环
for (int i = 0; i < detector.taskCount; i++) {
TaskMonitorInfo* task = &detector.tasks[i];
if (task->waitingFor != NULL) {
// 找到持有这个互斥量的任务
TaskHandle_t holder = NULL;
for (int j = 0; j < detector.mutexCount; j++) {
if (detector.mutexes[j].mutex == task->waitingFor) {
holder = detector.mutexes[j].holder;
break;
}
}
if (holder != NULL) {
// 检查持有者是否在等待当前任务持有的互斥量
if (isTaskWaitingForTaskMutex(holder, task->handle)) {
return true; // 发现循环等待
}
}
}
}
return false;
}
bool isTaskWaitingForTaskMutex(TaskHandle_t waitingTask, TaskHandle_t holdingTask) {
// 找到等待任务正在等待的互斥量
SemaphoreHandle_t waitingMutex = NULL;
for (int i = 0; i < detector.taskCount; i++) {
if (detector.tasks[i].handle == waitingTask) {
waitingMutex = detector.tasks[i].waitingFor;
break;
}
}
if (waitingMutex == NULL) return false;
// 检查这个互斥量是否被holdingTask持有
for (int i = 0; i < detector.mutexCount; i++) {
if (detector.mutexes[i].mutex == waitingMutex &&
detector.mutexes[i].holder == holdingTask) {
return true;
}
}
return false;
}
void handleDeadlock() {
Serial.println("=== 死锁检测器:检测到死锁 ===");
// 打印系统状态
printSystemState();
// 死锁处理策略
Serial.println("死锁处理选项:");
Serial.println("1. 重启系统");
Serial.println("2. 终止部分任务");
Serial.println("3. 强制释放互斥量");
Serial.println("4. 记录日志并继续");
// 在这个演示中,我们选择记录日志并继续
Serial.println("选择策略4:记录日志并继续监控");
// 可以在这里实现更复杂的恢复策略
// 例如:esp_restart() 重启系统
}
void printSystemState() {
Serial.println("=== 系统状态快照 ===");
Serial.println("互斥量状态:");
for (int i = 0; i < detector.mutexCount; i++) {
MutexMonitorInfo* mutex = &detector.mutexes[i];
Serial.printf(" %s: 持有者=%p, 获取时间=%lu\n",
mutex->name, mutex->holder, mutex->acquireTime);
}
Serial.println("任务状态:");
for (int i = 0; i < detector.taskCount; i++) {
TaskMonitorInfo* task = &detector.tasks[i];
Serial.printf(" %s: 等待=%p, 等待时间=%lu, 心跳=%lu\n",
task->name, task->waitingFor, task->waitStartTime, task->lastHeartbeat);
}
Serial.println("=====================");
}
// 安全的互斥量操作包装函数
BaseType_t safeMutexTake(SemaphoreHandle_t mutex, TickType_t timeout) {
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
// 通知检测器任务开始等待
deadlockDetector_taskWaiting(currentTask, mutex);
BaseType_t result = xSemaphoreTake(mutex, timeout);
if (result == pdTRUE) {
// 通知检测器互斥量被获取
deadlockDetector_mutexTaken(mutex, currentTask);
} else {
// 通知检测器任务停止等待
deadlockDetector_taskResumed(currentTask);
}
return result;
}
BaseType_t safeMutexGive(SemaphoreHandle_t mutex) {
// 通知检测器互斥量被释放
deadlockDetector_mutexGiven(mutex);
return xSemaphoreGive(mutex);
}
// 测试任务
void deadlockTestTask1(void *parameter) {
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
for(;;) {
// 发送心跳
deadlockDetector_heartbeat(currentTask);
Serial.println("任务1:尝试获取互斥量A");
if (safeMutexTake(testMutexA, 3000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.println("任务1:获取互斥量A成功");
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println("任务1:尝试获取互斥量B");
if (safeMutexTake(testMutexB, 2000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.println("任务1:获取互斥量B成功");
vTaskDelay(500 / portTICK_PERIOD_MS);
Serial.println("任务1:释放互斥量B");
safeMutexGive(testMutexB);
} else {
Serial.println("任务1:获取互斥量B超时");
}
Serial.println("任务1:释放互斥量A");
safeMutexGive(testMutexA);
} else {
Serial.println("任务1:获取互斥量A超时");
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
void deadlockTestTask2(void *parameter) {
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
vTaskDelay(1500 / portTICK_PERIOD_MS); // 错开启动时间
for(;;) {
// 发送心跳
deadlockDetector_heartbeat(currentTask);
Serial.println("任务2:尝试获取互斥量B");
if (safeMutexTake(testMutexB, 3000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.println("任务2:获取互斥量B成功");
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println("任务2:尝试获取互斥量A");
if (safeMutexTake(testMutexA, 2000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.println("任务2:获取互斥量A成功");
vTaskDelay(500 / portTICK_PERIOD_MS);
Serial.println("任务2:释放互斥量A");
safeMutexGive(testMutexA);
} else {
Serial.println("任务2:获取互斥量A超时");
}
Serial.println("任务2:释放互斥量B");
safeMutexGive(testMutexB);
} else {
Serial.println("任务2:获取互斥量B超时");
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
}
}
void deadlockTestTask3(void *parameter) {
TaskHandle_t currentTask = xTaskGetCurrentTaskHandle();
for(;;) {
// 发送心跳
deadlockDetector_heartbeat(currentTask);
Serial.println("任务3:正常工作中");
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
性能瓶颈的定位方法
当系统出现性能问题时,我们需要系统性的方法来定位瓶颈。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
// 性能分析工具
typedef struct {
const char* name;
uint64_t totalTime;
uint64_t minTime;
uint64_t maxTime;
uint32_t callCount;
uint64_t lastStartTime;
} PerformanceCounter;
#define MAX_PERFORMANCE_COUNTERS 20
PerformanceCounter perfCounters[MAX_PERFORMANCE_COUNTERS];
int perfCounterCount = 0;
// 性能分析宏
#define PERF_START(name) \
int perfIndex = getOrCreatePerfCounter(name); \
perfCounters[perfIndex].lastStartTime = esp_timer_get_time();
#define PERF_END(name) \
uint64_t endTime = esp_timer_get_time(); \
int perfIndex = getPerfCounter(name); \
if (perfIndex >= 0) { \
uint64_t duration = endTime - perfCounters[perfIndex].lastStartTime; \
updatePerfCounter(perfIndex, duration); \
}
// 性能分析函数
int getOrCreatePerfCounter(const char* name);
int getPerfCounter(const char* name);
void updatePerfCounter(int index, uint64_t duration);
void printPerformanceReport();
// 测试用的同步原语
SemaphoreHandle_t perfTestMutex;
SemaphoreHandle_t perfTestSemaphore;
void setup() {
Serial.begin(115200);
perfTestMutex = xSemaphoreCreateMutex();
perfTestSemaphore = xSemaphoreCreateCounting(5, 5);
Serial.println("性能瓶颈定位工具演示开始!");
// 创建性能测试任务
xTaskCreate(performanceTestTask, "PerfTest", 2048, NULL, 2, NULL);
xTaskCreate(performanceAnalysisTask, "PerfAnalysis", 2048, NULL, 1, NULL);
// 运行性能基准测试
runPerformanceBenchmarks();
}
void runPerformanceBenchmarks() {
Serial.println("=== 性能基准测试 ===");
// 测试互斥量性能
testMutexPerformance();
// 测试信号量性能
testSemaphorePerformance();
// 测试任务切换性能
testTaskSwitchPerformance();
// 测试内存分配性能
testMemoryAllocationPerformance();
}
void testMutexPerformance() {
Serial.println("测试互斥量性能...");
const int iterations = 1000;
for (int i = 0; i < iterations; i++) {
PERF_START("mutex_take_give");
xSemaphoreTake(perfTestMutex, portMAX_DELAY);
xSemaphoreGive(perfTestMutex);
PERF_END("mutex_take_give");
}
Serial.printf("互斥量获取/释放测试完成:%d 次迭代\n", iterations);
}
void testSemaphorePerformance() {
Serial.println("测试信号量性能...");
const int iterations = 1000;
for (int i = 0; i < iterations; i++) {
PERF_START("semaphore_take_give");
xSemaphoreTake(perfTestSemaphore, portMAX_DELAY);
xSemaphoreGive(perfTestSemaphore);
PERF_END("semaphore_take_give");
}
Serial.printf("信号量获取/释放测试完成:%d 次迭代\n", iterations);
}
void testTaskSwitchPerformance() {
Serial.println("测试任务切换性能...");
const int iterations = 100;
```cpp
void testTaskSwitchPerformance() {
Serial.println("测试任务切换性能...");
const int iterations = 100;
for (int i = 0; i < iterations; i++) {
PERF_START("task_yield");
taskYIELD();
PERF_END("task_yield");
}
Serial.printf("任务切换测试完成:%d 次迭代\n", iterations);
}
void testMemoryAllocationPerformance() {
Serial.println("测试内存分配性能...");
const int iterations = 100;
const int blockSize = 1024; // 1KB
for (int i = 0; i < iterations; i++) {
PERF_START("memory_alloc_free");
void* ptr = pvPortMalloc(blockSize);
vPortFree(ptr);
PERF_END("memory_alloc_free");
}
Serial.printf("内存分配/释放测试完成:%d 次迭代\n", iterations);
}
void performanceTestTask(void *parameter) {
for(;;) {
// 模拟各种同步操作的性能测试
// 测试1:互斥量操作
PERF_START("mutex_operation");
if (xSemaphoreTake(perfTestMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
// 模拟临界区操作
vTaskDelay(1 / portTICK_PERIOD_MS);
xSemaphoreGive(perfTestMutex);
}
PERF_END("mutex_operation");
// 测试2:信号量操作
PERF_START("semaphore_operation");
if (xSemaphoreTake(perfTestSemaphore, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
// 模拟资源使用
vTaskDelay(2 / portTICK_PERIOD_MS);
xSemaphoreGive(perfTestSemaphore);
}
PERF_END("semaphore_operation");
// 测试3:任务延迟
PERF_START("task_delay");
vTaskDelay(10 / portTICK_PERIOD_MS);
PERF_END("task_delay");
// 测试4:内存操作
PERF_START("memory_operation");
void* buffer = pvPortMalloc(512);
if (buffer != NULL) {
// 模拟内存操作
memset(buffer, 0, 512);
vPortFree(buffer);
}
PERF_END("memory_operation");
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
void performanceAnalysisTask(void *parameter) {
for(;;) {
// 定期打印性能报告
printPerformanceReport();
vTaskDelay(10000 / portTICK_PERIOD_MS); // 每10秒报告一次
}
}
int getOrCreatePerfCounter(const char* name) {
// 查找现有计数器
for (int i = 0; i < perfCounterCount; i++) {
if (strcmp(perfCounters[i].name, name) == 0) {
return i;
}
}
// 创建新计数器
if (perfCounterCount < MAX_PERFORMANCE_COUNTERS) {
perfCounters[perfCounterCount].name = name;
perfCounters[perfCounterCount].totalTime = 0;
perfCounters[perfCounterCount].minTime = UINT64_MAX;
perfCounters[perfCounterCount].maxTime = 0;
perfCounters[perfCounterCount].callCount = 0;
perfCounters[perfCounterCount].lastStartTime = 0;
return perfCounterCount++;
}
return -1; // 无法创建更多计数器
}
int getPerfCounter(const char* name) {
for (int i = 0; i < perfCounterCount; i++) {
if (strcmp(perfCounters[i].name, name) == 0) {
return i;
}
}
return -1; // 未找到
}
void updatePerfCounter(int index, uint64_t duration) {
if (index >= 0 && index < perfCounterCount) {
perfCounters[index].totalTime += duration;
perfCounters[index].callCount++;
if (duration < perfCounters[index].minTime) {
perfCounters[index].minTime = duration;
}
if (duration > perfCounters[index].maxTime) {
perfCounters[index].maxTime = duration;
}
}
}
void printPerformanceReport() {
Serial.println("=== 性能分析报告 ===");
for (int i = 0; i < perfCounterCount; i++) {
PerformanceCounter* counter = &perfCounters[i];
if (counter->callCount > 0) {
double avgTime = (double)counter->totalTime / counter->callCount;
Serial.printf("操作: %s\n", counter->name);
Serial.printf(" 调用次数: %u\n", counter->callCount);
Serial.printf(" 平均时间: %.2f 微秒\n", avgTime);
Serial.printf(" 最小时间: %llu 微秒\n", counter->minTime);
Serial.printf(" 最大时间: %llu 微秒\n", counter->maxTime);
Serial.printf(" 总时间: %llu 微秒\n", counter->totalTime);
}
}
// 找出性能瓶颈
int worstIndex = -1;
double worstAvgTime = 0;
for (int i = 0; i < perfCounterCount; i++) {
if (perfCounters[i].callCount > 0) {
double avgTime = (double)perfCounters[i].totalTime / perfCounters[i].callCount;
if (avgTime > worstAvgTime) {
worstAvgTime = avgTime;
worstIndex = i;
}
}
}
if (worstIndex >= 0) {
Serial.printf("\n潜在性能瓶颈: %s (平均 %.2f 微秒)\n",
perfCounters[worstIndex].name, worstAvgTime);
}
Serial.println("=====================\n");
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
高级话题:超越基础的深度探索
掌握了基础知识后,是时候深入探索一些高级话题了。这些高级技巧能够帮助你应对更复杂的多任务场景。
信号量集合的巧妙运用
在复杂的实时系统中,任务可能需要等待多个事件中的任意一个发生。信号量集合(Semaphore Sets)就是为这种场景设计的。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"
// 事件组标志位定义
#define BUTTON_PRESSED_BIT (1 << 0)
#define SENSOR_DATA_BIT (1 << 1)
#define TIMER_EXPIRED_BIT (1 << 2)
#define NETWORK_DATA_BIT (1 << 3)
#define ALL_EVENTS_BITS (BUTTON_PRESSED_BIT | SENSOR_DATA_BIT | TIMER_EXPIRED_BIT | NETWORK_DATA_BIT)
// 事件组句柄
EventGroupHandle_t eventGroup;
// 传统信号量
SemaphoreHandle_t buttonSemaphore;
SemaphoreHandle_t sensorSemaphore;
SemaphoreHandle_t timerSemaphore;
SemaphoreHandle_t networkSemaphore;
void setup() {
Serial.begin(115200);
// 创建事件组
eventGroup = xEventGroupCreate();
// 创建传统信号量
buttonSemaphore = xSemaphoreCreateBinary();
sensorSemaphore = xSemaphoreCreateBinary();
timerSemaphore = xSemaphoreCreateBinary();
networkSemaphore = xSemaphoreCreateBinary();
Serial.println("信号量集合演示开始!");
// 创建事件产生任务
xTaskCreate(buttonTask, "Button_Task", 2048, NULL, 2, NULL);
xTaskCreate(sensorTask, "Sensor_Task", 2048, NULL, 2, NULL);
xTaskCreate(timerTask, "Timer_Task", 2048, NULL, 2, NULL);
xTaskCreate(networkTask, "Network_Task", 2048, NULL, 2, NULL);
// 创建事件处理任务
xTaskCreate(eventHandlerTask, "Event_Handler", 2048, NULL, 3, NULL);
// 创建传统多信号量等待任务(用于对比)
xTaskCreate(traditionalMultiWaitTask, "Traditional_Wait", 2048, NULL, 3, NULL);
}
// 按钮事件生成任务
void buttonTask(void *parameter) {
for(;;) {
// 模拟按钮按下
vTaskDelay((2000 + random(3000)) / portTICK_PERIOD_MS);
Serial.println("按钮:检测到按下");
// 设置事件标志位
xEventGroupSetBits(eventGroup, BUTTON_PRESSED_BIT);
// 同时也释放传统信号量
xSemaphoreGive(buttonSemaphore);
}
}
// 传感器事件生成任务
void sensorTask(void *parameter) {
for(;;) {
// 模拟传感器数据到达
vTaskDelay((3000 + random(4000)) / portTICK_PERIOD_MS);
Serial.println("传感器:新数据可用");
// 设置事件标志位
xEventGroupSetBits(eventGroup, SENSOR_DATA_BIT);
// 同时也释放传统信号量
xSemaphoreGive(sensorSemaphore);
}
}
// 定时器事件生成任务
void timerTask(void *parameter) {
for(;;) {
// 模拟定时器到期
vTaskDelay(5000 / portTICK_PERIOD_MS);
Serial.println("定时器:时间到");
// 设置事件标志位
xEventGroupSetBits(eventGroup, TIMER_EXPIRED_BIT);
// 同时也释放传统信号量
xSemaphoreGive(timerSemaphore);
}
}
// 网络事件生成任务
void networkTask(void *parameter) {
for(;;) {
// 模拟网络数据到达
vTaskDelay((4000 + random(5000)) / portTICK_PERIOD_MS);
Serial.println("网络:收到数据包");
// 设置事件标志位
xEventGroupSetBits(eventGroup, NETWORK_DATA_BIT);
// 同时也释放传统信号量
xSemaphoreGive(networkSemaphore);
}
}
// 使用事件组的事件处理任务
void eventHandlerTask(void *parameter) {
for(;;) {
Serial.println("事件处理器:等待任意事件...");
// 等待任意事件发生
EventBits_t bits = xEventGroupWaitBits(
eventGroup, // 事件组
ALL_EVENTS_BITS, // 等待的位
pdTRUE, // 清除位
pdFALSE, // 等待任意位
portMAX_DELAY // 无限等待
);
// 检查哪些事件发生
if (bits & BUTTON_PRESSED_BIT) {
Serial.println("事件处理器:处理按钮事件");
// 处理按钮事件
}
if (bits & SENSOR_DATA_BIT) {
Serial.println("事件处理器:处理传感器事件");
// 处理传感器事件
}
if (bits & TIMER_EXPIRED_BIT) {
Serial.println("事件处理器:处理定时器事件");
// 处理定时器事件
}
if (bits & NETWORK_DATA_BIT) {
Serial.println("事件处理器:处理网络事件");
// 处理网络事件
}
}
}
// 使用传统信号量的多事件等待任务
void traditionalMultiWaitTask(void *parameter) {
for(;;) {
Serial.println("传统等待:轮询多个信号量...");
bool eventProcessed = false;
// 检查按钮信号量
if (xSemaphoreTake(buttonSemaphore, 0) == pdTRUE) {
Serial.println("传统等待:处理按钮事件");
eventProcessed = true;
}
// 检查传感器信号量
if (xSemaphoreTake(sensorSemaphore, 0) == pdTRUE) {
Serial.println("传统等待:处理传感器事件");
eventProcessed = true;
}
// 检查定时器信号量
if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) {
Serial.println("传统等待:处理定时器事件");
eventProcessed = true;
}
// 检查网络信号量
if (xSemaphoreTake(networkSemaphore, 0) == pdTRUE) {
Serial.println("传统等待:处理网络事件");
eventProcessed = true;
}
if (!eventProcessed) {
// 如果没有事件,短暂延迟后再次检查
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
条件变量与信号量的配合
条件变量是另一种同步机制,它允许任务等待特定条件满足。在FreeRTOS中,我们可以使用信号量和互斥量来实现类似的功能。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// 条件变量模拟结构
typedef struct {
SemaphoreHandle_t mutex;
SemaphoreHandle_t signal;
int waitingTasks;
} ConditionVariable;
// 共享数据结构
typedef struct {
int value;
bool dataReady;
ConditionVariable dataCondition;
} SharedData;
SharedData sharedData;
// 条件变量操作函数
void conditionVariable_init(ConditionVariable* cv);
void conditionVariable_wait(ConditionVariable* cv);
void conditionVariable_signal(ConditionVariable* cv);
void conditionVariable_broadcast(ConditionVariable* cv);
void setup() {
Serial.begin(115200);
// 初始化共享数据
sharedData.value = 0;
sharedData.dataReady = false;
conditionVariable_init(&sharedData.dataCondition);
Serial.println("条件变量演示开始!");
// 创建生产者和消费者任务
xTaskCreate(producerTask, "Producer", 2048, NULL, 2, NULL);
xTaskCreate(consumerTask, "Consumer1", 2048, (void*)1, 1, NULL);
xTaskCreate(consumerTask, "Consumer2", 2048, (void*)2, 1, NULL);
xTaskCreate(consumerTask, "Consumer3", 2048, (void*)3, 1, NULL);
}
void conditionVariable_init(ConditionVariable* cv) {
cv->mutex = xSemaphoreCreateMutex();
cv->signal = xSemaphoreCreateCounting(10, 0); // 支持多个等待任务
cv->waitingTasks = 0;
}
void conditionVariable_wait(ConditionVariable* cv) {
// 增加等待计数
cv->waitingTasks++;
// 释放互斥锁
xSemaphoreGive(cv->mutex);
// 等待条件信号
xSemaphoreTake(cv->signal, portMAX_DELAY);
// 重新获取互斥锁
xSemaphoreTake(cv->mutex, portMAX_DELAY);
// 减少等待计数
cv->waitingTasks--;
}
void conditionVariable_signal(ConditionVariable* cv) {
// 如果有等待任务,发送一个信号
if (cv->waitingTasks > 0) {
xSemaphoreGive(cv->signal);
}
}
void conditionVariable_broadcast(ConditionVariable* cv) {
// 向所有等待任务发送信号
for (int i = 0; i < cv->waitingTasks; i++) {
xSemaphoreGive(cv->signal);
}
}
void producerTask(void *parameter) {
for(;;) {
// 模拟数据生成
vTaskDelay((2000 + random(3000)) / portTICK_PERIOD_MS);
// 获取互斥锁
xSemaphoreTake(sharedData.dataCondition.mutex, portMAX_DELAY);
// 更新共享数据
sharedData.value = random(1, 100);
sharedData.dataReady = true;
Serial.printf("生产者:生成新数据 %d,通知所有消费者\n", sharedData.value);
// 通知所有等待的消费者
conditionVariable_broadcast(&sharedData.dataCondition);
// 释放互斥锁
xSemaphoreGive(sharedData.dataCondition.mutex);
}
}
void consumerTask(void *parameter) {
int consumerId = (int)parameter;
for(;;) {
// 获取互斥锁
xSemaphoreTake(sharedData.dataCondition.mutex, portMAX_DELAY);
Serial.printf("消费者%d:检查数据状态\n", consumerId);
// 如果数据未就绪,等待条件变量
while (!sharedData.dataReady) {
Serial.printf("消费者%d:数据未就绪,等待...\n", consumerId);
conditionVariable_wait(&sharedData.dataCondition);
}
// 处理数据
Serial.printf("消费者%d:处理数据 %d\n", consumerId, sharedData.value);
// 如果是最后一个消费者,重置数据状态
if (sharedData.dataCondition.waitingTasks == 0) {
sharedData.dataReady = false;
Serial.printf("消费者%d:重置数据状态\n", consumerId);
}
// 释放互斥锁
xSemaphoreGive(sharedData.dataCondition.mutex);
// 模拟处理时间
vTaskDelay((500 + random(1000)) / portTICK_PERIOD_MS);
}
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
中断服务程序中的同步机制
在中断服务程序(ISR)中使用同步机制需要特别小心,因为普通的API函数可能会导致系统崩溃。FreeRTOS为此提供了专门的"FromISR"版本的API。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
// 中断引脚定义
#define BUTTON_PIN 0 // 假设按钮连接到GPIO0
// 同步原语
SemaphoreHandle_t isrSemaphore;
SemaphoreHandle_t isrMutex;
SemaphoreHandle_t isrQueue;
// 中断计数器
volatile int interruptCount = 0;
void setup() {
Serial.begin(115200);
// 创建同步原语
isrSemaphore = xSemaphoreCreateBinary();
isrMutex = xSemaphoreCreateMutex();
isrQueue = xQueueCreate(10, sizeof(int));
if (isrSemaphore == NULL || isrMutex == NULL || isrQueue == NULL) {
Serial.println("同步原语创建失败!");
return;
}
Serial.println("中断同步机制演示开始!");
// 设置中断引脚
pinMode(BUTTON_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);
// 创建处理任务
xTaskCreate(isrHandlerTask, "ISR_Handler", 2048, NULL, 3, NULL);
xTaskCreate(normalTask, "Normal_Task", 2048, NULL, 1, NULL);
Serial.println("按下按钮触发中断");
}
// 中断服务程序
void IRAM_ATTR buttonISR() {
// 记录中断次数
interruptCount++;
// 发送信号量(使用ISR版本)
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(isrSemaphore, &xHigherPriorityTaskWoken);
// 向队列发送数据(使用ISR版本)
int value = interruptCount;
xQueueSendFromISR(isrQueue, &value, &xHigherPriorityTaskWoken);
// 如果唤醒了更高优先级的任务,请求上下文切换
if (xHigherPriorityTaskWoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
// 中断处理任务
void isrHandlerTask(void *parameter) {
for(;;) {
// 等待中断信号
if (xSemaphoreTake(isrSemaphore, portMAX_DELAY) == pdTRUE) {
Serial.println("中断处理任务:收到中断通知");
// 从队列读取中断数据
int value;
if (xQueueReceive(isrQueue, &value, 0) == pdTRUE) {
Serial.printf("中断处理任务:收到中断数据 %d\n", value);
}
// 处理中断
processInterrupt();
}
}
}
void processInterrupt() {
// 获取互斥量保护共享资源
if (xSemaphoreTake(isrMutex, 100 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.printf("处理中断:中断计数 = %d\n", interruptCount);
// 模拟处理时间
vTaskDelay(100 / portTICK_PERIOD_MS);
// 释放互斥量
xSemaphoreGive(isrMutex);
} else {
Serial.println("处理中断:无法获取互斥量");
}
}
// 普通任务
void normalTask(void *parameter) {
for(;;) {
// 获取互斥量访问共享资源
if (xSemaphoreTake(isrMutex, 1000 / portTICK_PERIOD_MS) == pdTRUE) {
Serial.printf("普通任务:访问共享资源,中断计数 = %d\n", interruptCount);
// 模拟处理时间
vTaskDelay(500 / portTICK_PERIOD_MS);
// 释放互斥量
xSemaphoreGive(isrMutex);
} else {
Serial.println("普通任务:无法获取互斥量");
}
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
void loop() {
// 模拟按钮按下(在实际项目中,这部分会被真实的按钮中断替代)
static unsigned long lastSimulatedInterrupt = 0;
if (millis() - lastSimulatedInterrupt > 5000) {
lastSimulatedInterrupt = millis();
Serial.println("模拟按钮按下...");
buttonISR();
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}