You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);

    }
}

问题原因与修复方法

核心问题

  1. buttonCheck任务无延迟,抢占CPU:buttonCheck的while循环里没有vTaskDelay,会一直占用CPU,FreeRTOS调度器无法切换到app_main的循环任务。FreeRTOS是抢占式调度,高优先级任务(这里buttonCheck优先级为14,app_main默认优先级为1)不主动释放CPU时,低优先级任务永远无法执行。
  2. 按键检测无消抖,逻辑异常:直接检测GPIO电平会因按键抖动触发多次状态切换,导致timer值频繁变化。
  3. 共享变量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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 08:39:55