资讯动态

STM32F103电源管理详解:PVD掉电检测与备份寄存器参数保存

发布时间:2026/10/22 11:47:16 来源:尧图企业网站定制
前阵子帮人调一套电池供电的环境监测装置设备大部分时间要睡在待机模式到点被闹钟唤醒采集数据采完再睡回去。调试中最折磨人的一个现象是设备只要电池掉到某个电压再充回来之前写入的配置参数就全部变成初始值。排查到最后发现问题压根不在应用层而是我对F103的电源管理和备份寄存器链路理解得不够透——电压异常时没有做掉电保护参数存在普通RAM里掉电自然全没了。这篇就把STM32F103XX的电源与备份寄存器一次讲透包括电源拓扑、PVD掉电检测、三种低功耗模式以及真正能扛住掉电的备份寄存器怎么用。对正在做低功耗仪表、需要参数断电保存的开发者应该都有参考价值。1. F103的电源拓扑3.3V进去之后至少要看几处很多新手拿到最小系统板只知道往VDD上怼3.3V板子能跑就万事大吉。直到某天自己画板子才发现F103的电源引脚名字多到让人犯晕VDD、VDDA、VREF、VBAT、VCAP……这些引脚不是摆设每一路都有它自己的职责。1.1 内部LDO与VCAP电容最容易虚焊的“命门”F103内部并不是直接用3.3V给内核供电而是先经过一个内部LDO把VDD电压降到1.8V左右再供给内核逻辑、存储器和大部分数字外设。这个内部LDO的输出会引到芯片的VCAP引脚上要求外面接一个2.2μF的陶瓷电容到地用来稳定1.8V电源。我见过某开发者画的板子VCAP电容漏焊症状非常诡异上电后芯片大部分时候能跑但跑着跑着随机复位有时候程序下载到一半突然断开。用示波器去抓VCAP引脚能看到1.8V上叠着一堆毛刺稳压器输出根本不干净。这个电容千万别省也千万别随手贴个0.1μF上去2.2μF是真正常规要求。放置位置要尽量靠近引脚地回路要短。顺带说一句VCAP这个引脚内部是LDO输出不要试图把它当普通IO去测量逻辑电平更不要在它上面再接任何负载。1.2 VDD、VDDA与VREF数字模拟电源的板级讲究数字部分的电源是VDD/VSS所有VDD引脚都应该接同一个3.3V电源平面每个引脚附近放一个0.1μF去耦电容。这个属于最基本的电源完整性操作但很多人布板时把去耦电容放得离引脚十万八千里效果大打折扣。模拟部分的VDDA和VSSA注意别直接和数字电源短接就完了。虽然F103内部VDDA和VDD之间有电源切换逻辑但外部通常建议在VDDA前加一个小电阻或者磁珠将模拟电源和数字电源做一点隔离再接1μF0.1μF滤波电容到地。VREF在大多数封装里内部已经和VDDA关联外部一般直接连到VDDA即可如果特定封装有单独的VREF引脚也按照数据手册接到模拟电源上。这一块的逻辑可以理解为数字电路的开关噪声大模拟电路对噪声敏感给模拟电源做一点“净化”ADC采样值才不会抖得厉害。1.3 VBAT与备份域给断电之后留下一条活路VBAT是很多人真正容易忽略的一路电源。F103内部存在一个独立的备份域这个域在正常工作时由VDD供电但如果VDD完全掉电备份域会自动切换为由VBAT引脚供电。备份域里有什么RTC实时时钟、20个16位的备份数据寄存器、侵入检测逻辑全部由备份域电源维持。所以VBAT的正确接法是接一个纽扣电池或超级电容电压范围1.8~3.6V。很多开发板图省事直接把VBAT接3.3V这在调试阶段没问题但一旦主电源断电备份域也跟着没电备份寄存器里存的东西照样全丢。如果你做设备需要“掉电保存参数”VBAT这一路必须有独立的后备电源哪怕是一个小容量的电容在短暂断电时也能扛一阵子。实际接电池时要在电池正极串联一个二极管防倒灌VDD正常时不要把电流灌进电池。CR2032纽扣电池配一个几十kΩ的限流电阻是低成本方案里最稳妥的选择。2. PVD掉电监测在电压垮掉之前抢出几毫秒做掉电保存的第一个念头往往是“电压不够了系统会自动复位复位前我来不及做事”。实际上F103的自动复位阈值很低低到你根本来不及反应所以芯片专门提供了一个可编程电压检测器即PVD用来在电源电压跌到某个可配置阈值时提前给你报警。2.1 POR/PDR自动复位管不了“快没电”这件事先分清两个概念POR/PDR是芯片内部的上电复位/掉电复位电路它监测的是芯片能不能稳定工作。这个阈值通常在1.8V上下也就是说3.3V电源掉到1.8V以下芯片才认为自己不行了。可对一套3.3V供电的系统来说电压从3.3V掉到1.8V这段时间外设早就乱套了Flash写入可能中断关键数据可能损坏。PVD则完全不同。它相当于一个可配置的比较器把VDD和一个软件可选的参考电压做比较。当VDD跌到参考电压以下时PVD触发中断或事件。你可以把报警点设得比实际临界电压高一点比如3.3V系统里设为2.9V这样电压一开始跌落就能知道留出时间保存现场。2.2 PVD判定逻辑与阈值档位PVD的阈值由PWR_CR寄存器的PLS[2:0]三位选择F103上常见的阈值档位如下PLS[2:0]阈值电压0002.2V0012.3V0102.4V0112.5V1002.6V1012.7V1102.8V1112.9V使能PVD后PWR_CSR寄存器的PVDO位会实时反映当前VDD是否低于阈值。PVDO为1表示电压已经掉到阈值以下为0表示正常。这个标志不需要中断也能读非常适合在主循环里做低速巡检。配置PVD本身很简单设置PLS选择阈值置PVDE使能然后打开EXTI16中断。注意PVD对应的外部中断线是EXTI16不是普通的GPIO外部中断线。2.3 PVD中断掉电保存代码与时间窗口下面是一段直接寄存器操作的PVD初始化代码思路清晰换成HAL库也能对应上void PVD_Config(void) { /* 使能PWR时钟PVD挂在PWR模块上 */ RCC-APB1ENR | RCC_APB1ENR_PWREN; /* 选择阈值PLS111对应2.9V */ PWR-CR ~PWR_CR_PLS; PWR-CR | PWR_CR_PLS_LEV7; /* 使能PVD */ PWR-CR | PWR_CR_PVDE; /* 配置EXTI16下降沿触发并打开中断 */ EXTI-IMR | (1UL 16); EXTI-FTSR | (1UL 16); NVIC_EnableIRQ(PVD_IRQn); } void PVD_IRQHandler(void) { if (EXTI-PR (1UL 16)) { EXTI-PR (1UL 16); /* 在这里写关键参数到备份寄存器后面细讲 */ bkp_write(1, runtime_counter); bkp_write(2, 0xBEEF); /* 掉电标志 */ } }PVD中断里的时间窗口到底有多长这取决于掉电速度和电源入口的储能电容。如果只是普通稳压器断电电压会缓慢下降2.9V到1.8V之间往往有几十毫秒甚至更长时间保存几十字节到备份寄存器绰绰有余。如果是插拔电源这种瞬间断电最好在电源入口加一个容量稍大的电解电容给MCU留出几毫秒的缓冲时间。还有一个实际经验PVD中断里不要做耗时操作别打印日志、别等Flash擦除完成、别调用复杂库函数。只做“把RAM里的关键值搬到备份域”这一件事其他什么掉电标志、状态恢复留给下次上电时处理。3. 三种低功耗模式选错一个电流直接差两个数量级F103提供了三种低功耗模式很多人误以为“睡眠就是少跑点代码”其实三者从机制上就完全不同。选错模式电流可能差两个数量级唤醒方式也完全不同。3.1 一张表看清睡眠、停止、待机的本质模式CPU外设时钟SRAM/寄存器典型电流量级主要唤醒源睡眠停止继续运行保持mA级别任意中断/事件停止停止全部停止保持μA到几十μA外部中断、RTC、PVD待机断电全部断电除备份域外丢失1~5μAPA0、RTC闹钟、NRST、独立看门狗睡眠模式只是CPU停了外设时钟还在跑所以功耗和“所有外设都在工作”差不多适合需要外设实时响应、CPU偶尔处理一下的场景。停止模式把几乎所有时钟都停了但SRAM和寄存器内容保留唤醒后能接着跑这是低功耗产品最常用的模式。待机模式最狠1.8V内核域直接断电SRAM内容全部丢失唤醒后等于一次复位。3.2 停止模式用外部中断唤醒代码其实就四步进入停止模式的寄存器操作非常少但有三个位特别容易搞混SCB-SCR里的SLEEPDEEP、PWR_CR里的PDDS和LPDS。SLEEPDEEP决定是否进入深度睡眠PDDS决定深度睡眠是停止还是待机LPDS决定停止模式下稳压器是否进入低功耗模式。进入停止模式的标准流程是这样的void EnterStopMode(void) { /* 1. 使能SLEEPDEEP进入深度睡眠 */ SCB-SCR | SCB_SCR_SLEEPDEEP_Msk; /* 2. PDDS0选择停止模式而不是待机 */ PWR-CR ~PWR_CR_PDDS; /* 3. LPDS1让内部稳压器也进入低功耗状态 */ PWR-CR | PWR_CR_LPDS; /* 4. 执行WFI等待外部中断唤醒 */ __WFI(); /* 唤醒后从这里继续执行 */ }进入停止模式之前必须先把要用的外部中断或者RTC唤醒源配置好否则WFI一睡不起。我用PA0引脚做外部中断唤醒上升沿触发进入停止模式后按下按键系统立即苏醒。有几个细节要注意。SLEEPDEEP这个位在复位后是清零的所以每次进入停止模式前都要重新置位。LPDS位如果不清零内部稳压器工作在高功耗模式停止电流会明显偏大但也换来更短的唤醒时间。如果你的产品对唤醒速度有要求可以考虑清LPDS用高性能稳压器模式如果追求低功耗那就保留LPDS置位代价是唤醒延迟会增加一些。停止模式唤醒后程序是“继续执行”的不是复位所以WFI后面的代码还能接着跑。但外设时钟在停止模式下全部停过唤醒后建议重新初始化一遍系统时钟和外设配置尤其是用PLL跑高主频时避免时钟稳定前的边角状态。3.3 待机模式唤醒等于复位数据要提前放进保险柜待机模式和停止模式最大的不同在于待机模式把1.8V内核域的供电直接切断了。这意味着CPU、SRAM、外设寄存器全部失效唤醒后芯片从头开始执行代码你要想保留任何数据只能依靠备份域里的备份寄存器和RTC。进入待机模式代码也很短void EnterStandbyMode(void) { /* 使能PA0作为唤醒引脚上升沿唤醒 */ PWR-CR | PWR_CR_EWUP; /* 深度睡眠 PDDS1进入待机 */ SCB-SCR | SCB_SCR_SLEEPDEEP_Msk; PWR-CR | PWR_CR_PDDS; __WFI(); /* 永远不会执行到这里唤醒后芯片等于复位 */ }关于待机模式最常见的坑有两个。第一个坑是唤醒后代码从头跑但很多人还想着“我刚才的变量哪去了”。答案很简单RAM里的都没了只有备份寄存器里的还在。第二个坑是唤醒后怎么知道自己是“冷启动”还是“待机唤醒”答案是PWR_CSR的SBF标志位。待机唤醒后SBF会被置1代码里可以检查这个位来判断if (PWR-CSR PWR_CSR_SBF) { PWR-CR | PWR_CR_CSBF; /* 清待机标志 */ /* 从待机唤醒去备份寄存器里读回现场 */ }4. 备份寄存器BKP掉电依然保留的“保险柜”怎么用备份寄存器是整个电源主题里最实用的部分它就是前面反复提到的“保险柜”。主电源断了、MCU复位了、甚至待机模式把所有SRAM都干掉了只要VBAT那一路上有电备份寄存器里的数据就不会丢。4.1 备份域里都有什么F103的备份域包含RTC、一组16位的备份数据寄存器、侵入检测逻辑。以常见的F103C8为例资料里常用的是10个16位备份寄存器BKP_DR1到BKP_DR10高容量型号在地址映射上会更多具体数量以你手里的参考手册为准。这些寄存器宽度16位写一次能存一个int类型的一半存计数器、标志位、校验值绰绰有余。备份寄存器在VBAT供电时RTC和BKP数据会继续走时和保存。这意味着你可以在系统彻底断电后靠一颗纽扣电池维持RTC时钟和参数保存下次上电直接当作“离开了一段时间”来处理。这和普通RAM、Flash掉电就丢的特性完全不同也是它最值钱的地方。4.2 写保护与访问顺序千万不要反着来备份寄存器最坑的一个点是写保护。直接往BKP-DR1写数据是写不进去的必须先打开备份域访问权限。标准流程分三步打开APB1上的PWR时钟和BKP时钟置位PWR_CR的DBP位解除备份域写保护再对BKP-DRx写入数据。读操作不需要DBP只要时钟已使能备份域有电随时可以读。但写操作必须DBP1很多人调了一天写不进数据就是漏了这一步。下面是一个标准的备份寄存器读写函数void bkp_write(uint8_t idx, uint16_t val) { RCC-APB1ENR | RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN; PWR-CR | PWR_CR_DBP; /* 解除写保护 */ switch (idx) { case 1: BKP-DR1 val; break; case 2: BKP-DR2 val; break; case 3: BKP-DR3 val; break; default: break; } PWR-CR ~PWR_CR_DBP; /* 写回保护防止程序跑飞误写 */ } uint16_t bkp_read(uint8_t idx) { uint16_t tmp 0; RCC-APB1ENR | RCC_APB1ENR_PWREN | RCC_APB1ENR_BKPEN; switch (idx) { case 1: tmp BKP-DR1; break; case 2: tmp BKP-DR2; break; case 3: tmp BKP-DR3; break; default: break; } return tmp; }注意一个细节写完数据后如果把DBP清零那么备份寄存器再次进入写保护状态。这个操作在安全要求高的场景里很重要可以防止程序跑飞时误改备份数据。4.3 TAMPER侵入检测为什么它会把数据清空备份寄存器还有一个容易误解的功能侵入检测。F103的PC13引脚复用了TAMPER功能使能侵入检测后一旦PC13上出现指定电平跳变芯片会把备份寄存器里的数据全部清空。这个逻辑初看很吓人但背后的设计思路其实很清晰——很多计量设备、密码设备不希望有人在物理上拆开外壳后还能保留关键数据一旦检测到拆机动作就主动销毁存储内容。这个机制具体怎么用BKP_CR的TPE位使能侵入检测TPAL选择高电平还是低电平触发。侵入事件发生时BKP_CSR的TIF标志置位备份数据寄存器被清零。如果开了TPIE还会产生侵入中断提醒程序。我自己的建议如果项目里没有防拆需求就不要碰TAMPER功能。特别是PCB上把PC13当普通IO用的时候一定要检查初始化代码里有没有意外打开TPE。我就见过一个项目PC13接了一个LED结果某次软件升级时误开了侵入检测导致备份数据莫名清零排查了很久才发现是这个功能在捣乱。5. 组合实验一个掉电保存加待机唤醒的完整流程前几节讲的东西分开看都不难但真正落地时要把它们串起来。我搭了一个很简单的实验平台把PVD掉电检测、备份寄存器保存、待机唤醒这三件事组合在一起完整验证一遍。5.1 试验平台与接线说明实验平台就是一块F103开发板外加几根杜邦线。核心接线如下VBAT接一个CR2032纽扣电池座电池正极经过1N4148二极管再接到VBATPA0接一个按键到3.3V用作待机唤醒PA1接一个LED用来显示程序状态VDD电源入口串一个开关方便模拟突然断电。实验的目标是主循环里每次累加一个计数同时把这个计数写进备份寄存器BKP_DR1。突然断开主电源再重新上电通过按键唤醒或者直接看程序读回的值判断BKP_DR1里的数据是不是还在。5.2 主流程代码框架主程序按“冷启动、掉电重启、待机唤醒”三种来源做分支处理int main(void) { SystemInit(); GPIO_LED_Init(); PVD_Config(); EXTI_Config(); if (PWR-CSR PWR_CSR_SBF) { /* 待机唤醒SRAM没了备份寄存器还在 */ PWR-CR | PWR_CR_CSBF; runtime_counter bkp_read(1); LED_ON(); } else if (bkp_read(2) 0xBEEF) { /* 掉电过又上电PVD中断里保存过数据 */ runtime_counter bkp_read(1); bkp_write(2, 0x0000); LED_BLINK(); } else { /* 完全冷启动 */ runtime_counter 0; bkp_write(1, runtime_counter); bkp_write(2, 0); LED_OFF(); } while (1) { runtime_counter; bkp_write(1, runtime_counter); delay_ms(200); LED_Toggle(); if (KEY_PRESSED) { EnterStandbyMode(); } } }这个框架把三种启动方式区分得很清楚正常冷启动时备份寄存器里没有掉电标志待机唤醒时SBF位是1掉电恢复时BKP_DR2里还留着PVD中断写入的0xBEEF。整个流程跑下来备份寄存器就像一个跨越各种断电方式的“记忆锚点”。实际测试时要注意VBAT如果不接电池这套验证逻辑根本不成立。只要主电一断备份域也没电BKP_DR里的数据立刻清零你的“掉电保存”就是空谈。5.3 实测现象与翻车总结我在这个实验里踩过几个很典型的坑逐个记录一下给读者打个预防针。第一个坑是写备份寄存器不生效。现象是代码里明明写了BKP_DR1读出来永远是0。排查后确认是APB1的BKP时钟没开以及DBP位没有置位。这个顺序问题几乎每个第一次用备份寄存器的人都会踩一遍记住一句话先开时钟再开DBP最后写数据。第二个坑是停止模式电流测出来还是很大。我最初进入停止模式后电流还在几毫安量级完全达不到数据手册上的微安级。最后发现是LPDS没置位内部稳压器还在全功耗模式另外调试器如果还接着SWD口调试器本身也会给芯片供电导致电流测量完全失真。测低功耗电流时一定要断开调试器用独立的电源给板子供电。第三个坑是掉电极快时保存失败。我把电源开关直接拨掉PVD中断里保存的数据偶尔是错的。原因是断电瞬间电压跌落太快从2.9V掉到1.8V的有效时间太短。解决办法是在电源入口加一个几百μF的电解电容或者选一个LDO使能脚做掉电预警给MCU多争取几毫秒。最后再分享一个小技巧如果设备长期用待机模式并且需要RTC继续走时记得把LSE低频晶振接到PC14/PC15上同时保证VBAT电池独立供电。整机待机电流里备份域走时的消耗只有几微安但换来的是“断电后时间不丢”这种非常实用的能力。电源与备份寄存器这套机制本质上就是让MCU在面对断电时提前把最重要的东西放进一个永不熄灭的保险柜。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑