ESP32-C3 深睡眠下 RTC 内存保持 Wi-Fi 快速重连的工程技巧
一、为什么需要快速重连?
在低功耗物联网应用中,ESP32-C3 常采用深睡眠(Deep Sleep)模式来降低功耗,典型电流可低至 5μA 左右。然而,唤醒后若重新初始化 Wi-Fi 并扫描、连接 AP,通常需要 1~3 秒,期间电流高达 100~200mA,导致平均功耗急剧上升。例如,若每 10 秒唤醒一次,重连耗时 2 秒,则平均功耗约为 (2s150mA + 8s0.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,并注册深睡眠唤醒回调。
#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() 即可,无需重新配置和连接。
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 事件。为了简化,可以使用事件组或信号量。
四、完整示例代码
下面是一个完整的示例,演示了深睡眠唤醒后快速重连,并测量重连时间。
#include <stdio.h>
#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 并避免重复连接。掌握这一技巧,你的电池供电设备将拥有更长的续航。