4. WiFi SoftAP 模式:初始化 SoftAP、配置热点参数、管理客户端连接、IP 地址分配

好,咱们进入第四章。这一章聊的是 ESP32 的 SoftAP 模式——说白了,就是让你的 ESP32 变成一个 WiFi 热点。手机、电脑都能连上来,就像连你家路由器一样。

我刚开始做物联网项目时,总觉得 SoftAP 就是个“临时方案”。后来发现,很多场景还真离不开它。比如设备配网、本地数据展示、甚至小范围的局域网通信。你想想看,没有路由器的地方,设备之间怎么通信?SoftAP 就是答案。

4.1 SoftAP 模式概述

SoftAP,全称 Software Access Point。ESP32 通过软件模拟出一个 WiFi 热点。它不需要额外的硬件,靠的就是 WiFi 模块的 AP 模式。

和路由器比,ESP32 的 SoftAP 能力有限。但够用。我做过一个项目,在农田里用 ESP32 做数据采集,周围没网没路由器。设备启动后自动开启 SoftAP,手机连上去就能看传感器数据。嗯,这种场景下,SoftAP 就是救命稻草。

特性 ESP32 SoftAP 普通路由器
最大客户端数 通常 4~10 个 几十到上百个
发射功率 约 20 dBm 可调,通常更高
IP 分配 内置 DHCP 服务器 独立 DHCP 服务
并发模式 支持 STA + AP 共存 纯 AP 模式
我的经验:ESP32 的 SoftAP 在连接 3~4 个客户端时最稳定。超过 6 个,丢包率会明显上升。如果你需要更多客户端,考虑用外部 WiFi 芯片或者路由器。

4.2 初始化 SoftAP

初始化 SoftAP 的流程,其实和 STA 模式很像。只是配置的参数不同。我习惯把初始化拆成三步:

  1. 初始化网络接口——调用 esp_netif_init() 和 esp_event_loop_create_default()
  2. 创建 AP 网络接口——esp_netif_create_default_wifi_ap()
  3. 配置并启动 WiFi——设置 SSID、密码、信道等参数

来看代码。这是最基础的 SoftAP 初始化:

#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"

void app_main(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) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // 2. 初始化网络接口
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_ap();

    // 3. 初始化 WiFi
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // 4. 配置 AP 参数
    wifi_config_t wifi_config = {
        .ap = {
            .ssid = "ESP32_AP",
            .ssid_len = strlen("ESP32_AP"),
            .password = "12345678",
            .max_connection = 4,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    printf("SoftAP 已启动,SSID: ESP32_AP\n");
}
注意:密码长度至少 8 位。如果设置空密码,authmode 必须设为 WIFI_AUTH_OPEN。我曾经在项目里忘了改这个,结果设备一直连不上,排查了半天才发现是认证模式不匹配。

4.3 配置热点参数

热点参数不只是 SSID 和密码。还有几个关键字段,我列个表给你看:

参数 说明 取值范围
ssid 热点名称 最长 32 字节
password 连接密码 8~64 字节,或空
ssid_len SSID 长度,0 表示自动计算 0 或实际长度
channel WiFi 信道 1~13
authmode 认证模式 WIFI_AUTH_OPEN / WPA / WPA2 / WPA3
max_connection 最大客户端数 1~10(推荐 ≤4)
beacon_interval 信标帧间隔(毫秒) 100~1000,默认 100

信道选择有讲究。2.4G 频段有 13 个信道,但 1、6、11 互不干扰。我一般选 1 或 6,因为大多数家用路由器默认用这些信道,兼容性好。

看一个更完整的配置示例:

wifi_config_t wifi_config = {
    .ap = {
        .ssid = "MyESP32",
        .ssid_len = 0,          // 自动计算
        .password = "securePass123",
        .channel = 6,           // 手动指定信道
        .authmode = WIFI_AUTH_WPA2_PSK,
        .max_connection = 4,
        .beacon_interval = 100, // 默认值
    },
};
避坑指南:如果你同时开启 STA 和 AP 模式(WiFi_MODE_APSTA),两个接口的信道必须一致。我曾经在调试时没注意这个,STA 连上了路由器,但 AP 端的客户端死活连不上。后来发现是信道冲突了。

4.4 管理客户端连接

客户端连上来之后,你怎么知道?怎么管理?ESP32 提供了事件回调机制。

注册两个事件:

  • WIFI_EVENT_AP_STACONNECTED——有设备连入
  • WIFI_EVENT_AP_STADISCONNECTED——有设备断开

事件回调里能拿到客户端的 MAC 地址。我习惯在连接时打印 MAC,断开时也打印,方便调试。

static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_id == WIFI_EVENT_AP_STACONNECTED) {
        wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
        printf("设备连入,MAC: " MACSTR "\n", MAC2STR(event->mac));
    } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
        printf("设备断开,MAC: " MACSTR "\n", MAC2STR(event->mac));
    }
}

// 注册事件
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));

你还可以主动获取已连接的客户端列表:

wifi_sta_list_t sta_list;
esp_wifi_ap_get_sta_list(&sta_list);
printf("当前连接数: %d\n", sta_list.num);

for (int i = 0; i < sta_list.num; i++) {
    printf("客户端 %d MAC: " MACSTR "\n", i, MAC2STR(sta_list.sta[i].mac));
}
实际项目经验:我在做一个智能家居网关时,需要限制只有白名单设备能连入。做法就是在 WIFI_EVENT_AP_STACONNECTED 事件里检查 MAC 地址,不在白名单的就调用 esp_wifi_deauth_sta() 踢掉。嗯,简单粗暴,但有效。

4.5 IP 地址分配

客户端连上 SoftAP 后,需要获取 IP 地址才能通信。ESP32 内置了 DHCP 服务器,默认自动分配 IP。

默认的 IP 池是 192.168.4.2 到 192.168.4.254,网关是 192.168.4.1。这个地址段可以改。

我经常需要自定义 IP 段,比如和现有网络不冲突。看代码:

// 获取 AP 的 netif 句柄
esp_netif_t *ap_netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");

// 配置静态 IP
esp_netif_ip_info_t ip_info;
memset(&ip_info, 0, sizeof(ip_info));
esp_netif_str_to_ip4("192.168.5.1", &ip_info.ip);
esp_netif_str_to_ip4("192.168.5.1", &ip_info.gw);
esp_netif_str_to_ip4("255.255.255.0", &ip_info.netmask);
esp_netif_set_ip_info(ap_netif, &ip_info);

// 配置 DHCP 地址池
esp_netif_dhcps_start(ap_netif);  // 先启动 DHCP
dhcps_lease_t lease;
lease.enable = true;
esp_netif_str_to_ip4("192.168.5.100", &lease.start_ip);
esp_netif_str_to_ip4("192.168.5.200", &lease.end_ip);
esp_netif_dhcps_option(ap_netif, ESP_NETIF_OP_SET, ESP_NETIF_DOMAIN_NAME_SERVER, &lease, sizeof(lease));

这里要注意顺序:先设置 IP 信息,再启动 DHCP,最后配置地址池。我刚开始搞反了顺序,结果 DHCP 分配的还是默认网段。

重要提醒:修改 IP 段后,记得检查子网掩码。如果掩码设成 255.255.0.0,地址池范围会很大,DHCP 分配可能变慢。保持 255.255.255.0 最稳妥。

4.6 知识结构总览

这一章的内容,我用一张图帮你理清脉络:

SoftAP 模式知识结构图 SoftAP 模式知识结构 ESP32 SoftAP 初始化流程 NVS → netif → WiFi init → start 热点参数配置 SSID / 密码 / 信道 认证模式 / 最大连接数 客户端管理 连接/断开事件回调 获取客户端列表 / 踢除 IP 地址分配 DHCP 服务器 / 自定义 IP 段 地址池范围 / 子网掩码

这张图把 SoftAP 的四个核心模块串起来了。初始化是基础,配置参数是定制,客户端管理是交互,IP 分配是通信的前提。四者缺一不可。

4.7 完整示例:一个可用的 SoftAP

最后,给你一个可以直接跑起来的完整示例。它包含了初始化、事件回调、IP 配置:

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"

static const char *TAG = "softap";

static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_id == WIFI_EVENT_AP_STACONNECTED) {
        wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
        ESP_LOGI(TAG, "设备连入: " MACSTR, MAC2STR(event->mac));
    } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
        ESP_LOGI(TAG, "设备断开: " MACSTR, MAC2STR(event->mac));
    }
}

void app_main(void)
{
    // 初始化 NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // 初始化网络接口
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();

    // 配置静态 IP
    esp_netif_ip_info_t ip_info;
    IP4_ADDR(&ip_info.ip, 192, 168, 5, 1);
    IP4_ADDR(&ip_info.gw, 192, 168, 5, 1);
    IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0);
    esp_netif_set_ip_info(ap_netif, &ip_info);

    // 初始化 WiFi
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // 注册事件
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));

    // 配置 AP
    wifi_config_t wifi_config = {
        .ap = {
            .ssid = "ESP32_Demo",
            .ssid_len = 0,
            .password = "demo1234",
            .channel = 6,
            .authmode = WIFI_AUTH_WPA2_PSK,
            .max_connection = 4,
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "SoftAP 已启动,SSID: ESP32_Demo,IP: 192.168.5.1");
}

这个示例里,我把 IP 改成了 192.168.5.1 网段,避免和常见路由器冲突。你烧录后,用手机搜一下 ESP32_Demo,密码 demo1234,连上去就能拿到 192.168.5.x 的 IP。

小技巧:调试时可以用 ping 192.168.5.1 测试连通性。如果 ping 不通,先检查手机是否拿到了正确的 IP 地址,再检查 ESP32 的 WiFi 状态。

好了,这一章就到这里。SoftAP 模式其实不复杂,但细节不少。初始化顺序、参数配置、事件处理、IP 分配,每一步都有坑。我踩过的坑都写在里面了,你照着做,应该能少走弯路。