最近在调一个基于 STM32WB55 的 BLE 项目,为了做安全的 OTA 升级,需要先把 SafeBoot 烧进去。结果第一次上手就被打脸:ST-Link 能正常连接芯片,但只要一点编程,STM32CubeProgrammer 就弹出一句 "Error when programming SafeBoot"。我整整折腾了两天,查遍了论坛和手册,最后发现真正的原因远不是一句"连接失败"那么简单。这篇就把整个排查链路和最终的解决方案整理出来,给正在踩同样坑的朋友一个参照。
这里说明一下适用的读者:如果你正在用 STM32CubeProgrammer 给 STM32WB55 烧 SafeBoot、烧无线栈、或者做安全启动相关的固件更新,遇到过 "Error when programming SafeBoot"、编程到一半失败、连接后无法下载这类问题,这篇文章基本覆盖了你需要检查的全部关键点。
1. 先说清楚:SafeBoot 究竟是哪一段程序,为什么这么难烧
1.1 STM32WB55 的双核启动链
STM32WB55 不是一颗普通的单核 MCU,它内部有 Cortex-M4 应用核和 Cortex-M0+ 无线核。M4 负责跑用户应用,M0+ 核专门处理蓝牙协议栈、Zigbee、802.15.4 这些射频任务。两个核之间通过 IPC 通信,M4 要发数据给射频,不是自己操作外设,而是把命令打包发给 M0+ 核去执行。
这带来一个很重要的特点:整颗芯片不只是"用户程序 + 系统 bootloader"两层结构,而是"用户程序 + 无线栈固件 + 安全启动服务 + 系统 bootloader"多层结构。ST 把安全启动、无线栈升级、密钥校验这些能力封装在底层服务里,其中就包含 FUS(Firmware Upgrade Services)和 RSS(Root Secure Services)。而在很多工程文档和讨论里,SafeBoot 指的就是处于启动链最前端的安全引导程序,它负责在 M4 应用核心运行之前,先校验镜像的签名和完整性,防止未授权固件被直接烧进去。
你可以把它理解成一道安检门:不是谁拿个包都能进候机厅,SafeBoot 先检查包的来源和标识,通过以后才放行。放到芯片上,SafeBoot 检查完下一级镜像后,才把启动控制权交给用户应用。
1.2 为什么编程 SafeBoot 时特别容易报错
正因为 SafeBoot 的角色特殊,它通常被放在 Flash 的保护区域内。有些情况下,芯片出厂后已经被配置成 RDP Level 1 甚至 Level 2,调试接口的访问权限受到严格限制;有些情况是用户为了防克隆,手动开启了写保护,把 SafeBoot 所在的扇区锁死了;还有些情况是 SafeBoot 与无线栈共用一些底层 flash 服务,直接按普通应用固件的方式去烧写,必然触发保护机制。
我遇到的 "Error when programming SafeBoot",核心原因就是这个:我要写的目标区域处于保护状态,或者当前芯片的调试权限不够,导致烧录器打开了连接、却写不进去。所以排查这个错误,必须沿着"硬件连接 -> 调试权限 -> 存储保护 -> 烧录方式"这条线走,缺一步都容易绕远路。
2. 我是怎么复现到这个报错的:三条典型路径
2.1 路径一:直接烧库函数代码,踩到写保护
第一种情况最容易发生在刚接触 STM32WB55 的开发者身上。很多人从 STM32F4、F1 系列转过来,习惯了直接打开 Keil 或 CubeIDE 点下载,以为只要 SWD 连上就能写。结果到了 STM32WB55 上,如果之前对 Flash 做过写保护配置,或者上一版程序启用了安全启动,你再直接烧用户代码,就会在烧写到 SafeBoot 区域时撞墙。
我当时就是先跑了一个官方示例,为了做测试把 RDP 改到了 Level 1。后来再想烧新程序时,CubeProgrammer 在连接阶段是正常的,能读到芯片型号、UID 和设备 ID,但点击下载后没过多久,进度条就停住,控制台输出 "Error when programming SafeBoot"。这个路径很典型,因为它说明软件和连接都正常,问题出在权限和存储保护上。
2.2 路径二:RDP 从 Level 1 降级失败
第二种情况更隐蔽。有人之前出于保密需求,把芯片设置成 RDP Level 1。Level 1 下调试接口还能连接,但已经不允许随意读写用户 Flash,尤其是访问安全相关的区域。这时候你想重新烧录 SafeBoot,系统会拒绝写入。正确的做法是先通过 Option Bytes 把 RDP 降到 Level 0,但这个降级动作会触发全片擦除,而且擦除过程本身也可能因为保护设置而失败。
我见过一个典型报错场景:在 CubeProgrammer 的 Option Bytes 页面,把 RDP 改成 Level 0,点 Apply,然后弹窗提示要擦除整个 Flash,选择确认后,界面卡住,最终在日志里看到和 "Error when programming SafeBoot" 类似的提示。这种问题通常不是芯片坏了,而是擦除动作和现有无线栈/FUS 分区发生了冲突。
2.3 路径三:先烧无线栈,再烧 SafeBoot,地址冲突
第三条路径和 STM32WB55 的双核架构直接相关。因为无线栈固件占用了 Flash 中专门划分的区域,而 SafeBoot 又处在安全启动段,如果你的下载配置里没有把这两个段的地址搞清楚,编程器可能会把 SafeBoot 镜像写到无线栈的区域,或者反过来,把无线栈的数据覆盖到保护区域。
我最开始犯的错误就是:因为急着测试,直接拿同事给的 BLE 项目包,把无线栈的 .bin 和 SafeBoot 的 .bin 一股脑塞进烧录脚本。结果无线栈烧录成功后,再烧 SafeBoot 就报错。后来看日志才知道,烧录器默认把 SafeBoot 当成普通用户代码,企图写到 0x08000000 起始地址,但那个区域前面已经被 FUS/RSS 相关的安全服务占用了。
这一条想说明的是:给 STM32WB55 烧录,不是"一个 hex 文件全搞定"那么简单。你首先得承认它是双核系统,要分开处理 M4 应用、M0+ 无线栈、SafeBoot 和系统服务区。
3. 第一层排查:硬件连接和电源,别让 ST-Link 背锅
3.1 VDDSW 没接,M0+ 核根本起不来
第一次排查时,我下意识认为是 ST-Link 连接不稳定,换了杜邦线、换了调试口,问题依旧。直到我翻到数据手册的电源域说明才反应过来:STM32WB55 有一个独立电源域 VDDSW,专门给 M0+ 无线核和射频部分供电。如果这个引脚没有正确供电,M0+ 核起不来,无线栈无法初始化,SafeBoot 相关的底层服务也就没有运行条件。
实际表现是:芯片从 SWD 接口看仍然能连接,因为你看到的是 M4 核,但一旦要访问与无线核相关的 flash 区域,或者要执行安全启动相关操作,底层服务不响应,编程就会中断。所以我给所有用 STM32WB55 做板子的人一个建议:画板时一定要确认 VDDSW 接法正确,不能想当然地认为它就是普通模拟电源。开发板、自制板、模块板都分别量一下 VDDSW 电压。
3.2 SWD 连接质量:NRST、短线、电平
硬件排查的第二件事是 SWD 连接线。这里有个容易被忽略的点:很多 STM32WB55 开发板在设计时已经把 NRST 引出来了,但你在调试时如果不是用 ST-Link 的 20 针全功能接口,而是自己接四根线(SWDIO、SWCLK、GND、3V3),往往省掉了 NRST。
省掉 NRST 不是完全不能烧,但在芯片处于异常状态、或者要修改 Option Bytes 时,没有复位信号会让你陷入"连接正常但操作失败"的怪圈。我后来特意把 NRST 加上,CubeProgrammer 的稳定性明显提升。另外,SWD 线最好控制在 10cm 以内,杜邦线质量差的时候,高速下载很可能在写 Flash 中途断连,报错日志也会出现类似 "Error when programming" 的信息。
3.3 先确认 ST-Link 和 CubeProgrammer 版本
这个问题看着基础,但我确实在版本上栽过。STM32CubeProgrammer 早期版本对 STM32WB 系列的支持并不完整,尤其是对 FUS 和 SafeBoot 相关的命令支持不够。如果你的版本低于 2.12,建议先升级到 STM32CubeProgrammer 最新版本;ST-Link 固件也要同步升级,用 ST 官方工具 ST-Link Upgrade 刷一遍。
在排查顺序上,我会先用 CubeProgrammer 做一次最小验证:连接芯片,读取 UID 和 Flash 大小。如果这一步都稳定,再继续往下查保护。如果连这步都时好时坏,那基本可以判断是硬件连接问题,先别急着动 Option Bytes。
4. 真正的主凶:RDP、写保护和 Option Bytes
4.1 RDP 三级权限与烧录失败的关系
排查完硬件后,问题大概率落在 RDP 和 Option Bytes 上。RDP 是读保护,但它不只是"防止别人读固件"那么简单,它直接决定了调试接口能不能访问 Flash,能不能修改 Option Bytes,甚至能不能全片擦除。
| RDP 等级 | 调试访问 | Flash 读/写 | Option Bytes 修改 | 解除方式 |
|---|---|---|---|---|
| Level 0 | 完全开放 | 正常读写 | 可以修改 | 无需解除 |
| Level 1 | 仍可连接,但受限 | 用户 Flash 读被限制,写可能被拒绝 | 受限 | 降级到 Level 0,会触发全片擦除 |
| Level 2 | 调试接口永久关闭 | 不可直接读写 | 不可修改 | 不可逆,芯片锁定 |
如果你之前用 Trusted Package Creator 或者安全启动脚本给芯片设置过保护,RDP 很可能不是 Level 0。这时候直接编程 SafeBoot 会报错,原因就在这。比较麻烦的是 Level 1,因为 CubeProgrammer 连接时看起来一切正常,但执行写操作就会失败,报错信息不直接提示"RDP is set",而是给你一个通用的烧写错误。
我建议在任何安全启动相关操作前,先用命令行工具读取 RDP 状态:
STM32_Programmer_CLI.exe -c port=SWD mode=UR连接成功后会列出 Option Bytes 摘要,其中 RDP 一栏会显示当前等级。如果你看到RDP: Level 1或RDP: Level 2,先不要烧录任何东西,降级处理是第一优先级。
4.2 WRP / PCROP 对 SafeBoot 区域的封锁
除了 RDP,还有一类保护和"防克隆"有关,叫写保护 WRP 和代码读保护 PCROP。STM32WB55 的 Flash 分为多个扇区组,Option Bytes 里可以对某几个扇区使能写保护。一旦写保护使能,用户程序无法往这些扇区写入,调试接口也不行。
SafeBoot 由于处在安全引导区域,经常被出厂配置或手动配置成写保护状态。这种情况下,编程动作会被硬件拒绝,CubeProgrammer 就会报 "Error when programming SafeBoot"。
实际操作中,我会在 CubeProgrammer 的 Option Bytes 页面里逐项检查:
RDP:必须为 Level 0WRP1A / WRP1B / WRP2A / WRP2B:需要确认 SafeBoot 对应的扇区组没有被置位PCROP:确认 SafeBoot 区域没有被设置成代码读保护BOOT0 / nSWBOOT0 / nBOOT1:确认启动模式不会干扰编程
如果你打算彻底还原芯片,最省事的办法是用 GUI 界面一键把保护全部取消。但注意,这个动作可能会触发全片擦除,包括你的无线栈。所以操作前先做好固件备份。
4.3 用 CubeProgrammer 一键解除保护的正确姿势
具体操作是这样:打开 STM32CubeProgrammer,连接成功后进入Option Bytes页面,在Read Out Protection下拉框里选Level 0,点击Apply。此时程序会弹窗提示 "This operation will erase all the device" 之类的警告,确认后等待完成。
如果是命令行环境,可以这样解锁:
STM32_Programmer_CLI.exe -c port=SWD mode=UR -ob RDP=0执行完以后,芯片会执行一次全片擦除,调试权限恢复到 Level 0。这时再连一次,确认 RDP 显示为 Level 0,就可以继续烧录 SafeBoot 了。需要特别强调的是 RDP Level 2 不可逆,一旦设置成 Level 2,调试口永久锁定,只能换芯片。所以除非你真的要做量产防破解,否则千万不要在生产之前乱开 Level 2。
5. 成功把 SafeBoot 烧进去的完整操作流程
5.1 烧录前的准备工作
我踩过坑之后形成了一个固定流程,每次给 STM32WB55 烧 SafeBoot 都按这个走,基本不再出问题。准备阶段要做三件事:
- 备份当前芯片的无线栈和用户固件。你可以用 CubeProgrammer 的
Read功能把整个 Flash 导出成一个 bin 文件,放在安全目录。这一条在你有无线栈、有校准数据、有量产固件时尤其重要,不然全片擦除之后哭都来不及。 - 拿到正确的 SafeBoot 镜像。不同 STM32WB 系列、不同无线协议栈版本,对应的 SafeBoot 可能不一样。最好从 ST 官方固件包
STM32Cube_FW_WB里找,不要随便用网上传的 bin。 - 确认烧录地址和下载算法。STM32WB55 的项目里,CubeProgrammer 会加载对应的外部加载器或内部 Flash 算法,如果选错,烧录时也会出现编程错误。
5.2 GUI 和 CLI 双版本操作
推荐用 GUI 版本做可视化烧录,用 CLI 版本做批量生产。GUI 的操作路径是:连接 ST-Link 后,在Download页面选择 SafeBoot 镜像文件,确认起始地址0x08000000(根据你工程的 MAP 文件确认),勾选Verify programming,然后点击Start。
如果你的 SafeBoot 镜像不是独立文件,而是需要配合 FUS 更新,那就要用Firmware Upgrade Services相关的流程。简单来说,ST 把无线栈和 SafeBoot 的底层升级集成在 FUS 里,你需要在 CubeProgrammer 的FUS页面连接底层服务,然后选择要升级的镜像。
CLI 版本的核心命令大致是这样的:
# 1. 解除读保护并全片擦除(慎用) STM32_Programmer_CLI.exe -c port=SWD mode=UR -ob RDP=0 # 2. 下载 SafeBoot 镜像到指定地址 STM32_Programmer_CLI.exe -c port=SWD mode=UR -w SafeBoot.bin 0x08000000 -v # 3. 如果是无线栈相关,先通过 FUS 接口确认状态 STM32_Programmer_CLI.exe -c port=SWD mode=UR -fwupgrade WirelessStack.bin我在生产脚本里还会在烧录之后加一个-v参数,也就是Verify,确保写入到 Flash 的数据和源文件一致。这个验证步骤能帮你排除大部分未发现的编程错误。
5.3 烧完怎么验证
烧完 SafeBoot 之后,不要急着断电。我一般做三步验证:
第一步,重新连接 CubeProgrammer,读取 Option Bytes,确认 RDP 等级没有意外变成 Level 1 或 Level 2。
第二步,通过Read功能,把刚写入的 SafeBoot 区域再读出来,和源 bin 做一次 MD5 或 SHA256 比对。命令行可以加-v自动校验,但手工比对更直观。
第三步,如果是带安全启动的项目,直接复位芯片,观察应用是否能够正常开机。如果 SafeBoot 校验不过,芯片会卡在启动阶段,表现就是 LED 不跑、串口没有输出、SWD 能连接到但程序不运行。这种情况第一时间检查签名、密钥和 SafeBoot 版本是否匹配。
6. 避坑总结:编程 SafeBoot 的快速检查表
最后把我这次排查中认为最值得记住的几点列成一个快速检查表,下次再遇到 "Error when programming SafeBoot",照着走可以省很多时间。
| 检查项 | 可能症状 | 解决方向 |
|---|---|---|
| ST-Link / 杜邦线 / NRST | 连接不稳定,烧到一半断开 | 换短线,接 NRST,升级 ST-Link 固件 |
| VDDSW 电源 | 能连接但无法写无线相关区域 | 检查 VDDSW 供电,万用表实测 |
| RDP 等级 | 编程被拒,全片擦除失败 | 降级到 Level 0,注意会擦除 Flash |
| WRP / PCROP | SafeBoot 区域无法写入 | Option Bytes 里取消写保护 |
| CubeProgrammer 版本 | 旧版本对 WB55 支持不全 | 升级到最新版本 |
| 镜像和地址不匹配 | 烧录成功后无法启动 | 核对 MAP 文件,重新选择起始地址 |
另外,我想特别建议一点:不要把 SafeBoot 烧录和普通应用烧录混在一个脚本里。STM32WB55 的 Flash 布局很讲究,SafeBoot、FUS、无线栈、用户应用、用户数据区,每一块都有各自的地址和保护策略。我在量产脚本里会把 SafeBoot 和无线栈的烧录分离,先烧底层服务,再烧用户应用,最后统一设置 RDP Level 1 做安全保护。
这次踩坑让我最大的体会是:遇到编程报错,第一步不是怀疑硬件坏了,也不是盲目去 Unlock,而是先把芯片当前的调试权限、保护等级和 Flash 布局搞清楚。很多时候问题就出在"你以为能写,但芯片根本不让你写"。ST 的安全启动机制本身就设计得比较谨慎,你越急着跳过去,它就越会在你最想不到的地方拦住你。希望这篇排查记录能帮你少走几趟弯路。