ESP32 ESP-IDF看门狗TWDT

本文介绍ESP32上任务看门狗计时器(TWDT)的使用方法,包括初始化、订阅任务、重置及触发后的处理流程。通过实例演示如何避免程序因任务阻塞而崩溃。

陈拓 2022/07/02-2022/07/02

1. 概述

此示例演示如何使用任务看门狗计时器Task Watchdog Timer (TWDT)的以下功能:

  • 如何初始化和取消初始化TWDT
  • 如何订阅和取消订阅TWDT的任务
  • 如何向阅和取消订阅TWDT的用户
  • 如何使任务和用户可以重置(即喂狗)TWDTs

2. 官方例程国内镜像

https://gitee.com/EspressifSystems/esp-idf/tree/master/examples/system/task_watchdog

3. 开发环境

《用乐鑫国内Gitee镜像搭建ESP32开发环境》

https://zhuanlan.zhihu.com/p/348106034

https://blog.youkuaiyun.com/chentuo2000/article/details/113424934?spm=1001.2014.3001.5501

4. 复制代码

  • 克隆官方例程

将官方例子项目复制到ESP-IDF开发工具之外:

cd ~/esp

cp -r ~/esp/esp-idf/examples/system/task_watchdog ~/esp/

  • 项目树

cd ~/esp/task_watchdog

tree

5. 构建项目

  • 刷新esp-idf环境

get_idf

  • 设定目标芯片

idf.py set-target esp32

  • 配置项目

idf.py menuconfig

1) 将闪存设置为4MB

2) 在任务看门狗超时时调用紧急处理程序

保存,退出。

  • 编译项目

idf.py build

  • 烧写项目

查看USB转串口的COM口号:

烧写:

idf.py -p /dev/ttyS3 -b 115200 flash

  • 启用监视器

idf.py monitor -p /dev/ttyS3

当示例正常运行时,将观察到以下输出:

用户可以注释掉esp_task_wdt_reset()或esp_task_wdt_reset_user()调用以测试触发TWDT,

//CHECK_ERROR_CODE(esp_task_wdt_reset(), ESP_OK);

这将导致以下输出:

看门狗被触发。

因为TWDT超时时间为3秒:

#define TWDT_TIMEOUT_S          3 // TWDT超时时间

延时为10秒:

vTaskDelay(pdMS_TO_TICKS(10000));   // 延时10

在10秒延时期间,看门狗有3次触发,之后程序会继续执行:

6. 看门狗触发后立即重启程序

如果你希望任务看门狗触发之后进行重启

  • 设置在任务看门狗超时时调用紧急处理程序

  • 增加esp_task_wdt_isr_user_handler()函数
void esp_task_wdt_isr_user_handler(void)
{
    esp_restart();
}

这样在看门狗触发时程序会重新启动:

7. 完整代码

/* Task_Watchdog Examples

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"

#define TWDT_TIMEOUT_S          3 // TWDT超时时间
#define TASK_RESET_PERIOD_S     2 // 任务重置周期时间

/*
 * 宏,用于检查TWDT的输出,如果检测到返回错误码则触发中止程序执行。
 */
#define CHECK_ERROR_CODE(returned, expected) ({                        \
            if(returned != expected){                                  \
                printf("TWDT ERROR\n");                                \
                abort();                                               \
            }                                                          \
})

static TaskHandle_t task_handles[portNUM_PROCESSORS];

// 回调由app_main()创建的用户任务
void reset_task(void *arg)
{
    // 为TWDT添加任务,并检查dwt状态看是否添加
    CHECK_ERROR_CODE(esp_task_wdt_add(NULL), ESP_OK);
    CHECK_ERROR_CODE(esp_task_wdt_status(NULL), ESP_OK);

    while(1){
        // 每2秒重置一次看门狗
        //CHECK_ERROR_CODE(esp_task_wdt_reset(), ESP_OK);  // 喂狗。注释此行以测试触发TWDT超时
        vTaskDelay(pdMS_TO_TICKS(TASK_RESET_PERIOD_S * 1000));
    }
}

void esp_task_wdt_isr_user_handler(void)
{
    esp_restart();
}

void app_main(void)
{
    printf("Initialize TWDT\n");
    // 初始化或者重新初始化TWDT
    CHECK_ERROR_CODE(esp_task_wdt_init(TWDT_TIMEOUT_S, false), ESP_OK);

    // 如果启动时未订阅空闲任务,则将其订阅到TWDT
#ifndef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0
    esp_task_wdt_add(xTaskGetIdleTaskHandleForCPU(0));
#endif
#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 && !CONFIG_FREERTOS_UNICORE
    esp_task_wdt_add(xTaskGetIdleTaskHandleForCPU(1));
#endif

    // 创建用户任务并添加到看门狗
    for(int i = 0; i < portNUM_PROCESSORS; i++){
        xTaskCreatePinnedToCore(reset_task, "reset task", 1024, NULL, 10, &task_handles[i], i);
    }

    printf("Delay for 10 seconds\n");
    vTaskDelay(pdMS_TO_TICKS(10000));   // 延时10秒

    printf("Unsubscribing and deleting tasks\n");
    // 从任务看门狗中删除并取消订阅用户任务,然后取消订阅空闲任务
    for(int i = 0; i < portNUM_PROCESSORS; i++){
        vTaskDelete(task_handles[i]);   // 首先删除用户任务(防止重置未订阅的任务)
        CHECK_ERROR_CODE(esp_task_wdt_delete(task_handles[i]), ESP_OK); // 从TWDT取消订阅任务
        CHECK_ERROR_CODE(esp_task_wdt_status(task_handles[i]), ESP_ERR_NOT_FOUND);  // 确认任务已取消订阅

        // 取消订阅空闲任务
        CHECK_ERROR_CODE(esp_task_wdt_delete(xTaskGetIdleTaskHandleForCPU(i)), ESP_OK); // 从TWDT取消订阅空闲任务
        CHECK_ERROR_CODE(esp_task_wdt_status(xTaskGetIdleTaskHandleForCPU(i)), ESP_ERR_NOT_FOUND); // 确认空闲任务已取消订阅
    }

    // 在所有任务取消订阅后取消TWDT
    CHECK_ERROR_CODE(esp_task_wdt_deinit(), ESP_OK);
    CHECK_ERROR_CODE(esp_task_wdt_status(NULL), ESP_ERR_INVALID_STATE);     //Confirm TWDT has been deinitialized 确认TWDT已解除初始化

    printf("Complete\n");
}

参考文档

  1. ESP32任务看门狗实践https://www.codeleading.com/article/25393323890/

<think> 我们正在讨论ESP32-S3在ESP-IDF开发环境下程序卡住的问题。根据提供的引用信息和一般调试经验,程序卡住可能由多种原因引起,包括但不限于:内存溢出、死锁、硬件初始化失败、任务阻塞等。以下将结合引用内容和调试方法进行详细说明。 --- ### 1. **检查日志输出** ESP-IDF提供了强大的日志系统(引用[2])。在程序卡住前,查看最后输出的日志信息尤为重要: - 使用不同级别的日志宏(如`ESP_LOGE`, `ESP_LOGW`)在关键函数中打印状态。 - 若程序完全卡死无输出,可能是硬件问题或看门狗复位未触发。 ```c // 示例:在关键函数中添加日志 #include "esp_log.h" static const char* TAG = "Main"; void app_main() { ESP_LOGI(TAG, "程序启动"); // ... 初始化代码 } ``` **调试建议**: - 检查`menuconfig`中的日志配置: ``` idf.py menuconfig > Component config > Log output ``` 确保日志输出级别足够详细(如设置为`Debug`)且输出目标正确(如串口)。 --- ### 2. **排查硬件初始化问题** 引用[1]和[4]涉及硬件驱动(如摄像头、触摸屏)。若程序在初始化外设时卡住: - **确认硬件连接**:检查引脚配置(如I2C的SDA/SCL)、电源稳定性。 - **检查驱动配置**:如引用[4]中的触摸屏初始化代码,需确保参数(如`x_max`, `y_max`, GPIO号)与实际硬件匹配。 - **简化测试**:注释掉可疑硬件初始化代码,逐步排除。 --- ### 3. **分析任务与内存状态** 程序卡住常见原因: - **堆栈溢出**:ESP-IDF默认会触发堆栈溢出检测(触发后输出`***ERROR***`日志)。可通过`menuconfig`调整任务堆栈大小: ``` Component config → FreeRTOS → Task stack overflow detection ``` - **死锁**:多个任务竞争同一资源(如互斥锁)导致循环等待。检查锁的获取/释放顺序是否成对。 - **看门狗复位**:若未定期喂狗,看门狗会复位系统(日志中可见`Task watchdog got triggered`)。解决方法: - 延长看门狗超时时间(不推荐)。 - 优化耗时操作(如拆分大循环,添加`vTaskDelay()`释放CPU)。 --- ### 4. **使用JTAG调试** 若日志无法定位问题,可启用JTAG: 1. 连接JTAG调试器(如ESP-Prog)。 2. 运行OpenOCD: ``` openocd -f board/esp32s3-builtin.cfg ``` 3. 使用GDB附加到目标,检查卡住时的线程栈和变量状态。 --- ### 5. **其他常见原因** - **中断处理不当**:ISR中执行耗时操作或未清除中断标志。 - **Cache配置错误**:ESP32S3的PSRAM/Cache配置错误可能导致指令读取失败(参考ESP-IDF文档的"Cache错误排查"章节)。 --- ### 总结步骤 1. **增加日志**,定位卡住前的最后操作。 2. **简化程序**:移除非必要组件,逐步缩小范围。 3. **检查硬件**:确认供电和信号稳定性。 4. **启用看门狗监控**:分析是否因任务阻塞触发复位。 5. **使用调试工具**:如JTAG或核心转储(coredump)。 §§相关问题§§ 1. 如何配置ESP-IDF看门狗以防止程序卡死? 2. ESP32S3硬件初始化失败的常见原因有哪些? 3. 如何使用JTAG调试ESP32S3的卡死问题? </think> ### 解决ESP32-S3在ESP-IDF中程序卡住问题的方法 #### 1. **日志诊断法(关键步骤)** - **启用详细日志**:在代码中添加多级日志输出(引用[2]),特别是卡住位置前的关键节点: ```c #include "esp_log.h" void task_function() { ESP_LOGI("TAG", "进入关键函数"); // 普通信息 critical_operation(); ESP_LOGD("TAG", "关键操作完成"); // 调试信息 } ``` - **日志配置**: 1. 修改 `sdkconfig` 提高日志等级:`idf.py menuconfig` → `Component config` → `Log output` → 设置 **最高日志级别** 为 `Debug` 2. 检查串口输出,定位最后一条日志(常为崩溃点) #### 2. **硬件初始化检查** - **外设驱动问题**:参考引用[4]的硬件初始化模式,检查: - GPIO配置冲突(尤其是复用引脚) - I2C/SPI总线速率是否过高(尝试降低至100kHz) - 电源稳定性(使用示波器检查3.3V电压纹波) - **最小系统测试**: ```c void app_main() { ESP_LOGI("MAIN", "启动最小测试"); while(1) { vTaskDelay(1000 / portTICK_PERIOD_MS); // 仅保留基础延时 } } ``` #### 3. **内存问题排查** - **堆栈溢出检测**: ```c // 在menuconfig中启用: // Component config → FreeRTOS → Enable task stack overflow detection ``` - **内存泄漏检查**: ```bash idf.py size-components # 查看各组件内存占用 idf.py monitor | grep "heap remaining" # 实时监控堆内存 ``` #### 4. **看门狗触发分析** - **任务阻塞检测**: ```c // 在卡住任务中添加喂狗操作 void blocked_task() { while(1) { esp_task_wdt_reset(); // 重置看门狗 vTaskDelay(10 / portTICK_PERIOD_MS); } } ``` - 配置看门狗超时时间:`menuconfig` → `Component config` → `ESP System Settings` → 调整 **Task Watchdog Timeout** #### 5. **中断冲突处理** - 检查ISR(中断服务程序): - 是否包含阻塞操作(如`vTaskDelay`) - 是否未清除中断标志 - 使用`xPortGetCoreID()`确认多核中断冲突 #### 6. **深度调试工具** - **JTAG调试**:使用OpenOCD连接开发板,捕获卡住时的寄存器状态 - **核心转储分析**: ```bash idf.py menuconfig → Component config → ESP System Settings → Enable core dump to Flash ``` 通过 `espcoredump.py` 解析闪存中的崩溃数据 > **典型解决方案流程**: > 日志定位 → 排除硬件初始化 → 内存检查 → 看门狗验证 → 中断审查 → 深度调试 > 引用[3]强调:ESP32模块复杂,建议从最小系统逐步添加功能[^3]
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晨之清风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值