概述
配网(把 Wi-Fi 账号密码发给设备的过程)是物联网设备联网的第一步:设备没有屏幕和键盘,无法手动输入路由器密码,需要用手机把密码「递」给它。BluFi 是乐鑫(Espressif)提出的开源配网协议,安信可将其移植到 Ai-WB2:设备通过 BLE 广播(大喇叭喊话)暴露一个「配网入口」,手机 App 连接后通过加密通道把 Wi-Fi 的 SSID(Wi-Fi 的名字)和密码发给设备,设备自动连上路由器。本教程完成全流程:烧录 blufi 示例 → 手机 App 配网 → 设备自动上网。
用大白话讲:配网就像给门锁录入指纹。新装的门锁(设备)不认识你家的门(路由器),你需要用手机贴着它,把「开门信息」录进去,之后它自己就能开门(联网)。BluFi 就是借 BLE 蓝牙这个「贴身通道」来录入——密码走蓝牙传输,比直接裸奔在 Wi-Fi 上安全得多。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/bluetooth/blufi编写,代码可在本地 SDK 中直接找到。
打开终端,进入官方 blufi 示例工程目录:
cd ~/Ai-Thinker-WB2/applications/bluetooth/blufi
说明:
cd是「进入目录」命令,这里进入 blufi 示例工程目录;后续的make编译、make flash烧录命令都必须先在这个目录里执行。
工程目录结构说明:
| 文件 | 作用 |
|---|---|
main/blufi_main.c |
主程序源码:配网初始化、事件回调、Wi-Fi 连接逻辑,本教程主要看的文件 |
Makefile / proj_config.mk |
工程编译配置,一般无需修改 |
打开 main/blufi_main.c,本示例无需修改任何参数,烧录即可使用。代码做的事可以归纳为「注册回调 + 启动服务」:
| 代码 | 作用 |
|---|---|
wifi_interface_init(blufi_wifi_event) |
注册 Wi-Fi 事件回调,配网成功/失败/拿到 IP 时通知到这里 |
at_blufi_start() |
启动 BluFi 配网服务:初始化 BLE 并注册配网回调,配网入口就是它 |
example_event_callback |
配网核心回调:App 发来的 SSID/密码在这里接收、保存、发起连接 |
blufi_security_init() |
App 连上后启动加密(AES 加解密 + CRC 校验),Wi-Fi 密码走蓝牙是加密传输的 |
💡 官方还注册了两个串口命令:
blufi_init启动配网、blufi_deinit退出配网(恢复为普通模式),调试时可配合 CLI 使用。
在工程目录执行编译:
make -j8
说明:
make是「编译」命令,把代码变成开发板能运行的固件(烧进开发板的程序);-j8表示用 8 个 CPU 核并行编译,更快。
编译成功后生成固件 build_out/blufi.bin。
⚠️ 若提示
riscv64-unknown-elf-gcc: command not found,说明工具链权限未配置,先执行cd toolchain/riscv/Linux && . chmod755.sh再重新编译。
开发板保持 USB 连接,确认串口设备号后执行烧录:
make flash p=/dev/ttyUSB0 b=921600
说明:
make flash是「烧录」命令,把编译好的固件写进开发板芯片。p=后面是串口设备号(要改成你电脑上实际的串口,可用ls /dev/ttyUSB*查看),b=921600是烧录波特率(串口传数据的速度),保持默认即可。
⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。
- 打开手机 EspBlufi App(给 App 开启蓝牙和定位权限),确认手机已连接你家 Wi-Fi(App 会读取手机当前 Wi-Fi 的名字)。
- 烧录完成后开发板会自动进入配网模式(BLE 广播中),App 首页应能看到名为 BLUFI 的设备,点击连接它。
- 连接成功后,App 界面填写/确认 SSID(Wi-Fi 名字)与密码,点击发送/配网按钮。
💡 注意区分:配网时手机连的是开发板的 BLE,不是开发板的 Wi-Fi;发送后请耐心等几秒,设备会自动去连接路由器。配网仅支持 2.4GHz 频段的路由器,5GHz 的连不上。
打开串口助手(波特率 921600,本示例走系统默认日志串口),观察配网全过程的日志:
blufi demo test
...
BLUFI init finish
BLUFI ble connect ← 手机 App 已连上开发板的 BLE
BLUFI requset wifi connect to AP
Recv STA SSID my_wifi ← 收到手机发来的 Wi-Fi 名字
Recv STA PASSWORD ******** ← 收到手机发来的 Wi-Fi 密码
BLUFI save ssid&&pwd ← 配网信息已保存
...
[WIFI] [EVT] CODE_WIFI_ON_GOT_IP ← 拿到 IP,联网成功
串口依次打印 BLUFI init finish → BLUFI ble connect → BLUFI save ssid&&pwd,且手机 App 界面显示设备已连接上路由器(Wi-Fi 状态为成功),即为配网成功;如果 App 搜不到 BLUFI 设备或发送后一直连不上路由器,见文末 FAQ。
💡 进阶验证:配网成功后给开发板断电再上电,设备会自动用已保存的 SSID/密码连接路由器(这就是「记住密码」),无需再次配网。想重新配网,按住复位重新进入配网模式即可。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
本文 API 汇总
说明:
at_blufi_start是本工程自定义的接口(定义在blufi_main.c);axk_hal_blufi_init、_blufi_host_and_cb_init、axk_blufi_adv_*、blufi_security_*来自 SDK 的components/network/blufi/组件;wifi_interface_init来自 SDK 的 Wi-Fi 接口层。
tcpip_init(callback, arg)
初始化 lwIP TCP/IP 协议栈(socket、DNS 等网络功能依赖它),配网成功后自动联网需要它。
参数:
callback:初始化完成回调函数指针,一般传NULLarg:回调参数,传NULL
返回值:无
at_blufi_start
启动 BluFi 配网服务:调用 axk_hal_blufi_init() 初始化 BLE 硬件,再调用 _blufi_host_and_cb_init() 注册配网回调(本工程自定义,定义在 blufi_main.c)。
返回值:成功返回 0;失败返回负值错误码
axk_hal_blufi_init
初始化 BluFi 依赖的 BLE 硬件(来自 components/network/blufi/blufi_hal.h),配网服务的第一步。
返回值:成功返回 0;失败返回负值错误码
_blufi_host_and_cb_init(callbacks)
注册 BluFi 回调(事件回调、加解密、校验函数),配网事件都从这里分发(来自 components/network/blufi/blufi_init.h)。
参数:
callbacks:_blufi_callbacks_t结构体指针,必填(本示例example_callbacks,含event_cb、encrypt_func、checksum_func等)
返回值:成功返回 0;失败返回负值错误码
blufi_security_init
初始化配网加密(AES + DH 密钥协商),Wi-Fi 密码在蓝牙通道上是加密传输的(来自 blufi_security.h)。
返回值:成功返回 0;失败返回负值错误码
wifi_interface_init(event_cb)
注册 Wi-Fi 事件回调(配网结果、连接状态、IP 获取等事件的通知入口)。
参数:
event_cb:事件回调函数指针,形如void cb(int event, void *param),必填(本示例blufi_wifi_event)
返回值:成功返回 0;失败返回负值错误码
xTaskCreate(task, name, stack, param, prio, handle)
创建任务并加入就绪队列,由调度器按优先级调度执行。
参数:
task:任务入口函数指针,形如void task(void *arg),必填name:任务名称字符串(调试用),本示例"main_entry"/"free_task"stack:任务栈大小(单位:字),本示例1024/750param:传给入口函数的参数指针,无参传NULLprio:任务优先级,可选值:0(最低)~19(最高,SDK 配置),本示例15/1handle:任务句柄输出指针,不需要可传NULL
返回值:成功返回 pdPASS;失败返回 pdFAIL(如内存不足)
完整代码
以下为 main/blufi_main.c 完整源码,与官方示例(applications/bluetooth/blufi/main/blufi_main.c)完全一致:
📜 点击展开 blufi_main.c 完整代码
#include <FreeRTOS.h>
#include <task.h>
#include <timers.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <lwip/tcpip.h>
#include <bl602_glb.h>
#include <bl_sys.h>
#include <bl_uart.h>
#include <cli.h>
//////////////////////////////////
#include "wifi_interface.h"
#include <../wifi_mgmr.h>
#include "blufi.h"
#include "blufi_api.h"
#include "blufi_hal.h"
#include "blufi_init.h"
#include "axk_blufi.h"
#include "ble_interface.h"
#include "blufi_security.h"
//////////////////////////////////
static int scan_counter;
static bool ble_is_connected = false;
static bool gl_sta_connected = false;
blufi_config_t g_blufi_config = {0};
static void cb_scan_item_parse(wifi_mgmr_ap_item_t *env, uint32_t *param1, wifi_mgmr_ap_item_t *item)
{
_blufi_ap_record_t *ap_list;
ap_list = (_blufi_ap_record_t *)env;
ap_list[scan_counter].rssi = item->rssi;
memset(ap_list[scan_counter].ssid, 0, sizeof ap_list[scan_counter].ssid);
memcpy(ap_list[scan_counter].ssid, item->ssid, item->ssid_len);
scan_counter++;
}
static void cb_scan_complete(void *data, void *param)
{
_blufi_ap_record_t *ap_list;
ap_list = (_blufi_ap_record_t *)malloc(WIFI_MGMR_SCAN_ITEMS_MAX * sizeof(_blufi_ap_record_t));
if (!ap_list)
{
printf("ap_list malloc fail");
return;
}
scan_counter = 0;
wifi_mgmr_scan_ap_all(ap_list, NULL, cb_scan_item_parse);
printf("scan complete:%ld \r\n", scan_counter);
if (ble_is_connected == true)
{
axk_blufi_send_wifi_list(scan_counter, ap_list);
}
else
{
printf("BLUFI BLE is not connected yet\n");
}
free(ap_list);
}
int wifi_scan_start(void)
{
return wifi_mgmr_scan(NULL, cb_scan_complete);
}
static void blufi_wifi_event(int event, void *param)
{
switch (event)
{
case BLUFI_STATION_CONNECTED:
gl_sta_connected = true;
break;
case BLUFI_STATION_DISCONNECTED:
gl_sta_connected = false;
break;
case BLUFI_STATION_GOT_IP:
{
axk_blufi_extra_info_t info;
memset(&info, 0, sizeof(axk_blufi_extra_info_t));
wifi_conn_ap_info_get(&g_blufi_config.wifi.sta);
memcpy(info.sta_bssid, g_blufi_config.wifi.sta.cwjap_param.bssid, 6);
info.sta_bssid_set = true;
info.sta_ssid = (uint8_t *)g_blufi_config.wifi.sta.cwjap_param.ssid;
info.sta_ssid_len = strlen(g_blufi_config.wifi.sta.cwjap_param.ssid);
if (ble_is_connected == true)
{
axk_blufi_send_wifi_conn_report(g_blufi_config.wifi.cwmode, _BLUFI_STA_CONN_SUCCESS, 0, &info);
}
else
{
printf("BLUFI BLE is not connected yet\r\n");
}
printf("BLUFI save ssid&&pwd \r\n");
g_blufi_config.wifi.cwmode = WIFIMODE_STA;
}
break;
default:
break;
}
}
static void example_event_callback(_blufi_cb_event_t event, _blufi_cb_param_t *param)
{
/* actually, should post to blufi_task handle the procedure,
* now, as a example, we do it more simply */
switch (event)
{
case AXK_BLUFI_EVENT_INIT_FINISH:
printf("BLUFI init finish\n");
axk_blufi_adv_start();
break;
case AXK_BLUFI_EVENT_DEINIT_FINISH:
printf("BLUFI deinit finish\n");
break;
case AXK_BLUFI_EVENT_BLE_CONNECT:
printf("BLUFI ble connect\n");
ble_is_connected = true;
axk_blufi_adv_stop();
blufi_security_init();
break;
case AXK_BLUFI_EVENT_BLE_DISCONNECT:
printf("BLUFI ble disconnect\n");
ble_is_connected = false;
blufi_security_deinit();
axk_blufi_adv_start();
break;
case AXK_BLUFI_EVENT_SET_WIFI_OPMODE:
printf("BLUFI Set WIFI opmode %d\n", param->wifi_mode.op_mode);
// if (axk_hal_wifi_mode_set(WIFIMODE_STA, 0) != BLUFI_ERR_SUCCESS)
// {
// printf("BLUFI axk_hal_wifi_mode_set fail\r\n");
// break;
// }
g_blufi_config.wifi.cwmode = WIFIMODE_STA;
break;
case AXK_BLUFI_EVENT_REQ_CONNECT_TO_AP:
{
cwjap_param_t cwjap_param = {0};
printf("BLUFI requset wifi connect to AP\n");
cwjap_param = g_blufi_config.wifi.sta.cwjap_param;
if (axk_hal_conn_ap_info_set(&cwjap_param) != BLUFI_ERR_SUCCESS)
{
printf("BLUFI axk_hal_conn_ap_info_set fail\r\n");
break;
}
g_blufi_config.wifi.sta.state = BLUFI_WIFI_STATE_CONNECTING;
}
break;
case AXK_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP:
printf("BLUFI requset wifi disconnect from AP\n");
axk_hal_disconn_ap();
break;
case AXK_BLUFI_EVENT_REPORT_ERROR:
printf("BLUFI report error, error code %d\n", param->report_error.state);
axk_blufi_send_error_info(param->report_error.state);
break;
case AXK_BLUFI_EVENT_GET_WIFI_STATUS:
{
wifi_mode_t mode;
mode = g_blufi_config.wifi.cwmode;
if (gl_sta_connected)
{
axk_blufi_extra_info_t info;
memset(&info, 0, sizeof(axk_blufi_extra_info_t));
wifi_conn_ap_info_get(&g_blufi_config.wifi.sta);
memcpy(info.sta_bssid, g_blufi_config.wifi.sta.cwjap_param.bssid, 6);
info.sta_bssid_set = true;
info.sta_ssid = (uint8_t *)g_blufi_config.wifi.sta.cwjap_param.ssid;
info.sta_ssid_len = strlen(g_blufi_config.wifi.sta.cwjap_param.ssid);
axk_blufi_send_wifi_conn_report(mode, _BLUFI_STA_CONN_SUCCESS, 0, &info);
}
else
{
axk_blufi_send_wifi_conn_report(mode, _BLUFI_STA_CONN_FAIL, 0, NULL);
}
printf("BLUFI get wifi status from AP\n");
break;
}
case AXK_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE:
printf("blufi close a gatt connection\r\n");
axk_blufi_disconnect();
break;
case AXK_BLUFI_EVENT_DEAUTHENTICATE_STA:
/* TODO */
break;
case AXK_BLUFI_EVENT_RECV_STA_BSSID:
memset(g_blufi_config.wifi.sta.cwjap_param.bssid, 0, 6);
memcpy(g_blufi_config.wifi.sta.cwjap_param.bssid, param->sta_bssid.bssid, 6);
// sta_config.sta.bssid_set = 1;
// esp_wifi_set_config(WIFI_IF_STA, &sta_config);
printf("Recv STA BSSID %s\r\n", param->sta_bssid.bssid);
break;
case AXK_BLUFI_EVENT_RECV_STA_SSID:
memset(g_blufi_config.wifi.sta.cwjap_param.ssid, 0, 33);
strncpy(g_blufi_config.wifi.sta.cwjap_param.ssid, (char *)param->sta_ssid.ssid, param->sta_ssid.ssid_len);
printf("Recv STA SSID %s\r\n", (char *)g_blufi_config.wifi.sta.cwjap_param.ssid);
break;
case AXK_BLUFI_EVENT_RECV_STA_PASSWD:
memset(g_blufi_config.wifi.sta.cwjap_param.pwd, 0, 64);
strncpy(g_blufi_config.wifi.sta.cwjap_param.pwd, (char *)param->sta_ssid.ssid, param->sta_ssid.ssid_len);
printf("Recv STA PASSWORD %s\r\n", (char *)g_blufi_config.wifi.sta.cwjap_param.pwd);
break;
case AXK_BLUFI_EVENT_RECV_SOFTAP_SSID:
break;
case AXK_BLUFI_EVENT_RECV_SOFTAP_PASSWD:
break;
case AXK_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM:
break;
case AXK_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE:
break;
case AXK_BLUFI_EVENT_RECV_SOFTAP_CHANNEL:
break;
case AXK_BLUFI_EVENT_GET_WIFI_LIST:
wifi_scan_start();
break;
case AXK_BLUFI_EVENT_RECV_CUSTOM_DATA:
printf("Recv Custom Data len:%d\r\n", param->custom_data.data_len);
printf("Custom Data:%.*s\n", param->custom_data.data_len, param->custom_data.data);
// echo
axk_blufi_send_custom_data(param->custom_data.data, param->custom_data.data_len);
break;
case AXK_BLUFI_EVENT_RECV_USERNAME:
/* Not handle currently */
break;
case AXK_BLUFI_EVENT_RECV_CA_CERT:
/* Not handle currently */
break;
case AXK_BLUFI_EVENT_RECV_CLIENT_CERT:
/* Not handle currently */
break;
case AXK_BLUFI_EVENT_RECV_SERVER_CERT:
/* Not handle currently */
break;
case AXK_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY:
/* Not handle currently */
break;
;
case AXK_BLUFI_EVENT_RECV_SERVER_PRIV_KEY:
/* Not handle currently */
break;
default:
break;
}
}
static _blufi_callbacks_t example_callbacks = {
.event_cb = example_event_callback,
.negotiate_data_handler = blufi_dh_negotiate_data_handler,
.encrypt_func = blufi_aes_encrypt,
.decrypt_func = blufi_aes_decrypt,
.checksum_func = blufi_crc_checksum,
};
int at_blufi_start(void)
{
int ret = -1;
axk_hal_blufi_init();
ret = _blufi_host_and_cb_init(&example_callbacks);
if (ret)
{
printf("%s initialise failed: %d\n", __func__, ret);
}
return ret;
}
static void cmd_blufi_init(char *buf, int len, int argc, char **argv)
{
at_blufi_start();
}
static void cmd_blufi_deinit(char *buf, int len, int argc, char **argv)
{
axk_blufi_profile_deinit();
axk_hal_blufi_deinit();
axk_blufi_adv_stop();
axk_hal_ble_role_set(BLE_ROLE_DEINIT);
}
const static struct cli_command cmds_user[] STATIC_CLI_CMD_ATTRIBUTE = {
{"blufi_init", "blufi deinit", cmd_blufi_init},
{"blufi_deinit", "blufi deinit", cmd_blufi_deinit},
};
void free_task(void *param)
{
uint8_t pcWriteBuffer[500];
while (1)
{
printf("=================================================\r\n");
printf("\r\nremaining memory = %ld,minimum memory = %ld\r\n", xPortGetFreeHeapSize(), xPortGetMinimumEverFreeHeapSize());
vTaskDelay(3 * 1000);
}
vTaskDelete(NULL);
}
static void proc_main_entry(void *pvParameters)
{
wifi_interface_init(blufi_wifi_event);
at_blufi_start();
vTaskDelete(NULL);
}
void main()
{
static TaskHandle_t proc_main_task;
bl_sys_init();
xTaskCreate(proc_main_entry, (char *)"main_entry", 1024, NULL, 15, &proc_main_task);
xTaskCreate(free_task, (char *)"free_task", 750, NULL, 1, NULL);
tcpip_init(NULL, NULL);
printf("blufi demo test\r\n");
}常见问题与踩坑提示
⚠️ App 搜不到 BLUFI 设备
原因:设备还没进入配网广播、距离太远,或手机蓝牙/定位权限没开
解决:等串口打印 BLUFI init finish 后再扫;Android 手机搜 BLE 必须开启「定位权限」;手机靠近开发板(1 米内);App 已连接过其它设备时先断开重扫
⚠️ App 连上了,发送后设备却连不上路由器
原因:路由器是 5GHz 频段、密码错误、或信号过弱
解决:确认路由器开 2.4GHz(5GHz 不支持);核对 SSID/密码(注意空格);开发板靠近路由器测试
⚠️ 配网成功断电重启后不自动连网
原因:配网信息未保存成功(未等到 BLUFI save ssid&&pwd 日志就断电)
解决:等串口打印 BLUFI save ssid&&pwd 并出现 CODE_WIFI_ON_GOT_IP 后再断电;重新走一遍配网流程
⚠️ 想换一个 Wi-Fi 重新配网
原因:设备已保存旧密码并自动连接,不再进入配网广播
解决:按住开发板 EN 键复位重新进入配网模式,或用串口 CLI 执行 blufi_deinit 后 blufi_init 重新开启配网
⚠️ 串口看不到日志
原因:串口助手波特率不对(本示例走系统默认日志串口 921600),或串口号选错
解决:串口助手波特率选 921600;确认串口号(ls /dev/ttyUSB*);烧录完成拔插一次 USB 重开串口
⚠️ 烧录一直卡住等待,进度条不动
原因:未进入下载模式,或数据线只能充电不能传数据
解决:烧录时按提示长按 EN 键进入下载模式;换一根能传数据的 Type-C 数据线后重试
运行自检
串口依次打印 BLUFI init finish → BLUFI ble connect → BLUFI save ssid&&pwd,且手机 EspBlufi App 显示设备已成功连接路由器,即 blufi 配网验证通过。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

