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

STM32 FreeRTOS调用DHT11时卡在vPortEnterCritical()问题排查

STM32F407 FreeRTOS项目调用外部函数触发DHT11读取时卡在vPortEnterCritical()的问题

问题现象

  • 在键盘任务中调用handle_authenticated_input()执行DHT11读取后,系统直接卡在vPortEnterCritical()函数内
  • 把handle_authenticated_input()里的代码直接复制到键盘任务循环中,系统就能正常多次读取温湿度,且任务栈空间充足

原因分析

  1. 隐性栈溢出:虽然任务栈整体空间足够,但调用handle_authenticated_input()会多一层函数调用栈帧,加上函数内的局部变量(如temp_buf[4]),可能刚好触发栈的局部溢出,破坏了FreeRTOS的临界区嵌套计数uxCriticalNesting。这个变量被篡改后,进入临界区的逻辑就会异常,导致系统卡住。
  2. 全局资源冲突:代码中GPIO_InitStruct看起来是全局变量,当handle_authenticated_input()调用dht11_get_data()时,如果其他任务或中断同时操作这个全局结构体,会导致GPIO初始化出错,进而触发vPortEnterCritical()里的断言失败(configASSERT)。直接把代码写在任务里时,执行路径更紧凑,冲突概率降低。
  3. 中断上下文冲突:vPortEnterCritical()里的断言会检查当前是否处于中断上下文(portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK == 0)。如果handle_authenticated_input()执行过程中意外触发了中断,且中断里调用了非FromISR版本的FreeRTOS API,就会导致进入临界区时断言失败,系统卡死。直接写在任务里时,执行时序不同,没触发这个场景。

解决方法

  • 增大任务栈空间:适当给键盘任务增加栈空间(比如提升50%),同时用uxTaskGetStackHighWaterMark()监控栈剩余空间,确认是否存在栈溢出风险。
  • 避免全局资源共享:把GPIO_InitStruct改成dht11_get_data()内的局部变量,防止多任务/中断并发修改。修改后的代码片段如下:
// 在dht11_get_data()内局部定义
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = DHT11_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(DHT11_GPIO_Port, &GPIO_InitStruct);
  • 规范中断API调用:所有中断服务函数里只使用FreeRTOS的FromISR版本API,避免在中断中调用普通API导致临界区冲突。同时检查delay_ms、delay_us这类延时函数是否会禁用中断或触发其他中断,导致进入临界区时处于中断上下文。
  • 优化函数调用:把handle_authenticated_input()改成静态函数,或者添加inline属性,减少函数调用带来的栈开销,避免栈层级过高。

相关代码

原键盘任务代码(卡住版本)

static void app_task_keyboard(void *pvParameter)
{
    int8_t key_val = 0;
    char convertion_val;
    uint8_t temp_buf[4];
    int8_t dht11_ret = 0;

    tft_t tft;

    BaseType_t uxHighWaterMark;

    for(;;)
    {
        key_val = matrix_key_scan();

        // dgb_printf_safe("keyboard\r\n");

        // 判断是否输入正确密码进入系统
        if(authenticated)
        {
            // 开始扫描用户按键,执行相应的功能
            if(key_val >0 && key_val < 17)
            {
                convertion_val = matrix_val_convert(key_val);

                dgb_printf_safe("%c\r\n", convertion_val);

                // 通过识别到的按键,进行功能选择
                handle_authenticated_input(convertion_val);
            }
        }

        vTaskDelay(1);
    }
}

handle_authenticated_input函数代码

void handle_authenticated_input(char input_val)
{
    uint8_t temp_buf[4];
    int8_t dht11_ret = 0;

    switch (input_val)
    {
    case 'A':
        // 温度查询
        dht11_ret = dht11_get_data(temp_buf);
        if(dht11_ret)
        {
            dgb_printf_safe("temp: %02d.%02d\r\n", temp_buf[2], temp_buf[3]);
        }
        else
            dgb_printf_safe("get temp failed:%d\r\n", dht11_ret);
        break;
    case 'B':
        // 湿度查询
        dht11_ret = dht11_get_data(temp_buf);
        if(dht11_ret)
        {
            dgb_printf_safe("humi: %02d.%02d\r\n", temp_buf[0], temp_buf[1]);
        }
            // dgb_printf_safe("humi: %02d.%02d\r\n", temp_buf[2], temp_buf[3]);
        else
            dgb_printf_safe("get humi failed:%d\r\n", dht11_ret);
        break;

    default:
        break;
    }
}

dht11_get_data函数代码

int8_t dht11_get_data(uint8_t *buf)
{
    uint16_t t;
    int8_t i, j,  byte;
    uint8_t *pbuf;
    uint32_t checksum = 0;

    pbuf = buf;

    GPIO_InitStruct.Pin = DHT11_Pin; //set GPIO pins
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;            //set pin mode, IN/OUT
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;           //set pin speed, higher speed with higher response but higher consumption
    GPIO_InitStruct.Pull = GPIO_NOPULL;        //specify pull-up or pull-down
    HAL_GPIO_Init(DHT11_GPIO_Port, &GPIO_InitStruct);                      //init pin

    PGout(9) = 0;
    delay_ms(25);   // 主机至少拉低18ms
    PGout(9) = 1;
    delay_us(30);   // 主机拉高20-40us

    GPIO_InitStruct.Pin = DHT11_Pin; //set GPIO pins
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;            //set pin mode, IN/OUT
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;           //set pin speed, higher speed with higher response but higher consumption
    GPIO_InitStruct.Pull = GPIO_NOPULL;        //specify pull-up or pull-down
    HAL_GPIO_Init(DHT11_GPIO_Port, &GPIO_InitStruct);                      //init pin

    t = 0;
    while (PGin(9))    // 等待DHT响应(低电平)
    {
        t++;
        delay_us(1);
        if(t >= 100)
            return -1;
    }

    t = 0;
    while (PGin(9) == 0)  // DHT响应信号(低电平)80us
    {
        t++;
        delay_us(1);
        if(t >= 100)
            return -2;
    }

    t = 0;
    while (PGin(9))   // DHT拉高80us
    {
        t++;
        delay_us(1);
        if(t >= 100)
            return -3;
    }

    for(i = 0; i < 5; i++)
    {
        byte = 0;
        for(j = 7; j >= 0; j--)
        {
            t = 0;
            while (PGin(9) == 0)
            {
                t++;
                delay_us(1);
                if(t > 60)
                    return -4;
            }

            delay_us(40);

            if(PGin(9) == 1)
            {
                byte |= (1 << j);

                t = 0;
                while(PGin(9) == 1)
                {
                    t++;
                    delay_us(1);
                    if(t > 60)
                        return -5;
                }
            }
        }
        pbuf[i] = byte;
    }

    checksum = pbuf[0] + pbuf[1] + pbuf[2] + pbuf[3];

    if(checksum != pbuf[4])
        return -6;

    return 1;
}

vPortEnterCritical函数代码

void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS();
    uxCriticalNesting++;

    /* This is not the interrupt safe version of the enter critical function so
    assert() if it is being called from an interrupt context.  Only API
    functions that end in "FromISR" can be used in an interrupt.  Only assert if
    the critical nesting count is 1 to protect against recursive calls if the
    assert function also uses a critical section. */
    if( uxCriticalNesting == 1 )
    {
        configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
    }
}

正常运行的键盘任务代码

static void app_task_keyboard(void *pvParameter)
{
    int8_t key_val = 0;
    char convertion_val;
    uint8_t temp_buf[4];
    int8_t dht11_ret = 0;

    tft_t tft;

    BaseType_t uxHighWaterMark;

    for(;;)
    {
        key_val = matrix_key_scan();

        // dgb_printf_safe("keyboard\r\n");

        // 判断是否输入正确密码进入系统
        if(authenticated)
        {
            // 开始扫描用户按键,执行相应的功能
            if(key_val >0 && key_val < 17)
            {
                convertion_val = matrix_val_convert(key_val);

                dgb_printf_safe("%c\r\n", convertion_val);

                switch (convertion_val)
                {
                case 'A':
                    // 温度查询
                    dht11_ret = dht11_get_data(temp_buf);
                    if(dht11_ret)
                    {
                        dgb_printf_safe("temp: %02d.%02d\r\n", temp_buf[2], temp_buf[3]);
                    }
                    else
                        dgb_printf_safe("get temp failed:%d\r\n", dht11_ret);
                    break;
                case 'B':
                    // 湿度查询
                    dht11_ret = dht11_get_data(temp_buf);
                    if(dht11_ret)
                    {
                        dgb_printf_safe("humi: %02d.%02d\r\n", temp_buf[0], temp_buf[1]);
                    }
                        // dgb_printf_safe("humi: %02d.%02d\r\n", temp_buf[2], temp_buf[3]);
                    else
                        dgb_printf_safe("get humi failed:%d\r\n", dht11_ret);
                    break;

                default:
                    break;
                }

                // 通过识别到的按键,进行功能选择
                // handle_authenticated_input(convertion_val);
            }
        }

        vTaskDelay(1);
    }
}

内容的提问来源于stack exchange,提问作者mingxun wei

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 18:17:03