最近在调一块基于 STM32G031J6M6 的小板子,想把复位引脚 NRST 从默认的双向复位模式改成普通复位输入模式,结果被 STM32CubeProgrammer 反复提示 Unable to change NRST_MODE,前前后后折腾了一个下午。这个报错在 STM32G0 系列上其实不算冷门,尤其是 G031J6M6 这种引脚很少的小封装芯片,在修改 Option Bytes(选项字节)时更容易踩中。如果你也遇到过类似情况,或者正准备给 G0 系列调整 NRST 复位行为,这篇内容应该能帮你少走不少弯路。
先从问题本质说起:NRST_MODE 不是一个普通寄存器位,而是一个必须在复位后由硬件从 Flash 选项字节区加载的配置。这个机制决定了你不能像改一个 GPIO 配置寄存器那样随手写一下就完事,修改流程、连接方式、甚至芯片当前的安全等级都会影响最终结果。下面我会从复位模式原理讲起,再逐个排查“改不了”的原因,最后给出三种可复现的修改方案。
1. 先搞清楚 NRST_MODE 是什么,为什么有人要改它
1.1 STM32G031J6M6 这颗芯片的使用场景
STM32G031J6M6 是 STM32G0 系列里非常典型的一颗小封装芯片:Cortex-M0+ 内核,主频最高 64MHz,内置 32KB Flash、8KB SRAM,封装形式常见的是 SOP-8 这种引脚极少的类型。因为封装小、外设丰富、价格便宜,很多做传感器采集、小家电控制、简单电机驱动的工程师都喜欢拿它做小型化方案。
但小封装带来的直接问题就是引脚极度紧张。8 个引脚里要分配电源、地、SWD 调试口,剩下来能做事的引脚本来就没几个。NRST 这个引脚单独占了物理引脚位置,虽然它不能复用为普通 GPIO,但在整个复位方案的设计上,你依然要对它做明确处理:默认接一颗 100nF 到 1µF 的电容到地,可能还要连外部复位芯片。一旦外部复位电路设计不当,NRST_MODE 就成了第一个背锅的配置项。
1.2 双向复位与普通复位:两种模式到底差在哪
在 STM32G0 系列里,NRST 引脚有两种工作模式,由选项字节中的 NRST_MODE 位决定:
默认的双向复位模式(Bidirectional Reset Mode,NRST_MODE=0):NRST 既是输入也是输出。内部任何一个复位源产生复位事件时,NRST 引脚会被内部电路主动拉低一段时间,用来复位外部电路;同时外部也可以主动拉低 NRST 来复位芯片。这个模式的好处是复位信号可以双向共享,外部复位芯片、调试器、看门狗都能通过同一条线协同工作。
普通复位输入模式(Normal Reset Input Mode,NRST_MODE=1):NRST 只作为输入引脚使用,芯片内部复位源不会再把引脚拉低,外部给低电平依然可以触发复位。这种模式更适合低功耗应用,避免了复位输出驱动带来的额外功耗,也防止内部复位信号干扰外部敏感电路。
很多人想改成普通模式,并不是为了省出这个引脚,而是为了解决实际电路问题。比如外部挂了独立的上电时序控制芯片,如果芯片内部看门狗或软件复位时把 NRST 拉低,可能影响外部复位设备的判断,导致系统无法正常上电。改完模式后,NRST 就纯粹是一个输入检测引脚,系统行为会更可控。
这里要特别提醒一点:选项字节不是普通寄存器,它在上电复位(POR)或 NRST 复位时才被硬件加载到内部配置寄存器里。所以无论你用哪种方式修改 NRST_MODE,改完后都必须要给芯片做一次完整的断电重上电,或者拉低 NRST 复位一次,新配置才会真正生效。这也是很多“改完没反应”问题的根源。
2. 排查思路:先确认“改不了”的具体表现
遇到 NRST_MODE 改不了,第一步不是去试各种命令,而是先判断你遇到的是哪一类“改不了”。不同表现对应完全不同的根因,盲目操作容易把问题搞得更复杂。
2.1 表现一:STM32CubeProgrammer 直接报错
在 CubeProgrammer 的 Option Bytes 页面里把 NRST Mode 从 Bidirectional 改成 Normal,点 Apply 后弹窗报错,常见错误包含 Option Bytes programming failed、OB: OPB Programming Error、Data error 等。这类报错说明软件确实尝试写入选项字节,但芯片没有接受这次编程。
最典型的场景是:点击 Apply 时弹出了一个警告,提示修改 Option Bytes 会擦除用户 Flash,而 CubeProgrammer 默认可能不勾选擦除选项,或者你下意识点了 Cancel。G0 系列在部分选项字节编程场景下,芯片要求先擦除或重新编程相关区域,否则会直接返回编程错误。这时候如果你一直忽略擦除提示,就会陷入“怎么改都报错”的循环。
另一个常见场景是连接时没有选对模式。如果芯片内部程序已经把 SWD 引脚复用成了普通 GPIO,或者芯片正处于某种异常复位状态,CubeProgrammer 虽然在连接界面显示成功,但实际的调试握手并不稳定,此时修改 Option Bytes 也会失败。
2.2 表现二:改完显示成功,实际却没生效
CubeProgrammer 提示 Option Bytes 编程成功,读回来也变成了目标值,但只要一断电重新上电,NRST_MODE 又变回默认模式。这种情况比直接报错更让人头疼,因为它表面上看起来一切正常,实际上芯片根本没有加载新配置。
常见原因有两个。第一个是修改后没有做完整的断电重上电,只是点了 CubeProgrammer 里的复位按钮。软件复位不一定触发选项字节重新加载,很多 STM32 系列必须由 POR 或 NRST 复位才能装载新选项。第二个是芯片在跑用户程序时,程序启动阶段主动把选项字节改回了默认值。这种情况在带有 bootloader 或“恢复出厂设置”逻辑的工程里很常见,CubeProgrammer 改的配置被用户代码在初始化时覆盖,看起来就是“改完不生效”。
还有一种隐蔽情况:CubeProgrammer 修改成功后,你重新连接时看到的值确实是对的目标值,但芯片内部实际加载的仍然是旧值,因为你连接时使用的复位方式不对,调试器的 shadow 寄存器显示和硬件加载不一致。这种情况在 under reset 连接模式下偶尔会出现,重新上电后读回才能确认真实状态。
2.3 表现三:代码里调用 HAL 接口,返回错误或卡死
排除了 CubeProgrammer 的问题后,有些人会改用手写代码的方式修改 NRST_MODE,比如在程序里调用 HAL_FLASHEx_OBProgram。结果发现 HAL 函数返回 HAL_ERROR,或者调用 HAL_FLASH_OB_Launch 后芯片没有按预期复位,甚至程序直接跑飞。
这类问题大概率是流程没走完整。修改选项字节的完整流程包含解锁 Flash、解锁选项字节、修改目标配置、编程写入、启动加载等步骤,缺少任何一环都可能失败。很多人在修改用户选项字节时没有先读回当前选项字节配置,导致编程时把其他关键选项覆盖了,虽然 NRST_MODE 写进去了,但其他配置异常导致启动时校验失败,最终芯片回退到默认值。
此外,直接在代码里操作 FLASH->OPTR 寄存器也是一大坑。Option Bytes 区域必须通过 Flash 控制器接口编程,单纯往寄存器的影子值里写数据,断电后不会保留。真正可靠的代码修改方式,一定要走 HAL 或 LL 库提供的选项字节编程接口。
3. 五大常见根因与解决步骤
3.1 选项字节没有解锁,或连接阶段不对
STM32G0 的选项字节区域在上电后默认处于锁定状态,不解锁直接写入,Flash 控制器会直接拒绝。CubeProgrammer 正常会自动完成解锁,但如果你修改时连接不稳定,或者芯片正处于低功耗模式,解锁操作可能没有成功。
解决思路很简单:在 CubeProgrammer 中不要用 hot plug 模式连接,尽量使用 Mode = Under Reset。这种模式下,调试器会先把 NRST 拉低,让芯片处于复位状态,然后在复位释放瞬间建立连接。对于一开始就连不上、或者连接成功后操作异常的情况,under reset 是最稳的选择。连接稳定后再进入 Option Bytes 页面修改 NRST_MODE,成功率会高很多。
代码修改时也同理。HAL 库的流程是先调用 HAL_FLASH_Unlock() 解锁 Flash,再调用 HAL_FLASH_OB_Unlock() 解锁选项字节。如果你在程序里跳过了第二步,后续的编程操作必然失败。检查代码时优先确认这两个解锁函数都被正确调用,且返回值没有错误。
3.2 修改选项字节与 Flash 擦除的关系
这是最容易被误判的根因。在 STM32G0 系列上,修改部分选项字节会触发 Flash 内容的擦除,尤其是与读保护、写保护相关的配置。CubeProgrammer 在检测到需要擦除时,会弹出确认框,如果这个确认框被忽略或取消,修改操作就会直接失败,报错信息就是那串 Option Bytes programming failed。
很多工程师不理解“改个复位模式为什么要擦程序”,其实这是芯片的保护机制:选项字节决定了 Flash 的访问权限,如果同步修改了保护相关选项,芯片必须通过擦除来确保新的安全状态被连贯加载。NRST_MODE 本身虽然不直接涉及 Flash 保护,但它在用户选项字节区域里,和其他选项共享同一套编程流程,某些情况下工具会选择全片擦除的方式来简化操作。
实操建议是:改 NRST_MODE 之前,先把当前 Flash 完整读取备份成 .hex 或 .bin 文件。然后在 CubeProgrammer 中点 Apply 时,如果弹出擦除确认框,直接确认擦除,等修改完成后再把备份的固件烧回去。不要为了保留 Flash 里的旧程序而强行取消擦除提示,那样只会陷入“改不了”的死循环。
3.3 RDP 读保护等级把选项字节锁死了
这个原因最容易踩但也很少有人第一时间想到。如果芯片的选项字节里 RDP(读保护)等级不是 Level 0,而是 Level 1 或 Level 2,那么通过调试器修改选项字节会受到严格限制。
Level 1 状态下,调试口仍然可以有限访问,但修改选项字节可能被拒绝,或者要求先执行整片擦除将 RDP 降回 Level 0。Level 2 则是永久锁定状态,调试口完全关闭,任何通过调试器修改选项字节的操作都会失败。如果芯片被设置过 Level 2,基本只能换一片芯片处理。
排查方法很简单:在 CubeProgrammer 的 Option Bytes 页面里直接看 RDP 等级。如果显示不是 Level 0,先用 Remove Protection 功能解除读保护,这个过程会擦除整个 Flash,需要提前做好备份。解除后再修改 NRST_MODE,就不会被 RDP 挡路了。
3.4 NRST 被外部硬件拉低,芯片一直处于复位状态
回到标题里这颗 STM32G031J6M6,SOP-8 封装的引脚间距很小,如果开发板上 NRST 引脚的飞线、排针、复位芯片连接有问题,导致 NRST 长期处于低电平,芯片就会一直处于复位状态,所有调试操作都会失败。
这种故障有个典型特征:CubeProgrammer 基本连不上目标芯片,即使选择 under reset 模式,也可能在连接瞬间又因为外部拉低而立刻回到复位状态。这时候不要急着折腾软件,先拿万用表量一下 NRST 引脚的直流电平。正常工作时应该接近 VDD(高电平),如果量到接近 0V,优先断开外部复位电路、飞线和电容,再重新测量。
另外,NRST 引脚外围的电容也很关键。如果电容值取得太大,比如 10µF,复位释放时间会变长,under reset 连接模式下调试器可能等不到芯片完全启动就超时。G0 系列手册推荐的 NRST 滤波电容通常是 100nF 到 1µF 范围。如果你在调试的时候发现连接时好时坏,可以换一颗更小的电容试试,或者干脆先拿掉电容,确认修改成功后再加回去。
3.5 用户程序每次上电都把配置改回去
最后一种根因比较隐蔽。目标芯片里烧录的程序如果自己就包含选项字节修改逻辑,比如 bootloader 启动时强制把 NRST_MODE 设置为双向模式,那么你用 CubeProgrammer 再怎么改,只要一执行用户程序,配置就会被覆盖回去。
如何确认是这个原因?先修改 NRST_MODE 成功后,不要运行用户程序,直接再次连接并读回选项字节。如果值是你要的目标值,但一执行用户程序再读回就变回旧值,基本可以断定是程序主动改写。解决办法有两种:一种是临时烧录一个空程序,让芯片跑一个空 main,修改完选项字节后再烧回正式固件;另一种是在工程代码里定位到写选项字节的地方,把相关代码去掉或加条件判断,确保不在每次启动时覆盖已有配置。
4. 实操:三种方式修改 NRST_MODE(附完整步骤)
4.1 方式一:STM32CubeProgrammer 图形界面修改
这是最推荐给新手的方式,全程可视化,步骤清晰。前提是你已经正确连接好 ST-Link 或兼容调试器。
第一步,打开 STM32CubeProgrammer,在右上角选择连接方式。接口选 SWD,Mode 选择 Under Reset,然后点击 Connect。如果正常连接,左侧导航栏会显示芯片型号 STM32G031J6M6。
第二步,进入 Option Bytes 页面。在左侧菜单里找到 Option Bytes,点击后在主界面能看到 OB 配置列表。找到 NRST Mode 对应行,默认值一般是 Bidirectional Reset Mode(双向复位模式)。把这个值改成你要的模式,比如 Normal Reset Input Mode。
第三步,点右上角的 Apply 按钮。此时如果弹出任何关于擦除 Flash 的提示,不要急着取消,仔细看一下提示内容。确认可以接受擦除后,点确认继续。如果 CubeProgrammer 提示编程成功,说明选项字节已经写入。
第四步,断开连接,移除目标板电源,等待几秒后重新上电。这一步非常关键,只有完整断电后重新上电,新的 NRST_MODE 才会被硬件加载。上电后再用 CubeProgrammer 连接,进入 Option Bytes 页面确认 NRST Mode 已经是目标值。
注意:CubeProgrammer 版本差异会导致界面文字略有不同,但核心步骤一致。如果 Apply 后弹出 "Error: Option bytes programming failed",优先检查你是不是取消了一个需要擦除 Flash 的确认框。
4.2 方式二:命令行工具 STM32_Programmer_CLI
如果需要在产线上批量配置芯片,或者不想打开图形界面,可以用命令行工具完成同样的操作。STM32CubeProgrammer 安装目录下自带 STM32_Programmer_CLI 可执行文件。
修改 NRST_MODE 为普通复位输入模式的命令大致如下:
STM32_Programmer_CLI -c port=SWD mode=UR -ob NRST_MODE=1如果想把 NRST_MODE 改回双向复位模式,把最后的值换成 0:
STM32_Programmer_CLI -c port=SWD mode=UR -ob NRST_MODE=0命令里的-c port=SWD mode=UR表示使用 SWD 接口并在复位期间连接,-ob NRST_MODE=1表示把选项字节中的 NRST_MODE 设置为 1。不同版本的 STM32_Programmer_CLI 对参数名的解析略有差别,有些版本接受Normal或Bidirectional这样的字符串值,如果不识别就改用数字 0/1。
如果修改过程中提示需要擦除 Flash,可以先把固件备份好,然后单独执行一次全片擦除:
STM32_Programmer_CLI -c port=SWD mode=UR -e all擦除完成后再执行选项字节修改命令。修改完成后同样要断电重上电,再执行选项字节读取命令确认:
STM32_Programmer_CLI -c port=SWD -ob displ这条命令会列出当前所有选项字节的状态,注意看 NRST_MODE 或类似名称的字段。
4.3 方式三:HAL 库代码在用户程序里修改
有时候现场没有调试器,只能通过芯片里已有的程序来修改本身。这种情况下需要在固件里加入一段选项字节编程代码,通过自定义触发条件执行一次。
下面是一个基于 STM32G0 系列 HAL 库的示例函数,功能是把 NRST_MODE 设置为普通复位输入模式:
#include "stm32g0xx_hal.h" void Set_NRST_Mode_Normal(void) { FLASH_OBProgramInitTypeDef ob = {0}; /* 1. 解锁 Flash 和选项字节 */ HAL_FLASH_Unlock(); HAL_FLASH_OB_Unlock(); /* 2. 读取当前选项字节配置,避免覆盖其他关键项 */ HAL_FLASHEx_OBGetConfig(&ob); /* 3. 配置需要修改的类型为用户选项字节,并修改 NRST_MODE */ ob.OptionType = OPTIONBYTE_USER; ob.USERType = OB_USER_NRST_MODE; ob.USERConfig = OB_USER_NRST_MODE_RESET; /* 对应普通复位输入模式 */ /* 4. 写入选项字节 */ if (HAL_FLASHEx_OBProgram(&ob) != HAL_OK) { Error_Handler(); } /* 5. 启动选项字节加载,执行后芯片会立即复位 */ HAL_FLASH_OB_Launch(); }不同版本的 HAL 库在宏命名上可能有差异,比如OB_USER_NRST_MODE_RESET和OB_USER_NRST_MODE_SET的映射方向,建议在编译前先查一下你使用的stm32g0xx_hal_flash_ex.h头文件里这两个宏的具体定义。如果你是想改回双向复位模式,把USERConfig换成对应的另一个宏即可。
这里有两个必须注意的坑:
第一个是HAL_FLASH_OB_Launch()调用后芯片会立刻复位。如果你的整个程序都依赖每次启动时重新调用Set_NRST_Mode_Normal(),那么这次复位后又会被你的代码改一遍,等于没有修改。所以这段代码最好放在一个通过外部信号触发的引导模块里,并且执行后设置一个标志位,保证只写一次。
第二个是不要试图直接修改FLASH->OPTR寄存器来绕过流程。FLASH->OPTR只是加载后的影子寄存器,直接写它不会进入 Flash 的选项字节区,断电后一定丢。所有修改都必须通过HAL_FLASHEx_OBProgram或等价的 Flash 编程接口完成。
4.4 修改后必须做的验证与动作
不管用哪种方式修改,最终都要做一轮验证,否则无法确定配置真的生效。
首先是最直接的软件验证:重新连接 CubeProgrammer,在 Option Bytes 页面读回 NRST_MODE,确认值和目标一致。这一步能确认选项字节区的值与预期一致,但还不能百分百保证硬件已经加载。
其次是硬件验证:用示波器或万用表观察 NRST 引脚。如果是双向复位模式,触发一次看门狗复位或软件复位,NRST 引脚会出现一个低电平脉冲;如果是普通复位输入模式,执行同样操作时 NRST 引脚不会主动被拉低,只有外部给一个低电平信号时芯片才会复位。
最后是断电重上电验证:断开所有调试器连接,完全断电再上