Skip to content

概述

配网(把 Wi-Fi 账号密码发给设备的过程)是物联网设备联网的第一步:设备没有屏幕和键盘,无法手动输入路由器密码,需要用手机把密码「递」给它。BluFi 是乐鑫(Espressif)提出的开源配网协议,安信可将其移植到 Ai-WB2:设备通过 BLE 广播(大喇叭喊话)暴露一个「配网入口」,手机 App 连接后通过加密通道把 Wi-Fi 的 SSID(Wi-Fi 的名字)和密码发给设备,设备自动连上路由器。本教程完成全流程:烧录 blufi 示例 → 手机 App 配网 → 设备自动上网。

用大白话讲:配网就像给门锁录入指纹。新装的门锁(设备)不认识你家的门(路由器),你需要用手机贴着它,把「开门信息」录进去,之后它自己就能开门(联网)。BluFi 就是借 BLE 蓝牙这个「贴身通道」来录入——密码走蓝牙传输,比直接裸奔在 Wi-Fi 上安全得多。

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

🎯本页目标用手机 EspBlufi App 通过 BLE 给 Ai-WB2 配网,设备自动连接 2.4GHz 路由器,掌握「蓝牙配网」这一产品级联网方式。
🧰前置条件① Ai-WB2 开发板一块 ② 2.4GHz 路由器(记住 SSID 与密码)③ 手机安装 EspBlufi App(Android 见 [EspressifApp/EspBlufi](https://github.com/EspressifApp/EspBlufi),iOS 在 App Store 搜索 EspBlufi)④ 已按 [SDK 安装](../sdk/sdk_intro) 完成开发环境搭建。
🔗相关章节Wi-Fi 连接原理见 [连接 Wi-Fi](../wifi/wifi_connect);BLE 广播概念见 [BLE 简介](./ble_intro)。

进入示例工程

打开终端,进入官方 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 键进入下载模式,等待进度条完成即烧录成功。

手机 App 配网
  1. 打开手机 EspBlufi App(给 App 开启蓝牙和定位权限),确认手机已连接你家 Wi-Fi(App 会读取手机当前 Wi-Fi 的名字)。
  2. 烧录完成后开发板会自动进入配网模式(BLE 广播中),App 首页应能看到名为 BLUFI 的设备,点击连接它。
  3. 连接成功后,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 finishBLUFI ble connectBLUFI 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_initaxk_blufi_adv_*blufi_security_* 来自 SDK 的 components/network/blufi/ 组件;wifi_interface_init 来自 SDK 的 Wi-Fi 接口层。

bl_sys_init

初始化系统时钟、外设等基础资源,使用 BLE/Wi-Fi 前必须先调用(本示例在 main() 里调用)。

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

tcpip_init(callback, arg)

初始化 lwIP TCP/IP 协议栈(socket、DNS 等网络功能依赖它),配网成功后自动联网需要它。

参数

  • callback:初始化完成回调函数指针,一般传 NULL
  • arg:回调参数,传 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_cbencrypt_funcchecksum_func 等)

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

axk_blufi_adv_start

开始 BLE 广播,手机 App 靠它才能搜到「配网入口」(来自 components/network/blufi/axk_blufi.h)。

返回值:无

axk_blufi_adv_stop

停止 BLE 广播(App 连接成功后调用,避免配网入口一直暴露)。

返回值:无

blufi_security_init

初始化配网加密(AES + DH 密钥协商),Wi-Fi 密码在蓝牙通道上是加密传输的(来自 blufi_security.h)。

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

blufi_security_deinit

释放配网加密资源(App 断开连接后调用)。

返回值:无

wifi_interface_init(event_cb)

注册 Wi-Fi 事件回调(配网结果、连接状态、IP 获取等事件的通知入口)。

参数

  • event_cb:事件回调函数指针,形如 void cb(int event, void *param),必填(本示例 blufi_wifi_event

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

xPortGetFreeHeapSize

返回当前可用堆内存字节数(排查内存不足问题,本示例定时打印)。

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

xPortGetMinimumEverFreeHeapSize

返回系统运行以来曾经最少的剩余堆内存字节数(排查内存峰值是否踩线)。

返回值:历史最小剩余堆字节数(无失败)

vTaskDelay(ms)

让当前任务挂起指定毫秒数,期间让出 CPU 给其他任务。

参数

  • ms:延时毫秒数,本示例 3000(每 3 秒打印一次内存)

返回值:无

xTaskCreate(task, name, stack, param, prio, handle)

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

参数

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

返回值:成功返回 pdPASS;失败返回 pdFAIL(如内存不足)


完整代码

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

📜 点击展开 blufi_main.c 完整代码
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_deinitblufi_init 重新开启配网

⚠️ 串口看不到日志
原因:串口助手波特率不对(本示例走系统默认日志串口 921600),或串口号选错
解决:串口助手波特率选 921600;确认串口号(ls /dev/ttyUSB*);烧录完成拔插一次 USB 重开串口

⚠️ 烧录一直卡住等待,进度条不动
原因:未进入下载模式,或数据线只能充电不能传数据
解决:烧录时按提示长按 EN 键进入下载模式;换一根能传数据的 Type-C 数据线后重试

运行自检

串口依次打印 BLUFI init finishBLUFI ble connectBLUFI save ssid&&pwd,且手机 EspBlufi App 显示设备已成功连接路由器,即 blufi 配网验证通过。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

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