跳转至

Hello BLE

广播与连接 — BLE GAP 广播、扫描、连接

学习目标

  • 理解 BLE 的 Peripheral(广播方)和 Central(扫描方)角色模型
  • 掌握 Peripheral 端广播配置和启动流程
  • 掌握 Central 端扫描、匹配和连接流程
  • 理解 BLE vs SLE 在广播/连接上的关键差异
  • 能够在两块 WS63 开发板之间建立 BLE 连接

规格与功能

规格项 Peripheral 端 Central 端
广播模式 可连接可扫描(CONN_SCAN_UNDIR)
广播间隔 30ms
扫描 PHY 1M
扫描间隔/窗口 30ms / 30ms(持续扫描)
连接间隔 30ms(由 Central 决定)
监管超时 5 秒
匹配方式 广播名 ble_server 扫描结果中搜索 ble_server
断连行为 自动重新广播 自动重新扫描

基本概念

Peripheral 和 Central

BLE 的两大角色分工明确:

角色 职责 对标 SLE
Peripheral 周期性广播宣告自身存在,等待被连接,通常做数据提供方 Terminal (T)
Central 监听广播、发起连接,通常做数据消费方 Grant (G)

关键差异:BLE 中连接参数由 Central 决定(Peripheral 可以请求更新但 Central 不保证同意),而 SLE 中 G/T 通过协商确定。

通信流程

sequenceDiagram
    participant P as Peripheral
    participant C as Central

    P->>P: 配置广播参数 + 广播数据
    P->>P: 启动广播

    C->>C: 配置扫描参数
    C->>C: 启动扫描

    loop 广播/扫描
        P-->>C: CONN_SCAN_UNDIR 广播包 (ble_server)
    end

    C->>C: 匹配到 "ble_server"
    C->>C: 停止扫描
    C->>P: 发起连接请求
    Note over P,C: 连接建立

广播模式

模式 能被扫描 能接受连接 典型场景
不可连接不可扫描 仅广播信息,不接受连接
可连接不可扫描 定向连接,不暴露自身
不可连接可扫描 信息发布,不需要连接
可连接可扫描 常规交互——本案例使用

BLE vs SLE 关键差异

BLE SLE
角色名称 Peripheral / Central Server(数据方) / Client(消费方),G(调度方) / T(终端)
连接间隔决定权 Central 决定 G/T 协商
服务模型 GATT (Service→Characteristic→Descriptor) SSAP (Service→Property→Descriptor)
广播 PHY 1M / 2M / Coded 1M / 2M / 4M

如果你先学了 SLE——BLE 的 Peripheral ≈ SLE 的 Server+Terminal,Central ≈ SLE 的 Client+Grant。但服务模型差异较大,后续 hello-notify 中会详细对比。

涉及 API

API 谁调用 用途
bts_le_gap_adv_set_param() Peripheral 设置广播参数
bts_le_gap_adv_set_data() Peripheral 设置广播数据
bts_le_gap_adv_start() Peripheral 启动广播
bts_le_gap_scan_set_param() Central 设置扫描参数
bts_le_gap_scan_start() Central 启动扫描
bts_le_gap_scan_stop() Central 停止扫描
bts_le_gap_connect() Central 发起连接
bts_le_gap_register_callbacks() 双方 注册 GAP 回调(广播/扫描/连接状态)

案例说明

两块 WS63:Peripheral 周期性广播名为 ble_server 的广播包,Central 扫描到后发起连接。

案例操作指导

第一步:编译

fbb build ws63-liteos-app

第二步:烧录并运行

Peripheral 先上电(让广播先跑起来),Central 后上电。

第三步:验证

Peripheral 串口输出:

[ble peripheral] start advertising...
[ble peripheral] connected, conn_id=0xXX

Central 串口输出:

[ble central] start scanning...
[ble central] found ble_server, connecting...
[ble central] connected, conn_id=0xXX

关键配置

广播参数

// 广播间隔:30ms — BLE 最小广播间隔,平衡发现速度和功耗
// 广播模式:CONN_SCAN_UNDIR(可连接可扫描)
gap_ble_adv_param_t adv_param = {
    .adv_int_min = 0x30,          // 30 × 0.625ms = 18.75ms
    .adv_int_max = 0x30,          // 固定间隔
    .adv_type    = CONN_SCAN_UNDIR,       // 可连接可扫描
    .own_addr_type = 0x00,
    .channel_map   = 0x07, // 37/38/39 三信道
};

扫描参数

// 扫描间隔/窗口:30ms — 持续扫描
gap_ble_scan_param_t scan_param = {
    .scan_interval = 0x30,        // 30 × 0.625ms
    .scan_window   = 0x30,        // 窗口 = 间隔 = 持续扫描
    .scan_type     = ACTIVE_SCAN,
    .scan_phy      = PHY_1M,
};

BLE vs SLE 参数对比

参数 BLE 典型值 SLE 典型值 说明
广播间隔 30ms 25ms BLE 最小 20ms,SLE 最小约 10ms
连接间隔 30ms 12.5ms BLE 最小 7.5ms
广播信道 37/38/39 76/77/78 不同频段

代码详解

Peripheral 端

static int ble_peripheral_task_handler(void *data)
{
    (void)data;

    // 1. 注册 GAP 回调
    gap_ble_register_callbacks(&g_gap_callbacks);

    // 2. 配置广播参数
    gap_ble_adv_param_t adv_param = { ... };  // 见上方关键配置
    gap_ble_adv_set_param(&adv_param);

    // 3. 配置广播数据(设备名 = ble_server)
    uint8_t adv_data[31];
    adv_data[0] = 0x02; adv_data[1] = 0x01; adv_data[2] = 0x06;  // Flags
    adv_data[3] = 0x0B; adv_data[4] = 0x09;                      // Complete Local Name
    memcpy(&adv_data[5], "ble_server", 10);
    gap_ble_adv_set_data(adv_data, sizeof(adv_data), NULL, 0);

    // 4. 启动广播
    gap_ble_adv_start();
    return 0;
}

Central 端

static int ble_central_task_handler(void *data)
{
    (void)data;

    gap_ble_register_callbacks(&g_gap_callbacks);
    gap_ble_scan_set_param(&scan_param);  // 见上方关键配置
    gap_ble_scan_start();
    return 0;
}

// 扫描结果回调
static void scan_result_cb(const gap_ble_scan_result_t *result)
{
    // 按广播名匹配目标设备
    if (strstr((char *)result->adv_data, "ble_server") != NULL) {
        gap_ble_scan_stop();                          // 停止扫描
        memcpy(&g_peer_addr, &result->addr, sizeof(bd_addr_t));
        gap_ble_connect(&g_peer_addr);                 // 发起连接
    }
}

连接状态回调

static void conn_state_cb(uint16_t conn_id, const bd_addr_t *addr,
                           gap_ble_conn_state_t state)
{
    if (state == CONNECTED) {
        g_conn_id = conn_id;
        printf("connected, conn_id=0x%04X\n", conn_id);
    } else if (state == DISCONNECTED) {
        g_conn_id = 0;
        // 自动恢复
        gap_ble_adv_start();   // Peripheral 重广播
        // gap_ble_scan_start(); // Central 重扫描
    }
}