Skip to content

概述

联网是物联网设备的第一步。Ai-WB2 以 STA(Station)模式(让开发板当「客户端」,像手机一样去连路由器)作为客户端连接路由器,获取 IP(网络里的「门牌号」)后即可进行 TCP/UDP/HTTP/MQTT 等网络通信。本教程演示完整连接流程:Wi-Fi 初始化 → 连接路由器 → 获取 IP,并通过事件回调(设闹钟,到点自动响)观察各阶段状态。

用大白话讲:STA 模式就是让开发板像手机一样去连你家的路由器。Wi-Fi 的名字叫 SSID(网络的名字),密码对了才能连上;连上后路由器会发给开发板一个 IP 地址(网络里的「门牌号」),有了它才能和其他设备通信。整个过程跟手机连 Wi-Fi 一模一样:搜索 → 输密码 → 连接成功 → 状态栏出现 Wi-Fi 图标。

本教程基于安信可官方 SDKAi-Thinker-Open/Ai-Thinker-WB2,版本 release_bl_iot_sdk_1.6.40)的官方示例 applications/wifi/station 编写,代码可在本地 SDK 中直接找到。

🎯本页目标通过事件回调观察 STA 连接全流程(INIT → MGMR → CONNECTED → GOT IP),掌握 Wi-Fi 事件驱动框架与连接 API。
🧰前置条件① Ai-WB2 开发板一块(Type-C 数据线)② 2.4GHz 路由器(记住 SSID 与密码)③ 已按 [SDK 安装](../sdk/sdk_intro) 完成环境搭建。
🔗相关章节事件框架概念见 [Wi-Fi 简介](./wifi_intro);连接后做网络通信见 [TCP 客户端](./tcp_client)。

进入示例工程

打开终端,进入官方 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 DONEMGMR DONEConnectingconnectedGOT IP出现 GOT IP 即联网成功;如果一直卡在 Connecting 或反复 disconnect,说明没连上路由器(SSID/密码/2.4GHz 频段问题),见文末 FAQ;如果串口完全没输出,先检查串口与烧录是否正常(见文末 FAQ)。

💡 进阶验证:断开路由器电源再恢复,串口会打印 disconnectReconnect → 重新 GOT IP,说明 SDK 自动重连机制生效。在 GOT_IP 回调中创建业务任务即可开始网络应用(见 TCP 客户端)。

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):


本文 API 汇总

tcpip_init(callback, arg)

初始化 lwIP TCP/IP 协议栈(socket、DNS、netif 等依赖它),main 函数最先调用。

参数

  • callback:初始化完成回调函数指针,一般传 NULL
  • arg:回调参数,传 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_WIFI
  • code:事件码,可选值:CODE_WIFI_ON_INIT_DONE(初始化完成)等
  • value:事件附加值,无需传 NULL

返回值:成功返回 0;失败返回负值错误码

hal_wifi_start_firmware_task

启动 Wi-Fi 固件任务,开启无线硬件(用户一般不直接使用)。

返回值:无

wifi_mgmr_start_background(conf)

启动 Wi-Fi 管理器(连接/热点/扫描的调度核心)。

参数

  • confwifi_conf_t 结构体指针,可选字段:country_code(国家码,中国用 "CN"

返回值:成功返回 0;失败返回负值错误码

wifi_mgmr_sta_enable

启用站点(Station)模式,可连接路由器。

返回值:成功返回接口句柄(wifi_interface_t);失败返回 NULL

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;失败返回负值错误码

aos_now_ms

返回系统上电以来经过的毫秒数(可做超时判断/时间戳)。

返回值:系统启动毫秒数(uint64_t,无失败)

xPortGetFreeHeapSize

返回当前可用堆内存字节数(排查内存不足问题)。

返回值:剩余堆字节数(无失败)

xTaskCreate(fn, name, stack, arg, prio, handle)

创建任务并加入就绪队列,由调度器按优先级调度执行。

参数

  • fn:任务入口函数指针,形如 void task(void *arg),必填
  • name:任务名称字符串(调试用),如 "main_entry"
  • stack:任务栈大小(单位:字),可选值:如 1024
  • arg:入口函数参数指针,无参传 NULL
  • prio:任务优先级,可选值:0(最低)~19(最高),如 15
  • handle:任务句柄输出指针,不需要可传 NULL

返回值:成功返回 pdPASS;失败返回 pdFAIL


完整代码

以下为 station/main.c 完整源码,与官方示例(applications/wifi/station/station/main.c)完全一致:

📜 点击展开 station/main.c 完整代码
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

Released under the MIT License. Build Time 2026-09-11 14:52:23