概述
联网是物联网设备的第一步。Ai-WB2 以 STA(Station)模式(让开发板当「客户端」,像手机一样去连路由器)作为客户端连接路由器,获取 IP(网络里的「门牌号」)后即可进行 TCP/UDP/HTTP/MQTT 等网络通信。本教程演示完整连接流程:Wi-Fi 初始化 → 连接路由器 → 获取 IP,并通过事件回调(设闹钟,到点自动响)观察各阶段状态。
用大白话讲:STA 模式就是让开发板像手机一样去连你家的路由器。Wi-Fi 的名字叫 SSID(网络的名字),密码对了才能连上;连上后路由器会发给开发板一个 IP 地址(网络里的「门牌号」),有了它才能和其他设备通信。整个过程跟手机连 Wi-Fi 一模一样:搜索 → 输密码 → 连接成功 → 状态栏出现 Wi-Fi 图标。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/wifi/station编写,代码可在本地 SDK 中直接找到。
打开终端,进入官方 station 示例工程目录:
cd ~/Ai-Thinker-WB2/applications/wifi/station
说明:
cd是「进入目录」的命令,进入官方示例工程;后面所有make命令都要在这个目录下执行。
打开 station/main.c,修改开头的 SSID(Wi-Fi 的名字)与密码为你的路由器:
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
例如:
#define ROUTER_SSID "my_wifi"
#define ROUTER_PWD "12345678"
💡 仅支持 2.4GHz 频段;密码为空(开放网络)时第二个参数传
NULL或空字符串。
打开 station/main.c,本步完整代码已移至文末,见:
📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(
applications/wifi/station/station/main.c)完全一致。
代码要点:
| 代码 | 作用 |
|---|---|
tcpip_init(NULL, NULL) |
初始化网络协议栈,不先初始化,后面所有网络函数都用不了 |
aos_register_event_filter(EV_WIFI, cb, NULL) |
注册事件回调,不注册就收不到任何 Wi-Fi 通知 |
hal_wifi_start_firmware_task() |
启动 Wi-Fi 固件任务,不开无线硬件就没法联网 |
aos_post_event(EV_WIFI, CODE_WIFI_ON_INIT_DONE, 0) |
投递初始化事件,触发后面的连接流程自动跑起来 |
wifi_mgmr_start_background(&conf) |
启动 Wi-Fi 管理模块,不启动时连接函数会直接失败 |
wifi_mgmr_sta_enable() |
让开发板以「客户端」身份就位,准备连路由器 |
wifi_mgmr_sta_connect(if, ssid, pwd, ...) |
真正发起连接,SSID/密码写错就永远连不上 |
CODE_WIFI_ON_GOT_IP 回调 |
拿到 IP 才算联网成功,网络应用都应在这里启动 |
💡 官方还支持配网事件(
CODE_WIFI_ON_PROV_SSID/PASSWD/CONNECT),用于 app/网页配网后自动连接,实际配网产品常用此机制。
在工程目录执行编译:
make -j8
说明:
make是「编译」命令,把代码变成开发板能运行的固件(烧进开发板的程序);-j8表示用 8 个 CPU 核并行编译,更快。
编译成功后生成固件 build_out/station.bin。
开发板保持 USB 连接,确认串口设备号后执行烧录:
make flash p=/dev/ttyUSB0 b=921600
说明:
make flash是「烧录」命令,把编译好的固件下载进开发板;p=后面是串口设备号(Linux 下常为/dev/ttyUSB0,Windows 下是COM3之类,以你电脑实际为准),b=921600是烧录波特率(串口传数据的速度),保持默认即可。
⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。
烧录完成后开发板自动重启运行,打开串口助手(波特率 921600,串口传数据的「语速」,两边必须一致)观察事件日志:
[OS] Starting TCP/IP Stack...
[OS] proc_main_entry task...
[APP] [EVT] INIT DONE 1
[APP] [EVT] MGMR DONE 2
[APP] [EVT] Connecting 3
[APP] [EVT] connected 4
[APP] [EVT] GOT IP 5
[SYS] Memory left is 267232 Bytes
事件顺序即为连接流程:INIT DONE → MGMR DONE → Connecting → connected → GOT IP。出现 GOT IP 即联网成功;如果一直卡在 Connecting 或反复 disconnect,说明没连上路由器(SSID/密码/2.4GHz 频段问题),见文末 FAQ;如果串口完全没输出,先检查串口与烧录是否正常(见文末 FAQ)。
💡 进阶验证:断开路由器电源再恢复,串口会打印
disconnect→Reconnect→ 重新GOT IP,说明 SDK 自动重连机制生效。在GOT_IP回调中创建业务任务即可开始网络应用(见 TCP 客户端)。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
本文 API 汇总
tcpip_init(callback, arg)
初始化 lwIP TCP/IP 协议栈(socket、DNS、netif 等依赖它),main 函数最先调用。
参数:
callback:初始化完成回调函数指针,一般传NULLarg:回调参数,传NULL
返回值:无
aos_register_event_filter(evt, cb, arg)
注册事件过滤器回调,联网状态变化时被调用。
参数:
evt:事件类型,可选值:EV_WIFI(Wi-Fi 事件)cb:回调函数指针,形如void cb(uint32_t event, void *val, void *arg),必填arg:透传参数,无参传NULL
返回值:成功返回 0;失败返回负值错误码
aos_post_event(evt, code, value)
向事件循环投递事件(触发 Wi-Fi 初始化流程)。
参数:
evt:事件类型,可选值:EV_WIFIcode:事件码,可选值:CODE_WIFI_ON_INIT_DONE(初始化完成)等value:事件附加值,无需传NULL
返回值:成功返回 0;失败返回负值错误码
wifi_mgmr_start_background(conf)
启动 Wi-Fi 管理器(连接/热点/扫描的调度核心)。
参数:
conf:wifi_conf_t结构体指针,可选字段:country_code(国家码,中国用"CN")
返回值:成功返回 0;失败返回负值错误码
wifi_mgmr_sta_connect(if, ssid, pwd, NULL, NULL, 0, 0)
按 SSID/密码连接路由器(异步,结果通过事件回调通知)。
参数:
if:STA 接口句柄(wifi_mgmr_sta_enable()返回值)ssid:路由器名称字符串,必填(如"FAE@Seahi")pwd:Wi-Fi 密码字符串,必填(如"fae12345678")- 参数 4~7:可选参数(BSSID、信道、安全类型、索引),传
NULL, NULL, 0, 0
返回值:成功返回 0;失败返回负值错误码
xTaskCreate(fn, name, stack, arg, prio, handle)
创建任务并加入就绪队列,由调度器按优先级调度执行。
参数:
fn:任务入口函数指针,形如void task(void *arg),必填name:任务名称字符串(调试用),如"main_entry"stack:任务栈大小(单位:字),可选值:如1024arg:入口函数参数指针,无参传NULLprio:任务优先级,可选值:0(最低)~19(最高),如15handle:任务句柄输出指针,不需要可传NULL
返回值:成功返回 pdPASS;失败返回 pdFAIL
完整代码
以下为 station/main.c 完整源码,与官方示例(applications/wifi/station/station/main.c)完全一致:
📜 点击展开 station/main.c 完整代码
/**
* @file main.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2022-10-09
*
* @copyright Copyright (c) 2022
*
*/
#include <FreeRTOS.h>
#include <task.h>
#include <stdio.h>
#include <string.h>
#include "blog.h"
#include <aos/yloop.h>
#include <aos/kernel.h>
#include <lwip/tcpip.h>
#include <wifi_mgmr_ext.h>
#include <hal_wifi.h>
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
static wifi_conf_t conf =
{
.country_code = "CN",
};
static void wifi_sta_connect(char* ssid, char* password)
{
wifi_interface_t wifi_interface;
wifi_interface = wifi_mgmr_sta_enable();
wifi_mgmr_sta_connect(wifi_interface, ssid, password, NULL, NULL, 0, 0);
}
static void event_cb_wifi_event(input_event_t* event, void* private_data)
{
static char* ssid;
static char* password;
switch (event->code)
{
case CODE_WIFI_ON_INIT_DONE:
{
blog_info("[APP] [EVT] INIT DONE %lld", aos_now_ms());
wifi_mgmr_start_background(&conf);
}
break;
case CODE_WIFI_ON_MGMR_DONE:
{
blog_info("[APP] [EVT] MGMR DONE %lld", aos_now_ms());
//_connect_wifi();
wifi_sta_connect(ROUTER_SSID, ROUTER_PWD);
}
break;
case CODE_WIFI_ON_SCAN_DONE:
{
blog_info("[APP] [EVT] SCAN Done %lld", aos_now_ms());
// wifi_mgmr_cli_scanlist();
}
break;
case CODE_WIFI_ON_DISCONNECT:
{
blog_info("[APP] [EVT] disconnect %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTING:
{
blog_info("[APP] [EVT] Connecting %lld", aos_now_ms());
}
break;
case CODE_WIFI_CMD_RECONNECT:
{
blog_info("[APP] [EVT] Reconnect %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTED:
{
blog_info("[APP] [EVT] connected %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_PRE_GOT_IP:
{
blog_info("[APP] [EVT] connected %lld", aos_now_ms());
}
break;
case CODE_WIFI_ON_GOT_IP:
{
blog_info("[APP] [EVT] GOT IP %lld", aos_now_ms());
blog_info("[SYS] Memory left is %d Bytes", xPortGetFreeHeapSize());
}
break;
case CODE_WIFI_ON_PROV_SSID:
{
blog_info("[APP] [EVT] [PROV] [SSID] %lld: %s",
aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (ssid)
{
vPortFree(ssid);
ssid = NULL;
}
ssid = (char*)event->value;
}
break;
case CODE_WIFI_ON_PROV_BSSID:
{
blog_info("[APP] [EVT] [PROV] [BSSID] %lld: %s",
aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (event->value)
{
vPortFree((void*)event->value);
}
}
break;
case CODE_WIFI_ON_PROV_PASSWD:
{
blog_info("[APP] [EVT] [PROV] [PASSWD] %lld: %s", aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (password)
{
vPortFree(password);
password = NULL;
}
password = (char*)event->value;
}
break;
case CODE_WIFI_ON_PROV_CONNECT:
{
blog_info("[APP] [EVT] [PROV] [CONNECT] %lld", aos_now_ms());
blog_info("connecting to %s:%s...", ssid, password);
wifi_sta_connect(ssid, password);
}
break;
case CODE_WIFI_ON_PROV_DISCONNECT:
{
blog_info("[APP] [EVT] [PROV] [DISCONNECT] %lld", aos_now_ms());
}
break;
default:
{
blog_info("[APP] [EVT] Unknown code %u, %lld", event->code, aos_now_ms());
/*nothing*/
}
}
}
static void proc_main_entry(void* pvParameters)
{
aos_register_event_filter(EV_WIFI, event_cb_wifi_event, NULL);
hal_wifi_start_firmware_task();
aos_post_event(EV_WIFI, CODE_WIFI_ON_INIT_DONE, 0);
vTaskDelete(NULL);
}
static void system_thread_init()
{
/*nothing here*/
}
void main()
{
system_thread_init();
puts("[OS] Starting TCP/IP Stack...");
tcpip_init(NULL, NULL);
puts("[OS] proc_main_entry task...");
xTaskCreate(proc_main_entry, (char*)"main_entry", 1024, NULL, 15, NULL);
}常见问题与踩坑提示
⚠️ 一直打印 Connecting 不成功
原因:SSID/密码错误、路由器是 5GHz 频段、或信号过弱
解决:核对 ROUTER_SSID/ROUTER_PWD;确认路由器开 2.4GHz;开发板靠近路由器测试
⚠️ 连接成功但没有 GOT IP
原因:路由器 DHCP 未开启,或 IP 被占用
解决:确认路由器开启 DHCP;连接数受限的路由器(如手机热点)限制接入设备数
⚠️ 打印 disconnect 后反复重连
原因:密码错误会触发循环重连,或路由器 MAC 过滤拒绝
解决:确认密码正确;检查路由器是否开启 MAC 过滤(需放行 Ai-WB2 的 MAC)
⚠️ 修改 SSID 后重新编译报错
原因:中文 SSID 编码问题或宏字符串未加引号
解决:SSID 使用 ASCII 字符(中文 SSID 需转 UTF-8 编码);确认宏格式为 #define ROUTER_SSID "xxx"
⚠️ 串口找不到设备 / 打不开
原因:USB 转串口驱动未装、权限不足,或数据线只能充电不能传数据
解决:Linux 用 lsusb/dmesg 查看设备,权限不足可 sudo chmod 666 /dev/ttyUSB0;Windows 装驱动后到设备管理器查 COM 口;换一根能传数据的线
⚠️ 烧录一直等待 / 失败
原因:未进入下载模式、波特率不对、或串口号填错
解决:烧录时按提示长按 EN 键进入下载模式;确认 p=/dev/ttyUSB0 换成你实际的串口;换 USB 口或数据线重试
运行自检
串口按 INIT DONE → MGMR DONE → Connecting → connected → GOT IP 顺序打印事件,即 STA 连接验证通过。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

