BLE协议栈分层与STM32WB实现

BLE协议栈,说白了就是一套让蓝牙设备能互相说话的规则。我刚开始接触时也觉得这东西挺抽象,但拆开来看,其实每一层干的事都很明确。

BLE协议栈的五层结构

BLE协议栈从下往上分为五层。每一层各司其职,像流水线一样传递数据。

层级 全称 核心职责
PHY 物理层 负责2.4GHz射频收发,调制解调
LL 链路层 连接管理、数据包收发、重传
HCI 主机控制器接口 Host与Controller之间的通信桥梁
GAP 通用访问规范 设备发现、连接建立、广播
GATT 通用属性规范 数据交互、服务与特征值管理

PHY和LL属于Controller部分,跑在硬件上。GAP和GATT属于Host部分,跑在CPU上。HCI是中间人。

关键理解:应用层只跟GATT打交道,不需要关心射频怎么发数据。这就是分层的好处——各层专注自己的事。

STM32WB的BLE Stack架构

STM32WB是双核架构。一个M4内核跑应用,一个M0+内核专门跑蓝牙协议栈。我最初做项目时没太在意这个分工,结果发现M4去操作蓝牙寄存器,直接导致连接不稳定。

STM32WB的协议栈分为两部分:

  • M0+核心:运行完整的BLE协议栈(LL + HCI + GAP + GATT),通过IPCC中断与M4通信
  • M4核心:运行用户应用程序,通过API调用蓝牙服务

这种架构的好处很明显——蓝牙时序由M0+严格保证,不会因为M4跑复杂算法而丢包。

我的习惯:调试时先确认M0+固件版本。我遇到过几次因为M0+固件太旧导致连接参数协商失败的问题,升级后就正常了。

启动流程与初始化

BLE协议栈的启动,不是简单调一个函数就完事。我把它拆成四个步骤:

  1. 硬件初始化:时钟、GPIO、IPCC、RCC
  2. 协议栈加载:启动M0+核心,加载BLE固件
  3. 配置注册:设置GAP角色、GATT服务、MTU大小
  4. 启动广播:开始广播,等待连接

下面是一个典型的初始化代码片段:

int main(void)
{
    /* 1. 硬件初始化 */
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_IPCC_Init();

    /* 2. 启动BLE协议栈 */
    hci_init();
    hci_reset();

    /* 3. 配置GAP */
    aci_gap_init(GAP_PERIPHERAL_ROLE, 0, 0x07, &service_handle, &dev_name_char_handle, &appearance_char_handle);

    /* 4. 配置GATT服务 */
    aci_gatt_add_service(UUID_TYPE_16, primary_service_uuid, PRIMARY_SERVICE, 7, &service_handle);

    /* 5. 启动广播 */
    aci_gap_set_discoverable(ADV_IND, 0, 0, PUBLIC_ADDR, NO_WHITE_LIST_USE, 0, NULL, 0, NULL, 0, 0);
}

我曾经踩过的坑:初始化顺序不能乱。有一次我先启动了广播,再注册GATT服务,结果手机连上后读不到任何特征值。正确的顺序是:先注册服务,再启动广播。

BLE协议栈分层关系图

下面这张图展示了各层之间的调用关系:

BLE协议栈分层架构与STM32WB实现 用户应用程序(M4核心) 传感器采集 / 数据处理 / 人机交互 GATT 通用属性规范 服务 / 特征值 / 属性协议 GAP 通用访问规范 广播 / 扫描 / 连接管理 Host(M4核心) HCI 主机控制器接口 IPCC中断通信 / 命令与事件 LL 链路层 连接状态机 / 数据包重传 / 加密 PHY 物理层 2.4GHz射频 / GFSK调制 / 跳频 M4核心 M0+核心

初始化流程中的关键配置

初始化时有几个参数直接影响性能和功耗。我列个表对比一下:

参数 推荐值 影响
广播间隔 100ms(快速)/ 1s(省电) 间隔越短,被发现越快,但功耗越高
连接间隔 30ms ~ 50ms 间隔越小,数据吞吐越大,功耗也越大
MTU大小 247字节(默认23) MTU越大,单次传输数据越多,效率越高
发射功率 0dBm(默认+6dBm) 功率越高,距离越远,但功耗翻倍

我建议:开发阶段用快速广播(100ms间隔),方便调试。量产时再改成省电模式。连接间隔不要低于20ms,否则M0+处理不过来,容易断连。

避坑指南

做BLE项目,有些坑几乎是必踩的。我分享几个亲身经历:

  • M0+固件未启动:上电后M4跑得飞快,但M0+还没加载完。调用hci_init前必须等待M0+就绪标志位。
  • IPCC中断优先级:IPCC中断优先级不能设得太低,否则蓝牙事件响应延迟,导致连接超时。
  • GATT服务数量:STM32WB的GATT服务数量有限制,最多支持20个服务。我有个项目塞了25个,结果注册失败。
  • 广播数据长度:广播包最大31字节,别想着把传感器历史数据全塞进去。超过31字节要用扫描响应包。

我曾经犯过的错:第一次做STM32WB项目时,我把所有初始化代码都写在main函数里,结果调试时发现每次复位后蓝牙连接都不稳定。后来才发现是M0+固件加载需要时间,M4不能一上来就发命令。正确的做法是加一个延时等待,或者轮询M0+状态寄存器。

嗯,BLE协议栈的分层设计,说白了就是让不同层次的开发者各取所需。做应用层的不用管射频,做底层的不用管业务逻辑。STM32WB的双核架构把这个思想发挥到了极致。你只要把初始化流程走顺,后面的开发就会顺畅很多。