# 引言 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,同时同步备份区;读取时先校验主区,若失败则尝试备份区,若两者均失败则恢复默认值。 ## 数据结构定义 ```c #include #include #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 实现 ```c 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; } ``` ## 写入与校验函数 ```c 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 内存。 ```c 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; } } ``` ## 恢复流程 ```c 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 加密或增加多备份,以应对更严苛的环境。