WIFI_AP_STA 模式下 ESP32 连接 AP 后为何无法接收 ESP-NOW?
结论
ESP32 可以在连接路由器的同时接收 ESP-NOW。题中这组 ESP8266 与 ESP32 应先调整两处配置:
- 启用 ESP32 的 SoftAP 接口,将 ESP8266 的发送目标从
WiFi.macAddress()返回的 STA MAC 地址改为 SoftAP MAC 地址。 - 将 ESP8266 的
esp_now_add_peer()第三个参数从写死的3改为实际使用的路由器信道。
MicroPython 的 ESP-NOW 文档记录了这样一种现象:ESP32 连接 Wi-Fi 后,无法通过 STA 地址接收 ESP8266 的数据,需要改用 AP 地址。这与题中的症状吻合,但不能据此认定所有 Arduino Core、SDK 版本都有相同行为。
为什么 WIFI_AP_STA 仍然收不到
开启 AP 和 STA 后,发送目标仍需选对
ESP32 的两个接口各有自己的 MAC 地址:
| 接口 | 用途 | Arduino 中的地址读取方法 |
|---|---|---|
| STA | ESP32 连接路由器 | WiFi.macAddress() |
| SoftAP | ESP32 自身提供接入点 | WiFi.softAPmacAddress() |
原代码只打印了 STA MAC:
Serial.printf("MAC Address: %s\n", WiFi.macAddress().c_str());
如果把这个地址填入 ESP8266 的 peer[],ESP8266 就会一直向 ESP32 的 STA 接口发送。设置 WIFI_AP_STA 不会自动将发送目标改为 SoftAP 地址。
排查题中故障时,应启动 SoftAP,读取它的 MAC,再更新 ESP8266 的目标地址。ESP8266 无须通过 WiFi.begin() 连接这个 SoftAP,也无须输入它的密码。
ESP8266 的两处信道配置不一致
代码中同时存在:
#define WIFI_CHANNEL 8
wifi_set_channel(WIFI_CHANNEL);
以及:
esp_now_add_peer(peer, ESP_NOW_ROLE_COMBO, 3, NULL, 0);
esp_now_add_peer() 的第三个参数是 peer 信道,这里写死为 3。即使将 WIFI_CHANNEL 改成路由器信道,这两处配置仍可能冲突。参数含义可在 ESP8266 SDK 头文件中核对。
AP 和 STA 共用同一个无线信道
WIFI_AP_STA 提供两个逻辑接口,两者共用无线硬件。ESP32 连接路由器后,SoftAP 必须跟随 STA 使用路由器所在的信道,无法让 STA 使用信道 8、SoftAP 独立使用信道 3。ESP-IDF Wi-Fi 文档说明了这一规则。
ESP32 中的:
peer.channel = 0;
表示使用当前信道,可以保留。这个设置不会让远端 ESP8266 自动寻找或跟随路由器信道。
调整步骤
- 将路由器的 2.4 GHz Wi-Fi 设为固定信道。
- 让 ESP32 连接路由器,连接成功后读取实际信道。
- 在 ESP32 上启动 SoftAP,并打印 STA、SoftAP 两个 MAC 地址。
- 将 ESP8266 的目标地址改为 ESP32 的 SoftAP MAC。
- 将 ESP8266 的本地信道和 peer 信道统一为路由器信道。
- 如果需要双向通信,让 ESP32 显式通过
WIFI_IF_AP向 ESP8266 的 STA MAC 发送。
验证这些配置后,再排查省电、扫描和版本差异。
ESP32 端代码调整
用以下代码替换原有 setup(),保留题中的全局变量和旧式 onRecv()、onSent() 签名。peer_addr 应填写 ESP8266 的 STA MAC。
#include <esp_err.h>
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_AP_STA);
WiFi.begin(routerSsid, routerPwd);
const uint32_t startedAt = millis();
while (WiFi.status() != WL_CONNECTED) {
if (millis() - startedAt >= 20000) {
Serial.println("Wi-Fi connection timeout");
while (true) {
delay(1000);
}
}
delay(200);
}
const uint8_t channel = WiFi.channel();
if (!WiFi.softAP(apSsid, apPwd, channel)) {
Serial.println("SoftAP start failed");
while (true) {
delay(1000);
}
}
// 排查期间关闭 Wi-Fi 省电。
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
Serial.print("Station IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("STA MAC: ");
Serial.println(WiFi.macAddress());
Serial.print("SoftAP MAC: ");
Serial.println(WiFi.softAPmacAddress());
Serial.print("Wi-Fi Channel: ");
Serial.println(WiFi.channel());
ESP_ERROR_CHECK(esp_now_init());
ESP_ERROR_CHECK(esp_now_register_recv_cb(onRecv));
ESP_ERROR_CHECK(esp_now_register_send_cb(onSent));
esp_now_peer_info_t peer = {};
memcpy(peer.peer_addr, peer_addr, 6);
peer.channel = 0;
peer.ifidx = WIFI_IF_AP;
peer.encrypt = false;
ESP_ERROR_CHECK(esp_now_add_peer(&peer));
}
这两个设置控制的方向不同:
- ESP8266 的目标 MAC 决定数据发往 ESP32 的哪个接口。
- ESP32 的
peer.ifidx = WIFI_IF_AP决定 ESP32 向该 peer 发送时使用哪个本地接口。
仅修改 peer.ifidx,不会改变 ESP8266 发来的数据的目标地址。
如果 ESP32 只接收题中这种未加密单播,就无须为接收添加发送方 peer。上面的 peer 配置用于保留原程序的双向通信。ESP-IDF ESP-NOW 文档说明了未加密接收与 peer 注册的关系。
ESP8266 端代码调整
先将原来的 peer[] 替换为 ESP32 串口打印的 SoftAP MAC。不要填路由器的 MAC,也不要继续使用 ESP32 的 STA MAC。
然后将:
esp_now_add_peer(peer, ESP_NOW_ROLE_COMBO, 3, NULL, 0);
改为:
if (esp_now_add_peer(
peer,
ESP_NOW_ROLE_COMBO,
WIFI_CHANNEL,
NULL,
0) != 0) {
Serial.println("esp_now_add_peer failed");
return;
}
保留本地信道设置,并检查执行结果:
if (!wifi_set_channel(WIFI_CHANNEL)) {
Serial.println("wifi_set_channel failed");
return;
}
Serial.print("ESP8266 STA MAC: ");
Serial.println(WiFi.macAddress());
Serial.print("ESP8266 Wi-Fi Channel: ");
Serial.println(wifi_get_channel());
这些代码应放在原来的 WiFi.mode(WIFI_STA)、WiFi.disconnect() 之后,ESP-NOW 初始化之前。WIFI_CHANNEL 必须填写 ESP32 连接成功后打印出的信道。
题中使用的是 ESP8266 旧版 API,发送回调应与头文件中的类型一致:
void onSent(uint8_t* mac, uint8_t status) {
Serial.print("send callback status: ");
Serial.println(status);
}
注册时直接传入函数:
esp_now_register_recv_cb(onRecv);
esp_now_register_send_cb(onSent);
如何理解测试中的异常现象
ESP32 发送成功,不代表它收到了 ESP8266 的应用数据
对于题中的单播,ESP_NOW_SEND_SUCCESS 表示发送的数据在 MAC 层得到了成功确认。它不能证明接收端的 onRecv() 已经处理数据,也不能证明反方向的 ESP-NOW 发送配置正确。ESP-IDF 文档区分了 MAC 层成功和应用层接收。
因此,ESP32 可以向 ESP8266 发送成功,同时收不到 ESP8266 主动发送的数据。要确认业务数据已经处理,应在接收回调之外安排应用层应答,并携带包序号。
“错误信道仍能接收”不能证明可以跨信道通信
应分别检查配置中的信道值、peer 记录中的信道,以及无线电当时实际使用的信道。原程序中的 WIFI_CHANNEL 与 peer 信道 3 已经存在不一致。
仅凭串口现象,无法判断旧版 ESP8266 SDK 当时是否切换了信道,或发生了其他状态变化。应先统一配置,必要时通过抓包确认,不要依赖所谓的跨信道接收。
连接不存在的 Wi-Fi 时,偶尔成功不能作为有效验证
STA 搜索 AP 时会扫描信道,无线电未必持续停留在 ESP-NOW 使用的信道。偶尔收到数据,可能是扫描时短暂经过了目标信道。
单次读到信道 14,也不足以确定整个连接过程的信道状态。调试时应为连接过程设置超时,避免在持续搜索 AP 的状态下判断 ESP-NOW 是否正常。
其他注意事项
- 信道
1无法自动解决此问题,两端实际工作信道必须一致。路由器自动换信道后,未连接路由器的 ESP8266 也需要更新信道。 esp_wifi_set_ps(WIFI_PS_NONE)可用于排除省电影响,但会增加功耗。题中已使用 AP 与 STA 共存模式,不能直接将故障归因于省电。Wi-Fi 文档说明了相关行为。- 上面的 ESP32 示例沿用题中的旧式回调签名。较新的 SDK 中,接收回调可能使用
const esp_now_recv_info_t*,发送回调也可能变化。应按安装版本的esp_now.h修改函数,避免用强制类型转换掩盖签名不匹配。 - 接收回调读取
data[0]前,应先检查len > 0。正式程序应把数据复制到队列,再在loop()或其他任务中打印、处理,避免长时间占用 Wi-Fi 回调任务。 - 两端示例里的 MAC 都是占位值,需要分别填写实际地址。不要通过给 STA MAC 最后一字节加一来猜测 SoftAP MAC。
备注:内容仅供参考。
