引言

ESP32-C3 作为一款高性价比的 Wi-Fi/BLE SoC,其深睡眠模式(Deep Sleep)可将功耗降至微安级别,同时通过 RTC 内存(RTC_SLOW_MEM)保留少量数据,以便唤醒后快速恢复现场。然而,深睡眠期间若发生异常复位(如电源跌落、看门狗超时、外部复位),RTC 内存内容可能被破坏,导致系统状态错乱。本文面向有经验的嵌入式开发者,介绍一种实用的数据完整性校验与恢复策略。

RTC 内存与深睡眠机制

ESP32-C3 内部包含 8KB 的 RTC 慢速内存(RTC_SLOW_MEM),地址范围 0x50000000 ~ 0x50001FFF。在深睡眠模式下,该内存由 RTC 域供电,数据得以保留。用户可通过 esp_sleep_get_retention_data() 或直接指针访问,但更推荐使用 RTC_NOINIT_ATTR 属性将全局变量放入 RTC 内存。

深睡眠唤醒后,CPU 从复位向量重新执行,但 RTC 内存内容不变。若发生异常复位(非正常深睡眠唤醒),RTC 内存可能处于未知状态,因此必须校验其有效性。

数据完整性校验方案

原理

采用 CRC32 校验和 + 双备份区策略。将关键数据(如传感器校准值、设备状态、计数器)存储在 RTC 内存的两个独立区域(主区和备份区),每个区域末尾附加 CRC32 校验值。写入时更新主区并计算 CRC,同时同步备份区;读取时先校验主区,若失败则尝试备份区,若两者均失败则恢复默认值。

数据结构定义

#include <stdint.h>
#include <string.h>
#include "esp_sleep.h"
#include "esp_log.h"

#define RTC_DATA_SIZE 64  // 自定义数据大小
#define CRC32_POLY 0xEDB88320

typedef struct {
    uint8_t data[RTC_DATA_SIZE];
    uint32_t crc32;
} rtc_entry_t;

// 定义两个 RTC 内存区域(使用属性强制放入 RTC_SLOW_MEM)
RTC_NOINIT_ATTR rtc_entry_t rtc_main;
RTC_NOINIT_ATTR rtc_entry_t rtc_backup;

CRC32 实现

uint32_t crc32_update(uint32_t crc, uint8_t byte) {
    crc ^= byte;
    for (int i = 0; i < 8; i++) {
        if (crc & 1) crc = (crc >> 1) ^ CRC32_POLY;
        else crc >>= 1;
    }
    return crc;
}

uint32_t crc32_compute(const uint8_t *buf, size_t len) {
    uint32_t crc = 0xFFFFFFFF;
    for (size_t i = 0; i < len; i++) {
        crc = crc32_update(crc, buf[i]);
    }
    return crc ^ 0xFFFFFFFF;
}

写入与校验函数

void rtc_data_write(const uint8_t *data, size_t len) {
    if (len > RTC_DATA_SIZE) return;
    // 写入主区
    memset(&rtc_main, 0, sizeof(rtc_entry_t));
    memcpy(rtc_main.data, data, len);
    rtc_main.crc32 = crc32_compute(rtc_main.data, RTC_DATA_SIZE);
    // 写入备份区
    memcpy(&rtc_backup, &rtc_main, sizeof(rtc_entry_t));
}

bool rtc_data_read(uint8_t *out, size_t len) {
    if (len > RTC_DATA_SIZE) return false;
    // 校验主区
    if (crc32_compute(rtc_main.data, RTC_DATA_SIZE) == rtc_main.crc32) {
        memcpy(out, rtc_main.data, len);
        return true;
    }
    // 主区失败,尝试备份区
    if (crc32_compute(rtc_backup.data, RTC_DATA_SIZE) == rtc_backup.crc32) {
        memcpy(out, rtc_backup.data, len);
        // 恢复主区(可选)
        memcpy(&rtc_main, &rtc_backup, sizeof(rtc_entry_t));
        return true;
    }
    return false; // 两者均损坏
}

异常复位恢复策略

复位原因检测

ESP32-C3 提供 esp_reset_reason() 函数,可获取复位原因。在启动时,根据原因决定是否信任 RTC 内存。

void check_reset_reason(void) {
    esp_reset_reason_t reason = esp_reset_reason();
    switch (reason) {
        case ESP_RST_DEEPSLEEP:
            ESP_LOGI("APP", "正常深睡眠唤醒,RTC 数据可信");
            break;
        case ESP_RST_POWERON:
        case ESP_RST_BROWNOUT:
        case ESP_RST_WATCHDOG:
        case ESP_RST_SW:
            ESP_LOGW("APP", "异常复位,需校验 RTC 数据");
            // 强制校验,若失败则恢复默认
            break;
        default:
            break;
    }
}

恢复流程

void app_main(void) {
    check_reset_reason();
    
    uint8_t user_data[RTC_DATA_SIZE];
    bool valid = rtc_data_read(user_data, sizeof(user_data));
    
    if (!valid) {
        ESP_LOGW("APP", "RTC 数据损坏,恢复默认值");
        // 恢复默认配置,例如全零或出厂校准值
        memset(user_data, 0, sizeof(user_data));
        rtc_data_write(user_data, sizeof(user_data)); // 重新初始化
    } else {
        ESP_LOGI("APP", "RTC 数据有效,使用恢复数据");
    }
    
    // 正常业务逻辑...
    
    // 进入深睡眠前写入最新数据
    rtc_data_write(user_data, sizeof(user_data));
    esp_deep_sleep(10 * 1000000); // 10秒
}

注意事项

  • 内存对齐:RTC 内存访问建议 4 字节对齐,避免非对齐访问导致异常。
  • 数据大小:RTC_SLOW_MEM 仅 8KB,需合理规划,避免溢出。
  • CRC 性能:软件 CRC32 在启动时计算,若数据量大可改用硬件 CRC(ESP32-C3 无硬件 CRC,但可用 ESP-ROM 的 crc32_le 函数加速)。
  • 备份区同步:每次写入都同步备份区,增加写入次数,但深睡眠唤醒频率低,可接受。
  • 复位原因:深睡眠唤醒也可能因外部 GPIO 触发,此时 RTC 数据仍有效,但建议仍校验一次,以防偶发错误。
  • 测试:通过 esp_deep_sleep_start() 后强制断电或触发看门狗,验证恢复逻辑。

总结

通过 CRC32 校验和双备份区,ESP32-C3 在深睡眠下的 RTC 内存数据可靠性得到显著提升。结合复位原因检测,系统能够区分正常唤醒与异常复位,并采取相应恢复措施。该策略实现简单、开销小,适用于大多数低功耗物联网场景。开发者可根据实际需求调整数据结构和校验算法,例如使用 AES 加密或增加多备份,以应对更严苛的环境。