ESP-IDF+vscode开发ESP32 联网篇第一讲——WIFI扫描

目录

前言

一、WIFI的相关术语

[1.1 基础术语](#1.1 基础术语)

[1.2 标准与协议](#1.2 标准与协议)

[1.3 安全相关](#1.3 安全相关)

[1.4 信号与性能](#1.4 信号与性能)

[1.5 组网与拓扑](#1.5 组网与拓扑)

[1.6 网络功能](#1.6 网络功能)

[1.7 实用场景](#1.7 实用场景)

二、工程实现

[2.1 项目结构全览](#2.1 项目结构全览)

[2.2 WIFI 组件深度解析](#2.2 WIFI 组件深度解析)

[2.2.1 模块架构](#2.2.1 模块架构)

[2.2.2 初始化管线](#2.2.2 初始化管线)

[2.2.3 事件处理流程](#2.2.3 事件处理流程)

[2.2.4 关键函数详解](#2.2.4 关键函数详解)

[2.3 系统构建](#2.3 系统构建)

[2.4 WIFI组件代码展览](#2.4 WIFI组件代码展览)

CMakeLists.txt

scan.c

scan.h

三、输出展示

注意


前言

ESP32 作为面向物联网应用的芯片,具备较强的无线联网能力。为便于系统介绍其联网功能,本文单独开设联网篇,首先讲解 Wi‑Fi 扫描的实现方法。

本文使用微雪 ESP32-C6-DEV-KIT-N16 开发板,开发环境为 ESP-IDF 6.0.1。工程模板与前文 ESP32-P4 示例模板基本一致,相关代码可从我的 Gitee 仓库获取。

https://gitee.com/pai-schoolmate/esp32_project.git


一、WIFI的相关术语

关于WIFI相信同学们已经很熟悉了,但是关于WIFI的一些专业知识肯定有一些薄弱,那么这里来进行一个复习。

1.1 基础术语

AP(Access Point,接入点)

无线接入点,发出 WiFi 信号的设备。家用无线路由器本身就集成了 AP 功能。

STA(Station,站点)

连接到无线网络的终端设备,也就是你的手机、笔记本电脑、智能电视、智能音箱等。AP 和 STA 是 WiFi 中最核心的一对角色关系:AP 发信号,STA 连上来。

SSID(Service Set Identifier)

WiFi 网络的名字。你在设备上看到的 WiFi 列表里的每一个名称就是一个 SSID。

BSSID(Basic Service Set Identifier)

AP 的 MAC 地址,是物理层面唯一标识。同一个 SSID 下可能有多个 BSSID(比如 Mesh 网络的多个节点)。

频段(Band)

WiFi 使用的无线电频率范围,目前主要有三个:

  • 2.4GHz:穿墙能力强、覆盖远,但干扰多(蓝牙、微波炉、无线鼠标都用这个频段),速度相对慢

  • 5GHz:速度快、干扰少,但穿墙能力弱、覆盖距离短

  • 6GHz:WiFi 6E 和 WiFi 7 新增的频段,更宽的频道、更高的速度、更少的干扰

1.2 标准与协议

WiFi 4 / 5 / 6 / 7

IEEE 802.11 协议族的商业命名:

商业名 技术标准 推出年份 主要提升
WiFi 4 802.11n 2009 引入 MIMO,速度突破百兆
WiFi 5 802.11ac 2014 主攻 5GHz,速度达 Gbps 级别
WiFi 6 802.11ax 2019 高密场景优化,更低延迟,更省电
WiFi 7 802.11be 2024 320MHz 频宽,速度可达 30Gbps+

MIMO(Multiple-Input Multiple-Output)

多入多出技术。路由器用多根天线同时收发数据,就像多条车道同时跑车。常见标注如 2x2 MIMO、4x4 MIMO。

MU-MIMO(Multi-User MIMO)

多用户 MIMO,允许路由器同时和多个 STA 通信。WiFi 5 只支持下行 MU-MIMO,WiFi 6 支持上下行都支持。

OFDMA(Orthogonal Frequency Division Multiple Access)

正交频分多址,WiFi 6 引入的技术。把一个信道分成更小的子信道,可以在同一时刻给多个设备发送小数据包,大幅降低延迟、提升多设备场景下的效率。

1.3 安全相关

WEP

最早的加密协议,极其脆弱,几分钟就能破解,早已被淘汰。

WPA / WPA2 / WPA3

  • WPA:WEP 的替代品,现在也已不安全

  • WPA2:目前最广泛使用的标准,采用 AES 加密,对普通家庭足够安全

  • WPA3:最新标准,引入 SAE 握手(替代 WPA2 的 PSK),防暴力破解,即使密码弱也不易被离线破解

WPS(WiFi Protected Setup)

通过按路由器上的按钮或输入 PIN 码快速连接 WiFi。但 PIN 方式存在严重安全漏洞,建议在路由器后台关闭 WPS。

MAC 地址过滤

路由器设置只允许特定 MAC 地址的设备连接。看起来能增强安全,但实际上 MAC 地址可以伪造,防御能力有限。

1.4 信号与性能

dBm(分贝毫瓦)

信号强度的单位,通常为负值,越接近 0 越强:

  • -30 dBm:极强(紧贴路由器)
  • -50 dBm:良好
  • -67 dBm:可靠(高清视频流的最低要求)
  • -80 dBm:弱,可能不稳定
  • -90 dBm:几乎不可用

RSSI(Received Signal Strength Indicator)

接收信号强度指示,与 dBm 类似,用来衡量 STA 收到的 AP 信号强弱。经常直接用dBm做单位。

信道(Channel)

WiFi 工作的具体频率划分。2.4GHz 国内有 13 个信道(1-13),其中 1、6、11 互不重叠。5GHz 信道更多,且支持更宽的频宽(20/40/80/160MHz)。

信道干扰(Co-Channel Interference / Adjacent-Channel Interference)

邻居的 WiFi 用相同或相邻信道时产生的互相干扰,是导致网速变慢甚至不稳定的常见原因。

频宽(Channel Bandwidth)

信道的宽度,越宽速度越快但越容易被干扰。常见的:20MHz、40MHz、80MHz、160MHz。

1.5 组网与拓扑

Mesh(网状网络)

多个 AP 节点组成一个统一的 WiFi 网络,设备在不同节点间自动切换,全屋只有一个 SSID,实现无缝覆盖。三频 Mesh 会用单独一个 5GHz 频段做节点间回传,性能更好。

漫游(Roaming)

STA 从一个 AP 的覆盖范围移动到另一个 AP 时自动切换连接的过程。

802.11k / 802.11v / 802.11r(快速漫游协议)

  • 802.11k:让 STA 提前知道附近有哪些 AP 可选
  • 802.11v:让 AP 主动建议 STA 切换到哪个 AP
  • 802.11r:简化重认证流程,切换更快

这三者合称 快速漫游,对视频通话、在线游戏等实时业务体验提升明显。

WDS(Wireless Distribution System)

无线分布式系统,允许 AP 之间通过无线方式互联,扩展覆盖范围。相比 Mesh 配置复杂,性能也差一些。

中继(Repeater / Extender)

接收主路由的信号再转发出去,会损失一半带宽(半双工),而且会增加延迟。Mesh 在很大程度上已经取代了传统的中继模式。

1.6 网络功能

DHCP(Dynamic Host Configuration Protocol)

自动给接入的设备分配 IP 地址。路由器默认会开启 DHCP 服务,你连上 WiFi 时自动获取 IP 就是它在工作。

NAT(Network Address Translation)

网络地址转换,把局域网内多个设备的私有 IP 映射到一个公网 IP 上网。这是家用路由器最核心的功能之一。

LAN / WAN 端口

  • WAN 口:接光猫或入户宽带
  • LAN 口:接家里的有线设备

VLAN(Virtual Local Area Network)

虚拟局域网,把一个物理网络划分成多个逻辑隔离的网络。常用于区分访客网络和家庭网络,或者 IPTV 和普通上网走不同的 VLAN。

QoS(Quality of Service)

服务质量,路由器可以优先处理某些流量。比如设置游戏和视频通话的优先级高于下载,保证关键业务不卡顿。

端口转发(Port Forwarding)

把从外网访问特定端口的流量转发到内网某台设备。搭建 NAS、远程访问摄像头、自建网站等场景常用。

UPnP(Universal Plug and Play)

设备自动在路由器上开端口,简化配置。虽然方便但有安全隐患,建议在不必要时关闭。

1.7 实用场景

双频 / 三频

  • 双频:同时支持 2.4GHz 和 5GHz
  • 三频:一个 2.4GHz + 两个 5GHz,多见于高端 Mesh 路由器,其中一个 5GHz 专门用于节点间回传

天线增益(dBi)

天线具有将信号集中方向的能力。增益越高信号传得越远,但覆盖角度越小。家用路由器通常用 3-5dBi 的天线。

PoE(Power over Ethernet)

通过网线同时传输数据和电力。企业级 AP 常用 PoE 供电,只需要一根网线既上网又取电。

双频合一

路由器把 2.4GHz 和 5GHz 使用同一个 SSID,让设备自己选择连哪个频段。但有些设备会固执地连在 2.4GHz 上不切回 5GHz,所以很多人会分开设置两个 SSID。

Beamforming(波束成形)

路由器将信号定向发射到特定设备的方向,而不是全向广播,可以提升特定设备的信号质量和速度。

WAN 聚合 / LAN 聚合

把两个网口绑定成一个,实现双倍带宽。适合 NAS 等需要大带宽的场景。

这份算是比较完整的常用 WiFi 术语清单了。涉及到的专业名词较多,但都进行了非常通俗的解释,结合现实实例还是比较好理解的。

二、工程实现

2.1 项目结构全览

本工程采用 ESP-IDF 组件化架构(模式 A) 。顶层 CMakeLists.txt 通过 EXTRA_COMPONENT_DIRS 引入了 IDF 自带的 console 示例组件(cmd_systemcmd_nvscmd_wifi),免去手写系统命令的重复劳动。

2.2 WIFI 组件深度解析

2.2.1 模块架构

文件:components/WIFI/scan.c(300 行)------这是本工程代码量最大、逻辑最密集的模块,完成从 WiFi 子系统初始化到扫描结果解析输出的全部工作。模块架构如下

  • 公开 API:wifi_scan_init() ------ 唯一的对外接口,由 app_main() 调用
  • 事件回调:wifi_event_handler() ------ 注册到 ESP-IDF 事件循环,处理 WIFI_EVENT_SCAN_DONE
  • 控制台命令:scan_cmd_handler() + register_scan_cmd() ------ 让用户在 REPL 中手动触发重新扫描
  • 工具函数:cipher_type_to_str() ------ 将枚举转为可读字符串
  • 静态缓冲区:ap_records[100] ------ 文件作用域,避免每次扫描动态分配内存

2.2.2 初始化管线

⚠ 顺序不可打乱

ESP-IDF WiFi 初始化有严格的前置依赖。例如,若先调用 esp_wifi_init() 再初始化 NVS,驱动会因读不到 RF 校准数据而打印 warning 并使用默认射频参数。事件循环若未创建就注册回调,esp_event_handler_instance_register() 会返回 ESP_ERR_INVALID_STATE

2.2.3 事件处理流程

事件回调在扫描完成后由事件循环任务调用(非 ISR 上下文),因此可以安全地调用日志输出和内存操作。回调末尾的 esp_wifi_clear_ap_list() 是关键------不调用它,扫描结果会一直占用 WiFi 驱动的内部缓冲区,导致下一次扫描无法获取新结果。

2.2.4 关键函数详解

⚡ 初始化 wifi_scan_init(void)

完成 WiFi 子系统从零到可扫描的全部初始化,并注册控制台命令。是 WIFI 组件唯一的公开 API。

返回值 :无(内部通过 ESP_ERROR_CHECK 断言,失败直接 abort)。

注意 :NVS 初始化包含「损坏恢复」逻辑------若检测到 ESP_ERR_NVS_NO_FREE_PAGESESP_ERR_NVS_NEW_VERSION_FOUND,会自动擦除 NVS 分区后重试。这意味着首次烧录或 OTA 升级后不会因 NVS 格式变化而卡死。

⚡ 初始化 esp_event_loop_create_default(void)

创建系统默认事件循环。ESP-IDF 的 WiFi、IP、蓝牙等子系统都通过该事件循环分发事件到用户注册的回调。

返回值ESP_OK 成功;ESP_ERR_INVALID_STATE 已存在(重复调用安全);ESP_ERR_NO_MEM 内存不足。

注意 :必须在 esp_event_handler_instance_register() 之前调用,否则注册回调会返回 ESP_ERR_INVALID_STATE。默认事件循环会创建一个独立的 FreeRTOS 任务(栈大小由 CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE 决定,默认 3584 字节)。

⚡ 初始化 esp_wifi_init(const wifi_init_config_t *config)

初始化 WiFi 驱动程序,分配内部缓冲区、加载射频校准数据、初始化硬件寄存器。

config:指向 wifi_init_config_t 结构体的指针。通常使用 WIFI_INIT_CONFIG_DEFAULT() 宏生成默认配置,包含 TX/RX 缓冲区数量、AMSDU 阈值等参数。

返回值ESP_OK 成功;ESP_ERR_NO_MEM 内存不足;ESP_ERR_INVALID_ARG 参数非法。

注意 :依赖 NVS 已初始化(射频校准数据从 NVS 读取)和 esp_netif_init() 已完成。该函数只能调用一次,重复调用返回 ESP_ERR_INVALID_STATE

🔄 事件注册 esp_event_handler_instance_register(base, id, cb, arg, instance)

向事件循环注册一个事件处理回调。当指定类型的事件被 post 到事件循环时,回调函数将在事件循环任务上下文中被调用。

event_base:事件基类型,如 WIFI_EVENTIP_EVENT

event_id:具体事件 ID,ESP_EVENT_ANY_ID 表示订阅该 base 下的所有事件。

event_handler:回调函数指针,签名为 void (*)(void*, esp_event_base_t, int32_t, void*)

event_handler_arg:传递给回调的自定义参数(透传),本工程传 NULL。

instance:输出参数,返回注册实例句柄,可用于后续注销。本工程传 NULL 表示不保存。

返回值ESP_OK 成功;ESP_ERR_INVALID_ARG 参数非法;ESP_ERR_NO_MEM 内存不足。

注意:回调不在 ISR 中执行,而是在事件循环任务中串行执行,因此回调内可以调用日志、内存分配等非实时安全函数。但回调执行时间不宜过长,否则会阻塞后续事件的处理。

🔧 配置 esp_wifi_set_mode(wifi_mode_t mode)

设置 WiFi 工作模式。必须在 esp_wifi_start() 之前调用。

mode:可选值:WIFI_MODE_STA(站点模式,用于扫描/连接 AP)、WIFI_MODE_AP(软 AP 模式)、WIFI_MODE_APSTA(混合模式)、WIFI_MODE_NULL(关闭射频)。

返回值ESP_OK 成功;ESP_ERR_INVALID_ARG 模式值非法;ESP_ERR_WIFI_NOT_INIT WiFi 未初始化。

注意 :本工程使用 WIFI_MODE_STA------扫描功能要求 STA 模式。若设为 WIFI_MODE_NULL,射频关闭,扫描将返回错误。模式切换会触发 WIFI_EVENT_STA_STOP / WIFI_EVENT_STA_START 等事件。

🔧 控制 esp_wifi_start(void)

启动 WiFi 硬件,开启射频,并根据当前模式触发对应事件(STA 模式触发 WIFI_EVENT_STA_START)。

返回值ESP_OK 成功;ESP_ERR_WIFI_NOT_INIT 未初始化;ESP_ERR_WIFI_IF 内部错误。

注意 :必须在 esp_wifi_set_mode() 之后调用。启动后 WiFi 驱动开始占用 CPU 和内存资源。只有 start 之后才能调用 esp_wifi_scan_start()esp_wifi_connect() 等操作函数。对应的停止函数为 esp_wifi_stop()

📦 扫描 esp_wifi_scan_start(const wifi_scan_config_t *config, bool block)

触发一次 WiFi 扫描。扫描完成后产生 WIFI_EVENT_SCAN_DONE 事件,结果通过 esp_wifi_scan_get_ap_records() 获取。

config:扫描配置指针。NULL 表示使用默认配置(全信道、全 SSID、主动扫描)。可指定特定 SSID、信道列表、扫描类型(主动/被动)、各信道停留时间等。

block:true = 阻塞直到扫描完成再返回;false = 立即返回,结果通过事件异步通知。本工程使用 false 以避免阻塞主任务。

返回值ESP_OK 成功;ESP_ERR_WIFI_NOT_INIT 未初始化;ESP_ERR_WIFI_NOT_STARTED 未 start;ESP_ERR_WIFI_IF 正在扫描中或内部错误。

注意 :全信道主动扫描在 2.4 GHz 下约耗时 1.5~2 秒(13 个信道 × 120 ms/信道)。扫描期间 STA 无法收发数据。若上一次扫描结果未通过 esp_wifi_clear_ap_list() 清除,可能导致新扫描结果获取异常。

2.3 系统构建

顶层 CMakeLists.txt

复制代码
cmake_minimum_required(VERSION 3.22)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ESP32C6_v6_temaplat)

关键点:EXTRA_COMPONENT_DIRS 将 IDF 示例中的 cmd_systemcmd_nvscmd_wifi 组件引入本工程,避免重复实现。

组件依赖关系

组件 REQUIRES
main (隐式依赖 basic、WIFI)
WIFI basic · esp_event · esp_netif · esp_wifi
basic console · cmd_system · cmd_nvs · cmd_wifi · nvs_flash · esp_partition

2.4 WIFI组件代码展览

CMakeLists.txt

cpp 复制代码
idf_component_register(SRCS "scan.c"
                    INCLUDE_DIRS "include"
                    REQUIRES basic esp_event esp_netif esp_wifi)

scan.c

cpp 复制代码
/* ==========================================================================
 * WiFi 扫描模块 --- 实现文件
 *
 * 功能:
 *   1. 初始化 NVS、TCP/IP 协议栈、事件循环、WiFi 子系统
 *   2. 将 WiFi 设为 STA 模式并触发主动扫描
 *   3. 在 SCAN_DONE 事件中打印前若干 AP 的详细信息
 *
 * 依赖:
 *   - ESP-IDF: nvs_flash, esp_netif, esp_wifi, esp_event, esp_log
 *   - 本组件: scan.h(函数声明)、basic 组件
 * ========================================================================== */

/* ---------- 头文件 ---------- */
#include <stdio.h>               /* 标准 I/O */
#include <inttypes.h>            /* PRI 格式化宏(如 PRIu16) */

#include "scan.h"                /* 本模块接口声明 */
#include "esp_log.h"             /* ESP 日志系统 */
#include "nvs_flash.h"           /* 非易失性存储(NVS) */
#include "esp_netif.h"           /* TCP/IP 网络栈初始化 */
#include "esp_event.h"           /* 事件循环框架 */
#include "esp_wifi.h"            /* WiFi 驱动 API */

/* ---------- 宏定义 ---------- */

/** 模块日志标签,用于 ESP_LOGx 宏 */
static const char *TAG = "wifi_scan";

/** 单次扫描最多缓存的 AP 数量 */
#define MAX_AP_NUM  100

/**
 * 打印 AP 详情的个数上限。
 *
 * 注意:这里设为 5 仅用于演示,实际扫描到的 AP 可能远超这个数量。
 * 如需全部输出,请将 `DISPLAY_AP_COUNT` 改为 `ap_num`。
 */
#define DISPLAY_AP_COUNT  5

/* ---------- 模块级(文件作用域)变量 ---------- */

/** 存储扫描结果的全局缓冲区(文件内 static,外部不可见) */
static wifi_ap_record_t ap_records[MAX_AP_NUM];

/* ======================================================================
 * 内部辅助函数
 * ====================================================================== */

/**
 * @brief 将 wifi_cipher_type_t 枚举转为可读字符串
 *
 * 覆盖 ESP-IDF 定义的全部 13 种密码类型(wifi_cipher_type_t)。
 * 未知值返回 "未知(?)" 并附带原始数值,便于调试。
 */
static const char *cipher_type_to_str(wifi_cipher_type_t type)
{
    switch (type) {
    case WIFI_CIPHER_TYPE_NONE:         return "无";
    case WIFI_CIPHER_TYPE_WEP40:        return "WEP-40";
    case WIFI_CIPHER_TYPE_WEP104:       return "WEP-104";
    case WIFI_CIPHER_TYPE_TKIP:         return "TKIP";
    case WIFI_CIPHER_TYPE_CCMP:         return "CCMP (AES)";
    case WIFI_CIPHER_TYPE_TKIP_CCMP:    return "TKIP+CCMP";
    case WIFI_CIPHER_TYPE_AES_CMAC128:  return "AES-CMAC-128";
    case WIFI_CIPHER_TYPE_SMS4:         return "SMS4 (WAPI)";
    case WIFI_CIPHER_TYPE_GCMP:         return "GCMP";
    case WIFI_CIPHER_TYPE_GCMP256:      return "GCMP-256";
    case WIFI_CIPHER_TYPE_AES_GMAC128:  return "AES-GMAC-128";
    case WIFI_CIPHER_TYPE_AES_GMAC256:  return "AES-GMAC-256";
    case WIFI_CIPHER_TYPE_UNKNOWN:      return "未知";
    default:                            return "未知(?)";
    }
}

/* ======================================================================
 * 内部函数
 * ====================================================================== */

/**
 * @brief WiFi 事件处理回调
 *
 * 当前仅处理 `WIFI_EVENT_SCAN_DONE` 事件:
 *   - 获取扫描到的 AP 数量
 *   - 读取前 `DISPLAY_AP_COUNT` 个 AP 记录
 *   - 逐条打印 AP 的 MAC、RSSI、信道、SSID 等信息
 *
 * @note 扫描结果存储在文件级静态数组 `ap_records` 中。
 */
static void wifi_event_handler(void *event_handler_arg,
                               esp_event_base_t event_base,
                               int32_t event_id,
                               void *event_data)
{
    /* 仅处理扫描完成事件 */
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {

        /* ---- 1. 获取扫描到的 AP 总数 ---- */
        uint16_t ap_num = 0;
        ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_num));
        ESP_LOGI(TAG, "扫描完成,共发现 %" PRIu16 " 个 WiFi 网络", ap_num);

        /* ---- 2. 边界保护,防止数组越界 ---- */
        if (ap_num > MAX_AP_NUM) {
            ap_num = MAX_AP_NUM;
        }

        /* ---- 3. 读取 AP 记录到缓冲区 ---- */
        ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_num, ap_records));

        /* ---- 4. 打印前 DISPLAY_AP_COUNT 个 AP 的详细信息 ---- */
        ESP_LOGI(TAG, "========== 扫描结果(前 %d 个) ==========",
                 (ap_num < DISPLAY_AP_COUNT) ? ap_num : DISPLAY_AP_COUNT);

        for (int i = 0; (i < DISPLAY_AP_COUNT) && (i < ap_num); i++) {
            const wifi_ap_record_t *ap = &ap_records[i];

            /* 4.1 基本信息:MAC、RSSI、信道、SSID */
            ESP_LOGI(TAG,
                     "[%d] MAC: %02x:%02x:%02x:%02x:%02x:%02x  "
                     "RSSI: %d dBm  信道: %d  SSID: %s",
                     i + 1,
                     ap->bssid[0], ap->bssid[1], ap->bssid[2],
                     ap->bssid[3], ap->bssid[4], ap->bssid[5],
                     ap->rssi,
                     ap->primary,
                     ap->ssid);

            /* 4.2 安全信息:认证模式、加密类型 */
            ESP_LOGI(TAG,
                     "    认证模式: %s  配对密码: %s  组密码: %s",
                     ap->authmode == WIFI_AUTH_OPEN     ? "Open"      :
                     ap->authmode == WIFI_AUTH_WEP      ? "WEP"       :
                     ap->authmode == WIFI_AUTH_WPA_PSK  ? "WPA-PSK"   :
                     ap->authmode == WIFI_AUTH_WPA2_PSK ? "WPA2-PSK"  :
                     ap->authmode == WIFI_AUTH_WPA3_PSK ? "WPA3-PSK"  : "其他",
                     cipher_type_to_str(ap->pairwise_cipher),
                     cipher_type_to_str(ap->group_cipher));

            /* 4.3 频段与功能特性
             * 优先用 PHY 标志位判断,无法确定时用 primary 信道号兜底
             * 802.11b/g → 2.4 GHz(确定)  802.11a/ac → 5 GHz(确定)
             * 802.11n/ax → 多频段(需结合其他标志或信道号)           */
            const char *band_str = "未知";
            int has_24g = ap->phy_11b || ap->phy_11g;   /* b/g 是 2.4G 独有 */
            int has_5g  = ap->phy_11a || ap->phy_11ac;   /* a/ac 是 5G 独有  */

            if (has_24g && has_5g) {
                band_str = "2.4 / 5 GHz (双频)";
            } else if (has_24g) {
                band_str = "2.4 GHz";
            } else if (has_5g) {
                band_str = "5 GHz";
            } else if (ap->phy_11ax) {
                /* 仅 11ax 标志:Wi-Fi 6/6E,具体频段依赖信道号 */
                if (ap->primary >= 1 && ap->primary <= 14) {
                    band_str = "2.4 GHz (Wi-Fi 6)";
                } else if (ap->primary >= 36 && ap->primary <= 177) {
                    band_str = "5 GHz (Wi-Fi 6)";
                } else {
                    band_str = "6 GHz (Wi-Fi 6E)";
                }
            } else if (ap->phy_11n) {
                /* 仅 11n 标志:大概率 2.4G,用信道号确认 */
                if (ap->primary >= 36 && ap->primary <= 177) {
                    band_str = "5 GHz (802.11n)";
                } else {
                    band_str = "2.4 GHz (802.11n)";
                }
            } else {
                /* PHY 标志均无,完全依赖信道号兜底 */
                if (ap->primary >= 1 && ap->primary <= 14) {
                    band_str = "2.4 GHz";
                } else if (ap->primary >= 36 && ap->primary <= 177) {
                    band_str = "5 GHz";
                }
            }
            ESP_LOGI(TAG,
                     "    频段: %s  WPS: %s  FTM: %s",
                     band_str,
                     ap->wps            ? "支持" : "不支持",
                     ap->ftm_responder  ? "支持" : "不支持");

            /* 4.4 带宽与中心频率 */
            const char *bw_str = "未知";
            switch (ap->bandwidth) {
            case 1:  bw_str = "20 MHz";   break;
            case 2:  bw_str = "40 MHz";   break;
            case 3:  bw_str = "80 MHz";   break;
            case 4:  bw_str = "160 MHz";  break;
            default: bw_str = "未知";      break;
            }

            /* 从主信道号计算中心频率(MHz) */
            uint16_t center_freq = 0;
            if (ap->primary >= 1 && ap->primary <= 14) {
                center_freq = 2407 + ap->primary * 5;   /* 2.4 GHz */
            } else if (ap->primary >= 36 && ap->primary <= 177) {
                center_freq = 5000 + ap->primary * 5;   /* 5 GHz */
            }

            /* 4.5 GHz VHT 宽信道:使用 AP 上报的 VHT 频率 */
            if (ap->bandwidth >= 2 && ap->vht_ch_freq1 != 0) {
                center_freq = ap->vht_ch_freq1;
            }

            ESP_LOGI(TAG,
                     "    带宽: %s   中心频率: %d MHz",
                     bw_str,
                     center_freq);

        }
        ESP_LOGI(TAG, "==========================================");
        esp_wifi_clear_ap_list();   /* 清空扫描结果,释放内存 */
    }
}

/* ======================================================================
 * 控制台命令
 * ====================================================================== */

/** scan 命令的回调,转发到 wifi_scan_trigger() */
static int scan_cmd_handler(int argc, char **argv)
{
    ESP_LOGI(TAG, "触发重新扫描...");
    esp_err_t ret = esp_wifi_scan_start(NULL, false);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "扫描启动失败: %s", esp_err_to_name(ret));
    }
    return 0;
}

/** 注册 scan 控制台命令 */
void register_scan_cmd(void)
{
    const esp_console_cmd_t cmd = {
        .command  = "scan",
        .help     = "触发一次 WiFi 全信道扫描,结果通过日志输出",
        .hint     = NULL,
        .func     = &scan_cmd_handler,
    };
    ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}

/* ======================================================================
 * 公开 API
 * ====================================================================== */

/**
 * @brief 初始化 WiFi 并触发一次全信道扫描
 *
 * 本函数完成以下步骤:
 *   1. 初始化 NVS(WiFi 校准数据存储依赖)
 *   2. 初始化 TCP/IP 协议栈(esp_netif)
 *   3. 创建默认事件循环
 *   4. 初始化 WiFi 子系统(默认配置)
 *   5. 注册 WIFI_EVENT 回调
 *   6. 设置 WiFi 为 STA 模式并启动
 *   7. 发起主动全信道扫描(阻塞时间极短,结果通过事件异步返回)
 */
void wifi_scan_init(void)
{
    /* ---- 1. 初始化 NVS ---- */
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
        ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        /* NVS 分区损坏或版本不兼容,擦除后重新初始化 */
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    /* ---- 2. 初始化 TCP/IP 协议栈 ---- */
    ESP_ERROR_CHECK(esp_netif_init());

    /* ---- 3. 创建默认事件循环 ---- */
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* ---- 4. 初始化 WiFi 子系统(默认配置) ---- */
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    /* ---- 5. 注册 WiFi 事件回调 ---- */
    ESP_ERROR_CHECK(
        esp_event_handler_instance_register(WIFI_EVENT,
                                            ESP_EVENT_ANY_ID,
                                            &wifi_event_handler,
                                            NULL,   /* arg: 未使用 */
                                            NULL)); /* instance: 未保存句柄 */

    /* ---- 6. 设置为 STA 模式并启动 WiFi ---- */
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_start());

    /* ---- 7. 发起主动全信道扫描 ---- */
    ESP_ERROR_CHECK(esp_wifi_scan_start(NULL, false));
    ESP_LOGI(TAG, "WiFi 初始化完成,开始扫描 WiFi 网络...");
    register_scan_cmd(); // 注册scan控制台命令
}

scan.h

cpp 复制代码
#ifndef SCAN_H
#define SCAN_H

#include <string.h>              /* C 标准库:字符串处理 */
#include "esp_console.h"         /* ESP-IDF:控制台框架 */
#include "linenoise/linenoise.h" /* ESP-IDF:行编辑库 */
#include "nvs_flash.h"           /* ESP-IDF:非易失性存储(NVS) */
#include "esp_partition.h"       /* ESP-IDF:分区表查询 */
#include "esp_log.h"             /* ESP-IDF:日志系统 */
#include "FreeRTOS/FreeRTOS.h"   /* FreeRTOS:内核 */
#include "FreeRTOS/task.h"       /* FreeRTOS:任务管理 */
#include "FreeRTOS/semphr.h"     /* FreeRTOS:信号量管理 */

/**
 * @brief 初始化 WiFi 扫描功能
 *
 * 初始化 WiFi 扫描相关的数据结构和资源,并执行首次扫描。
 */
void wifi_scan_init(void);

#endif /* SCAN_H */

三、输出展示

配对密码(Pairwise Cipher)

单播加密 --- AP 与单个客户端之间通信使用的加密算法。

当你的设备连接到 WiFi 后,你和 AP 之间的数据包就是用这个算法加密的,每个客户端拥有一把独立的密钥。

组密码(Group Cipher)

广播/组播加密 --- AP 向所有已连接客户端广播数据时使用的加密算法。

AP 有时需要向所有客户端发送同样的信息(如 ARP 广播),这时用的就是组密码。所有连接到同一个 AP 的设备共享同一把组密钥。取值与配对密码相同(同一个枚举)。

为什么需要两种?

  • 单播密钥每人一把 → 更安全,互不窃听
  • 组播密钥大家共用 → AP 只需加密一次,所有客户端都能解密,效率更高

中心频率(Center Frequency)

WiFi 信道不是单一频率,而是一段频率范围。中心频率就是这个范围的正中间位置。

ESP32-C6 的 WiFi 射频模块 仅支持 2.4 GHz 频段,不支持 5 GHz。

注意

乐鑫官网明确说明ESP32-C6 的 WiFi 射频模块 仅支持 2.4 GHz 频段,不支持 5 GHz。ESP32-C6 的射频硬件根本无法接收5 GHz频率的信号,所以无论怎么扫都扫不到。

总结

本文基于 ESP32-C6 与 ESP-IDF 6.0.1,系统梳理了 WiFi 基础术语、协议标准、安全机制、信号与组网等概念,并结合组件化工程示例,完整实现并解析了一个可通过控制台命令触发的全信道 WiFi 扫描功能,包含初始化流程、事件回调、AP 信息解析与日志输出等关键细节,为后续拓展更多联网功能提供了可复用的基础框架和实践参考。

相关推荐
Irene19912 天前
怎么设置 PyCharm 使用 VSCode 的快捷键,推荐保留的原生快捷键(特色功能不建议覆盖,否则丧失效率优势)Ctrl+Alt+L:代码格式化
vscode·pycharm·快捷键
放学校门口见2 天前
vscode部署stm32开发调试环境
ide·vscode·stm32
2501_916008893 天前
iOS应用开发工具全面解析:如何选择与优化开发效率
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
csdn_aspnet3 天前
如何将 Cursor MCP 与 VS Code 连接
vscode·cursor·mcp·composio
小杨小杨、努力变强!3 天前
VS Code运行HBuilder X中的uni-app项目
vscode·uni-app·uni-app run
游戏开发爱好者84 天前
iOS开发IDE有哪些 Xcode 和 快蝎 轻量替代方案
ide·vscode·ios·个人开发·xcode·swift·敏捷流程
开发者联盟league4 天前
vscode 进入docker容器
vscode·docker
张太行_4 天前
vscode在open folder如何设置默认路径
ide·vscode·编辑器
AI的探索之旅5 天前
AI辅助原理图评审:电源去耦、BOOT引脚、VCAP——19项逐一核查,遗漏?不存在的
人工智能·vscode·嵌入式硬件