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

13 阅读20分钟

目录

前言

一、WIFI的相关术语

1.1 基础术语

1.2 标准与协议

1.3 安全相关

1.4 信号与性能

1.5 组网与拓扑

1.6 网络功能

1.7 实用场景

二、工程实现

2.1 WIFI组件代码展览

CMakeLists.txt

scan.c

wifi_event.c

scan.h

wifi_event.h

2.2 代码逻辑讲解

三、输出展示

注意

总结


前言

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

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

gitee.com/pai-schoolm…


一、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 WIFI组件代码展览

CMakeLists.txt

idf_component_register(SRCS "wifi_event.c" "scan.c"
                    INCLUDE_DIRS "include"
                    REQUIRES basic esp_event esp_netif esp_wifi)

scan.c

/* ==========================================================================
 * WiFi 扫描模块 — 实现文件
 *
 * 职责:
 *   1. 初始化 NVS、TCP/IP 协议栈、事件循环及 WiFi 子系统
 *   2. 将 WiFi 设为 STA 模式并触发主动全信道扫描
 *   3. 注册 "scan" 控制台命令,供用户手动触发重新扫描
 *
 * 依赖:
 *   - ESP-IDF:nvs_flash、esp_netif、esp_wifi、esp_event、esp_console、esp_log
 *   - 本组件:scan.h(函数声明)、wifi_event.h(事件处理回调)
 * ========================================================================== */

/* ---------- 头文件 ---------- */
#include "scan.h"                  /* 本模块接口声明 */
#include "wifi_event.h"            /* WiFi 事件处理回调 */

/* ---------- 模块常量 ---------- */

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

/* ======================================================================
 * 控制台命令(内部函数)
 * ====================================================================== */

/**
 * @brief "scan" 命令回调 —— 触发一次全信道主动扫描
 *
 * @param[in] argc 参数个数(未使用)
 * @param[in] argv 参数列表(未使用)
 * @return 始终返回 0
 */
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;
}

/**
 * @brief 向控制台框架注册 "scan" 命令
 */
static 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_init()
 *   3. 创建默认事件循环     —— 供后续事件注册使用
 *   4. 初始化 WiFi 子系统   —— 使用默认配置 WIFI_INIT_CONFIG_DEFAULT()
 *   5. 注册 WiFi 事件回调   —— 监听 WIFI_EVENT 下全部事件
 *   6. 设置为 STA 模式并启动 WiFi
 *   7. 发起主动全信道扫描
 *   8. 注册 "scan" 控制台命令
 *
 * @note 本函数依赖 NVS、esp_netif、WiFi 驱动的正确初始化,
 *       任一子步骤失败将通过 ESP_ERROR_CHECK 直接 abort。
 */
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 事件回调(监听 WIFI_EVENT 下全部事件) ---- */
    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 网络...");

    /* ---- 8. 注册 scan 控制台命令 ---- */
    register_scan_cmd();
}

wifi_event.c

/* ==========================================================================
 * WiFi 事件处理模块 — 实现文件
 *
 * 功能:
 *   1. 统一处理 WiFi 及 IP 事件(扫描完成、连接/断开、获取 IP 等)
 *   2. 管理扫描结果(AP 记录缓冲区)
 *   3. 断开连接时按条件自动重连
 *
 * 依赖:
 *   - ESP-IDF: esp_wifi, esp_event, esp_log, esp_netif
 *   - FreeRTOS: vTaskDelay(重连延时)
 * ========================================================================== */

/* ---------- 头文件 ---------- */
#include <stdio.h>                 /* printf */
#include <string.h>                /* strcmp, strncpy */
#include <inttypes.h>              /* PRIu16 */
#include "wifi_event.h"            /* 本模块接口声明及 wifi_event */

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

/** 模块日志标签 */
static const char *TAG = "wifi_event";

/* ---------- 全局实例定义 ---------- */

/** WiFi 事件模块全局状态(其他模块通过 wifi_event 直接访问) */
struct wifi_event_flags wifi_event = {
    .max_retry     = 20,
    .retry_delay_ms = 500,
    .retry_enabled = true,
};

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

/**
 * @brief 将密码类型枚举转为可读字符串
 *
 * 覆盖 ESP-IDF 定义的全部密码类型(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 "未知(?)";
    }
}

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

/**
 * @brief 将认证模式枚举转为简短可读字符串
 *
 * 供 scan 打印列表和 station 显示连接信息共同使用。
 */
const char *authmode_to_short_str(wifi_auth_mode_t mode)
{
    switch (mode) {
    case WIFI_AUTH_OPEN:            return "Open";
    case WIFI_AUTH_WEP:             return "WEP";
    case WIFI_AUTH_WPA_PSK:         return "WPA";
    case WIFI_AUTH_WPA2_PSK:        return "WPA2";
    case WIFI_AUTH_WPA_WPA2_PSK:    return "WPA/WPA2";
    case WIFI_AUTH_WPA3_PSK:        return "WPA3";
    case WIFI_AUTH_WPA2_WPA3_PSK:   return "WPA2/WPA3";
    case WIFI_AUTH_WAPI_PSK:        return "WAPI";
    default:                        return "Other";
    }
}

/* ======================================================================
 * 事件处理回调
 * ====================================================================== */

/**
 * @brief WiFi 及 IP 事件处理回调
 *
 * 处理的事件:
 *   - WIFI_EVENT_SCAN_DONE       : 扫描完成,打印 AP 列表
 *   - WIFI_EVENT_STA_START       : STA 启动
 *   - WIFI_EVENT_STA_CONNECTED   : 成功关联到 AP
 *   - WIFI_EVENT_STA_DISCONNECTED: 与 AP 断开,按条件自动重连
 *   - IP_EVENT_STA_GOT_IP        : 获取到 IP 地址,连接完成
 *
 * @note 扫描结果存储在 wifi_event.ap_records 中。
 */
void wifi_event_handler(void *event_handler_arg,
                        esp_event_base_t event_base,
                        int32_t event_id,
                        void *event_data)
{
    /* ==================== WiFi 事件 ==================== */
    if (event_base == WIFI_EVENT) {

        switch (event_id) {

        /* ---- 扫描完成 ---- */
        case WIFI_EVENT_SCAN_DONE: {
            uint16_t ap_num = 0;
            ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_num));
            ESP_LOGI(TAG, "扫描完成,共发现 %" PRIu16 " 个 WiFi 网络", ap_num);

            /* 边界保护,防止超出缓冲区 */
            if (ap_num > WIFI_MAX_AP_NUM) {
                ap_num = WIFI_MAX_AP_NUM;
            }

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

            /* 打印全部 AP 详情 */
            printf("\r\n");
            printf("========== WiFi 扫描结果(共 %d 个) ==========\r\n",
                   wifi_event.ap_count);

            for (int i = 0; i < wifi_event.ap_count; i++) {
                const wifi_ap_record_t *ap = &wifi_event.ap_records[i];

                /* 频段字符串(ESP32-C6 仅 2.4 GHz) */
                const char *band_str = "2.4 GHz";
                if (ap->phy_11ax) {
                    band_str = "2.4 GHz (Wi-Fi 6)";
                } else if (ap->phy_11n) {
                    band_str = "2.4 GHz (802.11n)";
                } else if (ap->phy_11g) {
                    band_str = "2.4 GHz (802.11g)";
                } else if (ap->phy_11b) {
                    band_str = "2.4 GHz (802.11b)";
                }

                /* 带宽字符串 */
                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: break;
                }

                /* 中心频率(2.4 GHz: 信道 1~14) */
                uint16_t center_freq = 0;
                if (ap->primary >= 1 && ap->primary <= 14) {
                    center_freq = 2407 + ap->primary * 5;
                }

                /* 第一行:编号 MAC RSSI 信道 SSID */
                printf("\r\n");
                printf("[%2d] MAC:%02x:%02x:%02x:%02x:%02x:%02x"
                       " \tRSSI:%ddBm \tCh:%d \tSSID:%s\r\n",
                       i + 1,
                       ap->bssid[0], ap->bssid[1], ap->bssid[2],
                       ap->bssid[3], ap->bssid[4], ap->bssid[5],
                       ap->rssi, ap->primary, (char *)ap->ssid);

                /* 第二行:安全 配对密码 频段 带宽 中心频率 WPS FTM */
                printf("     Authmode:%s \tCipher:%s \tBand:%s  "
                       "\tBW:%s \tFreq:%dMHz \tWPS:%s \tFTM:%s\r\n",
                       authmode_to_short_str(ap->authmode),
                       cipher_type_to_str(ap->pairwise_cipher),
                       band_str, bw_str, center_freq,
                       ap->wps           ? "支持" : "不支持",
                       ap->ftm_responder ? "支持" : "不支持");
            }

            printf("================================================\r\n");
            printf("提示: connect <编号> <密码> 连接网络 "
                   "(开放网络: connect <编号> \"\")\r\n\r\n");
            break;
        }

        /* ---- STA 启动 ---- */
        case WIFI_EVENT_STA_START:
            ESP_LOGI(TAG, "WiFi STA 模式已启动");
            break;

        /* ---- 成功关联到 AP ---- */
        case WIFI_EVENT_STA_CONNECTED: {
            wifi_event_sta_connected_t *event =
                (wifi_event_sta_connected_t *)event_data;
            wifi_event.is_connected = true;
            wifi_event.retry_count = 0;
            wifi_event.retry_enabled = true;
            ESP_LOGI(TAG, "已连接到 AP: %s (信道 %d)",
                     event->ssid, event->channel);
            break;
        }

       /* ---- 与 AP 断开连接 ---- */
        case WIFI_EVENT_STA_DISCONNECTED: {
            wifi_event_sta_disconnected_t *event =(wifi_event_sta_disconnected_t *)event_data;
            wifi_event.is_connected = false;

            /* 认证/密码错误 — 不重试 */
            if (event->reason == WIFI_REASON_AUTH_FAIL ||
                event->reason == WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT ||
                event->reason == WIFI_REASON_AUTH_EXPIRE) {
                wifi_event.retry_enabled = false;
                printf("\r\n错误: 密码错误或认证失败,请检查后重试\r\n");
                break;
            }
            else if (wifi_event.retry_enabled == false) {
                break;
            }

            /* 意外断开 — 尝试自动重连 */
            if (wifi_event.retry_count < wifi_event.max_retry) {
                wifi_event.retry_count++;
                ESP_LOGW(TAG, "意外断开 (原因码: %d),%d 毫秒后第 %d/%d 次重连...",
                         event->reason,
                         wifi_event.retry_delay_ms,
                         wifi_event.retry_count,
                         wifi_event.max_retry);
                vTaskDelay(pdMS_TO_TICKS(wifi_event.retry_delay_ms));
                esp_wifi_connect();
            } else {
                wifi_event.retry_enabled = false;
                ESP_LOGE(TAG, "已达最大重连次数 (%d),停止重连",
                         wifi_event.max_retry);
                printf("\r\n错误: 连接失败,已达最大重试次数 (%d),"
                       "请检查网络后重试\r\n",
                       wifi_event.max_retry);
            }
            break;
        }

        default:
            break;
        }
    }
    /* ==================== IP 事件 ==================== */
    else if (event_base == IP_EVENT) {

        if (event_id == IP_EVENT_STA_GOT_IP) {
            ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;

            ESP_LOGI(TAG, "========================================");
            ESP_LOGI(TAG, "WiFi 连接成功!已获取 IP 地址");
            ESP_LOGI(TAG, "  IP 地址 : " IPSTR, IP2STR(&event->ip_info.ip));
            ESP_LOGI(TAG, "  子网掩码: " IPSTR, IP2STR(&event->ip_info.netmask));
            ESP_LOGI(TAG, "  网关    : " IPSTR, IP2STR(&event->ip_info.gw));
            ESP_LOGI(TAG, "========================================");
        }
    }
}

scan.h

#ifndef SCAN_H
#define SCAN_H

#include <stdint.h>   
#include "esp_wifi.h" 

/**
 * @brief 初始化 WiFi 子系统并触发首次全信道扫描
 *
 * 依次完成 NVS、TCP/IP 协议栈、事件循环、WiFi 驱动的初始化,
 * 将 WiFi 设为 STA 模式并发起主动扫描。同时注册 "scan" 控制台命令。
 *
 * @note 必须在 wifi_station_init() 之前调用。
 * @note 任一步骤失败将通过 ESP_ERROR_CHECK 直接 abort。
 * @note 本函数不创建默认 STA 网络接口,如有 DHCP 需求需额外调用
 *       esp_netif_create_default_wifi_sta()。
 */
void wifi_scan_init(void);

#endif /* SCAN_H */

wifi_event.h

#ifndef WIFI_EVENT_H
#define WIFI_EVENT_H

/* ---------- C 标准库 ---------- */
#include <stdbool.h>               /* bool */
#include <stdint.h>                /* uint16_t */
#include <string.h>                /* 字符串处理 */

/* ---------- ESP-IDF ---------- */
#include "esp_console.h"           /* 控制台框架 */
#include "esp_event.h"             /* 事件循环 */
#include "esp_log.h"               /* 日志系统 */
#include "esp_netif.h"             /* 网络接口(IP 地址获取) */
#include "esp_partition.h"         /* 分区表查询 */
#include "esp_wifi.h"              /* WiFi API */
#include "linenoise/linenoise.h"   /* 行编辑库 */
#include "nvs_flash.h"             /* 非易失性存储(NVS) */

/* ---------- FreeRTOS ---------- */
#include "FreeRTOS/FreeRTOS.h"     /* 内核 */
#include "FreeRTOS/task.h"         /* 任务管理 */
#include "FreeRTOS/semphr.h"       /* 信号量管理 */

/* ======================================================================
 * 宏定义
 * ====================================================================== */

/** 单次扫描最多缓存的 AP 数量(编译时常量,用于数组定长) */
#define WIFI_MAX_AP_NUM  100

/* ======================================================================
 * WiFi 事件状态结构体
 * ====================================================================== */

/**
 * @brief WiFi 事件模块全部运行时状态
 *
 * 其他模块通过全局实例 wifi_event 直接访问成员,不使用 accessor 函数。
 */
struct wifi_event_flags {
    /* ---- 扫描结果 ---- */
    uint16_t          ap_count;                       /**< 实际缓存的 AP 数量 */
    wifi_ap_record_t  ap_records[WIFI_MAX_AP_NUM];    /**< AP 记录缓冲区 */

    /* ---- 连接状态 ---- */
    bool     is_connected;                            /**< 当前是否已连接到 AP */
    bool     retry_enabled;                           /**< 重连机制开关 */
    int      retry_count;                             /**< 当前重连尝试计数 */
    int      max_retry;                               /**< 最大重连次数(默认 3) */
    int      retry_delay_ms;                          /**< 重连间隔毫秒(默认 3000) */
    char     last_ssid[33];                           /**< 最近连接的 SSID */
    char     last_password[65];                       /**< 最近连接的密码 */
};

/** WiFi 事件模块全局实例(定义在 wifi_event.c) */
extern struct wifi_event_flags wifi_event;

/* ======================================================================
 * 函数声明
 * ====================================================================== */

/**
 * @brief WiFi 及 IP 事件处理回调
 *
 * 统一处理 WiFi 事件(扫描完成、STA 启动/连接/断开)和 IP 事件(获取 IP 地址)。
 * 内部通过 esp_event_handler_instance_register() 注册,不由用户直接调用。
 */
void wifi_event_handler(void *event_handler_arg,
                        esp_event_base_t event_base,
                        int32_t event_id,
                        void *event_data);

#endif /* WIFI_EVENT_H */

2.2 代码逻辑讲解

首先我们需要初始化NVS,因为ESP-IDF 的 WiFi 子系统在底层会把 校准数据(PHY/RF calibration data) 持久化到 NVS 分区中。这些数据包括:

  • 射频校准参数 — 芯片在不同温度、电压下的 TX 功率、IQ 偏移等校准值
  • WiFi 配置信息 — 通过 esp_wifi_set_config() 保存的 SSID、密码等(虽然本项目只做扫描,不涉及连接)
  • 信道校准缓存 — 各信道的扫描校准结果,避免每次开机都重新校准

跳过 nvs_flash_init() 直接调用 esp_wifi_init(),会导致esp_wifi_init() 返回错误,因为底层尝试读写 NVS 时会失败。

擦除整个 NVS 分区再重新初始化是没关系的,WiFi 校准数据会在下次启动时自动重新生成并写入。

接着依次执行esp_netif_init、esp_event_loop_create_default、esp_wifi_init。顺序不要乱,到这一步,wifi初始化完成了,接下来需要注册 WiFi 事件回调,用来处理各种WiFi事件。

这里使用默认的注册回调函数esp_event_handler_instance_register,WIFI_EVENT是官方WIFI事件声明,直接调用。ID就全选ESP_EVENT_ANY_ID。

接着依次使用esp_wifi_set_mode、esp_wifi_start和esp_wifi_scan_start开启wifi扫描。

对WIFI_EVENT这是总共进行了以下四个判断

  • WIFI_EVENT_SCAN_DONE        : 扫描完成,打印 AP 列表
  •  WIFI_EVENT_STA_START        : STA 模式已启动
  •  WIFI_EVENT_STA_CONNECTED    : 成功关联到 AP
  •  WIFI_EVENT_STA_DISCONNECTED : 与 AP 断开,按条件自动重连

其中只有WIFI_EVENT_SCAN_DONE是本次工程需要的,其他判断可以忽略,是为了后续实验准备的。

一旦触发了WIFI_EVENT_SCAN_DONE,便会将wifi信号的多种信息打印出来和记录在结构体wifi_event_flags中。

三、输出展示

配对密码(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 信息解析与日志输出等关键细节,为后续拓展更多联网功能提供了可复用的基础框架和实践参考。