第一章:蓝牙协议栈基础
各位同学,咱们今天聊聊蓝牙协议栈。说实话,我刚入行那会儿,看到蓝牙协议栈的文档就头疼——几百页的规范,各种角色、服务、特性,简直像天书。但做嵌入式血压计,这块儿必须啃下来。别担心,我会用最接地气的方式,带你摸清BLE的底细。
1.1 BLE协议栈架构:从物理层到应用层
BLE协议栈,说白了就是一套分层的软件架构。每一层各司其职,就像公司里的部门——物理层管收发,链路层管连接,GAP管广播,GATT管数据交换。我习惯把它分成三层来看:
- Controller(控制器):包含物理层(PHY)和链路层(LL)。负责最底层的射频收发、数据包组装、连接管理。这部分通常由芯片硬件实现,我们开发者基本不用碰。
- Host(主机):包含L2CAP、SMP、ATT、GAP、GATT。这是咱们开发的重点。L2CAP负责数据包的分段重组,SMP管配对加密,ATT和GATT管数据读写。
- Application(应用层):你的血压计业务逻辑。比如测量血压、存储数据、上传到手机App。
嗯,这里要注意:Host和Controller之间通过HCI(Host Controller Interface)通信。在nRF5芯片上,Host和Controller是集成在一起的,所以HCI对我们透明。但如果你用外部蓝牙芯片(比如TI的CC2540),就得自己处理HCI命令了。
核心要点:做血压计开发,你主要跟GAP和GATT打交道。GAP管连接,GATT管数据。记住这个,后面就好理解了。
1.2 GAP与GATT角色:谁广播?谁连接?谁读写?
GAP(Generic Access Profile)定义了设备在蓝牙网络中的角色。说白了就是:谁在喊话,谁在听。
- Broadcaster(广播者):只发广播,不连接。比如蓝牙信标。
- Observer(观察者):只收广播,不连接。比如扫描设备。
- Peripheral(外设):广播并接受连接。你的血压计就是Peripheral。
- Central(中心):扫描并发起连接。手机App就是Central。
我在项目中遇到过一个问题:血压计作为Peripheral,广播间隔设得太短(比如20ms),结果手机扫描时发现多个广播包,导致连接不稳定。后来我把广播间隔调到100ms,问题就解决了。你想想看,广播间隔太短,信道冲突概率就高,反而影响连接成功率。
GATT(Generic Attribute Profile)定义了数据交换的模型。它基于属性(Attribute)和特性(Characteristic)。
- Service(服务):一组相关特性的集合。比如血压计服务(Blood Pressure Service)。
- Characteristic(特性):一个数据点。比如收缩压、舒张压、脉搏。
- Descriptor(描述符):特性的附加信息。比如单位、格式。
举个例子:你的血压计有一个"血压测量"服务,里面包含三个特性:收缩压(uint16)、舒张压(uint16)、脉搏(uint8)。手机App通过读写这些特性来获取数据。说白了,GATT就是一张表格,手机查表拿数据。
个人经验:设计GATT服务时,尽量遵循蓝牙SIG定义的标准服务(比如Blood Pressure Service)。这样手机App不用写自定义解析代码,兼容性更好。我早期做过一个项目,用了自定义服务,结果用户换手机后App不识别,折腾了好久。
1.3 蓝牙4.0/5.0差异:速度、距离、广播
蓝牙4.0是BLE的起点,蓝牙5.0是重大升级。咱们做血压计,主要关注这几点差异:
| 特性 | 蓝牙4.0/4.2 | 蓝牙5.0 | 对血压计的影响 |
|---|---|---|---|
| 物理层速率 | 1Mbps | 2Mbps(可选) | 5.0传输更快,适合大数据量 |
| 广播数据长度 | 31字节 | 255字节(扩展广播) | 5.0可广播更多信息,比如设备名称+服务UUID |
| 传输距离 | 约10米 | 约40米(编码PHY) | 5.0覆盖更广,但功耗更高 |
| 广播信道 | 3个(37/38/39) | 3个(不变) | 广播信道没变,但5.0支持周期性广播 |
我个人建议:如果你的血压计只需要传输少量数据(比如每次测量结果几十字节),蓝牙4.2完全够用。但如果你要做连续血压监测(比如每5分钟上传一次数据),蓝牙5.0的2Mbps速率能显著降低传输时间,从而省电。
避坑指南:我曾经在蓝牙5.0项目里用了2Mbps PHY,结果发现某些老手机(比如iPhone 6)不支持,连接失败。后来我加了降级逻辑:先尝试2Mbps,失败后回退到1Mbps。嗯,兼容性永远是第一位的。
1.4 Nordic nRF5 SDK环境搭建:从零开始
Nordic的nRF5 SDK是开发nRF51/nRF52系列芯片的官方工具。我推荐用nRF5 SDK 17.0.2版本,稳定且文档齐全。搭建步骤很简单:
- 下载SDK:从Nordic官网下载nRF5 SDK 17.0.2,解压到本地(路径不要有中文)。
- 安装工具链:推荐用Segger Embedded Studio(SES),Nordic官方支持,免费且好用。或者用Keil MDK,但需要许可证。
- 配置工程:打开SDK中的示例工程(比如
examples/ble_peripheral/ble_app_hrs),编译下载到开发板。 - 测试:用nRF Connect App扫描并连接,看能否发现服务和特性。
这里有个小技巧:SDK里的示例工程默认用SoftDevice(Nordic的蓝牙协议栈固件)。编译前要确认SoftDevice版本匹配。比如nRF52832用SoftDevice S132,nRF52840用S140。我刚开始搞混过,编译报错半天找不到原因。
// 一个最简单的BLE广播示例(基于nRF5 SDK)
#include "ble_advdata.h"
#include "ble_gap.h"
static void advertising_init(void)
{
ret_code_t err_code;
ble_advdata_t advdata;
ble_advdata_t scanrsp;
// 初始化广播数据
memset(&advdata, 0, sizeof(advdata));
advdata.name_type = BLE_ADVDATA_FULL_NAME;
advdata.include_appearance = true;
advdata.flags = BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE;
// 初始化扫描响应数据
memset(&scanrsp, 0, sizeof(scanrsp));
scanrsp.uuids_complete.uuid_cnt = 1;
scanrsp.uuids_complete.p_uuids = &m_adv_uuids;
// 配置广播参数
ble_gap_adv_params_t adv_params;
memset(&adv_params, 0, sizeof(adv_params));
adv_params.type = BLE_GAP_ADV_TYPE_ADV_IND;
adv_params.p_peer_addr = NULL; // 未指定对端地址
adv_params.fp = BLE_GAP_ADV_FP_ANY;
adv_params.interval = MSEC_TO_UNITS(100, UNIT_0_625_MS); // 100ms间隔
adv_params.timeout = 0; // 永不超时
// 启动广播
err_code = sd_ble_gap_adv_set_configure(&m_adv_handle, &advdata, &scanrsp, &adv_params);
APP_ERROR_CHECK(err_code);
err_code = sd_ble_gap_adv_start(m_adv_handle, APP_BLE_CONN_CFG_TAG);
APP_ERROR_CHECK(err_code);
}
这段代码做了三件事:设置广播数据(设备名称+外观)、配置广播参数(100ms间隔)、启动广播。你把它加到main函数里,血压计就能被手机扫描到了。
调试技巧:如果广播不成功,先检查SoftDevice是否初始化成功(nrf_sdh_enable_request())。我遇到过几次,都是因为SoftDevice版本不对或者没使能。用nRF Connect App看日志,能快速定位问题。
好了,第一章就到这里。蓝牙协议栈基础是后面所有章节的基石。你把这些概念搞清楚了,后面写血压计的GATT服务、处理配对加密、优化功耗,都会顺手很多。下一章咱们开始动手写血压计的GATT服务定义,敬请期待。