跳转至

BLE 开发指南

本文以 /samples/native_samples/ble 为对象,介绍如何使用两块 HiDiTing V100 完成 BLE 广播、扫描、配对、GATT 服务发现、读写和通知验证。协议选型和场景设计见蓝牙开发指南

BLE 驱动背景知识

Demo 在同一份固件中同时包含中央设备(Central)/GATT Client 和外围设备(Peripheral)/GATT Server 实现,通过 AT+BLEDEMOENABLE=<role> 选择本板角色。这里的 Central 和 Peripheral 是 BLE 协议及 SDK 接口中的连接角色,不是名为 Peripheral 的模块或特性。

参数 角色 本地名称 本地地址
0 中央设备(Central)+ GATT Client ble_client 11:23:34:45:56:61
1 外围设备(Peripheral)+ GATT Server ble_server 11:23:34:45:56:63

服务端创建 16 bit UUID 为 0x00AA 的服务和 UUID 为 0x01FF 的特征。UUID 在数组中按低字节在前存放,因此源码对应 {0xAA, 0x00}{0xFF, 0x01}。客户端必须在服务发现结果中按 UUID 匹配句柄,不能使用一个预设句柄。

快速跑通 BLE Demo

准备条件

  • 两块 HiDiTing V100 开发板,分别作为服务端和客户端。
  • 两个可独立操作的串口,波特率为 750000
  • 一站式 CLI 开发环境;环境安装和串口监视见一站式 CLI 开发环境使用指南

编译与烧录

完成一站式 CLI 环境配置后执行:

fbb set-target pack_diting_community
fbb build --clean

使用一站式 CLI 烧写固件并打开 UART2 串口监视器。以下为 Windows USB DFU 示例;将 COM3 替换为实际日志串口,其他平台和串口烧写参数参见一站式 CLI 开发环境使用指南

fbb flash -f "$env:FBB_SDK_DIR\output\3322\fwpkg\diting-community.fwpkg" --chip 3322 -d --timeout 180
fbb monitor --port COM3 --baud 750000

服务端操作

在第一块板输入:

AT+BLEDEMOENABLE=1

命令会完成协议栈初始化、GATT 服务注册和广播启动。日志应包含 ble init success、服务初始化成功和广播启动信息。

BLE 服务端使能并启动广播

客户端扫描与配对

在第二块板依次输入:

AT+BLEDEMOENABLE=0
AT+BLEDEMOSTARTSCAN=0

扫描回调发现名称为 ble_server 的对端后,Demo 停止扫描并发起配对。配对成功后自动发现全部 GATT 服务,日志会输出目标特征的 value handle

BLE 客户端扫描、配对并发现服务

如需手动对固定地址发起配对,先停止扫描,再使用对端地址:

AT+BLEDEMOSTARTSCAN=1
AT+BLEDEMOCONN=0x11,0x23,0x34,0x45,0x56,0x63

读写与通知

将下列 <value_handle> 替换为客户端服务发现日志中的实际句柄:

AT+BLEDEMOWRITE=<value_handle>
AT+BLEDEMOREAD=<value_handle>

Demo 的写入负载为固定测试数据,服务端写回调会按长度打印内容;读回调会输出句柄、长度和数据。

BLE 客户端读取特征值

在服务端使用连接后日志中的实例 ID 和属性 ID 发送通知:

AT+BLEDEMONOTIFY=<instance_id>,<attribute_id>

BLE 服务端发送通知

文件结构与代码走读

文件结构

/samples/native_samples/ble
├── CMakeLists.txt       # 组件、源文件和编译定义
├── ble_sample_common.c        # BLE 设备层、角色切换和 AT 命令
├── ble_sample_common.h        # AT 入参结构、校验规则和命令表
├── ble_client.c        # GATT Client 注册、服务发现、读写和通知回调
├── ble_client.h
├── ble_server.c        # GATT Server 服务模板、读写回调和通知
├── ble_server.h
└── README.md           # Demo 命令与编译入口

角色、状态和 AT 入参

ble_sample_common.h 中的角色与状态枚举是业务流程的核心数据:

typedef enum {
    BLE_API_BT_AS_CENTRAL,
    BLE_API_BT_AS_PERIPHERAL,
    BLE_API_BT_AS_MAX,
} ble_api_bt_role_t;

typedef struct {
    ble_bt_stack_state_t stack_status;
    ble_bt_discovery_state_t scan_status;
    ble_bt_discovery_state_t adv_status;
} ble_bt_stack_info_t;

ble_bt_stack_info_t 避免重复启动或停止协议栈、扫描和广播。扩展命令时,应在调用 SDK 前检查状态,在成功回调中更新最终状态。

初始化与回调驱动

bt_ble_init() 先初始化状态互斥锁,再注册设备层回调并使能 BLE。回调集中处理协议栈、连接、配对、扫描结果和移除配对结果:

static bs_ble_callbacks_t ble_cb = {
    .stack_state_cbk = process_ble_stack_state_change_cbk,
    .connect_state_cbk = process_ble_connect_state_changed_cbk,
    .pair_complete_cbk = process_ble_pair_cbk,
    .discover_result_cbk = process_ble_discover_cbk,
    .pair_remove_cbk = process_ble_remove_device_cbk,
};

外围设备角色使能成功后启动广播并初始化 GATT 服务;中央设备角色初始化 GATT Client。这两条分支由 g_role 决定,自定义业务时可以将单一角色拆成独立组件。

GATT Server 服务模板

ble_server.cbs_gatt_service_t 组织服务 UUID、实例回调和属性数组。业务需重点替换服务/特征 UUID、属性权限、最大长度和读写回调,而不是复制整份示例。注册流程只包含两个关键步骤:

ret = bs_gatts_register(&g_ble_app_uuid, &g_ble_demo_server_id);
if (ret != ERRCODE_BT_SUCCESS) {
    return ret;
}
ret = bs_gatts_service_instance_init(
    bs_get_ble_service_id(),
    (bs_gatt_service_template_t *)&g_ble_demo_gatt_service,
    &g_ble_demo_mobile_inst_id);

读写回调对 SDK 传入的指针和长度做校验,数据输出按 data_len 处理,不将二进制数据当作以 \0 结尾的字符串。

GATT Client 服务发现

ble_client.c 注册 Client 后,在配对成功回调中发现全部服务。发现回调遍历 service_count,只保存目标 UUID 对应的特征句柄:

static const uint8_t g_target_service_uuid[UUID16_LEN] = {0xAA, 0x00};
static const uint8_t g_target_character_uuid[UUID16_LEN] = {0xFF, 0x01};

bool is_target_service = service[s].service.uuid.uuid_len == UUID16_LEN &&
    memcmp(service[s].service.uuid.uuid,
        g_target_service_uuid, UUID16_LEN) == 0;

匹配到服务后再遍历其 character_list。找到目标特征后保存 value_handle,注册读写/通知回调并发起 MTU 协商。如果服务或特征未找到,流程会停止,避免用无效句柄读写。

读写和通知

Client 使用当前 dev_idclient_id 和发现得到的句柄调用 bs_gattc_read()/bs_gattc_write()。Server 发送通知时组装运行时实例和属性 ID:

bs_gatts_ntf_ind_t param = {
    .att_id = attribute_id,
    .value = g_sample_data,
    .value_len = sizeof(g_sample_data),
    .instance_id = instance_id,
};
ret = bs_gatts_notify_indicate(dev_id, server_id, &param);

接口结构体和返回值见 BLE 连接接口GATT 接口

基于 BLE Demo 开发自己的应用

  1. /samples/native_samples 下建立独立目录,复用 bleCMakeLists.txt 组件定义。
  2. 如产品只需要一个角色,只保留该角色的源文件和回调,减少全局状态。
  3. 为服务分配正式 UUID,设计特征的读、写、通知/指示权限和最大数据长度。
  4. 将广播名称、地址和扫描过滤从 Demo 常量替换为产品配置。
  5. 将回调中的打印替换为状态机事件;需延后使用的数据要按回调长度复制。
  6. 为业务层提供明确的启动、停止、连接和数据收发入口,从应用任务、服务或 UI 事件调用;不要让业务状态机依赖 AT 参数结构。
  7. 将组件加入目标配置,用 fbb set-target pack_diting_communityfbb build --clean 做全量编译,并通过业务入口验证初始化、连接、收发和反序释放。

如需保留板端串口回归,可再为同一组业务入口增加独立 AT 命令并分配不重复的 cmd_id。AT 层只负责参数校验和调用转发,注册失败不能被忽略;该步骤不是 BLE 业务组件的必需组成。

注意事项

  • dev_id、GATT Client/Server ID、服务实例 ID 和属性句柄都是运行时值,以当次回调和日志为准。
  • AT+BLEDEMOWRITE 是通路测试命令,负载由 Demo 固定;实际业务应将数据和长度作为入参并严格校验。
  • 去初始化顺序与初始化相反,先停止扫描/广播和业务,再停止自动连接、去使能协议栈并销毁同步资源。

常见错误

现象 原因与处理
BLEDEMOENABLE 失败 检查角色参数是否为 0/1,以及上一次协议栈是否已正常释放
客户端一直扫描 确认服务端已启动广播、名称为 ble_server,并确认通信双方未配置成相同角色
服务发现后没有目标句柄 核对对端服务 UUID 0x00AA 和特征 UUID 0x01FF,不要直接使用服务数组的固定下标
读写失败 使用当次发现日志中的 value handle,确认连接未断开且 MTU 协商已完成
收不到通知 确认使用当前 dev_id、实例 ID 和属性 ID,且客户端已注册通知回调