# ESP32 低功耗模式下 RTC 内存保持与 WiFi 快速重连的工程实践 在物联网应用中,ESP32 的 Deep-sleep 模式可将功耗降至微安级,但每次唤醒后重新连接 WiFi 通常需要 1-3 秒,期间电流高达数百毫安,严重浪费电池能量。本文将介绍一种工程级优化方案:利用 RTC 内存保存关键状态,结合 WiFi 快速扫描和静态 IP,将重连时间压缩到 100ms 以内。 ## 一、原理剖析:RTC 内存与 Deep-sleep ### 1.1 RTC 内存(RTC_DATA_ATTR) ESP32 内部有 8KB 的 RTC 快速内存(RTC_FAST_ATTR)和 8KB 的 RTC 慢速内存(RTC_SLOW_ATTR),它们在 Deep-sleep 期间由 RTC 电源域供电,数据不会丢失。使用 `RTC_DATA_ATTR` 修饰的全局变量会被放置在 RTC 内存中,唤醒后可直接读取。 ### 1.2 Deep-sleep 唤醒流程 - 调用 `esp_deep_sleep_start()` 后,CPU 停止,大部分 RAM 掉电,但 RTC 内存保持。 - 唤醒源(如定时器、GPIO)触发后,芯片复位,从 `app_main()` 重新执行。 - 此时可通过 `esp_sleep_get_wakeup_cause()` 判断唤醒原因,并读取 RTC 内存中的持久数据。 ### 1.3 WiFi 重连的瓶颈 默认 WiFi 连接流程:全信道扫描(2.4GHz 13 个信道)→ 认证 → 关联 → DHCP 获取 IP。其中扫描耗时约 200-500ms,DHCP 约 100-300ms。优化思路: - 使用 `esp_wifi_set_scan_method(WIFI_FAST_SCAN)` 只扫描指定信道(如上次连接的信道)。 - 使用静态 IP,跳过 DHCP。 - 在 RTC 内存中保存 BSSID、信道、IP 等信息,实现“热启动”。 ## 二、工程配置与代码实现 ### 2.1 硬件与开发环境 - 开发板:ESP32-DevKitC(或任意 ESP32 模组) - 框架:ESP-IDF v5.x(或 Arduino 框架,但本文以 ESP-IDF 为例) - 外设:无额外硬件,仅需 WiFi 路由器 ### 2.2 步骤 1:定义 RTC 内存变量 在代码顶部定义持久化数据结构: ```c // 定义 RTC 内存中的持久数据 RTC_DATA_ATTR struct { uint8_t bssid[6]; // 上次连接的 AP 的 BSSID int8_t channel; // 上次连接的信道 uint8_t ip[4]; // 静态 IP 地址(可选) uint32_t reconnect_count; // 重连计数 } rtc_store = {0}; ``` ### 2.3 步骤 2:WiFi 快速连接配置 在 `app_main()` 中,根据 RTC 内存中的信息配置 WiFi: ```c #include "esp_wifi.h" #include "esp_sleep.h" #include "nvs_flash.h" void wifi_fast_connect(void) { wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&cfg); // 设置扫描方法为快速扫描,并指定信道(如果已知) wifi_scan_config_t scan_conf = { .ssid = NULL, .bssid = rtc_store.bssid, .channel = rtc_store.channel, .show_hidden = false, .scan_type = WIFI_SCAN_TYPE_ACTIVE, .scan_time = { .active = { .min = 100, .max = 200 } } }; esp_wifi_set_scan_method(WIFI_FAST_SCAN); // 关键:只扫描指定信道 // 配置静态 IP(如果之前保存过) if (rtc_store.ip[0] != 0) { tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); tcpip_adapter_ip_info_t ip_info = {0}; IP4_ADDR(&ip_info.ip, rtc_store.ip[0], rtc_store.ip[1], rtc_store.ip[2], rtc_store.ip[3]); IP4_ADDR(&ip_info.gw, 192, 168, 1, 1); // 网关,可保存 IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0); tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); } // 启动 WiFi esp_wifi_set_storage(WIFI_STORAGE_RAM); esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_start(); // 连接 AP(使用保存的 BSSID 和信道) wifi_config_t wifi_cfg = {0}; strcpy((char*)wifi_cfg.sta.ssid, "YOUR_SSID"); strcpy((char*)wifi_cfg.sta.password, "YOUR_PASSWORD"); if (rtc_store.bssid[0] != 0) { memcpy(wifi_cfg.sta.bssid, rtc_store.bssid, 6); wifi_cfg.sta.channel = rtc_store.channel; } esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); esp_wifi_connect(); } ``` ### 2.4 步骤 3:保存连接信息到 RTC 内存 在 WiFi 连接成功事件中,获取 BSSID、信道和 IP 并保存: ```c 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_CONNECTED) { // 获取 BSSID 和信道 wifi_ap_record_t ap_info; esp_wifi_sta_get_ap_info(&ap_info); memcpy(rtc_store.bssid, ap_info.bssid, 6); rtc_store.channel = ap_info.primary; // 获取 IP(如果使用 DHCP,则在此保存) tcpip_adapter_ip_info_t ip_info; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); uint8_t* ip = (uint8_t*)&ip_info.ip.addr; memcpy(rtc_store.ip, ip, 4); // 增加重连计数 rtc_store.reconnect_count++; } } ``` ### 2.5 步骤 4:进入 Deep-sleep 并定时唤醒 主循环中,完成数据采集后进入低功耗: ```c void app_main(void) { // 初始化 NVS(WiFi 驱动需要) nvs_flash_init(); // 检查唤醒原因 esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); if (cause != ESP_SLEEP_WAKEUP_UNDEFINED) { // 从 Deep-sleep 唤醒,直接快速重连 wifi_fast_connect(); } else { // 首次启动,正常连接 wifi_normal_connect(); // 可复用 wifi_fast_connect,但无历史数据 } // 等待连接成功(事件驱动) // ... 执行任务 ... // 进入 Deep-sleep 10 秒 esp_deep_sleep_enable_timer_wakeup(10 * 1000000); esp_deep_sleep_start(); } ``` ## 三、完整代码示例(精简版) ```c // main.c #include #include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" #include "esp_system.h" #include "esp_wifi.h" #include "esp_event.h" #include "esp_log.h" #include "nvs_flash.h" #include "esp_sleep.h" #include "tcpip_adapter.h" RTC_DATA_ATTR struct { uint8_t bssid[6]; int8_t channel; uint8_t ip[4]; } saved; static EventGroupHandle_t wifi_event_group; #define WIFI_CONNECTED_BIT BIT0 static void event_handler(void* arg, esp_event_base_t base, int32_t id, void* data) { if (base == WIFI_EVENT && id == WIFI_EVENT_STA_START) { esp_wifi_connect(); } else if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) { // 重连 esp_wifi_connect(); } else if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) { // 保存 IP ip_event_got_ip_t* event = (ip_event_got_ip_t*) data; memcpy(saved.ip, &event->ip_info.ip.addr, 4); // 获取 BSSID 和信道 wifi_ap_record_t ap; esp_wifi_sta_get_ap_info(&ap); memcpy(saved.bssid, ap.bssid, 6); saved.channel = ap.primary; xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); } } void wifi_init(void) { tcpip_adapter_init(); esp_event_loop_create_default(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); esp_wifi_init(&cfg); esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL); esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL); // 快速扫描设置 esp_wifi_set_scan_method(WIFI_FAST_SCAN); wifi_config_t wifi_config = { .sta = { .ssid = "YOUR_SSID", .password = "YOUR_PASSWORD", .bssid_set = (saved.bssid[0] != 0) ? 1 : 0, .bssid = {0}, .channel = saved.channel, .scan_method = WIFI_FAST_SCAN, }, }; if (saved.bssid[0] != 0) memcpy(wifi_config.sta.bssid, saved.bssid, 6); // 静态 IP(如果保存过) if (saved.ip[0] != 0) { tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA); tcpip_adapter_ip_info_t ip_info = {0}; IP4_ADDR(&ip_info.ip, saved.ip[0], saved.ip[1], saved.ip[2], saved.ip[3]); IP4_ADDR(&ip_info.gw, 192, 168, 1, 1); IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0); tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); } esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_set_config(WIFI_IF_STA, &wifi_config); esp_wifi_start(); } void app_main(void) { nvs_flash_init(); wifi_event_group = xEventGroupCreate(); // 检查唤醒原因 esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); if (cause != ESP_SLEEP_WAKEUP_UNDEFINED) { ESP_LOGI("MAIN", "Wake from deep sleep, fast reconnect"); } else { ESP_LOGI("MAIN", "First boot, normal connect"); } wifi_init(); // 等待连接(超时 5 秒) xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, false, true, pdMS_TO_TICKS(5000)); // 模拟任务 ESP_LOGI("MAIN", "Task done, entering deep sleep"); // 进入 Deep-sleep 10 秒 esp_deep_sleep_enable_timer_wakeup(10 * 1000000); esp_deep_sleep_start(); } ``` ## 四、注意事项与优化建议 - **RTC 内存大小**:`RTC_DATA_ATTR` 变量不能超过 8KB,且不要存放指针(地址会失效)。 - **静态 IP 风险**:如果路由器 DHCP 分配范围变化,可能导致 IP 冲突。建议保存网关和子网掩码,或使用 DHCP 但跳过扫描(仍可节省时间)。 - **信道漂移**:如果路由器自动切换信道,保存的信道可能失效。可先尝试快速连接,失败后回退到全信道扫描。 - **功耗测量**:快速重连期间电流仍较高,建议使用 `esp_wifi_set_ps(WIFI_PS_MIN_MODEM)` 在连接后启用 Modem-sleep 降低功耗。 - **错误处理**:如果连接失败(如 AP 不可用),应增加重试次数限制,避免无限重连导致功耗飙升。 ## 五、总结 通过 RTC 内存保存 WiFi 连接信息,结合快速扫描和静态 IP,ESP32 在 Deep-sleep 唤醒后的重连时间可从秒级降至毫秒级。本文提供的代码可直接集成到你的工程中,实测重连时间约 80-120ms(取决于路由器响应)。这一技巧在电池供电的传感器节点、智能家居设备中非常实用,能显著延长电池寿命。希望本文能帮助你优化低功耗设计,如有疑问欢迎交流。