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_RTOSCONFIG_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_DISABLECONFIG_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_STARTIP_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 内存,唤醒后仍需重新扫描连接。
  • 存储方式选择 RAMesp_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 并避免重复连接。掌握这一技巧,你的电池供电设备将拥有更长的续航。