1. 认识蓝牙GAP的四大核心角色
刚接触蓝牙开发时,很多人会被GAP(Generic Access Profile)的各种角色搞晕。其实用生活中的场景来理解就简单多了:Broadcaster就像街头发传单的人,Observer是接传单的路人,Peripheral类似便利店收银台,Central则是排队结账的顾客。NimBLE协议栈把这四种角色的实现封装得非常清晰,我们先来看看它们的典型应用场景。
Broadcaster最常见的使用场景是ibeacon。我去年做过一个博物馆导览项目,每个展品旁边放一个广播设备,持续发送展品ID。游客手机上的App(Observer角色)收到信号后就能自动弹出讲解页面。这种单向通信模式对功耗要求极低,一节纽扣电池可以工作2年以上。
Peripheral和Central的配对使用就更普遍了。比如智能手环(Peripheral)和手机(Central)的连接:手环会广播自己的存在,手机扫描到后发起连接,之后就可以传输心率、步数等数据。在NimBLE中,这两种角色的API设计有明显区别,后面我们会用实际代码展示。
2. Broadcaster实战:从配置到广播
2.1 广播参数详解
先来看如何配置一个Broadcaster。关键是要理解广播数据包的结构,它由AD Type和AD Data组成。比如要创建一个ibeacon,需要这样设置广播数据:
static uint8_t beacon_data[] = { 0x02, // Length 0x01, // Flags AD Type 0x1A, // Flags值 0x1A, // 长度 0xFF, // Manufacturer Specific Data 0x4C, 0x00, // 苹果公司标识符 0x02, 0x15, // iBeacon子类型 // UUID 0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2, 0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0, 0x00, 0x01, // Major 0x00, 0x02 // Minor };广播间隔是个需要特别注意的参数。实测发现,设置100ms间隔时手机平均1秒内就能发现设备,但功耗会达到300μA;改为500ms后,发现时间延长到2-3秒,但功耗降到80μA。在医疗设备等对实时性要求不高的场景,建议选择更长的间隔。
2.2 广播事件处理
广播启动后可能会遇到这些典型事件:
- 广播超时(BLE_GAP_EVENT_ADV_COMPLETE)
- 广播被连接(BLE_GAP_EVENT_CONNECT)
- 广播参数更新(BLE_GAP_EVENT_ADV_PARAMS_CHANGE)
建议在事件回调函数里添加日志输出,方便调试。我曾经遇到过广播突然停止的问题,后来发现是广播缓冲区溢出导致的,添加以下错误处理就稳定了:
case BLE_GAP_EVENT_ADV_COMPLETE: if (event->adv_complete.reason != 0) { MODLOG_DFLT(ERROR, "广播异常停止,错误码:%d", event->adv_complete.reason); } break;3. Observer模式开发技巧
3.1 扫描参数优化
Observer的核心任务是扫描并解析广播数据。扫描窗口(scan_window)和扫描间隔(scan_interval)的比值直接影响功耗和响应速度。经过多次测试,我总结出这些经验值:
| 应用场景 | 窗口/间隔 | 平均功耗 | 发现延迟 |
|---|---|---|---|
| 实时定位 | 10ms/10ms | 1.2mA | <100ms |
| 低频检测 | 30ms/300ms | 0.15mA | 1-2s |
| 后台扫描 | 10ms/1000ms | 0.05mA | 随机 |
在NimBLE中启动扫描的代码示例:
struct ble_gap_disc_params disc_params = { .itvl = BLE_GAP_SCAN_FAST_INTERVAL, .window = BLE_GAP_SCAN_FAST_WINDOW, .filter_policy = 0, .limited = 0, .passive = 0, .filter_duplicates = 1 }; int rc = ble_gap_disc(0, BLE_HS_FOREVER, &disc_params, NULL, NULL);3.2 广播数据解析
收到广播包后,需要用ble_hs_adv_parse函数解析。这里有个实用技巧:先检查AD Type再处理数据,避免内存越界。比如获取设备名称的正确方式:
static void parse_adv_data(struct ble_hs_adv_fields *fields) { if (fields->name != NULL) { printf("设备名称: %.*s\n", fields->name_len, fields->name); } // 解析厂商特定数据 if (fields->mfg_data_len >= 4 && fields->mfg_data[0] == 0x4C && fields->mfg_data[1] == 0x00) { printf("发现iBeacon设备\n"); } }4. Peripheral与Central的深度配合
4.1 Peripheral服务注册
Peripheral需要先注册GATT服务才能被连接。建议把服务初始化放在单独的函数中,比如创建一个心率监测服务:
static int gatt_svr_init(void) { int rc; static const struct ble_gatt_svc_def svc_defs[] = { { .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = BLE_UUID16_DECLARE(0x180D), .characteristics = (struct ble_gatt_chr_def[]) { { .uuid = BLE_UUID16_DECLARE(0x2A37), .access_cb = hr_measure_cb, .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, }, {0} } }, {0} }; rc = ble_gatts_count_cfg(svc_defs); if (rc != 0) return rc; rc = ble_gatts_add_svcs(svc_defs); return rc; }4.2 Central连接管理
Central端需要处理连接参数协商。在项目中我发现,iOS设备对连接间隔有特殊要求,需要这样设置连接参数:
static struct ble_gap_conn_params params = { .scan_itvl = 16, // 扫描间隔 10ms .scan_window = 16, // 扫描窗口 10ms .itvl_min = 24, // 最小连接间隔 15ms .itvl_max = 40, // 最大连接间隔 25ms .latency = 0, // 从机延迟 .supervision_timeout = 100, // 超时 1s .min_ce_len = 16, // 最小连接事件长度 .max_ce_len = 32 // 最大连接事件长度 }; rc = ble_gap_connect(0, &peer_addr, 30000, ¶ms, NULL, NULL);连接成功后,建议立即启动服务发现流程。NimBLE提供了ble_gattc_disc_all_svcs函数来发现所有服务,但更高效的做法是指定目标服务的UUID:
static void discover_services(uint16_t conn_handle) { ble_uuid16_t svc_uuid = BLE_UUID16_INIT(0x180D); ble_gattc_disc_svc_by_uuid(conn_handle, &svc_uuid.u, NULL, NULL); }5. 安全与性能优化实践
5.1 配对加密实现
当传输敏感数据时,需要启用BLE安全功能。NimBLE支持多种配对方式,推荐使用LE Secure Connections:
static int pair_init(void) { ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_ONLY; ble_hs_cfg.sm_bonding = 1; ble_hs_cfg.sm_mitm = 1; ble_hs_cfg.sm_sc = 1; static const struct ble_store_config store_config = { .our_sec = ble_store_config_our_sec, .peer_sec = ble_store_config_peer_sec }; ble_store_config_conf(&store_config); return 0; }5.2 低功耗优化技巧
在电池供电设备上,这几个参数调整可以显著降低功耗:
- 适当增大连接间隔(connection interval)
- 合理设置从机延迟(slave latency)
- 优化广播数据长度
- 使用BLE_GAP_DISC_MODE_NONCONNECTABLE非连接模式
实测数据表明,将连接间隔从15ms调整到100ms,功耗可以从800μA降到120μA,而数据传输实时性仍能满足大多数场景需求。