跳转至

BR 开发指南

本文以 /samples/native_samples/br 为对象,介绍如何使用两块 HiDiTing V100 完成 BR/EDR 设备发现、配对、SPP 连接和双向数传验证。协议选型和更多 Profile 场景见蓝牙开发指南

BR 驱动背景知识

Demo 在同一份固件中同时注册 SPP Client 和 SPP Server 命令。测试时一块板只执行客户端命令,另一块板只执行服务端命令。

角色 本地名称 本地地址 App ID SPP 参数
Client demo_device_1 01:01:01:01:01:01 1 UUID {0x11, 0x22},通道 10,Port ID 6414
Server demo_device_2 02:02:02:02:02:02 2 UUID {0x11, 0x22},通道 10,Port ID 初值 6414

Client 扫描回调将地址为 02:02:02:02:02:02 的设备识别为目标并发起配对。连接和配对属于设备层,SPP Port 连接属于 Profile 层;设备连接成功不代表 SPP Port 已可发送数据。

快速跑通 BR Demo

准备条件

  • 两块 HiDiTing V100 开发板,分别作为 SPP Client 和 SPP Server。
  • 两个可独立操作的串口,波特率为 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+BRSERVERINIT
AT+BRSERVERSTACKENABLE
AT+BRSPPSERVERINIT
AT+BRSTACKBROADCAST
  1. BRSERVERINIT 注册设备层回调,配置地址、名称和 Class of Device。
  2. BRSERVERSTACKENABLE 使能 BR/EDR 协议栈。
  3. BRSPPSERVERINIT 使能 Port 服务,创建服务端口并注册 SPP 回调。
  4. BRSTACKBROADCAST 将设备设为可发现。

BR 服务端完成本地设备初始化

BR 服务端启动可发现

启动客户端并配对

在客户端开发板依次输入:

AT+BRCLIENTINIT
AT+BRCLIENTSTACKENABLE
AT+BRSPPCLIENTINIT
AT+BRSTARTSCAN

扫描日志会输出设备名、地址和 Class of Device。发现目标地址后 Demo 自动发起配对,配对回调中停止扫描。

BR 客户端完成本地设备初始化

BR 客户端发现目标服务端

建立 SPP 连接

从配对和连接回调日志中确认对端 dev_id。首次联调时通常为 0,但产品代码不应写死。在客户端输入:

AT+BRSPPCLIENTCONNECT=<dev_id>

服务端的连接请求回调会自动接受,双方的 Port 状态回调应上报 BR_PROFILE_STATE_CONNECTED。同时记录回调中的 Port ID,后续收发以该运行时值为准。

BR 客户端建立 SPP Port 连接

BR 服务端接受 SPP Port 连接

双向数传

当两端的 Port 均为已连接状态时,客户端发送:

AT+BRCLIENTSPPSENDDATA=<dev_id>,<port>,Hello,5

服务端发送:

AT+BRSPPSERVERSENDDATA=<dev_id>,<port>,World,5

命令的 data_len 不得大于字符串实际长度,Demo 单次最多接受 64 字节。两端接收回调应输出对端发送的数据。

BR 客户端发送 SPP 数据

BR 服务端发送 SPP 数据

BR 服务端接收 SPP 数据

断开与释放

在客户端依次输入:

AT+BRSPPCLIENTDISCONNECT=<dev_id>,<port>
AT+BRCLIENTREMOVEPAIR=<dev_id>
AT+BRSPPCLIENTDISABLE
AT+BRCLIENTSTACKDISABLE

在服务端确认 Port 断开后输入:

AT+BRSERVEREMOVEPAIR=<dev_id>
AT+BRSPPSERVERDISABLE
AT+BRSERVERSTACKDISABLE

如果只是临时断线并希望后续快速重连,可保留配对记录,不执行 REMOVEPAIR

文件结构与代码走读

文件结构

/samples/native_samples/br
├── CMakeLists.txt       # 组件、源文件和编译定义
├── br_client.c         # 客户端设备层、SPP Port 和数传流程
├── br_client.h         # 客户端 AT 入参结构、校验规则和命令表
├── br_server.c         # 服务端设备层、SPP Port 和数传流程
├── br_server.h         # 服务端 AT 入参结构、校验规则和命令表
└── README.md           # Demo 命令与编译入口

设备层回调

客户端和服务端都通过 bs_br_callbacks_t 注册协议栈、连接和配对回调,客户端额外注册扫描结果回调:

bs_br_callbacks_t callbacks = {
    .stack_state_cb = demo_stack_state_cb,
    .connect_state_cb = demo_connect_state_cb,
    .pair_complete_cb = demo_pair_complete_cb,
    .discovery_result_cb = demo_discovery_result_cb,
};

connect_state_cb 的实际签名包含 dev_id、连接状态、加密状态和原因码:

static void demo_connect_state_cb(uint16_t dev_id,
    uint8_t conn_state, uint8_t encrypt_state, uint32_t reason);

回调函数类型必须与 SDK 完全一致,不能用 Port 回调的参数形式代替设备层回调。

客户端扫描和配对

Client 在初始化时保存目标地址,扫描回调先校验指针,再只比较地址数组:

if (discover_result == NULL) {
    return;
}
if (memcmp(discover_result->addr.addr,
    g_target_device_addr.addr,
    sizeof(g_target_device_addr.addr)) == 0) {
    (void)bs_br_spp_client_start_pair(&discover_result->addr);
}

设备名字段的容量为 BS_BR_DEVICE_NAME_MAX_LEN,打印时限制最大长度,不假设输入一定以 \0 结尾。

SPP Port 初始化

服务端的核心是创建服务端口,客户端创建相同 UUID 和通道的远端端口描述:

bt_uuid_t spp_uuid = {
    .uuid_len = 0x02,
    .uuid = {0x11, 0x22},
};

ret = bs_br_port_enable();
ret = bs_br_port_create_remote_port(&spp_uuid, 10, 6414);
ret = bs_br_port_register_callbacks(&spp_uuid, &port_callbacks);

服务端将 conn_req_cbk 设为非空,在连接请求回调中调用 bs_br_port_send_conn_rsp() 接受连接;客户端不接收入站连接,该回调为空。

Port 状态和数据安全

demo_conn_state_cbk() 维护 g_spp_con_status,发送前必须为 BR_PROFILE_STATE_CONNECTED。AT 字符串最长 64 字节,发送函数同时检查入参指针、声明长度、实际字符串长度和最大长度:

size_t actual_len = strnlen((const char *)args->data,
    BR_SEND_DATA_LEN_MAX);
if (args->data_len > actual_len ||
    args->data_len > BR_SEND_DATA_LEN_MAX) {
    return AT_RET_SYNTAX_ERROR;
}

接收回调中的 data 是字节数组,只能在 data_len 范围内访问。实际产品应将数据交给带缓冲和分帧能力的业务任务,避免在回调中长时间处理。

AT 命令编号与注册

at_cmd_entry_t.cmd_id 要求全局不重复。Demo 为 BLE、BR Client 和 BR Server 分配独立区间,BR Client 使用 0x2310~0x2319,BR Server 使用 0x2320~0x2327。注册函数检查 uapi_at_cmd_table_register() 返回值,便于在日志中发现名称或编号冲突。

BR 设备层和 Port 结构体、枚举、入参及返回值见 BR/EDR 连接接口Port 接口

基于 BR Demo 开发自己的应用

  1. /samples/native_samples 下建立独立目录,从 br 组件复用与产品角色一致的 Client 或 Server 源文件。
  2. 为产品配置合法的本地地址、设备名和 Class of Device,不使用 Demo 的重复地址。
  3. 为 SPP 服务定义正式 UUID、通道和业务帧格式,发送前检查 Port 连接状态。
  4. 将目标设备过滤从固定地址替换为产品策略,同时校验地址、设备名和服务信息。
  5. 将连接、配对和 Port 回调转换为业务状态机事件,为扫描、连接和收发增加超时、重试和资源回收。
  6. 为业务层提供明确的初始化、扫描、连接、收发和释放入口,从应用任务、服务或 UI 事件调用,并对业务参数定义长度和取值范围。
  7. 将组件加入目标配置,用 fbb set-target pack_diting_communityfbb build --clean 做全量编译,并通过业务入口验证 SPP 生命周期和异常恢复。

如需保留板端串口回归,可再为同一组业务入口增加独立 AT 命令并分配不重复的 cmd_id。AT 层只负责参数校验和调用转发,不参与 BR 连接与 SPP 数据通路的生命周期。

注意事项

  • dev_id 和 Port ID 是运行时值,回调参数优先于 Demo 中的预设值和文档示例。
  • 客户端和服务端的 UUID、通道必须一致;只建立设备链路不能代替 SPP Port 连接。
  • 断开时先停止数据生产,再断开 Port、按需移除配对、去使能 Port 和协议栈。
  • Demo 的接收日志按字符显示;处理二进制业务数据时应使用十六进制日志,避免误判控制字节。

常见错误

现象 原因与处理
Client 扫描不到 Server 确认 Server 已执行 BRSTACKBROADCAST,本地地址为 02:02:02:02:02:02,扫描未被其他业务停止
发现 Server 但没有配对 检查扫描回调地址比较长度是否为地址数组长度,并查看 bs_br_start_pair() 错误码
BRSPPCLIENTCONNECT 失败 确认使用当次连接的 dev_id,双方 Port 服务已使能,UUID 和通道一致
发送提示未连接 等待 Port 状态回调上报 BR_PROFILE_STATE_CONNECTED,不要仅以设备连接回调为准
发送命令返回语法错误 data_len 必须大于 0、不大于字符串实际长度且不超过 64
断开后重连失败 确认 Port 断开回调已完成;如已移除配对,重新扫描和配对