概述
主机(Master) 是 BLE 通信中主动的一方:负责扫描(寻找)周围广播的设备,发现目标后发起连接,连上后双方即可互相传数据。本教程让 Ai-WB2 扮演主机角色,按 MAC 地址(蓝牙设备的「门牌号」)定向连接一台从机,并把串口(UART,电脑和开发板之间逐位传数据的通道)收到的数据通过 BLE 透传给从机,实现「串口 ⇄ 蓝牙」双向透传。适合做「手机/网关读取传感器」这类应用。
用大白话讲:主机就像拿着手机「找朋友」的你:先竖起耳朵听周围谁在广播(喊话:「我在这里!」),听到目标后主动上前「加好友」(连接),加完好友就能互相递纸条(传数据)。本教程里的开发板就是那个「找朋友」的人,它按门牌号(MAC 地址)精确找到约定的从机,加完好友后,你在电脑上往它串口里打的字,会原样递到从机手上。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/bluetooth/ble_master编写,代码可在本地 SDK 中直接找到。
打开终端,进入官方 ble_master 示例工程目录:
cd ~/Ai-Thinker-WB2/applications/bluetooth/ble_master
说明:
cd是「进入目录」命令,这里进入主机示例工程目录;后续的make编译、make flash烧录命令都必须先在这个目录里执行。
📌 注意:该工程由多个源文件组成(
main.c、ble_interface.c、ble_common_api.c、ble_central_api.c),BLE 扫描/连接的实现分散在ble_common_api.c、ble_central_api.c中。本文只展示main.c,其余文件见官方工程。
工程目录结构说明:
| 文件 | 作用 |
|---|---|
ble_master/main.c |
主程序:初始化、扫描连接、串口透传逻辑,本教程主要看的文件 |
ble_master/ble_interface.c/.h |
工程自定义接口层(供 main.c 调用的 BLE 函数声明) |
ble_master/ble_common_api.c |
通用 BLE API 实现(含 axk_HalBleInit) |
ble_master/ble_central_api.c |
主机角色 API 实现(扫描/连接/发送,含连接状态变量) |
打开 ble_master/main.c,找到开头的 MAC 地址数组(占位符 = 代码里先写好的示例内容,需要换成你自己的真实值):
/*填写从机mac地址*/
static uint8_t slave_mac[6] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88};
把 {0x88, 0x88, 0x88, 0x88, 0x88, 0x88} 改成你的从机开发板的实际 MAC 地址。怎么查从机的 MAC:用手机蓝牙 App(如 nRF Connect)扫描,从机广播的设备信息里就能看到它的 MAC(形如 88:88:88:88:88:88)。
💡 第 3 个参数
BLE_MASTER_AUTOCONN_ENABLE表示自动重连:连接断开后主机会自动再去找从机。MAC 填错或从机没开机,就会一直打印no ble connect!。
打开 ble_master/main.c,本步完整代码已移至文末,见:
📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(
applications/bluetooth/ble_master/ble_master/main.c)完全一致。
代码要点:
| API | 作用 |
|---|---|
axk_HalBleInit() |
初始化 BLE 主机功能(定义在 ble_common_api.c),不初始化后面扫描/连接都用不了 |
axk_HalBleCentralStartScan() |
开始扫描周围广播的设备,不扫描就发现不了从机 |
axk_HalBleCentralConnect(slave_mac, NULL, 1) |
按 MAC 地址定向连接从机并开自动重连,MAC 填错永远连不上 |
bleuart_connect_status == 1 |
连接状态标志(1=已连上),没连上时发数据会打印 no ble connect! |
axk_HalBleCentralTTWrite(ret, data) |
把串口收到的数据经 BLE 发给从机,返回负值对应不同错误原因 |
hosal_uart_receive(&ble_uart_dev, ...) |
轮询读串口,就是你往串口助手打字时数据进来的入口 |
在工程目录执行编译:
make -j8
说明:
make是「编译」命令,把代码变成开发板能运行的固件(烧进开发板的程序);-j8表示用 8 个 CPU 核并行编译,更快。
编译成功后生成固件 build_out/ble_master.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 键进入下载模式,等待进度条完成即烧录成功。两块开发板记得分别接线、分别烧录。
主机与从机两块板都上电运行(从机请先烧好 BLE 从机 示例)。给主机接两个串口助手窗口:一个看日志,一个发数据。
打开串口助手(波特率 115200——本示例在 hosal_uart_dev_t 配置里把串口设成了 115200),主机开机打印:
MY BLE MASTER
连接成功后,在主机串口输入任意字符,从机串口应原样收到;反过来从机串口发字符,主机串口也能收到——这就是「双向透传」。
主机串口打印 MY BLE MASTER 且与从机建立连接(不再打印 no ble connect!)即为成功;如果一直打印 no ble connect!,说明没连上从机(MAC 地址/从机是否开机/距离问题),见文末 FAQ。
💡 进阶验证:连接状态下拔掉从机电源再恢复,主机会自动重连(
BLE_MASTER_AUTOCONN_ENABLE生效),重连后透传自动恢复,无需重启主机。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
本文 API 汇总
说明:
axk_HalBle*系列是该官方工程自定义的接口(定义在ble_common_api.c/ble_central_api.c,通过extern声明后供main.c调用),不属于 SDK 平台层;hosal_uart_*、bl_sys_init为 SDK 平台层接口。
hosal_uart_init(uart)
按结构体配置初始化串口(本例 uart_id=1、TX=GPIO16、RX=GPIO7、波特率 115200)。
参数:
uart:hosal_uart_dev_t结构体指针,必填,含串口号、引脚、波特率等配置
返回值:成功返回 0;失败返回负值错误码
hosal_uart_send(uart, txbuf, size)
通过串口发送一段数据(本例用它打印日志和错误提示)。
参数:
uart:hosal_uart_dev_t结构体指针txbuf:待发送数据指针,必填size:发送字节数
返回值:成功返回发送的字节数(大于 0);失败返回负值错误码
hosal_uart_receive(uart, data, expect_size)
轮询接收串口数据(本例循环调用,作为「电脑输入 → 蓝牙透传」的入口)。
参数:
uart:hosal_uart_dev_t结构体指针data:接收缓冲区指针,必填expect_size:期望接收的字节数(缓冲区大小)
返回值:成功返回实际接收的字节数(大于 0);失败返回负值错误码
axk_HalBleInit
初始化 BLE 主机功能(工程自定义 API,定义在 ble_common_api.c),所有主机操作的第一步。
返回值:uint8_t,成功返回 0(工程内约定);失败返回非 0
axk_HalBleCentralStartScan
开始扫描周围广播的蓝牙设备(工程自定义 API,定义在 ble_central_api.c)。
返回值:uint8_t,成功返回 0;失败返回非 0
axk_HalBleCentralConnect(mac, uuid, autoConnect)
按 MAC 地址(或 UUID)定向连接从机(工程自定义 API,定义在 ble_central_api.c)。
参数:
mac:目标从机 MAC 地址数组(6 字节),必填(本示例slave_mac)uuid:目标服务 UUID,本示例传NULL(按 MAC 精确匹配)autoConnect:是否自动重连,可选值:BLE_MASTER_AUTOCONN_ENABLE(1,启用)/0(关闭)
返回值:uint8_t,成功返回 0;失败返回非 0
axk_HalBleCentralTTWrite(len, data)
把数据通过 BLE 发送给已连接的从机(工程自定义 API,定义在 ble_central_api.c)。
参数:
len:数据长度(uint16_t),必填data:数据指针,必填
返回值:int,>= 0 发送成功;-1 连接状态异常、-2 数据长度错误、-3 数据为空、其他负值为发送失败
xTaskCreate(task, name, stack, param, prio, handle)
创建任务并加入就绪队列,由调度器按优先级调度执行。
参数:
task:任务入口函数指针,形如void task(void *arg),必填name:任务名称字符串(调试用),本示例"ble master"stack:任务栈大小(单位:字),本示例1024param:传给入口函数的参数指针,无参传NULLprio:任务优先级,可选值:0(最低)~19(最高,SDK 配置),本示例15handle:任务句柄输出指针,不需要可传NULL
返回值:成功返回 pdPASS;失败返回 pdFAIL(如内存不足)
完整代码
以下为 ble_master/main.c 完整源码,与官方示例(applications/bluetooth/ble_master/ble_master/main.c)完全一致。该工程由多个源文件组成(main.c、ble_interface.c、ble_common_api.c、ble_central_api.c),本文只展示 main.c,其余文件见官方工程:
📜 点击展开 main.c 完整代码
/*
* Copyright (c) 2020 Bouffalolab.
*
* This file is part of
* *** Bouffalolab Software Dev Kit ***
* (see www.bouffalolab.com).
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of Bouffalo Lab nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <FreeRTOS.h>
#include <task.h>
#include <timers.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <vfs.h>
#include <aos/kernel.h>
#include <aos/yloop.h>
#include <event_device.h>
#include <cli.h>
#include <lwip/tcpip.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>
#include <lwip/tcp.h>
#include <lwip/err.h>
#include <netutils/netutils.h>
#include <bl602_glb.h>
#include <bl602_hbn.h>
#include <bl_uart.h>
#include <bl_chip.h>
#include <bl_sec.h>
#include <bl_cks.h>
#include <bl_irq.h>
#include <bl_dma.h>
#include <bl_adc.h>
#include <bl_timer.h>
#include <bl_gpio_cli.h>
#include <bl_wdt_cli.h>
// #include <hal_uart.h>
#include <hal_sys.h>
#include <hal_gpio.h>
#include <hal_boot2.h>
#include <hal_board.h>
#include <looprt.h>
#include <loopset.h>
#include <bl_sys_time.h>
#include <bl_sys_ota.h>
#include <bl_romfs.h>
#include <fdt.h>
#include <bl_sys.h>
#include <bl_timer.h>
#include <easyflash.h>
#include <bl60x_fw_api.h>
#include <utils_log.h>
#include <libfdt.h>
#include <blog.h>
// #include <ble_cli_cmds.h>
#include <hosal_uart.h>
#include "ble_interface.h"
/*填写从机mac地址*/
static uint8_t slave_mac[6] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88};
#define OS_CMP(s1, s2) (strcmp(s1, s2) == 0)
extern unsigned char bleuart_connect_status;
extern uint8_t axk_HalBleInit();
extern uint8_t axk_HalBleCentralStartScan(void);
extern uint8_t axk_HalBleCentralConnect(uint8_t *mac, uint8_t *uuid, uint8_t autoConnect);
extern int axk_HalBleCentralTTWrite(uint16_t len, uint8_t *data);
hosal_uart_dev_t ble_uart_dev = {
.config = {
.uart_id = 1,
.tx_pin = 16, // TXD GPIO
.rx_pin = 7, // RXD GPIO
.cts_pin = 255,
.rts_pin = 255,
.baud_rate = 115200,
.data_width = HOSAL_DATA_WIDTH_8BIT,
.parity = HOSAL_NO_PARITY,
.stop_bits = HOSAL_STOP_BITS_1,
.mode = HOSAL_UART_MODE_POLL,
},
};
void bleuart_printf(char *buf)
{
hosal_uart_send(&ble_uart_dev, buf, strlen(buf));
}
void ble_user_init(void)
{
axk_HalBleInit();
axk_HalBleCentralStartScan(); // 扫描周围的蓝牙设备
axk_HalBleCentralConnect(slave_mac, NULL, BLE_MASTER_AUTOCONN_ENABLE); // 通过MAC地址和UUID指定连接从机,开启自动重连
}
int str2hex(char *pbuf, int len)
{
int i = 0;
for (i = 0; i < len; i++)
{
if (((pbuf[i] >= '0') && (pbuf[i] <= '9')) || ((pbuf[i] >= 'A') && (pbuf[i] <= 'F')) || ((pbuf[i] >= 'a') && (pbuf[i] <= 'f')))
{
if ((pbuf[i] >= '0') && (pbuf[i] <= '9'))
{
pbuf[i] -= '0';
}
else if (((pbuf[i] >= 'A') && (pbuf[i] <= 'F')))
{
pbuf[i] -= 'A';
pbuf[i] += 0x0A;
}
else
{
pbuf[i] -= 'a';
pbuf[i] += 0x0A;
}
if (i % 2)
{
pbuf[i / 2] = (pbuf[i - 1] << 4) | pbuf[i];
}
}
else
{
return -1;
}
}
return 0;
}
static void ble_loop_proc(void *pvParameters)
{
char data[250];
int ret, rep;
ble_user_init();
while (1)
{
/* Uart receive poll */
ret = hosal_uart_receive(&ble_uart_dev, data, sizeof(data));
if (ret > 0)
{ /* Uart send poll */
// hosal_uart_send(&ble_uart_dev, data, ret);
if (bleuart_connect_status == 1)
{
rep = axk_HalBleCentralTTWrite(ret, (uint8_t *)data);
if (rep >= 0)
{
// hosal_uart_send(&ble_uart_dev, data, ret);//
}
else if (rep == -1)
{
bleuart_printf("ble status error!\r\n");
}
else if (rep == -2)
{
bleuart_printf("ble data len error!\r\n");
}
else if (rep == -3)
{
bleuart_printf("ble data null!\r\n");
}
else
{
bleuart_printf("ble send fail!\r\n");
}
}
else
bleuart_printf("no ble connect!\r\n");
}
vTaskDelay(100);
}
vTaskDelete(NULL);
}
static void uart_init(void)
{
hosal_uart_init(&ble_uart_dev);
}
void main()
{
/*Init UART In the first place*/
// bl_uart_init(0, 16, 7, 255, 255, 115200);//2 * 1000 * 1000
uart_init();
bleuart_printf("MY BLE MASTER\r\n");
bl_sys_init(); // if use ble ,must init
xTaskCreate(ble_loop_proc, "ble master", 1024, NULL, 15, NULL);
}常见问题与踩坑提示
⚠️ 一直打印 no ble connect!
原因:MAC 地址没改成从机实际的、从机没开机/没在广播、或两块板离得太远
解决:用手机蓝牙 App 查看从机实际 MAC,替换 slave_mac 后重新编译烧录;确认从机已烧录并上电;两块板放近一些(1 米内)测试
⚠️ 串口看不到 MY BLE MASTER 日志
原因:串口助手波特率选错——本示例串口配置为 115200,不是常用的 921600
解决:把串口助手波特率改成 115200;确认串口号选对(ls /dev/ttyUSB* 查看)
⚠️ 连上了,但主机发数据从机收不到
原因:从机烧的不是 ble_slave 示例、或从机服务 UUID 不匹配
解决:确认从机板烧录的是 BLE 从机 示例(串口打印 [OS] proc_main_entry task...);两台设备距离太远时数据可能丢包,靠近测试
⚠️ 编译报错找不到 ble_interface.h 或其它头文件
原因:在错误的目录执行了编译命令,或工程文件不完整
解决:确认在 ~/Ai-Thinker-WB2/applications/bluetooth/ble_master 目录内执行 make -j8;工程需要的 ble_interface.c 等文件都在该目录下,不要只拷贝 main.c
⚠️ 烧录一直卡住等待,进度条不动
原因:未进入下载模式,或数据线只能充电不能传数据
解决:烧录时按提示长按 EN 键进入下载模式;换一根能传数据的 Type-C 数据线后重试
⚠️ 串口找不到设备 / 打不开
原因:USB 转串口驱动未装、权限不足,或设备号不对
解决:Linux 用 lsusb/dmesg 查看设备,权限不足执行 sudo usermod -aG dialout $USER 后重新登录;Windows 到设备管理器查看 COM 口
运行自检
主机串口打印 MY BLE MASTER,与从机建立连接后不再打印 no ble connect!,且主机串口输入的字符能在从机串口收到、从机串口输入的字符能在主机串口收到,即双向透传验证通过。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

