尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

STM32U5低功耗STOP3唤醒后Flash写入失败根因与恢复指南

STM32U5低功耗STOP3唤醒后Flash写入失败根因与恢复指南 最近在调一块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里的低功耗位大概率能少熬几个夜。
返回列表