ESP32交通灯项目:xTaskCreate创建任务后后续代码失效求助
ESP32交通灯任务阻塞问题排查
我用ESP32开发交通灯模拟项目,两组交通灯互斥亮绿灯(一组红灯亮时另一组绿灯亮)。默认绿灯持续10秒后切换,按键触发后绿灯持续时间改为20秒。我写了buttonCheck任务并行检测按键,但创建该任务后,app_main里的交通灯控制循环无法正常运行。尝试过调整任务栈大小、优先级、添加延迟,都没解决。
相关代码片段
buttonCheck任务函数
void buttonCheck(void *parameters){ int *timer = (int *)parameters; esp_rom_gpio_pad_select_gpio(GPIO_NUM_0); gpio_set_direction(GPIO_NUM_0, GPIO_MODE_INPUT); gpio_set_pull_mode(GPIO_NUM_0, GPIO_PULLUP_ONLY); bool timeswitch = true; vTaskDelay(500/portTICK_PERIOD_MS); while(1){ if(gpio_get_level(GPIO_NUM_0) == 0 && timeswitch){ *timer = 2000; timeswitch = false; // vTaskDelay(500/portTICK_PERIOD_MS); } if(gpio_get_level(GPIO_NUM_0) == 0 && !timeswitch){ *timer = 10000; timeswitch = true; // vTaskDelay(500/portTICK_PERIOD_MS); } // vTaskDelay(10000/portTICK_PERIOD_MS); } }
app_main中的任务创建与交通灯循环
xTaskCreate(buttonCheck, "BUTTONPRESSCHECK", 150, &timer, 14, NULL); while (1) { if (EWgreen) { toRed(EWlight); vTaskDelay(2000 / portTICK_PERIOD_MS); toGreen(NSlight); EWgreen = false; } else { toRed(NSlight); vTaskDelay(2000 / portTICK_PERIOD_MS); toGreen(EWlight); EWgreen = true; } vTaskDelay(timer / portTICK_PERIOD_MS); }
完整代码
#include<stdio.h> #include<freertos/FreeRTOS.h> #include<freertos/task.h> #include<driver/gpio.h> struct ledstuff { gpio_num_t red; gpio_num_t yellow; gpio_num_t green; }; typedef struct ledstuff ledinfo; void toRed(ledinfo data) { gpio_set_level(data.green, 0); gpio_set_level(data.yellow, 1); vTaskDelay(2000 / portTICK_PERIOD_MS); gpio_set_level(data.yellow, 0); gpio_set_level(data.red, 1); } void toGreen(ledinfo data) { gpio_set_level(data.red, 0); gpio_set_level(data.yellow, 0); gpio_set_level(data.green, 1); } void trafficLights(void *params) { int *timer = (int *) params; bool EWgreen = true; ledinfo NSlight; NSlight.red = GPIO_NUM_19; NSlight.yellow = GPIO_NUM_18; NSlight.green = GPIO_NUM_5; ledinfo EWlight; EWlight.red = GPIO_NUM_27; EWlight.yellow = GPIO_NUM_26; EWlight.green = GPIO_NUM_25; gpio_set_level(NSlight.red, 1); gpio_set_level(EWlight.green, 1); vTaskDelay(3000 / portTICK_PERIOD_MS); while (1) { if (EWgreen) { toRed(EWlight); vTaskDelay(2000 / portTICK_PERIOD_MS); toGreen(NSlight); EWgreen = false; } else { toRed(NSlight); vTaskDelay(2000 / portTICK_PERIOD_MS); toGreen(EWlight); EWgreen = true; } vTaskDelay(*timer / portTICK_PERIOD_MS); } } void buttonCheck(void *parameters) { int *timer = (int *) parameters; esp_rom_gpio_pad_select_gpio(GPIO_NUM_0); gpio_set_direction(GPIO_NUM_0, GPIO_MODE_INPUT); gpio_set_pull_mode(GPIO_NUM_0, GPIO_PULLUP_ONLY); bool timeswitch = true; vTaskDelay(500 / portTICK_PERIOD_MS); while (1) { if (gpio_get_level(GPIO_NUM_0) == 0 && timeswitch) { *timer = 2000; gpio_set_level(GPIO_NUM_18, 1); timeswitch = false; // vTaskDelay(500/portTICK_PERIOD_MS); } if (gpio_get_level(GPIO_NUM_0) == 0 && !timeswitch) { *timer = 10000; gpio_set_level(GPIO_NUM_18, 0); timeswitch = true; // vTaskDelay(500/portTICK_PERIOD_MS); } // vTaskDelay(10000/portTICK_PERIOD_MS); } } void app_main() { int timer = 10000; bool EWgreen = true; gpio_set_direction(18, GPIO_MODE_OUTPUT); gpio_set_direction(19, GPIO_MODE_OUTPUT); gpio_set_direction(5, GPIO_MODE_OUTPUT); gpio_set_direction(27, GPIO_MODE_OUTPUT); gpio_set_direction(26, GPIO_MODE_OUTPUT); gpio_set_direction(25, GPIO_MODE_OUTPUT); ledinfo NSlight; NSlight.red = GPIO_NUM_19; NSlight.yellow = GPIO_NUM_18; NSlight.green = GPIO_NUM_5; ledinfo EWlight; EWlight.red = GPIO_NUM_27; EWlight.yellow = GPIO_NUM_26; EWlight.green = GPIO_NUM_25; gpio_set_level(NSlight.red, 1); gpio_set_level(EWlight.green, 1); vTaskDelay(3000 / portTICK_PERIOD_MS); xTaskCreate(buttonCheck, "BUTTONPRESSCHECK", 150, &timer, 14, NULL); // xTaskCreate(trafficLights,"TRAFFICLIGHTS",400,&timer,5,NULL); while (1) { if (EWgreen) { toRed(EWlight); vTaskDelay(2000 / portTICK_PERIOD_MS); toGreen(NSlight); EWgreen = false; } else { toRed(NSlight); vTaskDelay(2000 / portTICK_PERIOD_MS); toGreen(EWlight); EWgreen = true; } vTaskDelay(timer / portTICK_PERIOD_MS); } }
问题原因与修复方法
核心问题
- buttonCheck任务无延迟,抢占CPU:
buttonCheck的while循环里没有vTaskDelay,会一直占用CPU,FreeRTOS调度器无法切换到app_main的循环任务。FreeRTOS是抢占式调度,高优先级任务(这里buttonCheck优先级为14,app_main默认优先级为1)不主动释放CPU时,低优先级任务永远无法执行。 - 按键检测无消抖,逻辑异常:直接检测GPIO电平会因按键抖动触发多次状态切换,导致timer值频繁变化。
- 共享变量timer无同步保护:两个任务同时访问timer变量,可能引发数据竞争,导致值读取异常。
修复步骤
1. 给buttonCheck任务添加延迟,释放CPU
修改buttonCheck的while循环,添加消抖延迟和循环检测延迟,让调度器有机会切换其他任务:
while (1) { if (gpio_get_level(GPIO_NUM_0) == 0 && timeswitch) { *timer = 2000; gpio_set_level(GPIO_NUM_18, 1); timeswitch = false; vTaskDelay(500/portTICK_PERIOD_MS); // 消抖+延迟释放CPU } else if (gpio_get_level(GPIO_NUM_0) == 0 && !timeswitch) { *timer = 10000; gpio_set_level(GPIO_NUM_18, 0); timeswitch = true; vTaskDelay(500/portTICK_PERIOD_MS); // 消抖+延迟释放CPU } vTaskDelay(100/portTICK_PERIOD_MS); // 循环延迟,定期检测按键 }
2. 调整任务优先级与栈大小
降低buttonCheck任务优先级至与交通灯任务相同(如5),避免过度抢占;同时增大栈大小到256,防止栈溢出:
xTaskCreate(buttonCheck, "BUTTONPRESSCHECK", 256, &timer, 5, NULL);
3. 添加共享变量的同步保护
创建互斥锁保护timer变量的读写,避免数据竞争:
// 全局互斥锁 SemaphoreHandle_t timer_mutex; // 在app_main中初始化 timer_mutex = xSemaphoreCreateMutex(); // buttonCheck中修改timer时加锁 if (gpio_get_level(GPIO_NUM_0) == 0 && timeswitch) { if(xSemaphoreTake(timer_mutex, portMAX_DELAY)){ *timer = 2000; xSemaphoreGive(timer_mutex); } // ...其他代码 } // 交通灯循环中访问timer时加锁 if(xSemaphoreTake(timer_mutex, portMAX_DELAY)){ vTaskDelay(timer / portTICK_PERIOD_MS); xSemaphoreGive(timer_mutex); }
4. 优化任务结构(可选)
将app_main中的交通灯循环移到单独任务中,让两个任务并行执行,结构更清晰:
// 在app_main中创建交通灯任务 xTaskCreate(trafficLights, "TRAFFICLIGHTS", 256, &timer, 5, NULL); // 注释掉app_main里的while循环,挂起app_main任务 while(1){ vTaskDelay(portMAX_DELAY); }
修复后的完整代码示例
#include<stdio.h> #include<freertos/FreeRTOS.h> #include<freertos/task.h> #include<freertos/semphr.h> #include<driver/gpio.h> struct ledstuff { gpio_num_t red; gpio_num_t yellow; gpio_num_t green; }; typedef struct ledstuff ledinfo; SemaphoreHandle_t timer_mutex; void toRed(ledinfo data) { gpio_set_level(data.green, 0); gpio_set_level(data.yellow, 1); vTaskDelay(2000 / portTICK_PERIOD_MS); gpio_set_level(data.yellow, 0); gpio_set_level(data.red, 1); } void toGreen(ledinfo data) { gpio_set_level(data.red, 0); gpio_set_level(data.yellow, 0); gpio_set_level(data.green, 1); } void trafficLights(void *params) { int *timer = (int *) params; bool EWgreen = true; ledinfo NSlight = {GPIO_NUM_19, GPIO_NUM_18, GPIO_NUM_5}; ledinfo EWlight = {GPIO_NUM_27, GPIO_NUM_26, GPIO_NUM_25}; gpio_set_level(NSlight.red, 1); gpio_set_level(EWlight.green, 1); vTaskDelay(3000 / portTICK_PERIOD_MS); while (1) { if (EWgreen) { toRed(EWlight); vTaskDelay(2000 / portTICK_PERIOD_MS); toGreen(NSlight); EWgreen = false; } else { toRed(NSlight); vTaskDelay(2000 / portTICK_PERIOD_MS); toGreen(EWlight); EWgreen = true; } // 加锁访问timer if(xSemaphoreTake(timer_mutex, portMAX_DELAY)){ vTaskDelay(*timer / portTICK_PERIOD_MS); xSemaphoreGive(timer_mutex); } } } void buttonCheck(void *parameters) { int *timer = (int *) parameters; esp_rom_gpio_pad_select_gpio(GPIO_NUM_0); gpio_set_direction(GPIO_NUM_0, GPIO_MODE_INPUT); gpio_set_pull_mode(GPIO_NUM_0, GPIO_PULLUP_ONLY); bool timeswitch = true; vTaskDelay(500 / portTICK_PERIOD_MS); while (1) { if (gpio_get_level(GPIO_NUM_0) == 0 && timeswitch) { // 加锁修改timer if(xSemaphoreTake(timer_mutex, portMAX_DELAY)){ *timer = 2000; xSemaphoreGive(timer_mutex); } gpio_set_level(GPIO_NUM_18, 1); timeswitch = false; vTaskDelay(500/portTICK_PERIOD_MS); // 消抖 } else if (gpio_get_level(GPIO_NUM_0) == 0 && !timeswitch) { // 加锁修改timer if(xSemaphoreTake(timer_mutex, portMAX_DELAY)){ *timer = 10000; xSemaphoreGive(timer_mutex); } gpio_set_level(GPIO_NUM_18, 0); timeswitch = true; vTaskDelay(500/portTICK_PERIOD_MS); // 消抖 } vTaskDelay(100/portTICK_PERIOD_MS); // 释放CPU,定期检测 } } void app_main() { int timer = 10000; // 初始化GPIO gpio_set_direction(GPIO_NUM_18, GPIO_MODE_OUTPUT); gpio_set_direction(GPIO_NUM_19, GPIO_MODE_OUTPUT); gpio_set_direction(GPIO_NUM_5, GPIO_MODE_OUTPUT); gpio_set_direction(GPIO_NUM_27, GPIO_MODE_OUTPUT); gpio_set_direction(GPIO_NUM_26, GPIO_MODE_OUTPUT); gpio_set_direction(GPIO_NUM_25, GPIO_MODE_OUTPUT); // 创建互斥锁 timer_mutex = xSemaphoreCreateMutex(); // 创建任务 xTaskCreate(buttonCheck, "BUTTONPRESSCHECK", 256, &timer, 5, NULL); xTaskCreate(trafficLights, "TRAFFICLIGHTS", 256, &timer, 5, NULL); // 挂起app_main任务 while(1){ vTaskDelay(portMAX_DELAY); } }
内容的提问来源于stack exchange,提问作者Ryan Perez
相关产品推荐
相关产品推荐

