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 扫描到后发起连接。
案例操作指导
第一步:编译
第二步:烧录并运行
Peripheral 先上电(让广播先跑起来),Central 后上电。
第三步:验证
Peripheral 串口输出:
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 重扫描
}
}