# ESP32-C3 深睡眠下 RTC 内存保持 Wi-Fi 快速重连的工程技巧 ## 一、为什么需要快速重连? 在低功耗物联网应用中,ESP32-C3 常采用深睡眠(Deep Sleep)模式来降低功耗,典型电流可低至 5μA 左右。然而,唤醒后若重新初始化 Wi-Fi 并扫描、连接 AP,通常需要 1~3 秒,期间电流高达 100~200mA,导致平均功耗急剧上升。例如,若每 10 秒唤醒一次,重连耗时 2 秒,则平均功耗约为 (2s*150mA + 8s*0.005mA)/10s ≈ 30mA,远高于纯睡眠时的 0.005mA。因此,缩短重连时间至关重要。 ## 二、原理:RTC 内存与 Wi-Fi 上下文 ESP32-C3 内部包含一块 RTC 快速内存(RTC Fast Memory),在深睡眠期间由 RTC 域供电,数据不会丢失。Wi-Fi 协议栈在连接 AP 后,会生成一份“上下文”数据,包括 BSSID、信道、认证信息、密钥等。默认情况下,这些数据存储在普通 RAM 中,深睡眠时丢失。但 ESP-IDF 提供了 `esp_wifi_set_ps(WIFI_PS_MIN_MODEM)` 和 `esp_wifi_set_storage(WIFI_STORAGE_RAM)` 等接口,允许将 Wi-Fi 配置和状态保存到 RTC 内存。 具体机制: - `esp_wifi_set_storage(WIFI_STORAGE_RAM)` 使 Wi-Fi 配置不写入 flash,而是保存在 RAM 中,但深睡眠时 RAM 掉电,因此需要配合 `esp_wifi_set_keep_alive` 或使用 `esp_wifi_bt_rtc_resume` 等 API。 - 更直接的方式是使用 `esp_wifi_set_storage(WIFI_STORAGE_RAM)` 并调用 `esp_wifi_get_mac` 等,但关键在于启用 `CONFIG_ESP32_WIFI_RTOS` 和 `CONFIG_ESP32_WIFI_SAVE_STATE` 选项,让 Wi-Fi 驱动在深睡眠前自动保存上下文到 RTC 内存。 实际上,ESP-IDF 提供了 `esp_wifi_set_storage(WIFI_STORAGE_RAM)` 和 `esp_wifi_set_ps(WIFI_PS_MIN_MODEM)`,但快速重连的核心是 `esp_wifi_stop()` 和 `esp_wifi_start()` 之间的状态保留。通过 `esp_wifi_set_storage(WIFI_STORAGE_RAM)` 并启用 `CONFIG_ESP32_WIFI_SAVE_STATE`,驱动会将连接信息存入 RTC 内存,唤醒后 `esp_wifi_start()` 可直接恢复,无需重新扫描。 ## 三、配置步骤 ### 1. 启用必要配置 在 `menuconfig` 中设置: - `Component config → Wi-Fi → WiFi storage` 选择 `RAM`(或使用代码设置)。 - `Component config → Wi-Fi → Enable WiFi state save` 勾选(即 `CONFIG_ESP32_WIFI_SAVE_STATE=y`)。 - `Component config → Power Management` 启用 `CONFIG_PM_ENABLE`,并选择 `CONFIG_PM_SLP_DISABLE` 或 `CONFIG_PM_SLP_IRAM_OPT` 等,确保深睡眠可用。 ### 2. 代码初始化 在 `app_main` 中,先初始化 NVS(用于存储校准数据),然后设置 Wi-Fi 存储为 RAM,并注册深睡眠唤醒回调。 ```c #include "esp_wifi.h" #include "esp_sleep.h" #include "nvs_flash.h" void app_main() { // 初始化 NVS esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { nvs_flash_erase(); nvs_flash_init(); } // 初始化 Wi-Fi wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&cfg); esp_wifi_set_storage(WIFI_STORAGE_RAM); // 关键:配置存 RAM esp_wifi_set_mode(WIFI_MODE_STA); // 配置连接参数 wifi_config_t wifi_config = { .sta = { .ssid = "YOUR_SSID", .password = "YOUR_PASS", .threshold.authmode = WIFI_AUTH_WPA2_PSK, }, }; esp_wifi_set_config(WIFI_IF_STA, &wifi_config); esp_wifi_start(); // 连接并等待成功 esp_wifi_connect(); // 等待连接事件... // 进入深睡眠前,确保 Wi-Fi 已连接,然后设置唤醒源 esp_sleep_enable_timer_wakeup(10 * 1000000); // 10秒唤醒 esp_deep_sleep_start(); } ``` ### 3. 唤醒后快速重连 在唤醒后,`esp_wifi_start()` 会自动恢复之前保存的状态,但需要确保调用顺序正确。通常,在 `esp_sleep_wakeup_cause()` 判断为定时器唤醒后,直接调用 `esp_wifi_start()` 即可,无需重新配置和连接。 ```c void on_wakeup() { if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER) { // 直接启动 Wi-Fi,驱动会从 RTC 内存恢复上下文 esp_wifi_start(); // 此时 Wi-Fi 已自动连接,无需调用 esp_wifi_connect() } } ``` 但注意,`esp_wifi_start()` 是异步的,需要等待 `WIFI_EVENT_STA_START` 和 `IP_EVENT_STA_GOT_IP` 事件。为了简化,可以使用事件组或信号量。 ## 四、完整示例代码 下面是一个完整的示例,演示了深睡眠唤醒后快速重连,并测量重连时间。 ```c #include #include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" #include "esp_wifi.h" #include "esp_event.h" #include "esp_sleep.h" #include "nvs_flash.h" #include "esp_log.h" static const char *TAG = "fast_reconnect"; static EventGroupHandle_t wifi_event_group; #define WIFI_CONNECTED_BIT BIT0 #define WIFI_FAIL_BIT BIT1 static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { esp_wifi_connect(); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { xEventGroupSetBits(wifi_event_group, WIFI_FAIL_BIT); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); } } void wifi_init() { wifi_event_group = xEventGroupCreate(); esp_event_loop_create_default(); esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL); esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&cfg); esp_wifi_set_storage(WIFI_STORAGE_RAM); esp_wifi_set_mode(WIFI_MODE_STA); wifi_config_t wifi_config = { .sta = { .ssid = "YOUR_SSID", .password = "YOUR_PASS", .threshold.authmode = WIFI_AUTH_WPA2_PSK, }, }; esp_wifi_set_config(WIFI_IF_STA, &wifi_config); esp_wifi_start(); } void app_main() { esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { nvs_flash_erase(); nvs_flash_init(); } // 判断唤醒原因 if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER) { ESP_LOGI(TAG, "Wakeup from deep sleep, fast reconnecting..."); // 直接启动 Wi-Fi,无需重新配置 esp_wifi_start(); // 等待连接事件(通常 < 50ms) EventBits_t bits = xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, 2000 / portTICK_PERIOD_MS); if (bits & WIFI_CONNECTED_BIT) { ESP_LOGI(TAG, "Reconnected successfully!"); } else { ESP_LOGE(TAG, "Reconnect failed, fallback to full init"); // 重新初始化 wifi_init(); } } else { // 首次启动 wifi_init(); // 等待连接 xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, 10000); } // 模拟工作,然后进入深睡眠 vTaskDelay(1000 / portTICK_PERIOD_MS); ESP_LOGI(TAG, "Entering deep sleep..."); esp_wifi_stop(); // 停止 Wi-Fi,但状态会保存到 RTC esp_sleep_enable_timer_wakeup(10 * 1000000); esp_deep_sleep_start(); } ``` ## 五、注意事项 - **必须启用 `CONFIG_ESP32_WIFI_SAVE_STATE`**:否则 Wi-Fi 状态不会保存到 RTC 内存,唤醒后仍需重新扫描连接。 - **存储方式选择 RAM**:`esp_wifi_set_storage(WIFI_STORAGE_RAM)` 确保配置不写入 flash,减少 flash 磨损,但需配合状态保存。 - **深睡眠前调用 `esp_wifi_stop()`**:这会将当前连接上下文保存到 RTC 内存。若直接进入深睡眠而不停止 Wi-Fi,可能丢失状态。 - **唤醒后不要立即调用 `esp_wifi_connect()`**:因为驱动会自动恢复连接,若手动调用可能导致重复连接或冲突。 - **事件处理**:快速重连时,`WIFI_EVENT_STA_START` 事件会触发,但无需在事件中调用 `esp_wifi_connect()`,否则会中断自动恢复。建议在事件处理中判断是否首次启动。 - **RTC 内存大小**:ESP32-C3 的 RTC 快速内存为 8KB,Wi-Fi 上下文大约占用 4KB,足够使用。但若同时使用其他 RTC 数据,需注意容量。 - **功耗测量**:实际重连时间取决于 AP 响应速度,通常 10~50ms。建议用 GPIO 翻转测量时间,优化唤醒流程。 - **兼容性**:此技巧适用于 ESP-IDF v4.4 及以上版本,旧版本可能不支持 `CONFIG_ESP32_WIFI_SAVE_STATE`。 ## 六、总结 通过利用 ESP32-C3 的 RTC 内存保存 Wi-Fi 上下文,可以将深睡眠唤醒后的重连时间从秒级降至毫秒级,显著降低平均功耗。本文提供了完整的配置和代码示例,开发者可根据实际需求调整唤醒周期和重连策略。记住,关键点在于启用状态保存、正确停止 Wi-Fi 并避免重复连接。掌握这一技巧,你的电池供电设备将拥有更长的续航。