第一章:蓝牙协议栈基础

各位同学,咱们今天聊聊蓝牙协议栈。说实话,我刚入行那会儿,看到蓝牙协议栈的文档就头疼——几百页的规范,各种角色、服务、特性,简直像天书。但做嵌入式血压计,这块儿必须啃下来。别担心,我会用最接地气的方式,带你摸清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版本,稳定且文档齐全。搭建步骤很简单:

  1. 下载SDK:从Nordic官网下载nRF5 SDK 17.0.2,解压到本地(路径不要有中文)。
  2. 安装工具链:推荐用Segger Embedded Studio(SES),Nordic官方支持,免费且好用。或者用Keil MDK,但需要许可证。
  3. 配置工程:打开SDK中的示例工程(比如examples/ble_peripheral/ble_app_hrs),编译下载到开发板。
  4. 测试:用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服务定义,敬请期待。