4. Wi-Fi 扫描机制:主动扫描与被动扫描、扫描结果解析、信道管理
Wi-Fi 扫描,说白了就是设备在找网络。ESP32 怎么知道周围有哪些热点?信号强度怎么样?能不能连?这些信息全靠扫描来获取。
我刚开始做 Wi-Fi 产品时,总觉得扫描就是调个 API 的事。后来在量产项目上被坑过几次,才明白扫描机制选不对,连接稳定性直接崩盘。今天咱们就把这块掰开揉碎了讲。
4.1 主动扫描 vs 被动扫描
ESP32 支持两种扫描模式。它们的工作方式完全不同,适用场景也不一样。
| 对比项 | 主动扫描 | 被动扫描 |
|---|---|---|
| 工作方式 | 发送 Probe Request,等待 AP 回复 Probe Response | 监听 AP 周期性发送的 Beacon 帧 |
| 扫描速度 | 快(通常 1-3 秒/信道) | 慢(需等待 Beacon 周期,通常 100ms-1s/信道) |
| 功耗 | 较高(主动发包) | 较低(仅接收) |
| 隐蔽性 | 低(暴露设备存在) | 高(不发送任何帧) |
| 获取信息 | 更丰富(可获取 AP 完整能力) | 仅 Beacon 中携带的信息 |
| 适用场景 | 快速连接、用户主动搜索 | 后台扫描、低功耗巡检 |
核心结论:ESP32 默认使用主动扫描。如果你做的是电池供电设备,建议在后台巡检时切到被动扫描,能省 30%-50% 的扫描功耗。
4.2 扫描流程与参数配置
ESP32 的扫描 API 在 esp_wifi.h 里。我习惯用 esp_wifi_scan_start() 配合回调来处理结果。
先看一个最基础的主动扫描示例:
#include "esp_wifi.h"
void scan_networks(void) {
wifi_scan_config_t scan_config = {
.ssid = NULL, // 扫描所有 SSID
.bssid = NULL, // 不指定 BSSID
.channel = 0, // 0 表示全信道扫描
.show_hidden = true // 显示隐藏网络
};
ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, true));
// true 表示阻塞等待扫描完成
}
这里有个细节——show_hidden 参数。我遇到过不少产品,用户说「我明明开了 Wi-Fi 但设备搜不到」。排查到最后,发现是 AP 开启了隐藏 SSID,而设备扫描时没设这个标志位。
我的习惯:量产固件里 show_hidden 一律设为 true。多花几十毫秒,少接一堆售后电话。
4.3 扫描结果解析
扫描完成后,结果存在 wifi_ap_record_t 结构体数组里。咱们看看里面到底有什么:
typedef struct {
uint8_t bssid[6]; // AP 的 MAC 地址
uint8_t ssid[33]; // 网络名称
uint8_t primary; // 主信道编号
wifi_second_chan_t second; // 第二信道(40MHz 带宽时使用)
int8_t rssi; // 信号强度(dBm)
wifi_auth_mode_t authmode; // 认证方式
wifi_cipher_type_t pairwise_cipher;
wifi_cipher_type_t group_cipher;
uint8_t ant; // 天线编号
uint32_t phy_11b: 1; // 是否支持 11b
uint32_t phy_11g: 1; // 是否支持 11g
uint32_t phy_11n: 1; // 是否支持 11n
uint32_t phy_lr: 1; // 是否支持 Long Range
uint32_t wps: 1; // 是否支持 WPS
uint32_t reserved: 27;
wifi_country_t country; // 国家码信息
} wifi_ap_record_t;
这里面最关键的三个字段:
- rssi:信号强度。我一般以 -70dBm 为分界线,低于这个值就提示用户信号弱。
- authmode:认证方式。WPA2-PSK 最常见,但有些老 AP 还在用 WEP,连接时要注意兼容。
- primary:主信道。这个在信道管理里会用到。
解析结果的代码也很直接:
uint16_t ap_count = 0;
wifi_ap_record_t ap_records[20];
esp_wifi_scan_get_ap_records(&ap_count, ap_records);
for (int i = 0; i < ap_count; i++) {
printf("SSID: %s, RSSI: %d dBm, CH: %d, Auth: %d\n",
ap_records[i].ssid,
ap_records[i].rssi,
ap_records[i].primary,
ap_records[i].authmode);
}
注意:esp_wifi_scan_get_ap_records() 的第二个参数是数组指针,数组大小要提前分配好。我见过有人传了个空指针进去,结果直接 panic。建议用 esp_wifi_scan_get_ap_num() 先获取数量,再动态分配内存。
4.4 信道管理
信道管理是 Wi-Fi 稳定性的核心。ESP32 工作在 2.4GHz 频段,共 13 个信道(中国区)。每个信道 20MHz 带宽,相邻信道有重叠。
我画了一张信道分布图,帮你理解:
你看,CH1、CH6、CH11 是三个互不重叠的信道。我建议在代码里做信道优选时,优先选这三个。
4.5 信道优选策略
扫描完成后,怎么选信道?我总结了一套实用策略:
- 信号强度优先:选 RSSI 最高的 AP 所在信道。
- 干扰最小化:如果多个 AP 信号强度接近,选 AP 数量最少的信道。
- 非重叠信道优先:CH1、CH6、CH11 优先于其他信道。
- 动态切换:连接后定期扫描,如果当前信道质量下降,主动切换。
实战经验:我在一个智能家居项目里,设备部署在商场。一开始固定用 CH6,结果一到周末人流量大,Wi-Fi 卡得不行。后来改成动态信道选择,每 5 分钟扫描一次,自动切到干扰最小的信道。丢包率从 15% 降到了 2% 以下。
4.6 扫描性能优化
扫描不是越快越好。太快了可能漏掉 AP,太慢了用户体验差。我一般这样调:
wifi_scan_config_t scan_config = {
.ssid = NULL,
.bssid = NULL,
.channel = 0,
.show_hidden = true,
.scan_type = WIFI_SCAN_TYPE_ACTIVE,
.scan_time.active.min = 100, // 每信道最少扫描 100ms
.scan_time.active.max = 300 // 每信道最多扫描 300ms
};
全信道扫描(13 个信道)大概需要 1.3-3.9 秒。如果你只需要连某个特定 SSID,可以指定信道,把扫描时间压缩到 200ms 以内。
我的建议:产品首次配网时做全信道扫描,后续重连时只扫描上次连接的信道及其相邻信道。这样既保证成功率,又提升速度。
4.7 常见扫描问题排查
最后列几个我踩过的坑:
- 扫描结果为空:检查 Wi-Fi 模式是否设为
WIFI_MODE_STA,以及天线是否正常。 - 扫描超时:
esp_wifi_scan_start()的阻塞时间不要超过 10 秒,否则任务会被看门狗复位。 - RSSI 值异常:ESP32 的 RSSI 精度在 ±2dBm 左右,别拿它做精确定位。
- 信道切换失败:切换信道前要确保 Wi-Fi 处于断开状态,否则会触发异常。
曾经踩过的坑:有一次我在扫描回调里直接调了 esp_wifi_connect(),结果死锁了。后来才搞清楚,扫描回调是在 Wi-Fi 任务上下文中执行的,不能在里面调用其他 Wi-Fi API。正确的做法是发事件给应用任务,由应用任务处理连接。
嗯,扫描机制这块就讲这么多。记住一句话:扫描是 Wi-Fi 连接的「眼睛」,眼睛不好使,后面跑得再快也没用。