概念先知道
- Central(主机):主动扫描、发起连接的一方,相当于“客户端”。
- 扫描与过滤:例程用
bt_le_scan_start扫描,回调里解析广播包中的设备名,与CONFIG_BT_DEVICE_NAME(默认BTBLE-DEV)匹配才发起连接。 - 服务发现(GATT Discovery):连接后按“主服务 → 特征 → 描述符”逐级枚举对端的数据结构,拿到特征句柄后才能读写/订阅。
- 订阅通知:对支持 NOTIFY 的特征写 CCC 描述符(
bt_gatt_subscribe),对端一推送就能在回调里收到。
例程功能简介
本页对应博流官方 SDK 的 central 例程(examples/btble/central),演示 BLE **主机(Central)**完整流程:
- 扫描(
bt_le_scan_start)→ 按设备名BTBLE-DEV匹配 → 连接(bt_conn_create_le)→ 发现主服务/特征/描述符(bt_gatt_discover)→ 订阅 NOTIFY(bt_gatt_subscribe)→ 每秒写入数据(bt_gatt_write_without_response),断开后自动重新扫描。 - 配套:与 BLE 从机(peripheral 例程)配对使用效果最佳。
操作步骤
在终端进入 SDK 的 BLE central(主机)例程目录(前提:环境已按快速开始(Linux)或Windows搭好):
cd examples/btble/centralAi-M61 与 Ai-M62 统一填写 bl616:
make CHIP=bl616 BOARD=bl616dk按住 BOOT 键不放、短按 EN/RST 进入下载模式后烧录:
make flash CHIP=bl616 COMX=/dev/ttyUSB0本页例程会扫描设备名为 BTBLE-DEV(CONFIG_BT_DEVICE_NAME)的外设,并连接其测试服务(UUID 07af27a5-...)。在另一块开发板烧录 BLE 从机 例程,或准备一台同名且可连接的外设。
串口助手波特率 2000000。日志依次打印 Start scan successfully → [DEVICE FOUND] ... → Connection starting → Connected: ... → 服务/特征发现;随后模块每秒向写特征写入 244 字节测试数据,打印 Write success。
代码执行流程
例程调用的 API 介绍
bt_le_scan_start(&scan_param, device_found)
开始 BLE 扫描;每次发现设备回调 device_found。
参数:
scan_param:bt_le_scan_param(主动扫描/间隔/窗口)device_found:发现回调
返回值:成功返回 0;失败返回负值
bt_conn_create_le(addr, ¶m)
向指定地址发起 LE 连接。
参数:
addr:目标bt_addr_le_tparam:连接参数(间隔/延迟/超时)
返回值:bt_conn * 连接对象;失败返回 NULL
bt_gatt_discover(conn, &discover_params)
发现对端 GATT 服务/特征/描述符,结果逐项回调。
参数:
conn:连接对象params:发现参数(类型/句柄范围/回调)
返回值:成功返回 0;失败返回负值
bt_gatt_subscribe(conn, &subscribe_params)
订阅特征的 NOTIFY/INDICATE(写 CCC 描述符),推送数据走 notify 回调。
参数:
conn:连接对象params:订阅参数(ccc 句柄/值/回调)
返回值:成功返回 0;失败返回负值
bt_gatt_write_without_response(conn, handle, data, len, false)
无应答写:向特征句柄写入数据(Write Without Response),不需要对端确认。
参数:
conn:连接对象handle:特征值句柄data:数据len:长度(≤MTU-3)false:无需应答
返回值:成功返回 0;失败返回负值
完整代码
以下为 central 例程完整源码,与官方示例一致,默认折叠,点击展开:
📜 点击展开 central/main.c 完整代码
#include "shell.h"
#include <FreeRTOS.h>
#include "task.h"
#include "board.h"
#include "bluetooth.h"
#include "conn.h"
#include "conn_internal.h"
#if defined(BL702) || defined(BL602)
#include "ble_lib_api.h"
#else
#include "btble_lib_api.h"
#include "rfparam_adapter.h"
#endif
#if defined(BL616)
#include "bl616_glb.h"
#elif defined(BL618DG)
#include "bl618dg_glb.h"
#elif defined(BL616CL)
#include "bl616cl_glb.h"
#endif
#include "hci_driver.h"
#include "hci_core.h"
#include "gatt.h"
#include "bt_log.h"
#include "bflb_mtd.h"
#include "easyflash.h"
typedef struct{
TaskHandle_t handle;
uint32_t wr_hdl;
}wr_arg_t;
const char* discovery_str[] = {
"primary",
"secondary",
"include",
"characteristic",
"descriptor",
"attribute",
};
static u16_t gatt_mtu_size;
static wr_arg_t wr_arg;
static struct bflb_device_s *uart0;
static struct bt_conn *default_conn;
static struct bt_gatt_discover_params discover_params;
static struct bt_gatt_subscribe_params subscribe_params;
static void ble_start_scan(void);
static void start_discovery(u8_t type);
extern void shell_init_with_task(struct bflb_device_s *shell);
static void ble_write_task(void *arg)
{
wr_arg_t* args = (wr_arg_t*)arg;
uint8_t data[244] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
for(int i = 0; i < sizeof(data); ++i){
data[i] = i & 0xff;
}
while(args->handle){
int err = bt_gatt_write_without_response(default_conn, (uint32_t)arg & 0xffff, data,
(gatt_mtu_size - 3) > sizeof(data) ? sizeof(data) : (gatt_mtu_size - 3), false);
if(err){
printf("Write without response (err %d)\r\n", err);
}
printf("Write success\r\n");
vTaskDelay(pdMS_TO_TICKS(1000));
}
printf("Deleted blewrite task\r\n");
vTaskDelete(NULL);
}
void ble_write_task_set(bool enable, uint16_t handle)
{
wr_arg.wr_hdl = handle + 0;
if(!enable){
if(wr_arg.handle){
wr_arg.handle = NULL;
}
return;
}
if(xTaskCreate(ble_write_task, (char*)"blewrite", 256, (void*)&wr_arg, configMAX_PRIORITIES - 5, &wr_arg.handle) == pdPASS){
printf("Create ble_write_task success, start write\r\n");
}
else{
printf("Create ble_write_task failed\r\n");
}
}
static u8_t notify_func(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, u16_t length)
{
if (!params->value) {
printf("Unsubscribed\r\n");
params->value_handle = 0U;
return BT_GATT_ITER_STOP;
}
printf("Subscribed\r\n");
return BT_GATT_ITER_CONTINUE;
}
static void do_subscribe(uint16_t ccc_handle, uint16_t val)
{
subscribe_params.ccc_handle = ccc_handle;
subscribe_params.value = val;
subscribe_params.notify = notify_func;
int err = bt_gatt_subscribe(default_conn, &subscribe_params);
if (err) {
printf("Subscribe failed (err %d)\r\n", err);
} else {
printf("Subscribed starting\r\n");
}
}
static void print_chrc_props(u8_t properties)
{
printf("Properties: ");
if (properties & BT_GATT_CHRC_BROADCAST) {
printf("[bcast]\r\n");
}
if (properties & BT_GATT_CHRC_READ) {
printf("[read]\r\n");
}
if (properties & BT_GATT_CHRC_WRITE) {
printf("[write]\r\n");
}
if (properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) {
printf("[write w/w rsp]\r\n");
}
if (properties & BT_GATT_CHRC_NOTIFY) {
printf("[notify]\r\n");
}
if (properties & BT_GATT_CHRC_INDICATE) {
printf("[indicate]\r\n");
}
if (properties & BT_GATT_CHRC_AUTH) {
printf("[auth]\r\n");
}
if (properties & BT_GATT_CHRC_EXT_PROP) {
printf("[ext prop]\r\n");
}
}
static u8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr, struct bt_gatt_discover_params *params)
{
struct bt_gatt_service_val *gatt_service;
struct bt_gatt_chrc *gatt_chrc;
struct bt_gatt_include *gatt_include;
char str[37];
if (!attr) {
printf( "Discover %s complete\r\n", discovery_str[discover_params.type]);
if(discover_params.type == BT_GATT_DISCOVER_PRIMARY){
start_discovery(BT_GATT_DISCOVER_CHARACTERISTIC);
}
else if(discover_params.type == BT_GATT_DISCOVER_CHARACTERISTIC){
start_discovery(BT_GATT_DISCOVER_DESCRIPTOR);
}
else{
memset(params, 0, sizeof(*params));
}
return BT_GATT_ITER_STOP;
}
switch (params->type) {
case BT_GATT_DISCOVER_SECONDARY:
case BT_GATT_DISCOVER_PRIMARY:
gatt_service = attr->user_data;
bt_uuid_to_str(gatt_service->uuid, str, sizeof(str));
printf("Service %s found: start handle %x, end_handle %x\r\n", str, attr->handle, gatt_service->end_handle);
break;
case BT_GATT_DISCOVER_CHARACTERISTIC:
gatt_chrc = attr->user_data;
bt_uuid_to_str(gatt_chrc->uuid, str, sizeof(str));
printf("Characteristic %s found: attr->handle %x chrc->handle %x \r\n", str, attr->handle,gatt_chrc->value_handle);
print_chrc_props(gatt_chrc->properties);
if(gatt_chrc->properties & BT_GATT_CHRC_NOTIFY){
do_subscribe(attr->handle + 2, BT_GATT_CCC_NOTIFY);
}
if(!wr_arg.handle){
ble_write_task_set(true, gatt_chrc->value_handle);
}
break;
case BT_GATT_DISCOVER_INCLUDE:
gatt_include = attr->user_data;
bt_uuid_to_str(gatt_include->uuid, str, sizeof(str));
printf("Include %s found: handle %x, start %x, end %x\r\n", str, attr->handle,
gatt_include->start_handle, gatt_include->end_handle);
break;
default:
bt_uuid_to_str(attr->uuid, str, sizeof(str));
printf("Descriptor %s found: handle %x\r\n", str, attr->handle);
break;
}
return BT_GATT_ITER_CONTINUE;
}
static void start_discovery(u8_t type)
{
int err;
discover_params.func = discover_func;
discover_params.start_handle = 0x0001;
discover_params.end_handle = 0xffff;
discover_params.type = type;
discover_params.uuid = NULL;
err = bt_gatt_discover(default_conn, &discover_params);
if (err) {
printf("Discover %s failed (err %d)\r\n", discovery_str[discover_params.type], err);
}
}
static void ble_connected(struct bt_conn *conn, u8_t err)
{
char addr[BT_ADDR_LE_STR_LEN];
if(conn->type != BT_CONN_TYPE_LE) {
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (err || conn->type != BT_CONN_TYPE_LE) {
printf("Failed to connect to %s (%u) \r\n", addr, err);
return;
}
printf("Connected: %s \r\n", addr);
if (!default_conn) {
default_conn = conn;
}
gatt_mtu_size = bt_gatt_get_mtu(default_conn);
start_discovery(BT_GATT_DISCOVER_PRIMARY);
}
static void ble_disconnected(struct bt_conn *conn, u8_t reason)
{
char addr[BT_ADDR_LE_STR_LEN];
if(conn->type != BT_CONN_TYPE_LE){
return;
}
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
printf("Disconnected: %s (reason %u) \r\n", addr, reason);
if (default_conn == conn) {
default_conn = NULL;
}
// restart scan
ble_start_scan();
// stop write task
ble_write_task_set(false, 0);
}
static struct bt_conn_cb ble_conn_callbacks = {
.connected = ble_connected,
.disconnected = ble_disconnected,
};
static bool data_cb(struct bt_data *data, void *user_data)
{
char *name = user_data;
u8_t len;
switch (data->type) {
case BT_DATA_NAME_SHORTENED:
case BT_DATA_NAME_COMPLETE:
len = (data->data_len > 30 - 1)?(30 - 1):(data->data_len);
memcpy(name, data->data, len);
return false;
default:
return true;
}
}
static void device_found(const bt_addr_le_t *addr, s8_t rssi, u8_t evtype,
struct net_buf_simple *buf)
{
char le_addr[BT_ADDR_LE_STR_LEN];
char name[30];
int ret;
struct bt_conn* conn = NULL;
struct bt_le_conn_param param = {
.interval_min = BT_GAP_INIT_CONN_INT_MIN,
.interval_max = BT_GAP_INIT_CONN_INT_MAX,
.latency = 0,
.timeout = 400,
};
(void)memset(name, 0, sizeof(name));
bt_data_parse(buf, data_cb, name);
if(!strcmp(name, CONFIG_BT_DEVICE_NAME)){
bt_addr_le_to_str(addr, le_addr, sizeof(le_addr));
printf("[DEVICE FOUND]: %s, AD evt type %u, RSSI %i %s \r\n",le_addr, evtype, rssi, name);
// Stop scan
ret = bt_le_scan_stop();
if(ret){
printf("Stop scan fail err = %d\r\n", ret);
return;
}
// Do connect
conn = bt_conn_create_le(addr, /*BT_LE_CONN_PARAM_DEFAULT*/¶m);
if(!conn){
printf("Connection failed\r\n");
}else{
printf("Connection starting\r\n");
}
}
}
static void ble_start_scan(void)
{
struct bt_le_scan_param scan_param;
int err;
memset(&scan_param, 0, sizeof(scan_param));
scan_param.type = BT_HCI_LE_SCAN_ACTIVE;
scan_param.filter_dup = BT_HCI_LE_SCAN_FILTER_DUP_DISABLE;
scan_param.interval = BT_GAP_SCAN_FAST_INTERVAL;
scan_param.window = BT_GAP_SCAN_FAST_WINDOW;
err = bt_le_scan_start(&scan_param, device_found);
if(err){
printf("Failed to start scan (err %d) \r\n", err);
}else{
printf("Start scan successfully \r\n");
}
}
static void bt_gatt_mtu_changed_cb(struct bt_conn *conn, int mtu)
{
if(conn == default_conn){
gatt_mtu_size = mtu;
}
}
static void ble_notification_all_cb_t(struct bt_conn *conn, u16_t handle,const void *data, u16_t length)
{
printf("handle %x, %s\r\n", handle, bt_hex(data, length));
}
void bt_enable_cb(int err)
{
if (!err) {
bt_addr_le_t bt_addr;
bt_get_local_public_address(&bt_addr);
printf("BD_ADDR:(MSB)%02x:%02x:%02x:%02x:%02x:%02x(LSB) \r\n",
bt_addr.a.val[5], bt_addr.a.val[4], bt_addr.a.val[3], bt_addr.a.val[2], bt_addr.a.val[1], bt_addr.a.val[0]);
bt_conn_cb_register(&ble_conn_callbacks);
#if defined(BFLB_BLE_MTU_CHANGE_CB)
bt_gatt_register_mtu_callback(bt_gatt_mtu_changed_cb);
#endif
// start advertising
ble_start_scan();
// Register notification callback
bt_gatt_register_notification_callback(ble_notification_all_cb_t);
}
}
static TaskHandle_t app_start_handle;
static void app_start_task(void *pvParameters)
{
// Initialize BLE controller
#if defined(BL702) || defined(BL602)
ble_controller_init(configMAX_PRIORITIES - 1);
#else
btble_controller_init(configMAX_PRIORITIES - 1);
#endif
// Initialize BLE Host stack
hci_driver_init();
bt_enable(bt_enable_cb);
vTaskDelete(NULL);
}
int main(void)
{
board_init();
configASSERT((configMAX_PRIORITIES > 4));
uart0 = bflb_device_get_by_name("uart0");
shell_init_with_task(uart0);
bflb_mtd_init();
/* ble stack need easyflash kv */
easyflash_init();
/* Init rf */
if (0 != rfparam_init(0, NULL, 0)) {
printf("PHY RF init failed!\r\n");
return 0;
}
#if defined(BL618DG)
#if defined(CONFIG_BTBLE_USE_STANDALONE_PATH)
printf("cmd_set_btble_standalone\r\n");
extern void cmd_set_btble_standalone(int argc, char **argv);
cmd_set_btble_standalone(0, 0);
#else
printf("cmd_set_btble_combo\r\n");
extern void cmd_set_btble_combo(int argc, char **argv);
cmd_set_btble_combo(0, 0);
#endif
#endif
xTaskCreate(app_start_task, (char *)"app_start", 1024, NULL, configMAX_PRIORITIES - 2, &app_start_handle);
vTaskStartScheduler();
while (1) {
}
}FAQ
一直扫描但连不上?
确认从机烧录的是 peripheral 例程且设备名匹配:central 通过 CONFIG_BT_DEVICE_NAME 匹配(默认 BTBLE-DEV),可在 defconfig 修改保持一致;距离放近一点。
连接上但没有服务输出?
bt_gatt_discover 需要连接建立后立刻执行;若从机没有注册任何 GATT 服务,会发现不到特征。从机例程的 ble_tp_svc 提供了读写/通知特征。
`Write without response` 失败?
数据长度不能超过 MTU-3(默认 20 字节);例程已按 gatt_mtu_size 裁剪。若从机特征不支持 WRITE_WITHOUT_RESP,改用 bt_gatt_write。
断开后会自动重连吗?
例程断开后自动重新扫描(ble_start_scan),发现设备后再次连接,形成“断开→扫描→连接”循环。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

