☰
STM32U5低功耗STOP3唤醒后Flash写入失败根因与恢复指南
2026/11/1 6:51:38 网站建设 项目流程

最近在调一块STM32U375的低功耗采集终端,遇到一个相当隐蔽的坑:系统进入STOP3模式之后,通过RTC闹钟唤醒,紧接着调用内部Flash写入接口保存数据,结果每次返回错误,严重的时候直接HardFault。换到之前的STM32L4平台,同样的逻辑跑得好好的,一度怀疑是不是这颗料有问题,甚至怀疑买到了工程样片。后来静下心来,对照参考手册把时钟、电源、Flash三部分的寄存器一条一条捋了一遍,才意识到问题根本不在Flash控制器本身,而是从STOP3醒来的那一瞬间,芯片的“工作现场”远没有恢复到可编程的状态。

这篇文章把这套问题的完整根因、排查思路和恢复步骤记录下来。无论你用的是U375还是U575,只要是基于STM32U5系列做低功耗产品,这篇内容都能帮你少走弯路。同时我也会把U5系列常用的STOP3唤醒方式一并整理出来,方便做功耗预算时参考。

1. 先从STOP3模式说起:睡下去之前,芯片到底关了哪些东西?

1.1 STOP3到底停掉了什么

这个问题不能只看Stop模式是“时钟停止”就完了。STM32U5系列的低功耗模式比L4复杂,分为STOP1、STOP2、STOP3等几档,每一档停掉的东西和保留的外设都不一样。STOP3在U5系列里属于比较深的一档,进入之后:

  • 大部分高频时钟直接停摆,包括PLL、HSI、MSIS、MSIK等,只有LSE、LSI以及部分外设时钟可以继续运行;
  • 系统进入低功耗状态下,AHB/APB上的绝大多数外设时钟被门控关闭;
  • 内核和总线进入停止状态,SRAM内容保留,但代码执行完全停下;
  • 供电系统可能自动切换到低功耗档位,内部LDO或SMPS降压运行,电压范围可能落在更低的一档。

很多人只看“RAM保住了”就以为唤醒后跟复位完全一样,其实不完全。从软件角度看,STOP3唤醒后的状态介于“复位”和“正常运行”之间:寄存器值大多保留,但时钟树已经被重置过,一些硬件状态机也已经回到初始状态。这就意味着唤醒后的恢复代码不能假设外设还处于睡眠前的状态。

1.2 唤醒后系统在什么状态下醒来

这里有个关键点:从STOP3唤醒之后,系统时钟源会被强制切回MSIS,而不是你睡眠前用的PLL或HSI。U5系列里MSIS默认频率通常是4MHz,这个值来自选项字节(如果有修改),但无论如何,它一定不是你跑满功耗模式主频时的那套配置。

同时,Flash控制器的等待周期配置、预取缓冲、指令缓存这些配置,在唤醒后也会丢失或者被重置成复位值。也就是说,当你唤醒后第一件事就调用Flash写入API时,软件看到的寄存器状态,可能还是“低频率下的默认配置”,而Flash编程操作对时序、电源、时钟都有硬性要求,在这种状态下直接操作,失败是必然的。

我在调试时踩的第一个误区就在这里:我以为Flash写入是纯库函数的事情,库内部会处理好时钟和等待周期。但HAL库的Flash写操作只关心Flash控制器本身,它不会替你把VOS、FLASH_ACR、PLL这些“外部条件”恢复好。Wakeup之后,外部条件恢复是你自己的责任,不是HAL的责任。

2. 为什么唤醒后Flash写入会失败:根因拆解

2.1 最容易被忽略的VOS电压范围问题

这个原因不说出来,很多人会一直卡在死循环里。U5系列的Flash编程对电源电压范围是有要求的。参考手册的“Flash programming”章节里都会给一张表,列出不同VOS电压范围下允许的Flash操作条件。如果在低功耗模式下VOS掉到了低档位,而你唤醒后没有把它升回来,Flash控制器可能根本不具备擦写所需的内核电压条件。

更常见的情况是:你自己的代码在进入STOP3之前,为了进一步降低功耗,主动把VOS降到了低档位。这是非常普遍的做法,因为低VOS下动态功耗会明显下降。但问题在于,唤醒之后恢复流程写得不完整,VOS还是停留在低档位,那Flash写入就会时不时地失败或者出现偶发错误。

我当时遇到的现象就是典型的“偶发失败”:写10次可能有几次失败,不是每次都失败,所以一开始根本没往VOS上想。后来通过寄存器查看器对比正常状态和唤醒状态,才发现PWR_CR里的VOS位果然还停在低档位。

2.2 Flash等待周期与系统时钟不匹配

Flash等待周期(Latency)是另一个大头。现在MCU主频越来越高,内部Flash的访问速度跟不上CPU,必须通过插入等待周期来补偿。STM32的惯例是:主频越高、电压越低,需要插入的等待周期越多。

问题出在唤醒后的恢复顺序。如果你先把系统时钟从4MHz切换到PLL的160MHz(或者100MHz),然后再去配置FLASH_ACR的等待周期,这中间就有一个短暂的时间窗口:CPU已经在高频下运行,但Flash的等待周期还是低频时的旧值。这个窗口里,Flash读取极不稳定,轻则指令预取错误,程序跑飞,重则直接HardFault。如果在这个当口执行Flash编程,那失败的概率几乎是100%。

正确的做法永远是:先提高Flash等待周期,再提高系统频率。反过来,降低频率时才能先切频率再降等待周期。这个顺序规则在STM32全系通用,U5也不例外,但低功耗唤醒时很多人会忽略它。

2.3 Flash电源和低功耗模式的退出状态

这部分是U5系列相对新加入的坑点。为了降低STOP模式下的漏电流,U5允许你在进入STOP前主动把Flash置于低功耗模式,甚至掉电模式。这通常在FLASH_CR1寄存器里有一个控制位(具体位名在不同版本参考手册里可能叫FSTPWG,或者类似用途的位)。

如果你在进入STOP3前置了这个控制位,那么唤醒后Flash实际上还处于“低功耗/掉电状态”。此时你直接发起Flash写入,控制器可能根本收不到有效的操作电压,表现就是PGSERR等错误标志被置位,或者写入操作挂起直到超时。

这块调试起来更难,因为很多人的代码里根本没有主动去设置这个位,而是使用了HAL_PWR_EnterSTOPMode之类的封装函数,库内部在进入Stop之前可能已经悄悄帮你把Flash低功耗位设置好了。唤醒后,HAL的PWR恢复函数会清除时钟门控,但不一定清掉Flash的低功耗控制位。所以这里必须要自己确认、手动清除并等待Flash退出低功耗。

2.4 时钟源切换顺序错误

再单独说一个很多人忽视的细节:从STOP3唤醒后不要急着切换PLL。如果你想用MSIS作为PLL的输入源,必须等MSIS稳定,即MSISRDY置1之后再配置PLL,否则PLL可能锁定失败,导致系统时钟源切到PLL后直接跑飞。

另外,如果你的设计里使用了HSI、HSI48等其它时钟源,也要等相应的就绪标志。唤醒后系统时钟是MSIS,但MSIS从启动到稳定需要一段时间。这个时间很短,但如果你不检查就绪标志就往下走,后续就是各种奇怪问题。

以上的几个根因,往往不是单独出现,而是叠加在一起。尤其是VOS、等待周期、Flash低功耗模式这三个因素,它们互相影响:VOS不对会影响Flash可用的最大频率,导致即使你配置了等待周期,也可能是无效配置。

3. 正确恢复流程:STOP3唤醒后的标准操作步骤

3.1 唤醒后首先要做的三件事

先讲结论:唤醒后别急着做业务逻辑,先把“电源、时钟、Flash接口”这三件套恢复好。

第一,确认当前电源状态,把VOS恢复到正常工作档位,并等待电压稳定标志置位。第二,确认当前系统时钟源和频率,按“先配Flash等待周期、再提频”的顺序恢复系统时钟。第三,检查Flash的低功耗控制位是否还挂着,如果挂着,先清除并等待Flash退出低功耗,同时清掉SR1里可能存在的错误标志。

这三件套做完,Flash编程的“环境”才算准备好。很多人的代码里,这三块配置散落在不同文件、不同阶段,或者干脆没写,这才会踩坑。

3.2 恢复系统时钟的配置顺序(含代码)

以U375上我实际使用的配置为例,过程大概是这样的:

static void SystemClock_RecoverAfterStop3(void) { /* 1. 等待MSIS稳定 */ while (READ_BIT(RCC->CR, RCC_CR_MSISRDY) == RESET) { } /* 2. 如果之前VOS被降过档,先恢复VOS范围 */ MODIFY_REG(PWR->CR5, PWR_CR5_VOS, PWR_CR5_VOS_0); /* 以实际寄存器为准 */ while (READ_BIT(PWR->SR2, PWR_SR2_VOSRDY) == RESET) { } /* 3. 在提高主频之前,先把Flash等待周期拉到目标频率所需的值 */ MODIFY_REG(FLASH->ACR, FLASH_ACR_LATENCY, FLASH_ACR_LATENCY_4WS); /* 根据目标主频选择 */ while (READ_BIT(FLASH->ACR, FLASH_ACR_LATENCY) != FLASH_ACR_LATENCY_4WS) { } /* 4. 配置PLL并等待锁定 */ RCC->PLLCFGR = ...; SET_BIT(RCC->CR, RCC_CR_PLLON); while (READ_BIT(RCC->CR, RCC_CR_PLLRDY) == RESET) { } /* 5. 切换系统时钟源到PLL */ MODIFY_REG(RCC->CFGR, RCC_CFGR_SW, RCC_CFGR_SW_PLL); while (READ_BIT(RCC->CFGR, RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL) { } }

这段代码里的具体位名和参数根据你的主频、版本、时钟树设计去调整,不要照抄。重点在于顺序:MSIS稳定、VOS恢复、Flash等待周期先提升、PLL锁定后切换。每步都有等待标志,不要省。

一个常见误区是使用HAL库的SystemClock_Config()函数来恢复时钟。这个函数在复位场景下是好的,但在STOP3唤醒场景下,时钟树默认已经是“被重置过”的状态,直接从它开始恢复有时会出问题。我更推荐自己维护一个专门用于唤醒恢复的时钟配置函数,里面只做唤醒所需的配置,不碰多余外设。

3.3 恢复Flash写入条件的确切操作

时钟和电压恢复之后,还要专门处理Flash自身的状态。我实际的恢复操作分五步:

static void Flash_RecoverAfterStop3(void) { /* 1. 如果FLASH_CR1里的FSTPWG还置位,先清除 */ CLEAR_BIT(FLASH->CR1, FLASH_CR1_FSTPWG); /* 2. 等待Flash退出低功耗状态,确认BSY位或状态标志已就绪 */ while (READ_BIT(FLASH->SR1, FLASH_SR1_BSY) != RESET) { } /* 3. 清除SR1里可能残留的错误标志,避免后续编程被旧的错误状态干扰 */ FLASH->SCR1 = FLASH_SCR1_PGSERR | FLASH_SCR1_PGAERR | FLASH_SCR1_PESD; /* 4. 解锁Flash写入接口(如果之前锁上了) */ if (READ_BIT(FLASH->CR1, FLASH_CR1_LOCK) != RESET) { WRITE_REG(FLASH->KEYR1, FLASH_KEY1); WRITE_REG(FLASH->KEYR1, FLASH_KEY2); } /* 5. 再次确认操作条件 */ if (READ_BIT(FLASH->SR1, FLASH_SR1_CFGBSY) != RESET) { /* 配置忙,说明Flash还在处理之前的配置,必须等待 */ while (READ_BIT(FLASH->SR1, FLASH_SR1_CFGBSY) != RESET) { } } }

这些寄存器名字不保证在你的版本里一模一样,但思路是通用的:清除低功耗控制位、等待忙标志、清错误标志、解锁、等待配置忙。特别是最后一步CFGBSY(配置忙)在U5系列很重要,如果Flash正在处理某些配置操作,编程命令发过去也会被丢弃。

3.4 恢复完成后进行Flash编程的示例

环境恢复好后,Flash编程就可以正常进行了。在U5上,Flash编程通常需要先解锁,然后往目标地址写数据,编程期间轮询BSY标志。

static int WriteDataToFlash(uint32_t addr, uint32_t *data, uint32_t len) { /* 解锁已在Flash_RecoverAfterStop3里完成,这里直接操作 */ for (uint32_t i = 0; i < len; i++) { WRITE_REG(*(volatile uint32_t *)addr, data[i]); addr += 4; while (READ_BIT(FLASH->SR1, FLASH_SR1_BSY) != RESET) { } if (READ_BIT(FLASH->SR1, FLASH_SR1_PGSERR) != RESET) { FLASH->SCR1 = FLASH_SCR1_PGSERR; return -1; } } return 0; }

如果你之前已经正确清掉了错误标志并且解锁了Flash,那么这段代码在唤醒后就能正常执行。如果仍然失败,回头查VOS和等待周期,这两个点最容易被忽略。

4. STM32U575/U375 STOP3唤醒方式选择与配置

4.1 支持的唤醒源有哪些

U5系列的STOP3模式下,常见的唤醒源包括RTC(闹钟、唤醒定时器、入侵事件)、外部WKUP引脚、LPUART(低功耗串口)、LPTIM(低功耗定时器)、以及部分IO外部中断。具体到STOP3这一档支持哪些,需要以参考手册的“低功耗模式Wakeup source”表格为准,但RTC和WKUP引脚是几乎所有低功耗模式下都支持的,也是最常用的两个。

我在项目里主要用了两种:一种是RTC闹钟定时唤醒,适合定时采集场景;另一种是WKUP引脚唤醒,适合外部触发场景,比如检测到人体红外或者按键事件。这两种在STOP3下都能跑,而且实现不复杂。

4.2 常用唤醒源配置要点

先说RTC唤醒。RTC在U5里可以由备份域供电,STOP3下只要LSE保持运行,RTC就可以继续走。配置时要注意:使能RTC唤醒定时器,设置自动重载值,然后在NVIC和EXTI里把RTC唤醒事件对应的中断线打开。

void RTC_Wakeup_Config(uint32_t seconds) { /* 使能RTC唤醒定时器 */ HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, seconds, RTC_WAKEUPCLOCK_RTCCLK_DIV16); /* 确保EXTI线使能,以便把RTC事件导出为中断 */ /* 具体EXTI线号参考RTC_IRQn的中断映射 */ }

然后进入STOP3时,用HAL_PWR_EnterSTOPMode或者直接写PWR_CR寄存器,等待唤醒。唤醒后RTC的中断处理函数里可以先做恢复流程,再跑业务。

再说WKUP引脚唤醒。U5的WKUP引脚通常可以配置上升沿、下降沿或者双边沿触发,并且可以在STOP3下工作。配置时要选择正确的EEV(外部事件)或者WKUP引脚,并设置极性。它的好处是不依赖RTC时钟,响应快,适合事件型唤醒。

还有一个容易忽略的点:调试器连接状态下,进入STOP3的行为可能和脱机运行时不一样。比如调试器可能会保持内核时钟,导致STOP模式没有完全进入,或者唤醒中断没有按预期触发。排查低功耗问题时建议先断开调试器验证。

4.3 如何正确识别唤醒来源

唤醒后建议先识别唤醒源,再决定执行哪套恢复和业务逻辑。U5里有相应的状态位可以查询,比如RTC的闹钟标志、WKUP引脚状态等。不要在唤醒中断里直接跑一堆公共初始化,因为不同的唤醒源可能需要不同的处理。

我一般会在唤醒中断里先保存唤醒源标志,然后回到主循环做恢复和业务。这样做的好处是:中断里不跑耗时的Flash操作,避免在中断上下文做复杂工作。毕竟Flash编程可能要几十毫秒,放在中断里会把其它外设的中断延迟拉大。

5. 现场排查实录:从“写失败”到定位的完整思路

5.1 第一步:看错误标志

如果你也遇到了STOP3唤醒后Flash写入失败,先别急着改代码,把Flash的SR1寄存器打出来看一眼。PGSERR置位,说明编程顺序有问题,可能是Flash没解锁、操作地址不对、或者编程条件不满足。PGAERR置位,说明写入地址没有对齐。PESD置位,说明有编程错误状态残留。先清零错误标志,再重新尝试写入。

如果清零后仍然失败,说明不是时序残留问题,而是环境条件问题,进入下一步。

5.2 第二步:看时钟配置

把RCC_CFGR的SWS位读出来,看看系统时钟源实际是不是PLL。同时读FLASH_ACR的LATENCY值,算一下当前的实际主频和等待周期是否匹配。这一步用调试器的寄存器窗口看最直观,几秒钟就能发现问题。

我当时就是这样发现的:SWS已经切到PLL,但LATENCY还是0,等于CPU在几十上百兆赫兹跑着,Flash却按复位值0等待周期访问,能不出问题吗。

5.3 第三步:验证VOS和Flash低功耗状态

再检查PWR模块的VOS状态位,确认它是不是你想要的运行档位。同时检查FLASH_CR1里的FSTPWG位是否还置位。这两个属于“很容易被忽略但非常致命”的原因,尤其是FSTPWG,如果你用的HAL库里自动设置了它而你没手动清,Flash会一直处于未完全上电的状态。

5.4 避坑清单汇总

我把这次调试中踩过的坑和对应解决办法整理成了表格,方便大家排查时对照:

现象可能原因排查/解决
Flash写入返回PGSERRFlash未解锁或编程顺序错误确认KEYR解锁流程;清错误标志后重试
Flash写入返回PGAERR写入地址未对齐确认地址4字节对齐
写一两次成功,随后失败FLASH_ACR等待周期不够提高等待周期并等待配置生效
唤醒后直接HardFault时钟切到高频但Flash等待周期太低先配等待周期,再切时钟源
写操作挂死超时FSTPWG未清除或Flash未退出低功耗清除低功耗控制位并等待BSY就绪
偶发失败,频率不稳定VOS没有恢复到正常档位恢复VOS并等待VOSRDY

除了上面这些,还有一个深坑:在Flash编程期间,不要随意关掉中断或者调度器。U5的Flash编程虽然可以在中断中执行,但如果你在编程过程中正好进入低功耗模式,或者被调度器切去睡了,那结果就不可控了。低功耗状态下不要发起Flash操作,这是最底层的原则。

这次把U375的STOP3唤醒Flash写入问题彻底解决之后,我最大的体会是:低功耗项目的坑,多数不在“怎么睡”上,而在“怎么醒”上。进入STOP3的代码写得很顺利,但唤醒后的恢复流程,每个环节都要当成独立的初始化来对待,不能依赖HAL自动搞定一切。把VOS、Flash等待周期、Flash低功耗控制位、时钟源就绪这些条件逐一确认后,U375和U575上Flash写入都很稳定。如果你也在调类似问题,记住先看SR1错误标志、再看ACR等待周期、最后查CR1里的低功耗位,大概率能少熬几个夜。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询