← 返回首页

ESP32-C3 深度休眠后 WiFi 连不上:AP_STA 与 RTC 内存

2026-07-26

墨水屏看板为了省电,每次刷完就进深度休眠,醒来重新联网取下一帧。这里有段时间我遇到一个很烦的循环:大部分时候能连上,偶尔一次连不上,设备就掉进配网热点模式,然后因为热点已经开着、又没有新的配置请求,它就那么待着耗电,直到下一次被唤醒又重复一遍。表现出来就是"这块屏有时候一整天不更新"。

现象和第一轮误判

最初我以为是路由器的问题,换了信道、换了 2.4G 频段、把 DHCP 租期调长,都没用。后来把串口日志挂上去看才发现,问题出在 WiFi.begin() 这一步:唤醒后不传 SSID 和密码、让它从 NVS 里读已存的凭据重连,返回的 WL_STATUS 经常卡在 WL_IDLE_STATUS 或者 WL_DISCONNECTED,超时之后我的代码就判定"没有可用凭据",直接切去配网模式。

也就是说,凭据其实还在,只是那一轮重连失败了。而我的超时逻辑把"重连失败"和"没有凭据"当成了同一件事,于是每次偶发失败都变成了一次误入配网。

改法一:用 AP_STA 并存,不要来回切模式

把 WiFi.mode(WIFI_STA) 改成 WiFi.mode(WIFI_AP_STA)。这样 STA 和 AP 同时开着,STA 在尝试连接的同时,配网热点一直是可用的,不需要先停 STA 再开 AP。原来那种"先切 STA、失败切 AP"的写法在模式切换的瞬间会把 STA 的连接尝试也打断,反而更容易失败。

同时加上 WiFi.persistent(true),让配置在运行期间会写回 NVS,避免某些情况下配置只存在于内存、休眠后丢失。

改法二:凭据放 RTC 内存,唤醒后显式传参

这是我认为最关键的一步。与其依赖 NVS 里那套隐式的凭据读写,不如把 SSID 和密码用 RTC_DATA_ATTR 变量存在 RTC 内存里——深度休眠时 RTC 内存是保活的,醒来数据还在。然后在唤醒后直接 WiFi.begin(ssid, pass),把参数显式传进去,绕开 NVS 这条路径。

这样做的另一个好处是,配网得到的新凭据我也直接写进 RTC 变量,下次唤醒用的就是最新的,不需要关心 NVS 到底有没有来得及提交。

改法三:降低发射功率,固定配网信道

两个小调整:把发射功率降到 8.5dBm,把配网热点的信道固定成 6。发射功率降下来之后电流峰值小很多,即使偶发重连失败也不会因为电流拉扯导致复位;固定信道则避免配网热点每次开在不同信道上,手机端缓存了旧信道导致连不上。

connectWiFi() 的写法

逻辑很简单:先用已存的凭据试直连,失败了才进配网,而不是把所有失败都当成没配置。

RTC_DATA_ATTR char rtcSsid[33] = "";
RTC_DATA_ATTR char rtcPass[65] = "";

bool connectWiFi() {
  WiFi.mode(WIFI_AP_STA);
  WiFi.persistent(true);
  WiFi.setTxPower(WIFI_POWER_8_5dBm);

  if (strlen(rtcSsid) == 0) {
    return false;              // 从没配过网,直接去配网
  }

  WiFi.begin(rtcSsid, rtcPass);
  unsigned long t0 = millis();
  while (millis() - t0 < 15000) {
    if (WiFi.status() == WL_CONNECTED) return true;
    delay(200);
  }
  return false;                // 这次没连上,留给调用方重试
}

void setup() {
  if (!connectWiFi()) {
    startConfigPortal();       // 只有真没凭据或多次失败才进这里
  }
}

注意那个 15 秒的超时返回值:调用方拿到 false 之后应该先再试一轮或者直接休眠等下一次唤醒,而不是立刻开配网。我现在的策略是连续两次失败才进配网,这个改动上线之后"一整天不更新"的情况就再没出现过。

小结

这三个改动里,真正解决问题的是第二个——显式传参。AP_STA 和降低功率更多是提高稳定性、减小副作用。如果你的设备也是深度休眠加定期联网,建议一开始就按这个结构写,不要等出了问题再改,因为这类偶发失败很难复现。