从拿到第一块 NUCLEO-WB55RG,到能稳定跑起 BLE 广播,我中间踩过一个非常典型的坑:程序烧进去了,板载 LED 也正常闪,但手机 nRF Connect 扫描列表里就是干干净净,一个设备都看不到。当时第一反应是板子坏了,后来翻官网、查社区、看协议栈日志,折腾到大半夜才发现根本不是硬件问题,而是地址类型没配对。
这篇文章就把这类"板卡没有发出 BLE 广播"的问题完整拆开,从硬件、协议栈、代码配置到调试工具,一条条过。无论你是刚拆开 NUCLEO-WB55RG 包装的新手,还是已经被厂商示例折腾到头秃的老玩家,照着下面的排查顺序走一遍,大概率能定位到问题。
1. 问题现场还原:先搞清楚是真的没广播,还是扫描端的问题
很多人一上来就去怀疑板卡硬件,实际上"手机搜不到广播"这个现象,有一半以上是扫描端或环境造成的假象。所以我建议先花五分钟做确认,再动硬件。
1.1 手机端那些最容易忽略的坑
BLE 广播扫描和手机系统的权限绑定非常深。我实测过 Android 12 以上的设备,如果你没给扫描 App 授权"附近设备"权限,nRF Connect 打开后界面看起来正常,但扫描列表永远空白。部分国产 ROM 更严格,蓝牙扫描还需要定位权限,你只开了蓝牙开关是不够的。
iOS 稍微好一点,但也要注意:nRF Connect 默认会列出所有设备,而一些第三方 BLE 工具会按 Service UUID 过滤,如果你的广播数据里没有任何服务 UUID,部分 App 会直接不显示。所以排查时至少要换两个工具交叉验证,比如 nRF Connect 加 LightBlue,或者 ST 官方的 ST BLE Sensor,不要只靠一个 App 下结论。
1.2 距离、干扰和信道问题
BLE 广播虽然号称覆盖几十米,但在室内环境下,2.4GHz 频段被 Wi-Fi、蓝牙外设、甚至是微波炉严重占用,板子放在电脑机箱后面或者金属桌面上,信号衰减非常明显。建议把板卡和手机放到同一张桌面上,距离控制在 1 米以内,中间不要有金属遮挡。
另外 BLE 广播只走 37、38、39 三个信道,通俗讲就是它只在这三个频率点上轮流喊话,每个信道停留时间很短。如果你所在环境刚好在这三个信道上干扰严重,手机可能丢失全部广播包。你可以在 nRF Connect 里开启"扫描所有信道",或者把板卡换到另一个房间再试一次,排除干扰因素。
1.3 用第二块板子做交叉验证
最靠谱的验证方式是双板互测。如果你手头有两块 NUCLEO-WB55RG,给第二块烧一个最简单的 Beacon 工程,或者仅仅用官方 BLE 示例,然后用第一块作为扫描方,反过来扫描第二块。如果第二块能收到第一块的广播,说明问题出在第一块的射频路径或协议栈状态;如果两边都收不到,那大概率是两颗芯片都存在相同问题,比如协议栈被擦除或者开发环境配置出错。
还有一个更实用的手段是使用带 USB 口的 BLE Dongle(比如 nRF52840 dongle)配合 nRF Connect Desktop 抓包。电脑蓝牙适配器往往只做连接用途,扫描效果不如专用 Dongle,不要用 Windows 自带的蓝牙设置去判断是否有广播,它经常会漏掉非可连接设备。
2. 从硬件入手:天线、供电和时钟
如果确认手机和扫描端都没问题,第二块板子也收不到广播,那就要开始怀疑板卡自身了。硬件排查的顺序一般是从天线到供电到时钟,层层往下收窄。
2.1 天线路径:从芯片到空气
NUCLEO-WB55RG 的射频输出从 STM32WB55RG 引出后,经过平衡不平衡转换器(BALUN)和 RF 开关,最后接到板载 PCB 天线。板上通常还预留了 SMA 座用于外接天线,这个路径切换由板上电阻或跳线决定。如果你在扩展板上插了某些 Arduino Shield,或者用杜邦线从 morpho 排针引线,刚好压在 PCB 天线区域,天线性能会急剧下降,甚至完全辐射不出去。
我遇到过一次类似问题:天线下面压了一根长杜邦线,手机在 30 厘米内才能偶偶搜到广播,拿开之后距离恢复到 10 米以上。建议先用肉眼检查天线区域有没有遮挡,确认板卡背面没有异常焊锡残留。如果板子自己焊接过 ANT 引脚或者 BALUN 周边,用放大镜仔细看看有没有短路或虚焊。
如果你在代码里通过 GPIO 控制 RF 开关切换到了 SMA 路径,但外接天线没接,或者接了一个坏天线,也会导致"完全没有广播"。这类配置在 CubeMX 里不一定有直观选项,需要读取芯片参考手册中射频开关控制引脚的定义,确认当前默认电平到底对应板载天线还是外部天线。
2.2 供电不足会让射频悄悄罢工
BLE 广播是典型的高脉冲电流负载,广播瞬间电流可以达到十几毫安甚至更高。NUCLEO-WB55RG 通过 ST-LINK USB 口供电时,3.3V 电源轨一般没有问题,但如果你同时给传感器、OLED、舵机等外设供电,板载稳压器的压降可能会让 3.3V 在广播瞬间掉到 3.0V 以下。很多芯片在欠压状态下不会立刻复位,而是射频模块初始化失败,表现出来的就是一点广播都没有。
排查方法很简单:用示波器或者万用表探头点住 3.3V 输出脚,在程序循环开启广播时观察电压波动。如果发现广播瞬间电压跌落明显,先断开所有外设,只用 USB 供电再测一次。我个人的习惯是,调试 BLE 广播时尽量使用外部 LDO 供电,避免板载 ST-LINK 的电流余量影响射频表现。
2.3 时钟与低功耗配置
WB55 的 BLE 协议栈对时钟有硬性要求。系统必须提供可用的 32 MHz HSE(高频外部晶振)用于射频,以及 32.768 kHz LSE(低频外部晶振)用于低功耗时的定时基准。如果你在 CubeMX 里为了省事把时钟源改成内部 HSI16 或者禁用了 LSE,M0 核上的协议栈初始化可能不报错,但广播始终起不来。
另一点是低功耗模式。很多自定义工程会在主循环里调用WFI或者直接进入 STOP 模式来省电,但如果没有正确配置 BLE 协处理器的唤醒源,M4 核睡着了,M0 核的协议栈虽然还在运行,但射频活动可能被挂起,广播自然停止。调试阶段我建议先关闭所有低功耗优化,把系统跑在最高性能模式,等确认广播正常后再逐步加入睡眠逻辑。
3. 软件与协议栈:真正的"重灾区"
排除硬件和扫描端问题后,剩下的十有八九是软件配置问题。这部分内容比较绕,尤其是第一次接触 STM32WB 双核架构的开发者,很容易掉进一些看似玄学的坑里。
3.1 全片擦除把 BLE 协议栈擦没了
这是我在社区里见过最多的问题,自己也踩过一次,必须放在最前面说。STM32WB55 是双核芯片:Cortex-M4 核运行你的用户代码,Cortex-M0+ 核运行预编译的 BLE 协议栈。蓝牙协议栈不是以库的形式链进你的工程,而是以固件形式烧录在 M0+ 核对应的 Flash 区域里,由名为 FUS(Firmware Upgrade Services)的系统管理。
NUCLEO-WB55RG 出厂时,厂商已经把 FUS 和 BLE 协议栈都烧好了。但很多人拿到板子后,为了清空用户代码,直接在 STM32CubeProgrammer 里点了 Full chip erase。这个操作会毫不留情地把 M0+ 区域的 FUS 和 BLE 协议栈一起擦掉。此后你烧录任何 M4 工程,M4 代码能正常跑、LED 能闪、串口能打印,但只要程序调用BLE_Init()或hci_init()跟 M0 通信,就会失败,广播当然也不存在。
排查方法:打开 STM32CubeProgrammer,连接板卡后在"STM32WB"相关页面查看 FUS 版本是否还存在。如果显示为空或者无法读取,说明协议栈区域已经被清空。恢复方案是从 STM32Cube_FW_WB 固件包中获取对应的 FUS 和 BLE 协议栈 bin 文件,用 CubeProgrammer 烧写回去。具体操作在不同 SDK 版本里有细微差别,我建议严格按照固件包 README 中"Firmware upgrade"章节的步骤执行,不要手动乱填起始地址,否则容易把两个区域的地址搞混。
3.2 初始化顺序与返回值逐个检查
很多人烧完官方示例后能广播,但程序一改成自己的工程就失败,问题往往出在初始化顺序上。BLE 协议栈的启动不是写一行BLE_Init()就能搞定的,它要求一系列调用按顺序执行,而且每步都可能返回错误。
标准的初始化顺序大致是:先配置系统时钟,再初始化 BLE HCI 层,让 M4 和 M0 建立通信,然后初始化 GAP 和 GATT 服务,接着设置地址、广播参数、广播数据,最后启动广播。每一步我都建议检查返回值,不要默认成功。比如aci_gap_set_advertising_configuration()返回非零,后面的aci_gap_set_advertising_data()和aci_gap_start_advertising()很可能也不会正常执行,但代码不一定会崩溃,只是广播没开。
我习惯在调试阶段写一个简单的错误打印函数,把返回值转到串口上,一目了然。这样能快速看到是协议栈没起、还是广播参数配置失败,而不是对着手机扫描列表干瞪眼。
3.3 别忽略地址类型:STM32WB 没有出厂 BLE 地址
很多单片机自带厂商标定的唯一 MAC 地址,但 STM32WB 系列没有。它的 BLE 地址需要你自己指定,要么用静态随机地址(Static Random Address),要么在系统 Flash 里存一个自己的公共地址。这是官方例程里已经处理好的细节,但自己建工程时很容易忽略。
如果你使用的是公共地址(Public Address)但代码里没有写入任何地址,协议栈可能返回错误,或者生成一个全零地址进行广播。手机端收到全零地址的设备时,部分扫描 App 会直接过滤掉。我强烈建议在做最小验证时使用静态随机地址类型,具体在aci_gap_set_advertising_configuration()的参数中指定,示例代码里经常用RANDOM_STATIC_ADDR。这个类型不需要事先烧录任何地址,协议栈会自动生成一个随机地址用于广播,简单可靠。
另外注意:如果在同一块板子上反复烧录不同工程,手机里会积累很多旧设备缓存。有时候广播其实已经发出了,但手机因为缓存了之前同名的设备,显示还是旧的。遇到这种情况,清除手机蓝牙缓存,或者重启手机蓝牙,往往就能搜到了。
3.4 广播参数配置的细节
广播参数包含广播类型、间隔、信道、过滤策略、广播数据等多个维度。最常用的广播类型是ADV_IND,这是可连接无定向广播,手机可以搜到也能连接。如果你不小心用了ADV_NONCONN_IND或ADV_SCAN_IND,手机虽然能搜到但无法连接,在某些应用场景里会让人觉得"设备有问题"。
广播间隔的单位是 0.625ms,很多人直接填一个很小的值,比如 20ms,结果是功耗飙升且广播包过于密集,反而容易和环境里其他信号冲突。反过来,如果你把间隔设成 1.28 秒以上,手机扫描时可能正好错过广播信道,表现为"偶尔能搜到,过一会儿就消失"。一般建议开发阶段使用 100ms 到 200ms 之间的间隔,兼顾响应速度和稳定性。
广播数据的格式也很有讲究。每个广播包由若干 AD Structure 组成,每个结构的第一字节是长度,第二字节是类型,随后是数据。如果长度字段和实际数据不匹配,或者把超过 31 字节的数据塞进传统广播包,手机端扫描器很可能直接丢弃整个包。BLE 5.0 引入了扩展广播,可以传输更长数据,但需要 WB55 协议栈和扫描端都支持,调试阶段建议先用 31 字节以内的传统广播,排错最简单。
4. 实际操作:从官方例程到最小广播工程
理论讲得再多,不如直接上板子跑一遍。这一节我给出一个通用的操作路径:先用官方例程验证硬件,再写一个最小广播工程定位问题,最后用日志和抓包确认结论。
4.1 不写代码验证:官方 P2P Server
拿到新板子,我建议第一件事不是新建工程,而是直接打开 STM32Cube_FW_WB 固件包里的 BLE P2P Server 示例。注意,这个示例的 README 里通常会要求先用 STM32CubeProgrammer 烧录 BLE 协议栈固件到 M0,再烧录 M4 应用。如果出厂板子没被擦过,直接烧 M4 应用也可以跑,但如果你不确定板子状态,按 README 走一遍完整流程是最稳妥的。
烧完官方示例,打开手机 nRF Connect,扫描列表中应该能看到名为P2P_Server之类设备,设备名可能因 SDK 版本不同略有差异。如果你能看到这个设备并能连接,说明板卡硬件、射频天线、协议栈都是完好的,问题几乎可以锁定在你自己的代码配置上。
如果连官方示例都搜不到,那就要回到前面第 3.1 节,检查协议栈是否还在。这个时候不要急着改代码,先把板卡恢复到一个确定能工作的状态,再做后续开发,否则所有问题都会混在一起。
4.2 写一个最小可广播工程
在确认官方例程能广播之后,我会自己建一个尽量精简的工程,只保留点灯和广播功能,用来做后续的"变量控制实验"。代码逻辑大致如下:
static uint8_t adv_data[] = { 0x02, 0x01, 0x06, // Flags: LE General Discoverable 0x09, 0x09, 'W', 'B', '5', '5', '_', 'T', 'e', 's', 't' // Name: WB55_Test }; int8_t BLE_StartAdvertising(void) { int8_t ret; /* 1. 配置广播参数:传统可连接广播,静态随机地址,间隔约100ms */ ret = aci_gap_set_advertising_configuration( 0, GAP_ADV_IND, RANDOM_STATIC_ADDR, NULL, 160, /* min interval, 0.625ms * 160 = 100ms */ 160, /* max interval */ 0x07, /* 37/38/39三个信道全开 */ NO_WHITE_LIST_USE ); if (ret != 0) return ret; /* 2. 设置广播数据 */ ret = aci_gap_set_advertising_data(0, sizeof(adv_data), adv_data); if (ret != 0) return ret; /* 3. 启动广播 */ ret = aci_gap_start_advertising(0, GAP_FAST_ADV, 0); return ret; }这个工程的关键在于每个返回值都打印出来。如果第 1 步就返回错误,说明问题在协议栈初始化和地址配置;如果第 3 步返回错误,问题大概率在广播数据或连接参数。通过这种"最小化变量"的方式,你能非常快地锁定问题层次。
4.3 用日志和抓包确认广播
软件层面排查完,如果一切正常但手机还是搜不到,就要借助专业工具做物理层确认。NUCLEO-WB55RG 板载 ST-LINK 的虚拟串口可以输出 HCI 日志,打开官方例程默认的日志功能,观察协议栈有没有打印广播启动成功的事件。
更高阶的做法是抓取空中的无线电包。ST 官方提供基于另一块 STM32WB 开发板的 BLE Sniffer 方案,也可以用 nRF52840 Dongle 配 Wireshark 抓 BLE 广播包。抓包时如果能看到 37、38、39 信道上周期性出现的 ADV_IND 数据包,那说明射频部分完全正常,问题一定在扫描端或手机权限;如果只有某个信道有包,说明环境干扰严重或者天线方向性导致覆盖面差。
我印象最深的一次排查是:板卡程序完全正常,广播包也能用 Sniffer 抓到,但手机就是搜不到。后来发现手机开启了"仅限已配对设备"的过滤选项,把未知广播设备全屏蔽了。这种问题不抓包根本想不到。
5. 问题速查表与排查顺序建议
把前面所有内容整理成一张速查表,遇到问题直接对照。
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 手机完全搜不到,LED 正常闪烁 | BLE 协议栈被全片擦除 | 检查 FUS 版本,重新烧录协议栈 |
| 手机完全搜不到,串口无日志 | 初始化未执行或卡死在 BLE_Init | 检查是否调用初始化函数,确认 M0 协议栈状态 |
| 所有扫描端都搜不到,板卡发热 | 射频开关/GIPO 配置错误 | 检查 RF 路径配置,恢复默认电平 |
| 手机放在 30 厘米内才能搜到 | 发射功率过低或天线被遮挡 | 调高 TX Power,清理天线区域 |
| 广播时有时无,间隔不稳定 | 低功耗模式/看门狗复位 | 关闭低功耗,检查复位原因寄存器 |
| 手机能搜到但无法连接 | 广播类型设置错误 | 改用 ADV_IND 可连接广播 |
| 只有部分手机能搜到 | 扫描端权限或过滤设置 | 检查定位/附近设备权限,换 App 验证 |
| 广播数据超过 31 字节 | 传统广播溢出,包被丢弃 | 缩短数据或改用扩展广播 |
推荐的排查顺序是:先确认手机权限和 App 设置,再用官方例程验证硬件和协议栈,接着用最小工程逐个检查初始化返回值,然后看串口日志,最后才动天线和供电。这个顺序能最大化减少变量,避免反复烧录浪费时间。
6. 最后分享几个习惯
排查 BLE 无广播问题,做得多了,我有几个固定习惯,分享给大家。
第一个习惯是拿到新板子后,第一件事不是烧自己代码,而是先备份 Flash 原始内容。用 STM32CubeProgrammer 的 Read 功能把整片 Flash 读出来存成 bin 文件。这样即使后面误全片擦除,也能快速恢复出厂状态,不用重新走一遍官方协议栈烧录流程。
第二个习惯是永远保留一个最小广播工程模板,不依赖厂商例程里的复杂业务逻辑。这个模板只有时钟初始化、协议栈初始化、一个 LED、一个广播启动函数。每次建新项目都从这个模板开始,扩展一步验证一步,问题范围会小很多。
第三个习惯是别忽略串口日志。STM32WB 的 HCI 层会返回非常详细的错误码,很多你以为的玄学问题,日志里其实写得很明白。花半小时把日志解析熟,比在网上查各种"没广播怎么办"的帖子高效得多。
按照这条链路走下来,NUCLEO-WB55RG 没有广播的问题,基本都能在一个小时内定位到根因。剩下的就是祝你调通之后,顺利进入 BLE 连接的下一关。