ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32WB55 BLE连接手机实战:CubeMX配置到GATT透传与调试

STM32WB55 BLE连接手机实战:CubeMX配置到GATT透传与调试 简介STM32WB55是意法半导体面向物联网设备推出的双核无线MCU片内集成BLE射频单元适合作为低功耗蓝牙产品原型开发的起步平台。许多开发者使用STM32CubeMX生成基础工程后常在手机APP扫描不到广播数据或连接建立失败时陷入排查困境这份资源的价值就在于提供了一套可直接运行的BLE简单应用示例。压缩包为ZIP格式共392个文件大小26.72MB除了占主体的C源文件与H头文件外还包含IOC配置、UVPROJX工程、AXF/HEX固件以及MAP符号表等并保留了O、D、CRF等编译中间产物方便深入理解CubeMX生成代码与编译器工作流程。资源内针对HAL库中的定时器、UART、RCC等外设驱动均有覆盖同时涉及BLE HCI相关源码可结合工程中的调用关系学习蓝牙协议栈的底层交互。已有2159人学习配合作者的CSDN博文和B站视频可以快速定位广播初始化、连接参数配置、回调函数处理等关键代码工程目录将应用层与驱动层分开便于查看HCI层、定时器和串口驱动的实现进而理解广播包组织、连接间隔设置等细节适合正在学习STM32WB系列、想用更短时间跑通第一个BLE通信demo的开发者。1. BLE 工程连手机比“能广播”多走了三步很多 STM32WB55 开发者卡在同一个地方CubeMX 生成工程编译下载手机能扫到设备名但一点连接就失败或者连上了看不到任何服务。这个标题里最关键的词不是 BLE而是“连接手机APP”。能广播只说明射频和协议栈起来了能稳定连接、发现服务、读写特征值才说明 GATT 服务配置、连接参数和事件回调这条链路真的通了。WB55 是双核架构M4 跑应用和 BLE 协议栈M0 单独跑射频固件所以排查问题时要多留一个心眼你改的代码在哪个核上执行日志又从哪里出来。这篇按“配置生成 → 读透生成代码 → 手机实测 → 产品化收尾”的顺序讲起点是 STM32CubeMX 的图形化配置终点是一台能连上、能收发数据的板子。2. 用 STM32CubeMX 定好 BLE 协议栈的服务与参数2.1 在 CubeMX 里打开 BLE 外设和双核工程新建工程时芯片选择STM32WB55RGV6NUCLEO-WB55RG 板载型号如果用的板上丝印是其他后缀选同封装的 WB55 系列即可。CubeMX 会在Pinout Configuration左侧的Connectivity分类下给出BLE选项勾选后会自动帮你处理一部分引脚冲突。此时要确认两个容易被忽略的配置RADIO相关的时钟源必须来自 HSEBLE IP 不能直接吃 MSI 或 HSI。调试口 SWD 要保留因为连接手机失败时第一件事往往是重新烧录。默认PA13/PA14不要挪作他用。在Project Manager里Toolchain/IDE选STM32CubeIDEMinimum Heap Size建议给到0x600以上BLE 协议栈的事件缓冲会在运行时动态分配内存Heap 太小会出现“编译正常、连接后随机死机”的怪问题。生成代码前把Copy only the necessary library files打开后续改协议栈配置时能少踩许多文件覆盖的坑。2.2 关键参数怎么填时钟、连接参数与服务数量生成代码前先到Clock Configuration界面把时钟树理清楚。BLE 协议栈要求射频内核有精确的 32MHz 参考时钟因此HSE必须启动System Clock Mux里把SYSCLK设为 64MHzWB55 最高主频LSE保持开启。LSE 那颗 32.768kHz 晶振关系到 BLE 的睡眠唤醒和连接事件定时关掉它虽然能跑但连接后功耗和时序都会变差。Connectivity - BLE的参数面板里真正影响手机连接的有四类参数见下表参数项推荐值说明Bluetooth Low Energy Stack选择完整栈部分 CubeMX 版本里区分 Full/LL-only选 Full 才有 GATTNumber of Services4~6每多一个服务占一块上下文内存默认值偏保守Number of Attributes按实际特征值数量调大特征值描述符都会占 attr 数量留足余量RX Buffer Size251配合手机端 MTU 协商低于 64 时很多 APP 会主动降级Number of Attributes是最容易被忽视的坑。新建一个自定义服务加一个含通知属性的特征值再带一个 CCCD 描述符就已经消耗了 5~6 个 attr。如果还想把电池服务、设备信息服务一起加上默认数量必然不够编译时协议栈初始化就会报BLE_STATUS_INSUFFICIENT_RESOURCES。2.3 生成代码后先改这几个文件结构点击GENERATE CODE后把工程先编译一次确保裸工程能过。然后打开Core/Inc/app_conf.h确认CFG_BLE_NUM_LINK至少为 1CFG_BLE_ATT_VALUE_ARRAY_SIZE如果是 0改成 160 以上。前者决定同时连接的设备数后者决定单个特征值可写长度都不影响编译但会在运行时直接决定手机端行为。文件结构上STM32CubeIDE 生成出来后主要看这几个Core/Src/main.c外设初始化和MX_App_Init()调用点Middlewares/ST/STM32_WPAN/App/app_ble.cBLE 应用层事件分发入口Middlewares/ST/STM32_WPAN/App/services.c服务列表注册Middlewares/ST/STM32_WPAN/App/ble_conf.h协议栈裁剪配置这时刻意先去读app_ble.c而不是去翻底层驱动。BLE 工程里 main 函数的逻辑相对简单真正的事件循环都在HAL_BLE_*回调后面由协议栈任务驱动。理解了这个模型后面加自己的协议才有明确的下手点。3. 读懂生成的 BLE 应用层从回调接手数据通路3.1 SVCCTL 事件驱动模型WB55 的 BLE 协议栈不是裸机轮询结构而是基于事件回调。芯片上电后M4 核初始化协议栈然后进入一个由SVCCTL_App_Notification()作为总入口的事件分发循环。所有链路层的连接、断开、MTU 更新、特征值写入最终都会汇聚到这个函数里。打开app_ble.c你会在里面看到类似这样的结构static void SVCCTL_App_Notification(SVCCTL_Evt_Ack_Type_t event) { switch (event.evt.evt_code) { case EVT_BLUE_GAP_CONNECTION_REQ: APP_DBG_MSG(BLE connected, handle%d\n, event.evt.pckt.packet_params.connection_req_params.connection_handle); break; case EVT_BLUE_GAP_DISCONNECTION_REQ: APP_DBG_MSG(BLE disconnected\n); break; case EVT_BLUE_GATT_ATTRIBUTE_MODIFIED: Handle_GATT_Attribute_Modified(event); break; default: break; } }EVT_BLUE_GATT_ATTRIBUTE_MODIFIED是数据通路里最重要的事件手机往可写特征值里写数据或者打开通知写 CCCD协议栈都会投递这个事件。代码里event.evt.pckt.packet_params.attribute_modified_params.att_handle告诉你哪个特征值被改了Data_Length和Att_Data[]告诉你改成了什么。注意这里只有Attribute Modified一种通知所以读操作手机端发起 read不会走这个事件而是由协议栈直接返回当前属性值。这意味着如果想让设备端在“被读”时做动态计算光靠生成代码做不到需要在 SVCCTL 之外挂一个Custom_Read_Request_Handler后面讲。3.2 添加自定义服务和特征值3.2.1 修改 UUID 宏在services.c里找到类似这样的定义段/* UUID 定义16-bit UUID 由 SIG 分配自定义服务推荐用 128-bit */ #define CUSTOM_SERVICE_UUID 0x0000, 0x0000, 0x0000, 0x0000, \ 0x0000, 0x0000, 0x0000, 0x0000 #define CUSTOM_READ_WRITE_CHAR_UUID 0x0001, 0x0000, 0x0000, 0x0000, \ 0x0000, 0x0000, 0x0000, 0x0000128-bit UUID 的字节序是“小端在前”也就是 UUID 字符串里最左边两位放在数组最前面。比如你想用的 UUID 是6E400001-B5A3-F393-E0A9-E50E24DCCA9E数组要写成#define CUSTOM_SERVICE_UUID 0x9E, 0xCA, 0xDC, 0x24, \ 0x0E, 0xE5, 0xA9, 0xE0, \ 0x93, 0xF3, 0xA3, 0xB5, \ 0x01, 0x00, 0x40, 0x6E这是个高频出错的点。手机端 nRF Connect 显示的是大端可读格式而固件里必须按小端存储。字节序写反手机端看到的就是一个完全不同的 UUID特征值全部错位。3.2.2 写透传回显回调拿到Attribute Modified事件后做一个最简单的数据透传回显用来验证通路static void Handle_GATT_Attribute_Modified(SVCCTL_Evt_Ack_Type_t event) { uint16_t handle event.evt.pckt.packet_params.attribute_modified_params.att_handle; uint16_t len event.evt.pckt.packet_params.attribute_modified_params.data_length; uint8_t *data event.evt.pckt.packet_params.attribute_modified_params.att_data; /* 判断事件来自哪个特征值handle 与 services.c 中注册的顺序有关 */ if (handle CUSTOM_READ_WRITE_CHAR_HANDLE) { /* 把收到的数据原样写回通知特征值 */ aci_gatt_srv_notify(connection_handle, CUSTOM_NOTIFY_CHAR_HANDLE, len, data); } }aci_gatt_srv_notify()的第二个参数是要发通知的特征值 handle这个值不是 UUID而是在services.c的aci_gatt_srv_add_char()返回值里记录下来的。生成代码里通常把它存在一个全局变量中命名类似Custom_Notify_Char_Handle直接引用即可。如果透传数据长度超过 MTUaci_gatt_srv_notify()会返回错误码所以线上工程里一般要做分片或协商 MTU最小可用的做法是在连接建立后主动发起一次 MTU 交换请求。3.3 把广播数据改成自己的设备名生成工程的广播名是ST开头的默认名称。手机 APP 一般是扫描后按名字筛选或直接显示所有结果正式演示前建议改成自己的设备标识。在app_ble.c里找到aci_gap_set_advertising_data()的调用处它的参数是一个字节数组格式是“长度 AD Type 数据”。改成你自己的名称static uint8_t adv_data[] { /* 长度名称长度1AD type 占一字节 */ 0x05, /* AD Type 0x09 Complete Local Name */ 0x09, W, B, 5, 5 }; aci_gap_set_advertising_data(0x00, sizeof(adv_data), adv_data);广播类型的设置在aci_gap_set_discoverability()和aci_gap_set_connectable()两个 API 里。常见做法是设为可连接可发现ADV_IND也就是手机主动扫描才响应且允许连接。如果设成不可连接广播ADV_NONCONN_IND手机能扫到但永远连不上这是新手最容易碰到的“假成功”。4. 手机 APP 连接实测nRF Connect 和调试要点4.1 最小验证流程推荐先用 nRF Connect 这个 APP 做验证它同时支持 Android 和 iOS而且能直接查看 GATT 表、发起读写省掉自己写 APP 的调试成本。整个流程固定为五步打开 nRF Connect点击右上角扫描按钮在扫描列表中找到自己的设备名点击 Connect连接成功后自动进入 GATT 页面展开Unknown Service自定义服务点击可写特征值的“写入”按钮发几个字节打开通知开关观察设备主动上报的数据如果第 3 步看不到任何服务先看服务数量。CubeMX 里Number of Services设成了 0 或 1而工程里又注册了电池服务、设备信息服务、自定义服务协议栈初始化时直接失败。此时 Flash 里其实还跑着上一个固件表现是手机老连上一个旧设备名。第 4 步写入后如果固件里接了串口并打了日志应该能看到Attribute Modified触发的打印。板上串口默认是USART1波特率 115200CubeMX 生成工程时已经配好直接打开串口助手看输出即可。4.2 连接不上的排查顺序把最常见的故障按“概率从高到低”列成表配合调试手段逐一排除现象最可能原因排查手段扫描不到设备广播数据没更新或广播没开启检查HAL_BLE_Init()返回值和广播 API 调用顺序能扫到连不上设备已被其他手机占用CFG_BLE_NUM_LINK改为 2或断开旧连接连接即断开配对策略不匹配查看APP_CFG_AUTHEN_ENABLE的配置能看到服务读写出错ATT handle 错位用 nRF Connect 对照 services.c 里的 handle 顺序数据传输偶发丢包MTU 或连接间隔过短连接参数里把Min Connection Interval放到 15ms 以上连接后立刻断开且串口打印EVT_BLUE_GAP_DISCONNECTION_REQ大概率是配对问题。WB55 默认配置可能要求加密但手机端如果用的是裸 GATT 连接而不弹配对框两端协商不一致就会被链路层主动断开。临时解法是在app_conf.h里把CFG_BLE_AUTHEN_ENABLE设为 0先跑通数据通路再回来补安全层。4.3 Android 与 iOS 的差异提醒Android 手机在扫描连接后系统层面对 GATT 的并发访问限制比较严格不能在同一个特征值上同时进行 write 和 notify必须串行操作。iOS 则对 MTU 上限更敏感iPhone 默认请求 MTU 185Android 部分机型只有 23 字节这会导致同一份固件在两台手机上表现不同。写应用层协议时解析接收数据的长度不要硬编码为固定值用传入的data_length做边界判断。另外Android 端开发时记得申请定位权限Android 6.0 以上扫描 BLE 需要ACCESS_FINE_LOCATION这跟蓝牙权限是两回事。iOS 端则在Info.plist里要声明NSBluetoothAlwaysUsageDescription不声明会导致 APP 直接崩溃。这些属于 APP 侧问题但排查时如果发现“同一块板子安卓能连、苹果不能连”基本就是 APP 权限问题不是固件问题。5. 往后做产品前先记住这几个小技巧BLE 应用开发的规律是协议栈本身不会错错的大多是配置、handle 和数据格式。以下几个技巧是从实际项目里沉淀出来的优先级从高到低排列。技巧一把特征值 handle 打印到串口。在services.c里每次添加特征值后立刻把返回值打印出来uint16_t char_handle; aci_gatt_srv_add_char(service_handle, 0x02, char_uuid, 16, CHAR_PROP_WRITE, 0, 0, char_handle); printf(Write char handle: 0x%04x\r\n, char_handle);手机端显示的实际句柄跟代码逻辑里的 handle 不一定一致因为描述符和声明属性都会占句柄号。把固件里拿到的 handle 和 nRF Connect 界面上显示的句柄对一遍能避免大量“为什么写入没反应”的排查时间。技巧二用aci_gatt_srv_notify()的返回值判断链路状态。很多工程师写完透传后遇到“手机收不到数据”第一反应是查广播、查配置其实最直接的做法是看 notify 函数返回什么。如果返回BLE_STATUS_INSUFFICIENT_RESOURCES说明发送缓冲不够需要把CFG_BLE_NB_ATT_MTU相应调大如果返回BLE_STATUS_CONNECTION_INTERNAL_ERROR往往是连接已经断开代码还在发。技巧三低功耗设计别一上来就开STOP2模式。WB55 的低功耗模式会联动 BLE 协议栈的时钟管理M4 进 STOP2 时射频内核的唤醒路径如果没配置正确会表现为“连接后第一次唤醒丢包”。常见做法是先保持SLEEPMODE_NOTIMER关闭把整个链路跑稳再用HAL_PWR_EnterSTOPMode()按官方例程逐级验证。低功耗和 BLE 连接稳定性在 WB55 上是强耦合的不适合单独调。技巧四用 STM32CubeMonitor-RF 抓空中包。板子本身不支持被动嗅探但配合官方工具可以看广播包和连接事件的时序这是排查连接参数是否生效的最快捷路径。通过监听窗口能看到实际连接间隔是多大、是否和 CubeMX 里配的一致很多手机兼容性问题在用示波器量引脚之前先用它确认链路层状态能省掉大半天。把生成的工程跑通只是第一步BLE 产品的核心难点在“数据链路稳定、功耗可控、异常可恢复”这三件事上后续调试时多留日志、多对照 GATT 表比多写功能代码更重要。本文还有配套的精品资源点击获取
返回列表