引言

在物联网设备大规模部署的实际场景中,系统稳定性是最核心的工程挑战之一。我们沧州虎王科技技术团队在过去三年里交付了超过200个ESP32物联网项目,从智慧农业土壤监测到工业设备状态采集,从智能家居网关到城市照明控制,每一个项目都绕不开同一个问题:设备在无人值守环境下长时间运行时,如何保证它不"死机"?

看门狗(Watchdog Timer, WDT)是嵌入式系统中最基础也是最关键的稳定性保障机制。但很多开发者对它的理解停留在"调用一下API让系统重启"的层面,真正工程化使用时才会发现:硬件看门狗和软件看门狗的区别是什么?FreeRTOS的任务看门狗怎么配置才合理?中断里喂狗有什么风险?如何设计一套从异常检测到分级恢复的完整自愈方案?

本文将基于ESP32 + FreeRTOS平台,从硬件原理到软件架构,系统性地讲解看门狗机制的工程设计,并给出一套可直接用于生产环境的系统稳定性方案。


一、ESP32看门狗硬件机制解析

1.1 ESP32看门狗硬件架构

ESP32芯片内部集成了两组硬件看门狗定时器:TIMG0_WDT(定时器组0看门狗)和TIMG1_WDT(定时器组1看门狗),此外还有RTC看门狗和系统级看门狗(SWD)。理解它们的层级关系是设计稳定性方案的基础。

┌─────────────────────────────────────────────────────────┐
│                    ESP32 看门狗硬件层级架构              │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐  │
│  │  RTC WDT    │    │  TIMG0_WDT  │    │  TIMG1_WDT  │  │
│  │ (深度睡眠   │    │ (系统主     │    │ (应用层     │  │
│  │  唤醒看门狗)│    │  看门狗)    │    │  看门狗)    │  │
│  └──────┬──────┘    └──────┬──────┘    └──────┬──────┘  │
│         │                  │                   │         │
│         ▼                  ▼                   ▼         │
│  ┌─────────────────────────────────────────────────┐    │
│  │            RTC Reset Controller                  │    │
│  │  (统一管理芯片复位: SW_RESET / WDT_RESET /      │    │
│  │   RTC_WDT_RESET / TG_WDT_SYS_RESET)             │    │
│  └──────────────────────┬──────────────────────────┘    │
│                         │                               │
│                         ▼                               │
│              ┌─────────────────────┐                    │
│              │   System Reboot     │                    │
│              │   (芯片级复位)      │                    │
│              └─────────────────────┘                    │
│                                                         │
└─────────────────────────────────────────────────────────┘

1.2 硬件看门狗工作原理

硬件看门狗本质上是一个递减计数器。计数器使能后,如果在超时时间内没有收到"喂狗"信号(即重置计数器),看门狗就会触发系统复位。ESP-IDF框架在启动时默认会初始化TIMG0_WDT作为系统看门狗。

#include "esp_task_wdt.h"
#include "esp_log.h"

static const char *TAG = "WDT_DEMO";

/* 硬件看门狗初始化配置 */
void init_hardware_watchdog(void)
{
    esp_err_t ret;

    /* 方式一:使用ESP-IDF默认的系统任务看门狗 */
    /* 默认超时5秒,可在menuconfig中配置 */
    ret = esp_task_wdt_init();
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "任务看门狗初始化失败: %s", esp_err_to_name(ret));
        return;
    }

    /* 将当前任务注册到任务看门狗 */
    ret = esp_task_wdt_add(NULL);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "任务注册看门狗失败: %s", esp_err_to_name(ret));
        return;
    }

    ESP_LOGI(TAG, "硬件看门狗初始化完成,超时时间: 5s");
}

/* 方式二:直接操作定时器组硬件看门狗 */
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"

void init_timg1_wdt_custom(void)
{
    /* 配置TIMG1硬件看门狗,用于应用层独立监控 */
    timer_group_t group = TIMER_GROUP_1;
    timer_config_t config = {
        .divider = 40000,           /* 分频系数,APB=80MHz/40000=2kHz */
        .counter_dir = TIMER_COUNT_DOWN,
        .counter_en = TIMER_PAUSE,
        .alarm_en = TIMER_ALARM_EN,
        .auto_reload = TIMER_AUTORELOAD_DIS,
    };
    timer_init(group, TIMER_0, &config);
    timer_set_counter_value(group, TIMER_0, 2000000); /* 2M ticks = 1000s */
    timer_start(group, TIMER_0);

    ESP_LOGI(TAG, "TIMG1自定义看门狗已启动,超时: 1000s");
}

1.3 RTC看门狗的特殊用途

RTC看门狗在深度睡眠模式下仍然工作,是保障设备从睡眠异常中恢复的关键机制。在低功耗场景中,如果设备因外部干扰无法正常唤醒,RTC看门狗会强制复位。

#include "soc/rtc_wdt.h"

void enable_rtc_wdt_for_deep_sleep(uint32_t timeout_ms)
{
    /* 使能RTC看门狗,在深度睡眠期间持续监控 */
    rtc_wdt_protect_off();
    rtc_wdt_disable();
    rtc_wdt_set_length_of_reset_signal(RTC_WDT_SYS_RESET_SIG, RTC_WDT_RESET_LENGTH_3_2us);
    rtc_wdt_set_stage(RTC_WDT_STAGE0, RTC_WDT_STAGE_ACTION_RESET_SYSTEM);
    rtc_wdt_set_time(RTC_WDT_STAGE0, timeout_ms);
    rtc_wdt_enable();
    rtc_wdt_protect_on();

    ESP_LOGI(TAG, "RTC看门狗已使能,超时: %lu ms", (unsigned long)timeout_ms);
}

二、FreeRTOS任务看门狗的工程化配置

2.1 任务看门狗的工作机制

ESP-IDF的任务看门狗(Task Watchdog Timer, TWDT)是对硬件看门狗的软件封装层。它的核心思想是:为每个FreeRTOS任务独立维护一个看门狗实例,任一任务超时未喂狗都会触发系统复位。

┌──────────────────────────────────────────────────────────┐
│              FreeRTOS 任务看门狗架构                      │
├──────────────────────────────────────────────────────────┤
│                                                          │
│   ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│   │ Task A   │  │ Task B   │  │ Task C   │              │
│   │ (传感器  │  │ (网络    │  │ (OTA     │              │
│   │  采集)   │  │  通信)   │  │  升级)   │              │
│   └────┬─────┘  └────┬─────┘  └────┬─────┘              │
│        │              │              │                    │
│        ▼              ▼              ▼                    │
│   ┌─────────────────────────────────────────┐            │
│   │         TWDT Subscribers List           │            │
│   │  (订阅列表: 记录每个任务的喂狗状态)      │            │
│   └────────────────────┬────────────────────┘            │
│                        │                                 │
│                        ▼                                 │
│   ┌──────────────────────────────────────────┐           │
│   │       TWDT Check Task (高优先级)          │           │
│   │  周期性扫描订阅列表                        │           │
│   │  超时任务 → 触发 esp_system_abort()       │           │
│   └────────────────────┬───────────────────┘           │
│                        │                                │
│                        ▼                                │
│              ┌──────────────────┐                       │
│              │  TIMG0_HW WDT    │                       │
│              │  → System Reset  │                       │
│              └──────────────────┘                       │
│                                                          │
└──────────────────────────────────────────────────────────┘

2.2 多任务看门狗配置实践

在实际项目中,不同任务的关键性不同,需要分层配置看门狗:

#include "esp_task_wdt.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

/* 任务看门狗配置参数 */
#define MAIN_TASK_WDT_TIMEOUT_MS    5000   /* 主任务: 5s */
#define SENSOR_TASK_WDT_TIMEOUT_MS  10000 /* 传感器任务: 10s */
#define NETWORK_TASK_WDT_TIMEOUT_MS 30000 /* 网络任务: 30s */
#define OTA_TASK_WDT_TIMEOUT_MS     120000 /* OTA任务: 120s(允许长耗时) */

typedef struct {
    TaskHandle_t     handle;
    const char      *name;
    uint32_t         timeout_ms;
    bool             wdt_enabled;
} task_wdt_config_t;

/* 任务看门狗配置表 */
static task_wdt_config_t g_task_configs[] = {
    {NULL, "main_task",     MAIN_TASK_WDT_TIMEOUT_MS,    true},
    {NULL, "sensor_task",   SENSOR_TASK_WDT_TIMEOUT_MS, true},
    {NULL, "network_task",  NETWORK_TASK_WDT_TIMEOUT_MS, true},
    {NULL, "ota_task",      OTA_TASK_WDT_TIMEOUT_MS,     false}, /* OTA单独管理 */
};

#define TASK_COUNT (sizeof(g_task_configs) / sizeof(g_task_configs[0]))

/**
 * @brief 注册任务到看门狗
 * @param task_name 任务名称
 * @param handle 任务句柄
 * @return ESP_OK on success
 */
esp_err_t register_task_to_wdt(const char *task_name, TaskHandle_t handle)
{
    for (int i = 0; i < TASK_COUNT; i++) {
        if (strcmp(g_task_configs[i].name, task_name) == 0) {
            if (!g_task_configs[i].wdt_enabled) {
                ESP_LOGW(TAG, "任务 %s 看门狗未启用,跳过注册", task_name);
                return ESP_OK;
            }
            g_task_configs[i].handle = handle;
            esp_err_t ret = esp_task_wdt_add(handle);
            if (ret == ESP_OK) {
                ESP_LOGI(TAG, "任务 %s 已注册看门狗,超时: %lu ms",
                         task_name, (unsigned long)g_task_configs[i].timeout_ms);
            }
            return ret;
        }
    }
    ESP_LOGW(TAG, "未找到任务配置: %s", task_name);
    return ESP_ERR_NOT_FOUND;
}

/**
 * @brief 喂狗(重置指定任务的看门狗)
 */
void feed_task_wdt(TaskHandle_t handle)
{
    esp_task_wdt_reset();
}

2.3 传感器采集任务示例

/* 传感器采集任务:周期性读取数据并喂狗 */
void sensor_task(void *arg)
{
    /* 注册到看门狗 */
    esp_err_t ret = esp_task_wdt_add(NULL);
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "传感器采集任务启动");

    uint32_t feed_counter = 0;
    while (1) {
        /* 1. 读取传感器数据 */
        sensor_data_t data;
        esp_err_t err = read_sensor_data(&data);

        if (err == ESP_OK) {
            /* 2. 数据处理 */
            process_sensor_data(&data);

            /* 3. 推送到队列 */
            xQueueSend(g_sensor_queue, &data, pdMS_TO_TICKS(100));
        } else {
            ESP_LOGW(TAG, "传感器读取失败: %s, 连续失败计数: %d",
                     esp_err_to_name(err), ++g_sensor_fail_count);

            /* 传感器连续失败超过阈值,主动触发系统恢复 */
            if (g_sensor_fail_count > MAX_SENSOR_FAIL_COUNT) {
                ESP_LOGE(TAG, "传感器连续失败超过阈值,触发系统复位");
                esp_system_abort("Sensor hardware failure");
            }
        }

        /* 4. 喂狗 —— 每个采集周期都要喂 */
        ret = esp_task_wdt_reset();
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "喂狗失败: %s", esp_err_to_name(ret));
        }
        feed_counter++;

        /* 5. 任务延时 */
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

三、中断与关键区的看门狗策略

3.1 中断喂狗的致命陷阱

很多开发者在遇到看门狗超时问题时,第一反应是在定时器中断里喂狗。这是一个严重的工程错误——中断喂狗会让看门狗完全失效,因为即使主任务已经死锁,中断仍然在运行并持续喂狗。

/* ❌ 错误示例:在中断中喂狗 */
void IRAM_ATTR timer_isr_handler(void *arg)
{
    /* 错误!中断喂狗使看门狗永远无法检测到任务死锁 */
    esp_task_wdt_reset();  /* 绝对不要这样做 */
}

/* ✅ 正确做法:中断只设置标志,任务中喂狗 */
volatile bool g_feed_wdt_flag = false;

void IRAM_ATTR timer_isr_handler(void *arg)
{
    /* 中断中只设置标志位 */
    g_feed_wdt_flag = true;
}

void main_task(void *arg)
{
    while (1) {
        if (g_feed_wdt_flag) {
            g_feed_wdt_flag = false;
            esp_task_wdt_reset();  /* 在任务上下文中喂狗 */
        }
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

3.2 关键区内的看门狗处理

在FreeRTOS中进入关键区(taskENTER_CRITICAL())会禁用任务调度和中断,如果关键区执行时间超过看门狗超时时间,就会触发不必要的复位。

/* 共享数据保护与看门狗的平衡策略 */
static portMUX_TYPE g_data_lock = portMUX_INITIALIZER_UNLOCKED;
static system_stats_t g_shared_stats;

void update_system_stats_safe(const system_stats_t *new_stats)
{
    /* 策略1:尽量缩短关键区,只做指针交换 */
    system_stats_t *old_ptr;
    taskENTER_CRITICAL(&g_data_lock);
    old_ptr = g_current_stats;
    g_current_stats = (system_stats_t *)new_stats;
    taskEXIT_CRITICAL(&g_data_lock);

    /* 耗时操作放到关键区外 */
    if (old_ptr != new_stats) {
        analyze_stats(old_ptr);  /* 在关键区外执行分析 */
    }
}

/* 策略2:长时间操作分片执行,每个分片后喂狗 */
void long_operation_with_wdt_feed(void *data, size_t len)
{
    const size_t CHUNK_SIZE = 4096;
    size_t processed = 0;

    while (processed < len) {
        size_t chunk = (len - processed > CHUNK_SIZE) ?
                       CHUNK_SIZE : (len - processed);

        /* 处理一个分片 */
        process_data_chunk((uint8_t *)data + processed, chunk);
        processed += chunk;

        /* 每个分片后喂狗,防止长时间操作触发复位 */
        esp_task_wdt_reset();

        /* 允许其他任务调度 */
        vTaskDelay(pdMS_TO_TICKS(1));
    }
}

四、系统自愈架构设计

4.1 分级恢复策略

真正可靠的系统不会一遇到异常就粗暴复位,而是采用分级恢复策略。我们团队在生产环境中使用的是三级恢复机制:

┌──────────────────────────────────────────────────────────┐
│              三级系统自愈架构                               │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  Level 1: 轻度异常 → 任务级恢复                           │
│  ┌──────────────────────────────────────────────────┐    │
│  │ 触发条件: 单个任务超时、传感器读取失败、          │    │
│  │          短暂网络断连                             │    │
│  │ 恢复动作: 重启该任务、重新初始化外设、            │    │
│  │          重连WiFi(不重置系统)                     │    │
│  │ 恢复时间: < 5s                                   │    │
│  └──────────────────────────────────────────────────┘    │
│                        │ 恢复失败                        │
│                        ▼                                 │
│  Level 2: 中度异常 → 模块级恢复                           │
│  ┌──────────────────────────────────────────────────┐    │
│  │ 触发条件: Level 1连续失败3次、多任务同时异常、    │    │
│  │          内存泄漏检测                            │    │
│  │ 恢复动作: 释放并重建所有FreeRTOS任务、            │    │
│  │          重新初始化网络栈、清理内存碎片           │    │
│  │ 恢复时间: 10-30s                                 │    │
│  └──────────────────────────────────────────────────┘    │
│                        │ 恢复失败                        │
│                        ▼                                 │
│  Level 3: 严重异常 → 系统级复位                           │
│  ┌──────────────────────────────────────────────────┐    │
│  │ 触发条件: Level 2恢复失败、内存严重不足、         │    │
│  │          看门狗超时、硬件故障检测                 │    │
│  │ 恢复动作: 保存关键状态到NVS → 芯片复位            │    │
│  │ 恢复时间: 30-60s (含启动)                        │    │
│  └──────────────────────────────────────────────────┘    │
│                                                          │
└──────────────────────────────────────────────────────────┘

4.2 自愈管理器实现

#include "nvs_flash.h"
#include "nvs.h"

/* 异常类型定义 */
typedef enum {
    EXCEPTION_NONE = 0,
    EXCEPTION_TASK_TIMEOUT,
    EXCEPTION_SENSOR_FAIL,
    EXCEPTION_NETWORK_LOST,
    EXCEPTION_MEMORY_LOW,
    EXCEPTION_WATCHDOG_TRIGGER,
    EXCEPTION_HARDWARE_FAULT,
    EXCEPTION_UNKNOWN,
} exception_type_t;

/* 恢复级别 */
typedef enum {
    RECOVERY_LEVEL_1_TASK = 1,    /* 任务级 */
    RECOVERY_LEVEL_2_MODULE,      /* 模块级 */
    RECOVERY_LEVEL_3_SYSTEM,      /* 系统级 */
} recovery_level_t;

/* 异常计数器 */
typedef struct {
    exception_type_t type;
    uint8_t          count;
    uint32_t         last_time;   /* 最后一次发生的时间戳 */
} exception_record_t;

static exception_record_t g_exceptions[EXCEPTION_UNKNOWN + 1];
static SemaphoreHandle_t  g_recovery_mutex;

/* NVS保存的关键状态 */
#define NVS_NAMESPACE "recovery"
#define NVS_KEY_BOOT_COUNT "boot_cnt"
#define NVS_KEY_RECOVERY   "recovery_lvl"

/**
 * @brief 初始化自愈管理器
 */
esp_err_t recovery_manager_init(void)
{
    g_recovery_mutex = xSemaphoreCreateMutex();
    if (g_recovery_mutex == NULL) {
        return ESP_ERR_NO_MEM;
    }

    memset(g_exceptions, 0, sizeof(g_exceptions));

    /* 从NVS读取上次的恢复记录 */
    nvs_handle_t handle;
    esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READONLY, &handle);
    if (ret == ESP_OK) {
        uint32_t boot_count = 0;
        uint8_t  last_recovery = 0;
        nvs_get_u32(handle, NVS_KEY_BOOT_COUNT, &boot_count);
        nvs_get_u8(handle, NVS_KEY_RECOVERY, &last_recovery);
        nvs_close(handle);

        ESP_LOGW(TAG, "系统启动,启动次数: %lu, 上次恢复级别: %d",
                 (unsigned long)boot_count, last_recovery);

        /* 如果上次是Level 3恢复,检查是否频繁重启 */
        if (last_recovery == RECOVERY_LEVEL_3_SYSTEM && boot_count > 3) {
            ESP_LOGE(TAG, "检测到频繁系统重启,进入安全模式");
            enter_safe_mode();
        }
    }

    return ESP_OK;
}

/**
 * @brief 上报异常
 */
void report_exception(exception_type_t type)
{
    xSemaphoreTake(g_recovery_mutex, portMAX_DELAY);

    g_exceptions[type].count++;
    g_exceptions[type].last_time = esp_timer_get_time() / 1000000;

    uint8_t count = g_exceptions[type].count;
    ESP_LOGW(TAG, "异常上报: type=%d, count=%d", type, count);

    /* 根据异常类型和次数决定恢复级别 */
    recovery_level_t level = determine_recovery_level(type, count);

    xSemaphoreGive(g_recovery_mutex);

    /* 执行恢复 */
    execute_recovery(level, type);
}

/**
 * @brief 决定恢复级别
 */
static recovery_level_t determine_recovery_level(exception_type_t type, uint8_t count)
{
    /* 单次轻微异常 → Level 1 */
    if (count <= 2) {
        switch (type) {
        case EXCEPTION_TASK_TIMEOUT:
        case EXCEPTION_SENSOR_FAIL:
        case EXCEPTION_NETWORK_LOST:
            return RECOVERY_LEVEL_1_TASK;
        default:
            break;
        }
    }

    /* 连续多次异常 → Level 2 */
    if (count >= 3 && count <= 5) {
        return RECOVERY_LEVEL_2_MODULE;
    }

    /* 严重异常或频繁异常 → Level 3 */
    if (type == EXCEPTION_HARDWARE_FAULT ||
        type == EXCEPTION_WATCHDOG_TRIGGER ||
        count > 5) {
        return RECOVERY_LEVEL_3_SYSTEM;
    }

    return RECOVERY_LEVEL_2_MODULE;
}

/**
 * @brief 执行恢复操作
 */
static void execute_recovery(recovery_level_t level, exception_type_t type)
{
    ESP_LOGW(TAG, "执行恢复: level=%d, exception=%d", level, type);

    switch (level) {
    case RECOVERY_LEVEL_1_TASK:
        /* Level 1: 重启单个任务 */
        restart_affected_task(type);
        break;

    case RECOVERY_LEVEL_2_MODULE:
        /* Level 2: 重建所有任务和模块 */
        ESP_LOGW(TAG, "Level 2恢复: 重建所有任务");
        save_critical_state_to_nvs();
        deinit_all_tasks();
        vTaskDelay(pdMS_TO_TICKS(1000));
        init_all_tasks();
        break;

    case RECOVERY_LEVEL_3_SYSTEM:
        /* Level 3: 保存状态后系统复位 */
        ESP_LOGE(TAG, "Level 3恢复: 系统复位");
        save_critical_state_to_nvs();
        save_recovery_level(RECOVERY_LEVEL_3_SYSTEM);
        vTaskDelay(pdMS_TO_TICKS(500)); /* 等待NVS写入完成 */
        esp_restart();
        break;
    }
}

/**
 * @brief 保存关键状态到NVS
 */
static void save_critical_state_to_nvs(void)
{
    nvs_handle_t handle;
    esp_err_t ret = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "NVS打开失败: %s", esp_err_to_name(ret));
        return;
    }

    /* 保存启动计数(递增) */
    uint32_t boot_count = 0;
    nvs_get_u32(handle, NVS_KEY_BOOT_COUNT, &boot_count);
    nvs_set_u32(handle, NVS_KEY_BOOT_COUNT, boot_count + 1);

    /* 保存设备配置快照 */
    nvs_set_blob(handle, "config_snap", &g_device_config, sizeof(g_device_config));

    nvs_commit(handle);
    nvs_close(handle);

    ESP_LOGI(TAG, "关键状态已保存到NVS");
}

五、崩溃日志与事后分析

5.1 崩溃信息捕获

ESP32的Panic Handler会在系统崩溃时自动打印寄存器和栈信息。我们可以利用Core Dump机制将崩溃信息持久化到Flash,便于事后分析。

#include "esp_core_dump.h"
#include "esp_partition.h"

/* 崩溃回调:在Panic Handler执行前调用 */
static void crash_handler_cb(void *arg)
{
    /* 写入崩溃原因和时间戳到RTC内存(复位后不丢失) */
    rtc_mem_t *rtc = (rtc_mem_t *)RTC_SLOW_MEM;
    rtc->crash_count++;
    rtc->last_crash_time = esp_timer_get_time() / 1000000;
    rtc->last_reset_reason = esp_reset_reason();

    ESP_LOGE(TAG, "=== 崩溃回调触发 ===");
    ESP_LOGE(TAG, "崩溃次数: %d, 复位原因: %d",
             rtc->crash_count, rtc->last_reset_reason);
}

/* 注册崩溃回调 */
void register_crash_handler(void)
{
    esp_register_shutdown_handler(crash_handler_cb, NULL);
    ESP_LOGI(TAG, "崩溃回调已注册");
}

/* 启动时检查上次崩溃原因 */
void check_previous_crash(void)
{
    esp_reset_reason_t reason = esp_reset_reason();
    const char *reason_str[] = {
        "未知", "上电复位", "外部复位", "看门狗复位",
        "软件复位", "深度睡眠唤醒", "Brownout复位", "RTC看门狗复位",
    };

    if (reason >= ESP_RST_POWERON && reason <= ESP_RST_RTC_WDT) {
        ESP_LOGW(TAG, "上次复位原因: %s (%d)",
                 reason_str[reason], reason);
    }

    /* 读取RTC内存中的崩溃记录 */
    rtc_mem_t *rtc = (rtc_mem_t *)RTC_SLOW_MEM;
    if (rtc->crash_count > 0) {
        ESP_LOGW(TAG, "累计崩溃次数: %d, 最近崩溃时间戳: %lu s",
                 rtc->crash_count, (unsigned long)rtc->last_crash_time);
    }
}

5.2 崩溃分析工具链

/* 运行时内存监控:定期检查内存健康度 */
void memory_monitor_task(void *arg)
{
    while (1) {
        size_t free_heap = esp_get_free_heap_size();
        size_t min_free  = esp_get_minimum_free_heap_size();
        size_t free_internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);

        ESP_LOGI(TAG, "内存状态: free=%u, min=%u, internal=%u",
                 (unsigned)free_heap, (unsigned)min_free, (unsigned)free_internal);

        /* 内存不足告警 */
        if (free_internal < 20480) {  /* < 20KB */
            ESP_LOGE(TAG, "内部内存严重不足: %u bytes", (unsigned)free_internal);
            report_exception(EXCEPTION_MEMORY_LOW);
        }

        /* 检测内存碎片化 */
        size_t largest_block = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
        if (largest_block < 8192 && free_internal > 30720) {
            ESP_LOGW(TAG, "内存碎片化严重: largest_block=%u, free=%u",
                     (unsigned)largest_block, (unsigned)free_internal);
            /* 触发碎片整理或模块级恢复 */
            report_exception(EXCEPTION_MEMORY_LOW);
        }

        vTaskDelay(pdMS_TO_TICKS(30000)); /* 每30秒检查一次 */
    }
}

六、完整系统稳定性方案集成

6.1 系统启动流程

将上述所有机制整合为完整的系统启动和运行流程:

/* 系统稳定性管理器 */
typedef struct {
    bool     wdt_initialized;
    bool     recovery_initialized;
    bool     crash_handler_registered;
    TaskHandle_t monitor_task_handle;
} stability_manager_t;

static stability_manager_t g_stability_mgr;

esp_err_t init_system_stability(void)
{
    esp_err_t ret;

    /* 1. 检查上次崩溃原因 */
    check_previous_crash();

    /* 2. 初始化自愈管理器 */
    ret = recovery_manager_init();
    ESP_ERROR_CHECK(ret);
    g_stability_mgr.recovery_initialized = true;

    /* 3. 注册崩溃回调 */
    register_crash_handler();
    g_stability_mgr.crash_handler_registered = true;

    /* 4. 初始化硬件看门狗 */
    init_hardware_watchdog();
    g_stability_mgr.wdt_initialized = true;

    /* 5. 启动内存监控任务 */
    xTaskCreate(memory_monitor_task, "mem_monitor", 4096, NULL, 3,
                &g_stability_mgr.monitor_task_handle);

    ESP_LOGI(TAG, "=== 系统稳定性管理器初始化完成 ===");
    ESP_LOGI(TAG, "看门狗: %s", g_stability_mgr.wdt_initialized ? "ON" : "OFF");
    ESP_LOGI(TAG, "自愈: %s", g_stability_mgr.recovery_initialized ? "ON" : "OFF");
    ESP_LOGI(TAG, "崩溃回调: %s", g_stability_mgr.crash_handler_registered ? "ON" : "OFF");

    return ESP_OK;
}

/* app_main入口 */
void app_main(void)
{
    ESP_LOGI(TAG, "沧州虎王科技 ESP32 物联网设备启动");

    /* 初始化系统稳定性管理 */
    init_system_stability();

    /* 初始化业务模块 */
    init_sensor_subsystem();
    init_network_subsystem();
    init_mqtt_client();

    /* 创建业务任务 */
    xTaskCreate(sensor_task, "sensor", 4096, NULL, 5, NULL);
    xTaskCreate(network_task, "network", 8192, NULL, 4, NULL);
    xTaskCreate(heartbeat_task, "heartbeat", 2048, NULL, 3, NULL);

    /* 主循环:看门狗喂狗 */
    while (1) {
        esp_task_wdt_reset();
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

6.2 系统稳定性架构全景图

┌─────────────────────────────────────────────────────────────┐
│           ESP32 物联网设备系统稳定性架构全景                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─── 应用层 ──────────────────────────────────────────┐   │
│  │  传感器采集 │ 网络通信 │ MQTT │ OTA升级 │ 业务逻辑   │   │
│  └────────┬─────────────────────────────────────────────┘   │
│           │                                                  │
│  ┌────────▼─ 稳定性管理层 ─────────────────────────────┐   │
│  │                                                     │   │
│  │  ┌──────────────┐  ┌──────────────┐              │   │
│  │  │ 自愈管理器    │  │ 崩溃日志     │              │   │
│  │  │ (三级恢复)    │  │ (Core Dump)  │              │   │
│  │  └──────┬───────┘  └──────┬───────┘              │   │
│  │         │                  │                       │   │
│  │  ┌──────▼──────────────────▼──────────────┐      │   │
│  │  │        内存监控 & 健康检查               │      │   │
│  │  │  (Heap检查 / 碎片检测 / 任务状态)         │      │   │
│  │  └──────────────────┬──────────────────────┘      │   │
│  │                     │                               │   │
│  └─────────────────────┼───────────────────────────────┘   │
│                        │                                    │
│  ┌─────────────────────▼── 看门狗层 ──────────────────┐    │
│  │                                                     │   │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────┐  │   │
│  │  │ TWDT       │  │ TIMG0_WDT  │  │ RTC WDT    │  │   │
│  │  │ (任务级)   │  │ (系统级)   │  │ (睡眠级)   │  │   │
│  │  └────────────┘  └────────────┘  └────────────┘  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌──────────────────── 硬件层 ─────────────────────────┐   │
│  │  NVS Flash (状态持久化) │ RTC Memory (崩溃记录)     │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

七、工程实践总结

7.1 看门狗配置经验清单

在实际项目中,我们总结了以下看门狗配置经验:

超时时间设置原则:看门狗超时时间应设置为任务正常执行周期的3-5倍。例如传感器采集任务每2秒执行一次,看门狗超时设为10秒。太短会导致误触发,太长会延迟故障检测。

任务分层原则:关键任务(通信、控制)用短超时看门狗,非关键任务(日志、OTA)用长超时或禁用看门狗。OTA升级这种长时间操作应临时暂停看门狗。

喂狗位置原则:在任务主循环的最后一步喂狗,确保任务确实完成了完整的工作周期。不要在子函数中喂狗,否则子函数死循环但主循环未完成时看门狗无法检测到。

恢复策略原则:避免"一复位了之"。对于网络断连等可恢复异常,应优先尝试任务级恢复;只有硬件故障或内存耗尽才触发系统复位。

7.2 与物联网平台的联动

在我们的物联网平台中,设备端的稳定性数据会通过遥测通道实时上报,运维团队可以在后台大屏上看到所有设备的崩溃次数、复位原因、内存使用趋势和看门狗触发记录。当某台设备在短时间内连续触发Level 3恢复时,平台会自动告警并标记该设备需要人工介入。

在实际开发调试阶段,**随身WiFi硬件调试工具(hardware.czkree.com)**是我们团队每名工程师的标配。它可以直接连接ESP32的串口,不受电脑USB口数量限制,在现场调试时能实时查看崩溃日志和看门狗触发信息,配合ESP32工具箱V2.0的日志分析功能,可以快速定位系统不稳定性的根因。


八、典型问题排查指南

8.1 "Task watchdog got triggered"排查

当看到如下日志时:

E (35678) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (35678) task_wdt:  - network_task (CPU 1)
E (35680) task_wdt: Tasks currently running:
E (35684) task_wdt: CPU 0: IDLE0
E (35689) task_wdt: CPU 1: network_task

排查步骤:

  1. 确认是哪个任务超时:日志中的network_task就是超时任务
  2. 检查该任务是否有死循环或阻塞:特别是while(1)内是否有vTaskDelay
  3. 检查是否有阻塞型API调用:如xQueueReceive没有设置超时或semphr_take等待时间过长
  4. 检查是否在中断中执行了耗时操作

8.2 频繁复位排查

/* 频繁复位检测与保护 */
void check_boot_loop(void)
{
    static uint32_t last_boot_time = 0;
    uint32_t current_time = esp_timer_get_time() / 1000000;

    if (last_boot_time > 0 && (current_time - last_boot_time) < 60) {
        static uint8_t rapid_boot_count = 0;
        rapid_boot_count++;

        if (rapid_boot_count >= 3) {
            ESP_LOGE(TAG, "检测到启动循环,进入安全模式");
            /* 进入安全模式:禁用所有非必要任务,只保留基本通信 */
            enter_safe_mode();
        }
    }
    last_boot_time = current_time;
}

九、结语

系统稳定性设计不是"加个看门狗API"那么简单。从硬件看门狗的层级理解,到FreeRTOS任务看门狗的精细化配置,再到分级自愈策略和崩溃事后分析,每一层都需要工程化的思考和验证。

我们在沧州虎王科技的实际项目中,通过这套完整的稳定性方案,将设备平均无故障运行时间(MTBF)从最初的72小时提升到了超过180天。关键在于:看门狗是最后一道防线,而不是唯一防线。只有将硬件看门狗、软件看门狗、内存监控、分级恢复和崩溃分析组合起来,才能构建真正可靠的物联网设备。

对于刚接触ESP32的工程师,建议从ESP32工具箱V2.0开始,它内置了看门狗配置向导和崩溃分析工具,能显著降低稳定性开发的门槛。配合随身WiFi硬件调试工具(hardware.czkree.com),在现场部署时也能快速排查设备异常。完整的稳定性方案还需要与物联网平台的设备管理能力结合,实现端云一体的运维体系。


作者:沧州虎王科技技术团队
标签:物联网、嵌入式、ESP32
产品推荐:ESP32工具箱V2.0 | 随身WiFi硬件调试工具(hardware.czkree.com) | 物联网平台

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐