WiFi 协议栈与配网
AC792N SDK 的 WiFi 子系统从应用层到硬件的完整技术参考:涵盖 STA / AP / P2P / Monitor / SmartConfig 多种工作模式、UART 命令行与 SmartConfig 等配网途径、SSID 记忆与自动重连、lwIP 协议栈集成、静态 IP 记忆以及底层 802.11 收包过滤适配,帮助开发者快速完成设备联网并理解各层之间的调用关系。
Purpose and Scope
本文档系统性地介绍 AC792N(WL83 系列)单芯片 WiFi 方案的协议栈架构与配网机制,覆盖以下内容:
- WiFi 工作模式(STA、AP、P2P GO/GC、Monitor、SmartConfig)的切换机制与 API 入口;
- 三种典型配网途径:UART 命令行配网、SmartConfig(smp_cfg)、AP 热点配网;
- SSID 记忆存储(VM)与信号最优自动重连逻辑;
- lwIP 网络协议栈集成:DHCP、静态 IP 记忆、AP 侧 DHCP Server;
- WiFi 扫描机制(同步信号量封装)与底层
wifi_adapter收包过滤/硬件寄存器适配。
以下主题属于兄弟页面,不在本文展开:WiFi 低功耗(sdk/apps/common/example/wifi/low_power)、Raw TX/RX 透传测试(raw_txrx / wifi_raw_test)、以及网络事件订阅的通用机制(event/net_event.h)。本文聚焦"如何让设备连上网、记住网、按需切换模式"这一能力边界。
Overview
AC792N 是杰理(Jieli)面向物联网场景的 WiFi SoC,SDK 采用单芯片双协议栈架构:上层应用通过 wifi/wifi_connect.h 提供的统一封装 API 控制 WiFi 协议栈,网络数据面接入 lwIP(lwip.h),实现 TCP/IP、DHCP、DNS 等能力;WiFi 协议栈内部按模式(STA/AP/P2P/Monitor/SmartConfig)驱动 MAC/PHY 硬件。
配网(Provisioning)是物联网设备接入路由器的关键环节。SDK 提供多条配网路径,其设计意图在于适应不同量产与调试场景:
- UART 命令行配网(
uart_cmd.c):调试与产测最直接的手段,串口输入sta <ssid> <pwd>即可入网,输入smp_cfg进入 SmartConfig; - SmartConfig 配网(
wifi_enter_smp_cfg_mode):手机 App 广播加密报文,设备监听并解析出 SSID/密码,适合无屏幕设备; - AP 配网:设备自建热点(默认 192.168.4.1,内嵌 DHCP Server),手机直连后通过本地页面/协议下发目标路由信息;
- SSID 记忆与自动重连:
wifi_store_mode_info/wifi_get_stored_sta_info/wifi_set_sta_connect_best_ssid组合实现"记住最优网络、开机自动回连"。
Architecture
flowchart TD
subgraph sg_App["应用层 (apps)"]
UartCmd["uart_cmd.c<br/>UART 命令行配网入口"]
WifiTask["wifi_demo_task.c<br/>模式切换 / SSID 记忆 / 静态IP"]
ScanList["examples/scan_list.c<br/>扫描同步封装"]
end
subgraph sg_Api["WiFi 统一 API 层"]
WifiConnect["wifi/wifi_connect.h<br/>enter_sta/ap/p2p/smp_cfg · scan · store"]
end
subgraph sg_Stack["WiFi 协议栈"]
StaMode["STA_MODE"]
ApMode["AP_MODE"]
P2pMode["P2P GO / GC"]
MonitorMode["MONITOR_MODE"]
SmpCfg["SMART_CONFIG"]
StackEvent["WIFI_EVENT_* / net_event"]
end
subgraph sg_Net["网络协议栈 lwIP"]
Netif["WIFI_NETIF netif"]
DhcpSrv["dhcp_srv (AP侧DHCP Server)"]
DhcpClient["DHCP Client (STA侧)"]
end
subgraph sg_Storage["持久化 syscfg/VM"]
VmSsid["SSID 记忆存储"]
VmIp["VM_STA_IPADDR_INDEX<br/>静态 IP 记忆"]
end
subgraph sg_Hw["硬件适配"]
Adapter["wifi_adapter.h<br/>RX 过滤 DROP_* / 寄存器"]
MacPhy["MAC / PHY 硬件"]
end
UartCmd --> WifiConnect
WifiTask --> WifiConnect
ScanList --> WifiConnect
WifiConnect --> StaMode
WifiConnect --> ApMode
WifiConnect --> P2pMode
WifiConnect --> MonitorMode
WifiConnect --> SmpCfg
StaMode --> Netif
ApMode --> DhcpSrv
StaMode --> DhcpClient
StaMode --> VmSsid
WifiTask --> VmIp
WifiTask --> VmSsid
StackEvent --> WifiTask
Netif --> Adapter
Adapter --> MacPhy
架构图说明:应用层通过 wifi_connect.h 统一 API 下发模式切换与扫描命令,协议栈内部按模式驱动硬件;STA 模式数据面接入 lwIP WIFI_NETIF,通过 DHCP Client 获取地址;AP 模式启用 dhcp_srv 为手机分配 192.168.4.x 地址;SSID 与 IP 信息通过 syscfg VM 持久化,保证掉电重连;Monitor/Raw 模式下应用可经 wifi_adapter.h 直接配置 RX 过滤规则与硬件 MAC/BSSID 寄存器。
核心 API 与数据流总览
wifi_connect.h 是应用访问协议栈的唯一窗口,其 API 按职责分为五组:
| 职责组 | 代表 API | 说明 |
|---|---|---|
| 模式切换 | wifi_enter_sta_mode / wifi_enter_ap_mode / wifi_enter_p2p_mode / wifi_enter_smp_cfg_mode | 进入对应工作模式 |
| 扫描 | wifi_scan_req / wifi_get_scan_result / wifi_clear_scan_result | 发起扫描、取结果、清缓存 |
| SSID 记忆 | wifi_store_mode_info / wifi_get_stored_sta_info / wifi_del_stored_sta_info / wifi_set_sta_connect_best_ssid | 存取/删除已保存网络 |
| 状态查询 | wifi_is_on / wifi_get_mode_cur_info / wifi_get_mode_stored_info / wifi_get_mac / wifi_get_bssid / wifi_get_channel | 当前模式、地址、信道 |
| 认证解析 | get_wifi_auth_mode | 将认证模式枚举转为可读字符串 |
典型配网数据流:uart_cmd 收到 sta <ssid> <pwd> → wifi_sta_connect(ssid, pwd, save) → wifi_enter_sta_mode → 协议栈扫描目标 BSSID 并完成 802.11 关联/握手 → lwIP WIFI_NETIF 启动 DHCP → 事件回调通知应用 → 应用按需调用 wifi_store_mode_info 保存网络 → 下次开机 wifi_return_sta_mode 自动回连。
说明:以上 API 签名均来自实际调用点(
wifi_demo_task.c、uart_cmd.c、scan_list.c),具体参数与返回语义以 SDK 中wifi/wifi_connect.h头文件为准。
配网方式详解
1. UART 命令行配网
uart_cmd.c 是 demo_wifi 工程的串口配网入口。它注册一个 uart_test_main 任务,打开 uart1(注释标明 Rx 为 PA1),配置循环接收缓冲并阻塞读取,每收到一行命令就交给 cmd_do 解析执行:
static u8 buf[1 * 1024] __attribute__((aligned(32))); //用于串口接收缓存数据的循环buf
static void uart_test_main(void *priv)
{
char recv_buf[256];
int len;
void *hdl = dev_open("uart1", NULL);
...
/* 1 . 设置串口接收缓存数据的循环buf地址 */
dev_ioctl(hdl, IOCTL_UART_SET_CIRCULAR_BUFF_ADDR, (int)buf);
/* 2 . 设置串口接收缓存数据的循环buf长度 */
dev_ioctl(hdl, IOCTL_UART_SET_CIRCULAR_BUFF_LENTH, sizeof(buf));
/* 3 . 设置接收数据为阻塞方式,需要非阻塞可以去掉,建议加上超时设置 */
dev_ioctl(hdl, IOCTL_UART_SET_RECV_BLOCK, 1);
...
while (1) {
len = dev_read(hdl, recv_buf, sizeof(recv_buf));
if (len <= 0) {
if (len == UART_CIRCULAR_BUFFER_WRITE_OVERLAY) {
dev_ioctl(hdl, IOCTL_UART_FLUSH, 0); //回卷覆盖则冲掉循环buf重新接收
} else if (len == UART_RECV_TIMEOUT) {
puts("UART_RECV_TIMEOUT...\r\n");
}
continue;
}
recv_buf[len] = '\0';
cmd_do(recv_buf);
}
}
Source: uart_cmd.c
设计意图:阻塞式 dev_read 配合 1KB 循环缓冲,既能保证命令按行完整接收,又通过 UART_CIRCULAR_BUFFER_WRITE_OVERLAY 错误码处理"用户长期不取数据导致缓冲回卷"的边界情况——此时直接 IOCTL_UART_FLUSH 丢弃积压数据,避免解析错乱。
cmd_do 是配网命令的分发核心,支持的命令覆盖了所有入网方式:
void cmd_do(char *buf)
{
char *cmd = buf;
...
if (0 == memcmp(buf, "sta", strlen("sta"))) { //串口输入 sta <ssid> <pwd> 进入STA模式。uart1 Rx为PA1
if (sscanf(buf, "sta %s %s", ssid, pwd) == 2) {
printf("Enter sta mode, ssid : %s, pwd: %s\n", ssid, pwd);
wifi_sta_connect(ssid, pwd, 1);
} else if (sscanf(buf, "sta %s", ssid) == 1) {
wifi_sta_connect(ssid, "", 1);
}
} else if (0 == memcmp(buf, "ap", strlen("ap"))) { //ap <ssid> <pwd> 进入AP模式
if (sscanf(buf, "ap %s %s", ssid, pwd) == 2) {
wifi_enter_ap_mode(ssid, pwd);
} else if (sscanf(buf, "ap %s", ssid) == 1) {
wifi_enter_ap_mode(ssid, "");
}
} else if (0 == memcmp(buf, "go", strlen("go"))) { //go <name> 进入GO模式
...
wifi_enter_p2p_mode(P2P_GO_MODE, P2P_Device_Name);
} else if (0 == memcmp(buf, "gc", strlen("gc"))) { //gc <name> 进入GC模式
...
wifi_enter_p2p_mode(P2P_GC_MODE, P2P_Device_Name);
} else if (0 == memcmp(buf, "scan", strlen("scan"))) { //scan
void wifi_get_list_example(void);
wifi_get_list_example();
} else if (0 == memcmp(buf, "get_mac", strlen("get_mac"))) { //get_mac 获取mac地址
wifi_get_mac(mac_addr);
printf("Mac addr: %02X:%02X:%02X:%02X:%02X:%02X", ...);
} else if (0 == memcmp(buf, "smp_cfg", strlen("smp_cfg"))) { //smp_cfg 进入smp_cfg模式
wifi_enter_smp_cfg_mode();
} else if (0 == memcmp(buf, "ssid_store_test", strlen("ssid_store_test"))) {
... // 见"SSID 记忆与自动重连"
}
}
Source: uart_cmd.c
值得注意的细节:sta 命令带 save=1 参数调用 wifi_sta_connect,说明通过串口配网成功后会自动保存 SSID;而 ap 命令直接调用 wifi_enter_ap_mode,AP 模式下设备不保存任何网络信息,这与 AP 配网的"临时热点"定位一致。smp_cfg 则一键进入 SmartConfig 监听状态。
2. SmartConfig 配网(smp_cfg)
SmartConfig 是面向无屏幕设备的经典配网方案:手机 App 将 SSID/密码编码进 UDP 广播报文的长度或序列中,设备处于混杂监听状态(SmartConfig 模式)捕获报文并解码。SDK 通过 wifi_enter_smp_cfg_mode() 一个 API 进入该模式(见上节 smp_cfg 命令),协议栈内部完成监听与解析,解析成功后通常以事件形式通知应用层,应用层再调用 wifi_sta_connect 完成入网。该模式适合量产阶段手机 App 一键配网,但要求手机与设备处于同一信道,且对信道环境有一定依赖。
3. AP 配网(热点配网)
设备先进入 AP 模式自建热点,手机连接该热点后即可访问设备(默认网关 192.168.4.1)。wifi_demo_task.c 中的 wifi_set_lan_setting_info 配置了 AP 侧完整的局域网参数:
static void wifi_set_lan_setting_info(void)
{
struct lan_setting lan_setting_info = {
.WIRELESS_IP_ADDR0 = 192, .WIRELESS_IP_ADDR1 = 168,
.WIRELESS_IP_ADDR2 = 4, .WIRELESS_IP_ADDR3 = 1,
.WIRELESS_NETMASK0 = 255, .WIRELESS_NETMASK1 = 255,
.WIRELESS_NETMASK2 = 255, .WIRELESS_NETMASK3 = 0,
.WIRELESS_GATEWAY0 = 192, .WIRELESS_GATEWAY1 = 168,
.WIRELESS_GATEWAY2 = 4, .WIRELESS_GATEWAY3 = 1,
.SERVER_IPADDR1 = 192, .SERVER_IPADDR2 = 168,
.SERVER_IPADDR3 = 4, .SERVER_IPADDR4 = 1,
.CLIENT_IPADDR1 = 192, .CLIENT_IPADDR2 = 168,
.CLIENT_IPADDR3 = 4, .CLIENT_IPADDR4 = 2,
.SUB_NET_MASK1 = 255, .SUB_NET_MASK2 = 255,
.SUB_NET_MASK3 = 255, .SUB_NET_MASK4 = 0,
};
net_set_lan_info(&lan_setting_info);
}
Source: wifi_demo_task.c
该配置将 AP 网关固定为 192.168.4.1、网段 /24,DHCP Server 从 192.168.4.2 起分配地址——这正是"设备作为热点被手机访问"的标准拓扑。AP 配网的完整链路是:ap <ssid> <pwd> → wifi_enter_ap_mode → lwIP 配置 lan_setting → dhcp_srv 为连入的手机分配 IP。手机端通常再通过本地 HTTP/TCP 服务下发目标路由器信息,设备随后切回 STA 模式入网。
4. STA 连接与 SSID 记忆、自动重连
wifi_demo_task.c 封装了 STA 模式的入口函数 wifi_sta_connect,并设计了一套"请求-保存"标志机制:
static char save_ssid_flag, request_connect_flag;
void wifi_sta_connect(char *ssid, char *pwd, char save)
{
save_ssid_flag = save;
request_connect_flag = 1;
wifi_set_sta_connect_best_ssid(0);
wifi_enter_sta_mode(ssid, pwd);
}
Source: wifi_demo_task.c
wifi_set_sta_connect_best_ssid(0) 关闭"信号最优选择",强制连接指定 SSID;之后在连接成功事件中由 wifi_sta_save_ssid() 根据 save_ssid_flag 决定是否落盘保存:
static void wifi_sta_save_ssid(void)
{
if (save_ssid_flag) {
save_ssid_flag = 0;
struct wifi_mode_info info;
info.mode = STA_MODE;
wifi_get_mode_cur_info(&info);
wifi_store_mode_info(STA_MODE, info.ssid, info.pwd);
}
}
Source: wifi_demo_task.c
而开机自动回连由 wifi_return_sta_mode 实现:优先读取记忆的 SSID,无记忆时回退到编译期默认 STA_SSID/STA_PWD,同时打开"连接信号最优已存网络"开关:
void wifi_return_sta_mode(void)
{
if (!wifi_is_on()) {
return;
}
struct wifi_mode_info info;
info.mode = STA_MODE;
int ret = wifi_get_mode_stored_info(&info);
if (ret) {//如果没保存过SSID
info.ssid = STA_SSID;
info.pwd = STA_PWD;
}
wifi_clear_scan_result(); //根据扫描结果连接信号最优ssid之前先清除之前结果,防止之前最优信号的ssid已下线
wifi_set_sta_connect_best_ssid(1); //自动连接保存过的最佳WIFI
save_ssid_flag = 0;
wifi_enter_sta_mode(info.ssid, info.pwd);
}
Source: wifi_demo_task.c
设计意图:wifi_clear_scan_result 在"按扫描结果挑选最优 SSID"前先清空缓存,防止上次扫描中已下线的热点残留导致误连;FORCE_DEFAULT_MODE 宏(默认 0)决定 wifi_on 之后进入"最后记忆模式"还是强制默认模式,取值 3~200 时作为 STA 连接超时秒数,超时后回退连接记忆/最优网络——这是对"路由器暂时不可达"场景的容错设计。
SSID 记忆的增删查由 ssid_store_test 命令完整演示:
u8 ssid_stored_cnt;
struct wifi_stored_sta_info wifi_stored_sta_info[32];
os_time_dly(5 * 100); //假设等待已经连接上路由器
wifi_store_mode_info(STA_MODE, "GJ12", "123456789");
wifi_store_mode_info(STA_MODE, "WL83", "12345678");
wifi_store_mode_info(STA_MODE, "WIFI_PS_TEST", "12345678");
ssid_stored_cnt = wifi_get_stored_sta_info(wifi_stored_sta_info);
for (int i = 0; i < ssid_stored_cnt; i++) {
printf("1:wifi_get_stored_sta_info[%d]= %s \r\n", i, wifi_stored_sta_info[i].ssid);
}
wifi_del_stored_sta_info("WIFI_PS_TEST"); //中途删除掉其中一个存储过的SSID
Source: uart_cmd.c
该示例表明:协议栈支持多网络记忆(数组上限 32 条,ssid[33] / pwd[65] 长度约束),并且删除操作按 SSID 精确匹配。多记忆 + 最优信号选择(wifi_set_sta_connect_best_ssid(1))构成了"设备在多环境间移动自动切换最优网络"的能力。
WiFi 扫描机制
扫描是配网的前置步骤(发现可用热点)与自动重连的关键(评估信号强度)。SDK 将异步扫描封装为同步阻塞接口,供应用以简单方式获取结果。scan_list.c 使用 OS 信号量 + 互斥锁实现"请求-等待-取结果"三步:
struct wifi_scan_ssid_info *wifi_scan_list_get(u32 *total_scan_num)
{
u32 sta_ssid_num;
struct wifi_scan_ssid_info *sta_ssid_info;
if (!os_sem_valid(&pscan_list->scan_sem)) {
os_sem_create(&pscan_list->scan_sem, 0);
}
if (!os_mutex_valid(&pscan_list->scan_mutex)) {
os_mutex_create(&pscan_list->scan_mutex);
}
scan_list_mutex_pend();
wifi_scan_req();
//等待WIFI_EVENT_STA_SCAN_COMPLETED事件
if (scan_list_sem_pend(500)) { //单位10ms
printf("get_scan_list timeout");
pscan_list->sem_flag = 0;
scan_list_mutex_post();
return NULL;
}
sta_ssid_num = 0;
sta_ssid_info = wifi_get_scan_result(&sta_ssid_num);
if (total_scan_num) {
*total_scan_num = sta_ssid_num;
}
wifi_clear_scan_result();
scan_list_mutex_post();
return sta_ssid_info;
}
void wifi_scan_list_release(struct wifi_scan_ssid_info *list)
{
if (list) {
free(list);
}
}
Source: scan_list.c
关键机制拆解:
- 信号量等待:
wifi_scan_req()发起扫描后,协议栈完成扫描会触发WIFI_EVENT_STA_SCAN_COMPLETED事件,事件处理回调中调用scan_list_sem_post()释放信号量(sem_flag作为"是否真的在等待"的守卫,防止事件迟到时误触发); - 超时保护:
scan_list_sem_pend(500)以 10ms 为单位,即最长等待 5 秒;超时返回NULL,调用方需自行处理失败; - 互斥串行:
scan_mutex保证同一时刻只有一个任务发起扫描,避免协议栈扫描状态机被并发请求破坏; - 内存所有权:
wifi_get_scan_result返回堆内存,使用完必须wifi_scan_list_release释放,而wifi_clear_scan_result清理协议栈内部缓存。
扫描结果的消费示例(scan 命令入口):
void wifi_get_list_example(void)
{
u32 scan_num = 0;
struct wifi_scan_ssid_info *list = wifi_scan_list_get(&scan_num);
if (!list) {
printf("wifi_scan_list_get fail\n");
return;
}
printf("sta_ssid_num: %d\n", scan_num);
for (int i = 0; i < scan_num; i++) {
log_info("wifi_sta_scan_channel_test ssid = [%s],rssi = %d,snr = %d, auth_mode = %s",
list[i].ssid, list[i].rssi, list[i].snr, get_wifi_auth_mode(list[i].auth_mode));
}
wifi_scan_list_release(list);
}
Source: scan_list.c
扫描结果结构体 wifi_scan_ssid_info 至少包含 ssid、rssi(信号强度)、snr(信噪比)、auth_mode(认证方式)四个字段,get_wifi_auth_mode 将认证枚举转为可读字符串——RSSI/SNR 正是"信号最优 SSID 选择"(wifi_set_sta_connect_best_ssid)的排序依据。
lwIP 协议栈集成与 IP 管理
网络接口与 DHCP
STA 模式入网后,协议栈将数据面挂接到 lwIP 的 WIFI_NETIF 网络接口;应用通过 lwip_get_netif_info(WIFI_NETIF, ...) 读取当前 IP/掩码/网关,通过 net_set_lan_info 写入局域网配置。AP 模式由内置 dhcp_srv 承担地址分配,STA 模式由 lwIP DHCP Client 向路由器获取地址。
静态 IP 记忆(WiFi 重连加速)
wifi_demo_task.c 在 CONFIG_STATIC_IPADDR_ENABLE 下实现了"DHCP 地址记忆复用":连接成功后把 IP/掩码/网关/DNS、关联的 SSID、本机 MAC、网关 MAC、信道一起写入 VM_STA_IPADDR_INDEX;下次连接时若 SSID、本机 MAC、网关 MAC 全部匹配,则跳过 DHCP 直接使用记忆地址,从而显著缩短重连上电时间:
static int compare_dhcp_ipaddr(void)
{
use_static_ipaddr_flag = 0;
u8 local_mac[6];
u8 gw_mac[6];
struct sta_ip_info sta_ip_info;
struct netif_info netif_info;
int ret = syscfg_read(VM_STA_IPADDR_INDEX, (char *)&sta_ip_info, sizeof(struct sta_ip_info));
if (ret < 0) {
log_info("compare_dhcp_ipaddr NO VM_STA_IPADDR_INDEX");
return -1;
}
lwip_get_netif_info(WIFI_NETIF, &netif_info);
struct wifi_mode_info info;
info.mode = STA_MODE;
wifi_get_mode_cur_info(&info);
wifi_get_bssid(gw_mac);
wifi_get_mac(local_mac);
if (!strcmp(info.ssid, (const char *)sta_ip_info.ssid)
&& !memcmp(local_mac, sta_ip_info.local_mac, 6)
&& !memcmp(gw_mac, sta_ip_info.gw_mac, 6)) {
use_static_ipaddr_flag = 1;
log_info("compare_dhcp_ipaddr Match");
return 0;
}
return -1;
}
Source: wifi_demo_task.c
static void store_dhcp_ipaddr(void)
{
struct sta_ip_info sta_ip_info = {0};
u8 sta_channel, local_mac[6], gw_mac[6];
if (use_static_ipaddr_flag) { //记忆IP匹配成功,不需要重新保存
return;
}
struct netif_info netif_info;
lwip_get_netif_info(WIFI_NETIF, &netif_info);
...
strcpy((char *)sta_ip_info.ssid, info.ssid);
memcpy(sta_ip_info.gw_mac, gw_mac, 6);
memcpy(sta_ip_info.local_mac, local_mac, 6);
sta_ip_info.ip = netif_info.ip;
sta_ip_info.netmask = netif_info.netmask;
sta_ip_info.gw = netif_info.gw;
sta_ip_info.chanel = sta_channel;
sta_ip_info.dns = *(u32 *)dns_getserver(0);
syscfg_write(VM_STA_IPADDR_INDEX, (char *) &sta_ip_info, sizeof(struct sta_ip_info));
log_info("store_dhcp_ipaddr");
}
Source: wifi_demo_task.c
compare_dhcp_ipaddr 的匹配条件刻意包含了网关 MAC(BSSID)与本机 MAC:只有"同一路由器 + 同一台设备"才复用 IP,防止换了路由器后误用旧网段的地址;注释还指出若路由器未联网(DHCP 拿不到公网 DNS),会放弃复用重新走 DHCP。dns_set_server 从记忆块中恢复 DNS,保证静态 IP 场景下域名解析可用。
底层硬件适配层(wifi_adapter.h)
wifi_bbm 工程中的 wifi_adapter.h 暴露了协议栈与硬件之间的底层适配接口,主要面向 Monitor/Raw 模式的高级用户(如协议分析、私有协议透传):
#define DROP_CRC_ERR BIT(0) //drop CRC error frame
#define DROP_PHY_ERR BIT(1) //drop physical error frame
#define DROP_UC_NOME BIT(2) //drop not to me unicast frame
#define DROP_NOT_MYBSS BIT(3) //drop frame that is not my BSSID
#define DROP_VER_ERR BIT(4) //drop 802.11 version error frame
#define DROP_DROP_MC BIT(5) //drop multicast frame
#define DROP_BC BIT(6) //drop broadcast frame
#define DROP_DUPL BIT(7) //drop duplicated frame
#define DROP_CFACK BIT(8) //drop CF-END + CF-ACK
#define DROP_CFEND BIT(9) //drop CF-END
#define DROP_ACK BIT(10) //drop ACK
#define DROP_CTS BIT(11) //drop CTS
#define DROP_RTS BIT(12) //drop RTS
#define DROP_PSPOLL BIT(13) //drop PS-POLL
#define DROP_BA BIT(14) //drop BA
#define DROP_BAR BIT(15) //drop BAR
#define DROP_CTRL_RSV BIT(16) //drop reserve control subtype
//复位对应的接收过滤包类型
void wf_rx_filt_set(unsigned int reg);
//设置对应的接收过滤包类型
void wf_rx_filt_reset(unsigned int reg);
//设置硬件MAC_BSSID寄存器 ,需要在wifi_raw_on后调用
void wf_asic_set_bssid(unsigned char *bssid);
//设置本机硬件MAC, 需要在wifi_raw_on后调用
void wf_asic_set_mac(unsigned char *mac);
//使能channel power检测,需要在wifi_raw_on后调用
void wf_channel_power_detect_enable(void);
//获取当前channel power
char get_current_channel_power(void);
Source: wifi_adapter.h
该层按位定义 802.11 帧类型的硬件 RX 过滤开关:wf_rx_filt_set/reset 分别复位/置位对应过滤位,例如开启 DROP_BC 后广播帧直接由硬件丢弃、不进协议栈,降低 CPU 负载;wf_asic_set_bssid/mac 在 wifi_raw_on 之后配置硬件匹配寄存器,用于 Raw 模式下精确收包;wf_channel_power_detect_enable 与 get_current_channel_power 提供信道功率检测,服务于产测与信道选择。这些接口属于协议栈对硬件的"后门",普通配网场景无需调用。
核心流程:STA 配网全链路
sequenceDiagram
participant U as 用户/手机App
participant UC as uart_cmd 任务
participant WT as wifi_demo_task
participant W as WiFi 协议栈 (wifi_connect)
participant LW as lwIP / DHCP
participant VM as syscfg VM 存储
U->>UC: "sta <ssid> <pwd>"
UC->>WT: wifi_sta_connect(ssid, pwd, save=1)
WT->>W: wifi_set_sta_connect_best_ssid(0)
WT->>W: wifi_enter_sta_mode(ssid, pwd)
W->>W: 扫描/关联/802.11 握手
W-->>WT: 连接成功事件 (net_event)
WT->>LW: 启动 DHCP 获取地址
LW-->>WT: IP/掩码/网关/DNS
WT->>WT: wifi_sta_save_ssid() 检查 save_ssid_flag
WT->>VM: wifi_store_mode_info(STA_MODE, ssid, pwd)
WT->>VM: store_dhcp_ipaddr() 写 VM_STA_IPADDR_INDEX
WT-->>U: 配网成功 (日志/状态指示)
Note over U,W: 下次上电
U->>WT: wifi_return_sta_mode()
WT->>VM: wifi_get_mode_stored_info() 读记忆 SSID
WT->>W: wifi_set_sta_connect_best_ssid(1)
WT->>W: wifi_enter_sta_mode(记忆SSID)
W->>W: 自动重连最优网络
flowchart TD
Start([上电 wifi_on]) --> Mode{"配置 FORCE_DEFAULT_MODE?"}
Mode -->|"0 最后记忆模式"| Last["恢复记忆模式"]
Mode -->|"1 强制默认"| Default["进入默认模式"]
Mode -->|"3-200 秒超时"| Timeout["STA 连接超时计时"]
Last --> Sta["STA 模式"]
Default --> Sta
Timeout -->|"超时未连上"| Fallback["回退连接记忆/最优网络"]
Sta --> Conn{"连接成功?"}
Conn -->|"否"| Timeout
Conn -->|"是"| Dhcp["DHCP / 静态IP复用"]
Dhcp --> Save["wifi_sta_save_ssid / store_dhcp_ipaddr"]
Save --> Done([联网就绪])
Configuration Options
以下配置集中在 sdk/apps/demo/demo_wifi/include/app_config.h 与 wifi_demo_task.c 顶部宏定义,按工程裁剪:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TCFG_WIFI_ENABLE | 宏开关 | 1(demo_wifi) | 使能 WiFi 功能及相应任务/内存分配 |
WIFI_AP_SSID / WIFI_AP_PWD | 字符串 | 工程定义 | AP 模式默认热点名/密码(AP_SSID/AP_PWD) |
WIFI_STA_SSID / WIFI_STA_PWD | 字符串 | 工程定义 | 无记忆 SSID 时的默认 STA 目标(STA_SSID/STA_PWD) |
FORCE_DEFAULT_MODE | int | 0 | wifi_on 后的初始模式:0=最后记忆模式,1=强制默认模式,3~200=STA 连接超时秒数,超时回退记忆/最优网络 |
CONNECT_BEST_SSID | int | 0 | 启动后 STA 模式是否挑选记忆过的信号最优 WiFi |
WIFI_MODE_CYCLE_TEST | 宏开关 | 关 | 循环测试模式切换:AP→STA→MONITOR→AP…(从默认或记忆模式开始) |
CONFIG_STATIC_IPADDR_ENABLE | 宏开关 | 工程定义 | 使能静态 IP 记忆复用(VM_STA_IPADDR_INDEX) |
CONFIG_ASSIGN_MACADDR_ENABLE | 宏开关 | 工程定义 | 使能 MAC 地址分配(mac_addr_succ_flag) |
| 运行期常量 | 类型 | 值 | 说明 |
|---|---|---|---|
| 扫描等待超时 | 10ms 单位 | 500 | scan_list_sem_pend(500),即 5 秒 |
| 扫描结果条数上限 | int | 32 | wifi_stored_sta_info[32] 记忆网络上限 |
| SSID / 密码缓冲 | bytes | 33 / 65 | ssid[33]、pwd[65],含结尾 \0 |
| UART 接收缓冲 | bytes | 1 * 1024 | buf[1*1024],循环缓冲,对齐 32 |
| UART 命令任务栈 | bytes | 4 * 1024 | os_task_create(..., 4*1024, ...) |
API Reference
以下 API 为 wifi/wifi_connect.h 在 demo 代码中的实际用法(签名以头文件为准),按调用场景组织:
模式切换
void wifi_enter_sta_mode(char *ssid, char *pwd) 进入 STA 工作站模式并尝试连接指定网络。
- 参数:
ssid目标热点名;pwd密码(开放网络传空串)。 - 说明:由
wifi_sta_connect/wifi_return_sta_mode调用,是配网的最终落点。
void wifi_enter_ap_mode(char *ssid, char *pwd) 进入 AP 热点模式,设备自建网络供手机接入(配合 net_set_lan_info + dhcp_srv)。
- 参数:
ssid热点名;pwd密码(可为空)。
void wifi_enter_p2p_mode(int mode, char *devname) 进入 P2P(Wi-Fi Direct)模式。
- 参数:
mode取值P2P_GO_MODE或P2P_GC_MODE;devname设备名(缺省JL_GO_TEST/JL_GC_TEST)。
void wifi_enter_smp_cfg_mode(void) 进入 SmartConfig 监听模式,等待手机 App 广播配网报文并解析 SSID/密码。
扫描
int wifi_scan_req(void):发起异步扫描,完成后协议栈上报 WIFI_EVENT_STA_SCAN_COMPLETED 事件。 struct wifi_scan_ssid_info *wifi_get_scan_result(u32 *num):获取扫描结果(堆内存),num 返回条数。 void wifi_clear_scan_result(void):清空协议栈内扫描缓存,防止陈旧结果干扰最优选择。
- 返回的内存须用
free释放(封装为wifi_scan_list_release)。
SSID 记忆
void wifi_store_mode_info(int mode, char *ssid, char *pwd):保存网络到 VM(多网络,上限 32 条)。 u8 wifi_get_stored_sta_info(struct wifi_stored_sta_info *info):读出全部已存网络,返回条数。 int wifi_del_stored_sta_info(char *ssid):按 SSID 精确删除一条记忆。 void wifi_set_sta_connect_best_ssid(int enable):开关"自动连接信号最优已存网络"。
状态查询
int wifi_is_on(void):WiFi 是否已上电使能。 int wifi_get_mode_cur_info(struct wifi_mode_info *info):读取当前模式及所连 SSID/密码(info.mode 置为 STA_MODE)。 int wifi_get_mode_stored_info(struct wifi_mode_info *info):读取记忆的模式信息,返回非 0 表示无记忆。 void wifi_get_mac(u8 *mac):读取本机 MAC(6 字节)。 void wifi_get_bssid(u8 *bssid):读取当前关联 AP 的 BSSID。 u8 wifi_get_channel(void):读取当前信道。 const char *get_wifi_auth_mode(int auth_mode):认证模式枚举转可读字符串。
底层适配(wifi_adapter.h,Raw/Monitor 场景)
void wf_rx_filt_set(unsigned int reg)/void wf_rx_filt_reset(unsigned int reg):复位/置位 RX 硬件过滤位(DROP_*位域)。void wf_asic_set_bssid(unsigned char *bssid)/void wf_asic_set_mac(unsigned char *mac):配置硬件 BSSID/MAC 寄存器,需在wifi_raw_on后调用。void wf_channel_power_detect_enable(void)/char get_current_channel_power(void):信道功率检测。
Failure Modes、Edge Cases 与并发
扫描超时与失败
scan_list_sem_pend(500)5 秒超时返回NULL,调用方必须判空(wifi_get_list_example即先if (!list) return;);- 事件迟到时由
sem_flag守卫,避免悬空信号量污染下一次扫描; - 扫描结果内存必须释放,否则堆泄漏(
wifi_scan_list_release)。
连接超时与回退
FORCE_DEFAULT_MODE取 3~200 时作为 STA 连接超时;超时后自动回连最后记忆或最优网络,保证"路由器重启/暂时不可达"时设备不长期挂起;- 无记忆 SSID 时回退编译期
STA_SSID/STA_PWD,保证开箱即有默认网络可连。
并发与互斥
- 扫描接口用
scan_mutex串行化,多任务并发调用wifi_scan_list_get会互斥等待; - 协议栈事件回调与业务任务之间通过信号量解耦(扫描完成事件 →
scan_list_sem_post),回调内不做耗时操作; save_ssid_flag/request_connect_flag为单任务内标志位,模式切换与保存动作由wifi_demo_task串行处理,避免竞态。
UART 边界
- 循环缓冲回卷(
UART_CIRCULAR_BUFFER_WRITE_OVERLAY)时冲掉全部数据重新接收,代价是可能丢命令,换取状态机不混乱; - 命令解析用
sscanf按空格分隔,SSID 含空格或超长(>32)会解析失败或截断,属于已知限制。
IP 复用边界
- 静态 IP 复用要求 SSID、本机 MAC、网关 MAC 三者全部匹配,任一不匹配则重新 DHCP;
- 路由器无外网(DHCP 拿不到有效 DNS)时放弃复用,重新走完整 DHCP 流程(见源码注释)。
Performance、运维与扩展点
性能与资源
- 扫描为阻塞 5 秒同步接口,业务设计上应避免在 UI/交互关键路径直接调用;批量命令(
ssid_store_test)前先os_time_dly(5*100)等连接稳定; - RX 过滤(
DROP_*)可在 Raw 模式下由硬件直接丢弃无关帧,是降低 CPU 中断负载的主要手段; compare_dhcp_ipaddr的记忆复用显著缩短重连上电时间(跳过 DHCP 交换)。
运维建议
- 产测/调试优先走 UART 命令(
sta/ap/scan/get_mac),日志标签[WIFI](log_tag_const_*_WIFI_AP_STA、WIFI_RAW等)可单独开关; WIFI_MODE_CYCLE_TEST用于回归测试模式切换状态机,量产固件应关闭;- SSID 记忆满 32 条后写入策略需按产品需求补充(示例代码未体现淘汰策略)。
扩展点
- 配网协议扩展:在
cmd_do中追加命令分支,复用wifi_sta_connect即可接入自有配网协议(如厂商 App 的 UDP/TCP 下发); - Raw 协议栈:
wifi_bbm的wifi_adapter+raw_txrx(raw_test.c/kcp_test.c)支持私有无线协议开发,与标准 TCP/IP 数据面并存; - 网络事件:订阅
net_event(event/net_event.h)可在连接/断线/扫描完成等时机挂接业务逻辑(demo 中扫描完成事件即由此驱动信号量)。
Tests 与验证手段
ssid_store_test:验证wifi_store_mode_info/wifi_get_stored_sta_info/wifi_del_stored_sta_info的增删查闭环;WIFI_MODE_CYCLE_TEST:循环切换 AP→STA→MONITOR 模式,回归模式状态机;scan命令:验证扫描、事件上报、结果解析(RSSI/SNR/认证方式)与内存释放;wifi_raw_test(demo_wifi/examples)与raw_txrx:Raw 收发与 KCP 传输测试,覆盖wifi_adapter底层接口。
Related Links
- wifi_demo_task.c — 模式切换/SSID记忆/静态IP核心实现
- uart_cmd.c — UART 配网命令入口
- scan_list.c — 扫描同步封装
- wifi_adapter.h — 底层 RX 过滤与硬件寄存器适配
- demo_wifi 应用配置 app_config.h
- 兄弟页面:WiFi 低功耗(low_power)、Raw TX/RX 透传(raw_txrx / wifi_raw_test)、网络事件机制(event/net_event.h)