直接说结论:STM32WB55报"Cannot access memory"、"Internal command error"、"Flash download failed"这三连,绝大多数时候芯片没坏,而是它的双核安全机制和选项字节状态把调试口拦住了。我手上有几片WB55CCU6,头一天还在正常跑BLE透传例程,第二天插上ST-Link准备烧新固件,STM32CubeProgrammer直接甩了三行红字。点Erase想擦干净重来,照样报错。整块芯片像死了一样。后面花了一个下午从接线查到选项字节,才把这批板子救回来。这篇文章把完整的排查思路和恢复流程写出来,适合正在被WB55折腾、或者马上要拿这颗料做项目的朋友参考。
1. 报错复现与第一直觉
1.1 三个典型报错及其真实含义
先把最容易出现的报错原文摆出来,大家对照一下自己是不是同款:
Error: Cannot access memory Error: Internal command error Flash download failed - Target DLL has been cancelled这三条报错常被误认为是"芯片锁死"或"芯片损坏",但它们的含义完全不同。第一条"Cannot access memory"说的是调试器发出的内存访问请求没有得到响应,也就是说内核压根没有在执行或者调试接口被屏蔽了。第二条"Internal command error"是ST-Link固件与芯片通信过程中,芯片返回了非预期的状态,常见于Flash处于保护状态或FUS占用了仲裁权限。第三条"Flash download failed"最直接,就是前两步失败后下载动作被终止,本质上是前两个问题的连带结果。
如果点连接时直接提示"Target no device found"或者"Connection error",那是硬件层面的问题,后面再说。凡是能弹出设备ID或者能进到主界面但读写失败,基本都指向调试权限和Flash保护。
1.2 硬件环境与复现条件
我这里复现用的环境给大家做个参考:STM32WB55CGU6自制核心板(VDD 3.3V供电),ST-Link/V2(固件版本V2.J37.M28),STM32CubeProgrammer 2.14.0,Win10 x64。前一晚烧录的是BLE_HeartRate例程,运行正常,整板断电。第二天上电后不烧录直接测试功能也正常,但一接ST-Link就报错。
这个细节很关键:芯片能跑、功能正常,但调试器读不了Flash。这说明内核和Flash本身没坏,问题出在连接阶段。很多人在这一步就开始怀疑芯片坏了,其实方向就错了。我见过很多人把报错截图发到社区,十有八九是RDP(读保护)等级被意外改为Level 1,或者SWD引脚被用户代码复用成了普通GPIO。
2. STM32WB55不是普通单核MCU:双核、FUS与调试权限
2.1 M4和M0+分工:用户区与无线协议栈
STM32WB55和普通STM32最大的区别是它有两个内核:Cortex-M4用来跑应用代码,Cortex-M0+专门负责无线协议栈(BLE、Zigbee、Thread)。这个M0+核心不直接开放给用户调试,它运行的是ST官方提供的协议栈固件,用户只能通过API和它交互。
问题就在这:Flash是两颗核心共享的,M0+跑协议栈时需要占用一部分Flash空间,并且拥有比M4更高的访问权限。如果你在调试时试图擦除整个Flash,而M0+正占用着协议栈区域,擦除操作就可能触发仲裁异常,Flash控制器返回错误,CubeProgrammer就报"Internal command error"。这也是为什么WB55的Flash擦除不能像F103那样随手全片擦。
2.2 FUS安全区与闪存仲裁
WB55内部还有一个FUS(Firmware Upgrade Service),它运行在系统存储器的安全区域,负责无线协议栈的升级和密钥管理。FUS占用的区域对用户是不可见的,但它拥有最高的访问优先级。当你使用低版本CubeProgrammer连接时,可能连FUS的版本信息都读不出来,更别说操作Flash。
我遇到过一种情况:WB55拿到手后被升级过FUS,但协议栈还是旧的,M4上电后等待协议栈就绪超时,反复软复位。这种情况下调试器连接时芯片状态一直在跳,连接不稳定,读Flash时一半成功一半失败,看起来就像"读不到"。解决思路不是反复重试,而是用较新的CubeProgrammer把FUS和协议栈版本对齐。
2.3 选项字节里的RDP等级:从0到2的后果
选项字节(Option Bytes)里有一个RDP(Read Protection)字段,分为三个等级,这个决定了你的调试器能不能正常访问Flash:
| RDP等级 | 含义 | 调试访问 | 恢复方式 |
|---|---|---|---|
| Level 0 | 无保护 | 完全开放 | 无需恢复 |
| Level 1 | 读保护 | 调试口被禁止,无法读Flash/内存 | 通过降级触发全片擦除 |
| Level 2 | 永久保护 | 彻底关闭调试口,无法降级 | 不可恢复 |
Level 0就是出厂状态,随便读随便写。Level 1是很多人在开发过程中不小心设置进去的。一旦进入Level 1,调试器连接时会发现内核ID,但一访问Flash或内存就报"Cannot access memory"。Level 1有个特性:允许把RDP降回Level 0,但降级操作会自动触发一次全片擦除,这是芯片设计层面的强制行为。Level 2则是一锤子买卖,写入之后芯片的调试接口永久关闭,任何工具都无法再连接,官方文档明确说这是不可逆操作。
我见过把RDP设为Level 2再求救的案例,那种情况只能换芯片。所以下面的所有恢复操作,你们切记不要把RDP改成Level 2,连试都不要试。
3. 排查全流程:从连接失败到确认保护等级
3.1 硬件连接排查:SWD引脚别接错
先做硬件排除,这个顺序不能反。STM32WB55的SWD引脚是PB14(SWCLK)和PB15(SWDIO),不是F1/F4上常见的PA13/PA14。原理图如果是从别的芯片模板直接改的,非常容易把SWD两根线接到PA13/PA14上,自然连不上。
硬件排查按这个顺序来:
- 确认VDD 3.3V稳定,用万用表量芯片供电脚,不要只量电源指示灯。WB55的射频部分启动瞬间电流较大,劣质LDO可能把电压拉到2.7V以下,导致调试器连接时芯片复位。
- 确认NRST引脚上的电容不要太大,100nF一般没问题,但有人放了4.7uF导致复位信号上升沿太慢,ST-Link的时序不满足,连接经常失败。
- 确认SWDIO、SWCLK、GND三根线必须共地,且尽量短。杜邦线超过20cm时,把SWD频率降到1.8MHz或更低试试。
- 确认BOOT0引脚状态,默认应该是低电平。虽然WB55的BOOT0不影响SWD连接,但会影响Bootloader模式选择,后续串口恢复时会用到。
3.2 连接模式选择:Connect under reset与Hot-Plug
硬件没问题但依然连不上,或者连上就掉,就要在CubeProgrammer的连接设置里动手脚。ST-LINK的连接模式有三种:Normal、Connect under reset、Hot-Plug。
Normal模式最常用,但对付不了"用户代码把SWD引脚复用成GPIO"的情况。比如你的代码在初始化里把PB14/PB15配置成了普通IO,芯片上电后立刻执行这段配置,调试器还没来得及接管就被踢下线了。这时候必须用Connect under reset模式:调试器先把NRST拉低,让芯片保持复位状态,然后再初始化SWD接口。因为复位期间引脚不执行用户代码,调试器就能抢先接管。
Hot-Plug模式适合芯片已经跑起来、但软件没有明显占用SWD引脚的场景。它的原理是调试器发送特殊序列唤醒调试接口。我的经验是:遇到连接问题优先试Connect under reset,勾选后如果连接成功但立刻断开,再把频率降到4MHz以下试一次。
3.3 用CubeProgrammer读取Option Bytes
连接成功之后,不要急着Download,先把Option Bytes读出来看看。CubeProgrammer左侧栏点"Option Bytes"标签页,如果能看到TZEN、RDP、WRP这些字段的值,说明芯片已经被正确识别。
RDP字段显示Level 1,那问题就找到了。这多半是之前某次烧录配置里勾选了"Enable read protection",或者某段代码执行了设置RDP的操作。在界面上把RDP改成Level 0(AA),点Apply,工具会弹窗警告"这将导致Flash全片擦除",确认后芯片自动执行擦除。这一步很简单,但要注意执行完后芯片的Flash是空的,连无线协议栈也会被清掉,后面需要重新烧录协议栈。
如果RDP显示Level 0,但读Flash还是报"Cannot access memory",那可能是Flash的WRP(Write Protection)区域被设置了。WRP是按页保护,保护区域既不能写也不能擦除。在Option Bytes里把WRP全部清掉再试。还有一种情况是Flash的锁定寄存器没解锁,CubeProgrammer连接时如果没发送正确的解锁序列,读写也会报错,重新执行一次连接或者拔插ST-Link再试通常能解决。
3.4 确认FUS是否占用Flash
在Option Bytes页面没有异常但仍报擦除失败时,要看下FUS状态。CubeProgrammer主界面有个"Firmware Upgrade Services"图标(一个齿轮加RF的图标),点进去能读到FUS版本号和协议栈版本号。
正常的配置是:FUS版本对应芯片批次,协议栈版本对应你的应用。如果FUS版本是0.0.0,说明芯片里的FUS不完整,这种芯片烧录无线协议栈时会失败。如果FUS版本很高但协议栈版本很老,需要在"Wireless"标签页用ST提供的固件包升级或者降级。
特别提醒:FUS操作不要频繁执行,每次升级都会擦写系统区,多次异常断电可能把FUS区搞坏。FUS区一旦损坏,芯片只剩下串口Bootloader一条路可走,而且不一定能恢复。
4. 解除封锁的完整操作路线
4.1 通过ST-Link降低RDP等级并全片擦除
如果你的芯片RDP确实处于Level 1,最正统的恢复流程是这样:
- 打开STM32CubeProgrammer,选择ST-LINK,勾选Connect under reset,把频率设为4MHz。
- 点击Connect,成功后左侧进入Option Bytes页面。
- 在RDP下拉框选择Level 0(AA)。
- 点击Apply,等待工具自动执行擦除和降级。
- 擦除完成后重新连接,Option Bytes里RDP显示Level 0,Flash内容为空。
这个流程我在多片WB55上验证过,成功率很高。唯一要注意的是执行过程中绝对不要拔USB线或断电,否则可能在擦除中途卡在未定义状态,需要重新上电再连接一次,一般能恢复。
4.2 命令行方式处理无法连接的情况
有时候CubeProgrammer图形界面连接不上,但命令行能连上。原因可能是图形界面初始化的时序和命令行略有差异。命令行方式也可以完成全流程,而且适合批量生产时写进脚本:
STM32_Programmer_CLI.exe -c port=SWD mode=UR reset=HWrst freq=4000 STM32_Programmer_CLI.exe -c port=SWD mode=UR reset=HWrst freq=4000 -ob RDP=0xAA第一条命令用于测试连接,能看到芯片ID和内存信息。第二条命令执行RDP降级,同样会触发全片擦除。执行完之后再读一次Option Bytes确认RDP为Level 0。
如果第二条命令返回"Error: Option byte programming failed",不要立刻重试。先把芯片完全断电,重新上电,再执行一次连接,然后再执行降级。JTAG/SWD引脚有时会因为上一次异常操作处于锁定状态,重新上电可以清除。
4.3 串口Bootloader兜底恢复
如果ST-Link无论如何连不上,还有一条路:用系统Bootloader通过USART1恢复。STM32WB55出厂时系统存储器里有一段Bootloader,支持UART协议。操作步骤如下:
- 把BOOT0引脚拉高,NRST拉低再松开,让芯片进入系统Bootloader模式。
- 用USB转TTL模块连接USART1(注意WB55的USART1引脚与F1不同),TX接RX、RX接TX,共地。
- 打开CubeProgrammer,选择UART接口,波特率建议115200或更低的9600,点击Connect。
- 连接成功后执行全片擦除或者修改Option Bytes。
- 完成后把BOOT0拉回低电平,复位芯片。
这个方法对RDP Level 1同样有效,因为系统Bootloader本身有权限做全片擦除。不过要注意,RDP Level 2也会锁死Bootloader的读操作,UART同样救不了。串口恢复的缺点是速度慢,而且需要额外接一个USB转串口模块,但关键时刻能救命。
4.4 恢复后的无线协议栈重烧
芯片解锁擦除后不要急着烧应用代码,先把无线协议栈烧回去,否则BLE例程跑不起来。使用CubeProgrammer的"Wireless"标签页,选择对应的协议栈固件文件,比如stm32wb5x_BLE_HCILayer_fw.bin,点击Start Wireless Stack Upgrade。
这一步很多人会漏掉,结果烧完应用代码后M4一直等M0+的协议栈响应,系统卡在启动阶段。另外,协议栈版本必须和FUS版本匹配,否则升级会失败。ST的固件包里有版本对应关系的说明,不同芯片批次出厂时的FUS版本不同,建议先用最新版CubeProgrammer读一下FUS版本,再选择匹配的协议栈。
5. 这些坑才是真正的元凶:经验总结
5.1 最容易踩的5个操作误区
WB55的读写擦失败,说到底大部分是操作习惯问题。我梳理了最常见、也最典型的五个误区:
- 烧录时勾选了"Reset after programming"但没有勾选"Connect under reset",结果芯片一复位就执行用户代码,把SWD引脚复用成GPIO,调试器当场失联。这不是芯片问题,是烧录配置问题。
- 直接从STM32F1/F4的工程模板移植,SWD引脚配置沿用PA13/PA14,根本没有初始化PB14/PB15的SWD功能。F4上正常的调试连接,在WB55上可能因为引脚配置冲突直接失败。
- 用ST-Link给整板供电。WB55射频发射时电流峰值能到几十毫安,ST-Link板载LDO余量不够,电压跌落导致调试器连接过程中芯片反复复位。正确做法是独立供电,ST-Link只接SWD三根线。
- 在代码里主动设置了读保护。某些参考例程里带了设置RDP的代码片段,或者通过CubeMX勾选了相关选项,烧录时没有注意,造成芯片进入Level 1。写代码前养成检查Option Bytes配置的习惯。
- 升级FUS时断电或者拔线。FUS升级过程比普通Flash写入更敏感,中途异常断电很可能把系统区写坏了。非要升级,就找个稳定电源,升级前把低功耗功能关掉。
5.2 量产和开发中的防护策略
开发阶段:
- 所有工程默认勾选Connect under reset,这个习惯能避免90%的"芯片失联"问题。
- 不要在产品化之前设置任何RDP保护,包括Level 1。等固件完全稳定、批量出货前再评估是否开启。
- 在代码里留一个调试后门,比如上电后检测某个引脚状态,如果为高则跳过用户初始化,直接进入死循环等待调试器。这样即使后面误开了保护,也有挽回余地。
量产阶段:
- 使用STM32CubeProgrammer的命令行模式统一烧录,脚本里在烧录前读取RDP等级并判断是否符合预期。
- 如果产品需要开启RDP Level 1保护固件,务必在产线最后一道工序执行,并且保持BOOT0引脚可操作,万一需要返工会方便很多。
- 千万不要批量设置RDP Level 2。我见过有人为了防抄板,把一批芯片全部设成Level 2,结果固件版本号烧错了要升级,整批芯片报废,代价非常大。
还有一个容易被忽略的点:WB55的擦写寿命虽然标称1万次,但FUS区域和协议栈区域属于高频擦写区域,反复升级可能导致这些区域提前老化。开发过程中尽量少做整片擦除,改用按地址擦除或者增量下载。我自己的项目里,平时只下载应用固件,只有协议栈更新时才碰Flash全片。
这次从"芯片读不了"到成功恢复,整个过程最深的体会就一句话:当嵌入式芯片报错时,先怀疑配置,再怀疑硬件,最后才考虑芯片本身。STM32WB55的双核架构决定了它比普通MCU多了一层保护机制,理解FUS、RDP、选项字节之间的关系,遇到这类问题就不慌了。如果你们手头还有更奇怪的WB55故障现象,欢迎来交流,我这边积累了不少案例,没准能帮上忙。