CH58X BLE 配对流程详解

本文基于 CH584 MCU 的 BLE 心率传感器项目,逐层拆解 BLE 外设的配对流程。文中的代码注释和配置均来自项目实际源码。

系统架构

整个 BLE 协议栈运行在沁恒 CH58x 系列的 TMOS 任务调度系统之上。调用链路如下:

Main.c 主循环
  └─ ble_peripheral_process() → TMOS_SystemProcess()
       └─ 事件分发 ble_peripheral_process_event()
            └─ GAPRole_PeripheralStartDevice() → 启动设备
                 └─ GAP → GATT → SM(安全管理器)
                      └─ CH58x BLE Controller(硬件链路层)

Main.c 在主循环中交替执行 ble_peripheral_process() 和应用调度器。协议栈事件通过 TMOS 消息机制异步回调到应用层。

编译期 BLE 配置

项目在 src/app_project_config.h 中覆盖了 SDK 的默认 BLE 参数:

参数 说明
BLE_MEMHEAP_SIZE 10240 (10 KB) 协议栈内存池
BLE_BUFF_MAX_LEN 251 (ATT_MTU = 247) 最大应用数据包长度
BLE_BUFF_NUM 8 收发缓冲区数量
BLE_TX_NUM_EVENT 6 单连接事件最大发包数
BLE_TX_POWER LL_TX_POWEER_MINUS_10_DBM 发射功率 -10 dBm

SDK 层 CONFIG.h 还控制了绑定存储的硬件参数:

参数 说明
BLE_SNV TRUE 启用 SNV 绑定存储
BLE_SNV_ADDR 0x77000 - FLASH_ROM_MAX_SIZE 存储起始地址
BLE_SNV_BLOCK 256 bytes 块大小
BLE_SNV_NUM 1 块数量
PERIPHERAL_MAX_CONNECTION 1 最大同时连接数

配对初始化

入口函数 ble_peripheral_init() 位于 src/ble_peripheral.c:103,分三步完成初始化。

第一步:底层初始化

CH58x_BLEInit();          // BLE 协议栈库初始化
HAL_Init();               // 硬件抽象层初始化
GAPRole_PeripheralInit(); // 配置为 Peripheral 角色

CH58x_BLEInit() 内部配置 SNV 的 Flash 读写回调,为绑定数据持久化准备硬件通路。

第二步:广播与连接参数

#define BLE_PERIPHERAL_ADV_INTERVAL      160  // 160 × 0.625 ms = 100 ms
#define BLE_PERIPHERAL_MIN_CONN_INTERVAL 6    // 6 × 1.25 ms = 7.5 ms
#define BLE_PERIPHERAL_MAX_CONN_INTERVAL 6    // 同上,固定连接间隔
#define BLE_PERIPHERAL_SLAVE_LATENCY     0    // 无延迟
#define BLE_PERIPHERAL_CONN_TIMEOUT      100  // 100 × 10 ms = 1 s

广播间隔 100 ms,连接间隔固定 7.5 ms,适合低延迟数据传输。

第三步:安全参数——核心决策

uint32_t passkey  = 0u;
uint8_t  pair_mode = GAPBOND_PAIRING_MODE_WAIT_FOR_REQ;  // 等主机发起
uint8_t  mitm      = FALSE;                               // 无 MITM 保护
uint8_t  bonding   = FALSE;                               // 不保存绑定
uint8_t  io_cap    = GAPBOND_IO_CAP_NO_INPUT_NO_OUTPUT;  // 无输入输出

GAPBondMgr_SetParameter(GAPBOND_PERI_DEFAULT_PASSCODE, sizeof(passkey), &passkey);
GAPBondMgr_SetParameter(GAPBOND_PERI_PAIRING_MODE, sizeof(pair_mode), &pair_mode);
GAPBondMgr_SetParameter(GAPBOND_PERI_MITM_PROTECTION, sizeof(mitm), &mitm);
GAPBondMgr_SetParameter(GAPBOND_PERI_IO_CAPABILITIES, sizeof(io_cap), &io_cap);
GAPBondMgr_SetParameter(GAPBOND_PERI_BONDING_ENABLED, sizeof(bonding), &bonding);

Bonding 关闭意味着每次断开后不会保存对端设备的密钥信息,重连时需要重新完成配对流程。

配对方式:Just Works

当前配置组合 (NoInputNoOutput + MITM=FALSE) 决定了采用 BLE 的 Just Works 配对:

Central(手机)                        Peripheral(CH58x)
     │                                       │
     │──── Pairing Request ─────────────────→│  主机发起配对
     │←─── Pairing Response ────────────────│  IO=NoInputNoOutput, Bonding=0
     │                                       │
     │  ← 双方用 TK=0 计算确认值和 STK →    │  无用户交互
     │                                       │
     │←── Encryption Start ─────────────────→│  链路加密建立

广播与扫描响应

设备启动后发出两类广播数据:

广播包(31 字节上限):

Flags: LE General Discoverable + BR/EDR Not Supported
Service UUID: 0xFFF0(自定义服务)

扫描响应(在主机主动扫描时返回):

设备名称: "CH584F_HR_PROTO"(15 字符完整名称)
连接间隔范围: 最小 6,最大 6(固定 7.5 ms)
TX Power: 0(未显式指定功率值)

广播过滤策略使用默认值 GAP_FILTER_POLICY_ALL,未启用白名单过滤。

连接与断开事件流

应用层通过 ble_peripheral_state_changed() 回调处理状态迁移。该函数在 ble_peripheral.c:330 实现:

状态机转换:

   [空闲/广播中]
        │ GAP_LINK_ESTABLISHED_EVENT

   [已连接]
        ├─ 记录 conn_handle
        ├─ 80 TU 后发起连接参数更新请求
        └─ 激活 TinyFrame 协议层

        │ GAP_LINK_TERMINATED_EVENT

   [等待/广播中]
        ├─ 清除连接句柄
        ├─ 清除协议层连接状态
        └─ 若之前广播已开启 → 自动恢复广播

连接参数更新请求始终使用相同的 min/max 间隔(6),本质上是在确认而非协商。断开后广播恢复逻辑由 ble_peripheral_set_advertising() 的状态变量 g_ble_peripheral_advertising_enabled 控制——如果应用层此前开启了广播,断开后自动延续。

绑定回调:当前全部为空

协议栈通过 gapBondCBs_t 结构体向应用层通知配对事件:

static gapBondCBs_t g_ble_peripheral_bond_callbacks = {
    NULL,  // passcodeCB   — 动态密码请求
    NULL,  // pairStateCB  — 配对状态变化
    NULL,  // oobCB        — OOB 安全配对
};

三个回调全部为 NULL 的影响:

SNV 绑定存储:硬件已就绪,尚未启用

BLE_SNV = TRUE 已在 CONFIG.h 中开启,Flash 读写回调已在 CH58x_BLEInit() 中注册。但由于 bonding = FALSE,协议栈不会实际写入任何绑定数据。

如果将来启用 bonding,每个绑定设备将占用 6 条 NV 记录,NV ID 计算公式为:

nvID = (bondIdx * 6 + offset) + 0x0200

offset 0: gapBondRec_t    — 设备地址、地址类型、状态标志
offset 1: gapBondLTK_t    — 本地 LTK(16 bytes)、ediv、rand、keySize
offset 2: gapBondLTK_t    — 对端 LTK
offset 3: IRK (16 bytes)  — 身份解析密钥
offset 4: CSRK (16 bytes) — 签名解析密钥
offset 5: SignCounter      — 签名计数器(uint32_t

存储区域位于 Flash 末尾(0x77000 - FLASH_ROM_MAX_SIZE),单块 256 bytes。如果设备需要绑定多台主机,需要调整 BLE_SNV_NUM 扩大存储空间。

启用安全配对的方法

如果需要提升安全性,按以下顺序修改:

1. 启用 Bonding

uint8_t bonding = TRUE;
GAPBondMgr_SetParameter(GAPBOND_PERI_BONDING_ENABLED, sizeof(bonding), &bonding);

2. 实现绑定回调

至少实现 pairStateCB,在 GAPBOND_PAIRING_STATE_BOND_SAVED 状态时记录绑定完成。如果需要动态密码,则实现 passcodeCB

3. 为 GATT 特征添加加密权限

当前 BLE UART 服务的读写权限不加加密限制(ble_uart_service_16bit.c)。需要修改两处:

RX 特征值(第 138 行),将 GATT_PERMIT_WRITE 替换为:

GATT_PERMIT_ENCRYPT_WRITE,

TX 特征 CCCD(第 124 行),将 GATT_PERMIT_READ | GATT_PERMIT_WRITE 替换为:

GATT_PERMIT_ENCRYPT_READ | GATT_PERMIT_ENCRYPT_WRITE,

加密后,主机必须先完成配对建立加密链路,才能读写这些特征。GATT_PERMIT_ENCRYPT_READGATT_PERMIT_ENCRYPT_WRITE 定义在 CH58xBLE_LIB.h 中,分别为 0x400x80

4. 按需启用 MITM

如果设备有显示或输入能力,可以启用 MITM 保护:

uint8_t mitm = TRUE;
uint8_t io_cap = GAPBOND_IO_CAP_DISPLAY_YES_NO;

注意:启用 MITM 后配对需要用户交互(确认数字或输入密码),对无屏设备不适用。

总结

当前固件的 BLE 配对策略可以归纳为几条关键事实: