STM32F103 复位来源诊断实战:RCC_CSR 标志位判优、BKP 复位计数持久化与六种复位源注入实测

文章目录

摘要

设备在现场莫名重启,日志里却只有一条"系统初始化"------看门狗超时、按键误触还是欠压掉电?凭猜排查效率极低。本文基于 STM32F103C8T6,以 RCC_CSR 复位标志寄存器为主线,建立"快照 → 优先级判优 → 清除"三步诊断流程,重点解决 NRST 标志污染导致的复位源误判;再利用 VBAT 备份域的 BKP 寄存器实现重启计数与累计运行时长的掉电持久化。实测六种复位源(上电、NRST、IWDG、WWDG、软件、选项字节装载)共 120 次注入全部识别正确;五组 IWDG 配置实测超时较 40kHz 标称值一致偏高 5.8%,反推 LSI 实际频率 37.8kHz;复位到串口首包时间 NRST 12.7ms、上电复位 38.2ms。文末附五类高频故障的完整排查链与工程级代码。

一、前言

一个真实的排查困境

去年经手返修的一批户外采集终端,客户反馈"设备隔三差五自己重启"。现场日志只记录了程序启动,至于上一次为什么退出,完全没有线索。硬件坚持说电源没问题,软件坚持说没写复位代码,看门狗确实也配了------于是这口锅在两个团队之间推了两个星期。后来我在固件里加了十几行复位标志解析代码,下一批设备跑回来,答案一目了然:八成"莫名重启"都是 IWDG 超时,且集中在网络重传的死等循环里。两个星期的扯皮,十几行代码就终结了。

这件事让我养成了习惯:任何量产固件,main 函数最前面的任务不是初始化外设,而是"验尸"------把上一次死亡的原因从 RCC_CSR 寄存器里抢救出来。复位标志是芯片唯一自带的"黑匣子",它不需要任何额外硬件,却在系统复位后依然保持,直到软件主动清除。

本文目标

读完这篇文章,你可以做到:

  • 理解 F103 三类复位(系统复位、电源复位、备份域复位)的影响范围与 RCC_CSR 标志位的保持规则
  • 写出一个"快照 → 优先级判优 → 清除"的复位诊断模块,避开 NRST 标志污染这个最大的坑
  • 用 VBAT 备份域的 BKP 寄存器持久化重启计数与运行时长,断电也不丢
  • 对"复位源识别错误、备份计数归零、IWDG 死循环复位"等五类高频问题,按完整排查链定位

前置条件:熟悉 GPIO、USART 和 SysTick 的基本使用,有一块 STM32F103 最小系统板、ST-Link、USB 转 TTL,以及一块可选的 CR2032 纽扣电池(VBAT 供电)。复位诊断本身是纯片内功能,纽扣电池只影响"掉电后计数是否保留"这一项能力。

本文完整工程代码可在 CSDN 下载频道 获取(VIP 免费)。

二、诊断方案对比:为什么用复位标志而不是别的

在动手前,把三条常见技术路线摆出来对比。方案级决策的理由:外部方案都要加器件、加布线,而"为什么重启"这个信息芯片自己就记录了,没有理由绕开它。

对比维度 软件日志记录(Flash/SRAM) 外部复位监测芯片 RCC_CSR 标志(本文方案)
额外硬件 专用芯片 + I2C 读取
能否区分 IWDG/WWDG/软件复位 能,但要自己埋点且 Flash 写入时机难保证 只能区分"外部复位/欠压",分不清看门狗类型 硬件自动区分,六种来源逐一置位
掉电/上电复位识别 死机瞬间来不及写日志,经常漏 欠压检测准确 PORRSTF 硬件置位,零遗漏
实现成本 高(日志框架、磨损均衡) 中(器件 + 驱动) 极低(几十行代码)
依赖条件 必须有 Flash 写入窗口 必须有 I2C 总线

唯一的短板是 RCC_CSR 只记录"最近一次"复位的原因,没有历史记录。这个缺口正好用 BKP 备份寄存器补上------第二套机制各司其职,这也是本文的架构主线。关于复位标志位体系的更多细节,可以参考这篇梳理:STM32复位源全解析:如何通过寄存器精准判断复位原因

三、复位体系原理:三类复位与标志位的保持规则

3.1 三类复位,影响范围完全不同

RM0008 把 F103 的复位分成三类,理解影响范围是所有诊断逻辑的前提:

系统复位 :NRST 引脚低电平、WWDG 计数结束、IWDG 计数结束、软件复位(Cortex-M3 SYSRESETREQ)、低功耗管理复位(WFI/WFE 进入 Standby 或以矛盾配置进入 Stop 时产生,详见 RM0008 复位章节)。系统复位会复位所有寄存器,但 RCC_CSR 中的复位标志和备份域寄存器除外------这正是诊断得以成立的物理基础。

电源复位 :上电/掉电(POR/PDR)与退出 Standby 模式。电源复位会复位包括 RCC 在内的绝大多数寄存器,但不会清除 RCC_CSR 里已置位的复位标志

备份域复位:RCC_BDCR 寄存器的 BDRST 位写 1 触发的软件复位,或 VDD 与 VBAT 同时掉电。备份域复位会把 BKP 寄存器和 RTC 一起清空------换句话说,只有"彻底断电且无电池"才会丢掉我们的持久化数据。

3.2 RCC_CSR 寄存器:诊断的"黑匣子"

诊断模块操作的核心寄存器只有 RCC_CSR 一个,逐位拆解(基地址层面它属于 RCC,偏移 0x74):

字段 含义 读取/清除方式
31 LPWRRSTF 低功耗管理复位标志 读 1 表示发生;写 RMVF 清除
30 WWDGRSTF 窗口看门狗复位标志 同上
29 IWDGRSTF 独立看门狗复位标志 同上
28 SFTRSTF 软件复位标志 同上
27 PORRSTF 上电/掉电复位标志(含 Standby 唤醒) 同上
26 PINRSTF NRST 引脚复位标志 同上
24 RMVF 清除标志控制位(写 1 清除所有复位标志) 只写
23 OBLRSTF 选项字节装载器复位标志 读;写 RMVF 清除
1 LSIRDY LSI 就绪标志 只读
0 LSION LSI 使能位 读写

关键规则有三条。第一,标志位由硬件置位、只能由软件写 RMVF 清除,任何复位都不会动它们。第二,标志是"或"关系------一次复位可能同时置位多个标志,判读必须讲顺序。第三,LSI 的启停状态受 LSION 控制,IWDG 复位后 LSI 会重新关闭,这与后面实测 LSI 频率时的操作顺序有关。

3.3 判优逻辑:PINRSTF 是个"污染源"

这是全文最重要的一个知识点,也是 90% 复位误判的根因。RM0008 明确写道:任何系统复位发生时,NRST 引脚都会被芯片内部拉低至少 20μs。也就是说,IWDG 超时、WWDG 溢出、软件复位之后,PINRSTF 同样会置位。如果判读代码先检查 PINRSTF 并提前返回,看门狗复位就会被永远误报成"按键复位"。

正确的做法是按优先级判优------内部复位源特征位优先于 PORRSTF,PORRSTF 优先于 PINRSTF:
#mermaid-svg-GMof1yYblu96UudS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GMof1yYblu96UudS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GMof1yYblu96UudS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GMof1yYblu96UudS .error-icon{fill:#552222;}#mermaid-svg-GMof1yYblu96UudS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GMof1yYblu96UudS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GMof1yYblu96UudS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GMof1yYblu96UudS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GMof1yYblu96UudS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GMof1yYblu96UudS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GMof1yYblu96UudS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GMof1yYblu96UudS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GMof1yYblu96UudS .marker.cross{stroke:#333333;}#mermaid-svg-GMof1yYblu96UudS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GMof1yYblu96UudS p{margin:0;}#mermaid-svg-GMof1yYblu96UudS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GMof1yYblu96UudS .cluster-label text{fill:#333;}#mermaid-svg-GMof1yYblu96UudS .cluster-label span{color:#333;}#mermaid-svg-GMof1yYblu96UudS .cluster-label span p{background-color:transparent;}#mermaid-svg-GMof1yYblu96UudS .label text,#mermaid-svg-GMof1yYblu96UudS span{fill:#333;color:#333;}#mermaid-svg-GMof1yYblu96UudS .node rect,#mermaid-svg-GMof1yYblu96UudS .node circle,#mermaid-svg-GMof1yYblu96UudS .node ellipse,#mermaid-svg-GMof1yYblu96UudS .node polygon,#mermaid-svg-GMof1yYblu96UudS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GMof1yYblu96UudS .rough-node .label text,#mermaid-svg-GMof1yYblu96UudS .node .label text,#mermaid-svg-GMof1yYblu96UudS .image-shape .label,#mermaid-svg-GMof1yYblu96UudS .icon-shape .label{text-anchor:middle;}#mermaid-svg-GMof1yYblu96UudS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GMof1yYblu96UudS .rough-node .label,#mermaid-svg-GMof1yYblu96UudS .node .label,#mermaid-svg-GMof1yYblu96UudS .image-shape .label,#mermaid-svg-GMof1yYblu96UudS .icon-shape .label{text-align:center;}#mermaid-svg-GMof1yYblu96UudS .node.clickable{cursor:pointer;}#mermaid-svg-GMof1yYblu96UudS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GMof1yYblu96UudS .arrowheadPath{fill:#333333;}#mermaid-svg-GMof1yYblu96UudS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GMof1yYblu96UudS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GMof1yYblu96UudS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GMof1yYblu96UudS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GMof1yYblu96UudS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GMof1yYblu96UudS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GMof1yYblu96UudS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GMof1yYblu96UudS .cluster text{fill:#333;}#mermaid-svg-GMof1yYblu96UudS .cluster span{color:#333;}#mermaid-svg-GMof1yYblu96UudS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GMof1yYblu96UudS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GMof1yYblu96UudS rect.text{fill:none;stroke-width:0;}#mermaid-svg-GMof1yYblu96UudS .icon-shape,#mermaid-svg-GMof1yYblu96UudS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GMof1yYblu96UudS .icon-shape p,#mermaid-svg-GMof1yYblu96UudS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GMof1yYblu96UudS .icon-shape .label rect,#mermaid-svg-GMof1yYblu96UudS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GMof1yYblu96UudS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GMof1yYblu96UudS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GMof1yYblu96UudS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是













main 最前部调用 Reset_Diag_Init
一次性快照 RCC_CSR

(先存后清,防止竞态)
IWDGRSTF = 1?
IWDG 超时复位
WWDGRSTF = 1?
WWDG 溢出复位
SFTRSTF = 1?
软件复位 NVIC_SystemReset
LPWRRSTF = 1?
低功耗管理复位
OBLRSTF = 1?
选项字节装载复位
PORRSTF = 1?
电源复位: 上电/掉电/Standby唤醒
PINRSTF = 1?
NRST 引脚复位: 按键/调试器
未知: 记录原始值人工分析
写 RCC_CSR.RMVF = 1

清除全部标志,供下次使用

一次 IWDG 复位从触发到"验尸"完成的完整时序如下,可以直观看到 PINRSTF 是怎么被"捎带"置位的:
main函数 RCC_CSR标志 NRST引脚(双向) IWDG计数器(LSI驱动) main函数 RCC_CSR标志 NRST引脚(双向) IWDG计数器(LSI驱动) #mermaid-svg-ZripXutwyIU1jwDK{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZripXutwyIU1jwDK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZripXutwyIU1jwDK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZripXutwyIU1jwDK .error-icon{fill:#552222;}#mermaid-svg-ZripXutwyIU1jwDK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZripXutwyIU1jwDK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZripXutwyIU1jwDK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZripXutwyIU1jwDK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZripXutwyIU1jwDK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZripXutwyIU1jwDK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZripXutwyIU1jwDK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZripXutwyIU1jwDK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZripXutwyIU1jwDK .marker.cross{stroke:#333333;}#mermaid-svg-ZripXutwyIU1jwDK svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZripXutwyIU1jwDK p{margin:0;}#mermaid-svg-ZripXutwyIU1jwDK .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZripXutwyIU1jwDK text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ZripXutwyIU1jwDK .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZripXutwyIU1jwDK .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ZripXutwyIU1jwDK .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ZripXutwyIU1jwDK .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ZripXutwyIU1jwDK #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ZripXutwyIU1jwDK .sequenceNumber{fill:white;}#mermaid-svg-ZripXutwyIU1jwDK #sequencenumber{fill:#333;}#mermaid-svg-ZripXutwyIU1jwDK #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ZripXutwyIU1jwDK .messageText{fill:#333;stroke:none;}#mermaid-svg-ZripXutwyIU1jwDK .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZripXutwyIU1jwDK .labelText,#mermaid-svg-ZripXutwyIU1jwDK .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ZripXutwyIU1jwDK .loopText,#mermaid-svg-ZripXutwyIU1jwDK .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ZripXutwyIU1jwDK .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ZripXutwyIU1jwDK .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ZripXutwyIU1jwDK .noteText,#mermaid-svg-ZripXutwyIU1jwDK .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ZripXutwyIU1jwDK .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZripXutwyIU1jwDK .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZripXutwyIU1jwDK .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ZripXutwyIU1jwDK .actorPopupMenu{position:absolute;}#mermaid-svg-ZripXutwyIU1jwDK .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ZripXutwyIU1jwDK .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ZripXutwyIU1jwDK .actor-man circle,#mermaid-svg-ZripXutwyIU1jwDK line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ZripXutwyIU1jwDK :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 主循环停止喂狗 判优: IWDGRSTF 优先 输出"IWDG 超时"而非"按键复位" 计数递减至 0 (超时) 内部复位逻辑拉低 NRST ≥20μs 复位结束, 硬件置位 IWDGRSTF 同时置位 PINRSTF (污染!) 全部寄存器复位, 程序从 0x08000000 重启 快照 CSR = 0x24000000 写 RMVF 清除标志

3.4 BKP 备份域:让计数跨复位、跨掉电存活

RCC_CSR 只能告诉你"上一次为什么死",要统计"总共死过几次、累计活了多久",就需要跨复位存储。BKP 备份寄存器(中容量 F103 有 10 个 16 位寄存器,BKP_DR1~DR10)位于独立供电域:VDD 正常时由 VDD 供电,VDD 掉电时自动切换到 VBAT 引脚的纽扣电池。系统复位和电源复位都不触碰备份域,只有备份域复位(BDRST)或 VDD、VBAT 双双掉电才会清零。供电域的隔离关系如下:
#mermaid-svg-4kLj9eT1Ejrwt5Eo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .error-icon{fill:#552222;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .marker.cross{stroke:#333333;}#mermaid-svg-4kLj9eT1Ejrwt5Eo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4kLj9eT1Ejrwt5Eo p{margin:0;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .cluster-label text{fill:#333;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .cluster-label span{color:#333;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .cluster-label span p{background-color:transparent;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .label text,#mermaid-svg-4kLj9eT1Ejrwt5Eo span{fill:#333;color:#333;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .node rect,#mermaid-svg-4kLj9eT1Ejrwt5Eo .node circle,#mermaid-svg-4kLj9eT1Ejrwt5Eo .node ellipse,#mermaid-svg-4kLj9eT1Ejrwt5Eo .node polygon,#mermaid-svg-4kLj9eT1Ejrwt5Eo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .rough-node .label text,#mermaid-svg-4kLj9eT1Ejrwt5Eo .node .label text,#mermaid-svg-4kLj9eT1Ejrwt5Eo .image-shape .label,#mermaid-svg-4kLj9eT1Ejrwt5Eo .icon-shape .label{text-anchor:middle;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .rough-node .label,#mermaid-svg-4kLj9eT1Ejrwt5Eo .node .label,#mermaid-svg-4kLj9eT1Ejrwt5Eo .image-shape .label,#mermaid-svg-4kLj9eT1Ejrwt5Eo .icon-shape .label{text-align:center;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .node.clickable{cursor:pointer;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .arrowheadPath{fill:#333333;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4kLj9eT1Ejrwt5Eo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4kLj9eT1Ejrwt5Eo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4kLj9eT1Ejrwt5Eo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .cluster text{fill:#333;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .cluster span{color:#333;}#mermaid-svg-4kLj9eT1Ejrwt5Eo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4kLj9eT1Ejrwt5Eo rect.text{fill:none;stroke-width:0;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .icon-shape,#mermaid-svg-4kLj9eT1Ejrwt5Eo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .icon-shape p,#mermaid-svg-4kLj9eT1Ejrwt5Eo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .icon-shape .label rect,#mermaid-svg-4kLj9eT1Ejrwt5Eo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4kLj9eT1Ejrwt5Eo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4kLj9eT1Ejrwt5Eo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4kLj9eT1Ejrwt5Eo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} VBAT备份域
VDD域
正常供电
正常供电
正常供电
VDD掉电时自动接管
唯一清零路径
CPU / SRAM / 外设寄存器

复位即丢失
RCC_CSR复位标志

系统复位不清, 掉电清
BKP_DR1~10 16位寄存器

复位不掉, 掉电不掉
RTC计数器

同备份域
VDD 3.3V
CR2032 3V

VBAT引脚
BDRST位 或

VDD+VBAT同时掉电

写入 BKP 之前有两道门必须依次打开:PWR 时钟、BKP 时钟,以及 PWR_CR 的 DBP 写保护位。漏掉任何一道,写入都会"看起来成功、实际无效",这是第五节要重点拆解的遗漏步骤。BKP 与 RTC 备份域的供电与写保护机制,这篇实战文展开得更细:STM32 BKP与RTC备份域:数据掉电保存与低功耗定时唤醒实战

四、硬件设计:最小系统,外加一颗电池

诊断功能对硬件几乎没有要求,我的测试台一共四样东西:

器件 数量 作用
STM32F103C8T6 最小系统板 1 主控,HSE 8MHz 晶振
ST-Link V2 1 下载调试 + 触发选项字节装载复位
USB 转 TTL(CH340) 1 USART1 输出诊断日志
CR2032 纽扣电池 + 电池座 1 VBAT 供电,验证掉电后 BKP 数据保持

接线只有两条:PA9(USART1_TX)接 TTL 的 RX,电池正极接 VBAT 引脚、负极接 GND。NRST 按键用板载的即可。

硬件上有两个细节值得单独说。第一,VBAT 不用也要接 :很多最小系统板出厂就把 VBAT 通过电池座预留,如果不装电池,务必确认电池座弹片有把 VBAT 可靠连到 VDD 或至少接到一个稳定电位------VBAT 悬空时备份域供电不定,RTC 和 BKP 的行为会变成"薛定谔的猫",我见过最诡异的表现是计数偶尔自己清零。第二,复位按键的电容不要加大:有同事为了让"复位更稳"给 NRST 并了 1μF,结果 NRST 上升沿变得极缓,上电十次有两三次起不来,这个坑在第六节 6.3 的实测里还会出现它的"识别混乱"变体。

五、软件设计:诊断模块逐行拆解

整个模块三个文件不超过 200 行有效代码,下面按"复位诊断 → BKP 持久化 → 运行时长累计"的顺序拆解。代码基于 STM32CubeF1 HAL 库,寄存器视角同步标注。

5.1 复位标志快照与判优(核心函数)

这一节是本文的心脏。两个关键顺序决定了模块的正确性:必须先快照、后清除判优必须从特征位最强的标志开始

c 复制代码
/* reset_diag.c ------ 复位来源诊断模块 */

typedef enum {
    RESET_SRC_UNKNOWN = 0,
    RESET_SRC_IWDG,     /* 独立看门狗超时 */
    RESET_SRC_WWDG,     /* 窗口看门狗溢出 */
    RESET_SRC_SOFT,     /* 软件复位 */
    RESET_SRC_LPWR,     /* 低功耗管理复位 */
    RESET_SRC_OBL,      /* 选项字节装载复位 */
    RESET_SRC_POWER,    /* 电源复位: POR/PDR 或 Standby 唤醒 */
    RESET_SRC_PIN,      /* NRST 引脚复位: 按键/调试器 */
} reset_src_t;

static reset_src_t s_last_src = RESET_SRC_UNKNOWN;
static uint32_t    s_csr_raw  = 0;

const char *Reset_Diag_String(reset_src_t src)
{
    switch (src) {
    case RESET_SRC_IWDG:  return "IWDG timeout";
    case RESET_SRC_WWDG:  return "WWDG overflow";
    case RESET_SRC_SOFT:  return "Software reset";
    case RESET_SRC_LPWR:  return "Low-power mgmt reset";
    case RESET_SRC_OBL:   return "Option-byte loader reset";
    case RESET_SRC_POWER: return "Power reset (POR/PDR)";
    case RESET_SRC_PIN:   return "NRST pin reset";
    default:              return "UNKNOWN";
    }
}

void Reset_Diag_Init(void)
{
    /* 顺序关键点 1: 必须在任何可能调用 RCC_ClearFlags()/HAL_RCC_DeInit()
       的代码之前快照, 否则证据会被提前清掉。 */
    uint32_t csr = RCC->CSR;          /* 一次性读出, 避免多次读取间标志变化 */
    s_csr_raw  = csr;

    /* 顺序关键点 2: 判优从"特征位最强"到"最弱"。
       任何系统复位都会拉低 NRST 引脚 ≥20μs, 因此 IWDG/WWDG/软件复位后
       PINRSTF 必然同时置位; 若先判断 PINRSTF 会把看门狗复位误报成按键复位。 */
    if      (csr & RCC_CSR_IWDGRSTF) s_last_src = RESET_SRC_IWDG;
    else if (csr & RCC_CSR_WWDGRSTF) s_last_src = RESET_SRC_WWDG;
    else if (csr & RCC_CSR_SFTRSTF)  s_last_src = RESET_SRC_SOFT;
    else if (csr & RCC_CSR_LPWRRSTF) s_last_src = RESET_SRC_LPWR;
    else if (csr & RCC_CSR_OBLRSTF)  s_last_src = RESET_SRC_OBL;
    else if (csr & RCC_CSR_PORRSTF)  s_last_src = RESET_SRC_POWER;
    else if (csr & RCC_CSR_PINRSTF)  s_last_src = RESET_SRC_PIN;
    else                             s_last_src = RESET_SRC_UNKNOWN;

    /* 顺序关键点 3: 解析完再写 RMVF(bit24)=1 清除全部标志,
       确保下次复位时标志记录的是"最近一次"事件。 */
    RCC->CSR |= RCC_CSR_RMVF;
}

三个顺序关键点里,第 1 点最容易被人忽视:HAL 的部分初始化路径和错误处理函数会顺手调 __HAL_RCC_CLEAR_RESET_FLAGS(),一旦它跑在诊断前面,CSR 就全是 0,你只能对着 UNKNOWN 干瞪眼。把 Reset_Diag_Init() 放在 HAL_Init() 之后、一切业务初始化之前,是最安全的位置。

5.2 BKP 复位计数持久化(两道写保护门)

先看容易遗漏的步骤------这一节单独强调,因为工具链不会替你做,漏了之后症状还非常隐蔽:

遗漏步骤:PWR/BKP 时钟与 DBP 写保护

CubeMX 不会因为你在代码里调用了 BKP_ReadBackupRegister 就自动补时钟;HAL 的 HAL_PWR_EnableBkUpAccess() 也只管 DBP 位,管不了 APB1 时钟。遗漏后的症状是:BKP_WriteBackupRegister() 执行无报错,但读回永远是 0,magic 永远对不上,计数每次复位都从 1 重新开始------看起来像"备份域坏了",实际是时钟没开。排查链见 7.2 节。

c 复制代码
/* bkp_counter.c ------ 复位计数与运行时长持久化
   BKP_DR1: 重启计数(16位)
   BKP_DR2: 魔术字(判断备份域是否被彻底清过)
   BKP_DR3/DR4: 累计运行秒数(低16位/高16位, 拼32位) */

#define BKP_MAGIC          0xA55AU
#define BKP_DR_BOOTCNT     BKP_DR1
#define BKP_DR_MAGIC       BKP_DR2
#define BKP_DR_UPTIME_LO   BKP_DR3
#define BKP_DR_UPTIME_HI   BKP_DR4

void BKP_Counter_Init(uint16_t *boot_cnt, uint32_t *uptime_prev)
{
    /* 顺序 1: 先使能 PWR 与 BKP 时钟 (RCC_APB1ENR bit28 PWREN / bit27 BKPEN),
       顺序不能反------时钟没开时 DBP 与 BKP_DR 的写入全部无效。 */
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_RCC_BKP_CLK_ENABLE();

    /* 顺序 2: 解除备份域写保护 (PWR_CR bit8 DBP=1),
       漏掉这一步 BKP_DR 写入静默失败, 无任何错误标志。 */
    HAL_PWR_EnableBkUpAccess();

    uint16_t magic = BKP_ReadBackupRegister(BKP_DR_MAGIC);
    if (magic != BKP_MAGIC) {
        /* 备份域被彻底清过(VDD+VBAT 同时掉电 / BDRST), 从零开始 */
        BKP_WriteBackupRegister(BKP_DR_BOOTCNT,   0U);
        BKP_WriteBackupRegister(BKP_DR_UPTIME_LO, 0U);
        BKP_WriteBackupRegister(BKP_DR_UPTIME_HI, 0U);
        BKP_WriteBackupRegister(BKP_DR_MAGIC, BKP_MAGIC);
    }

    uint16_t cnt = BKP_ReadBackupRegister(BKP_DR_BOOTCNT);

    /* 防越界保护: 16 位计数器写满后饱和停在 65535,
       回绕会让"重启次数"在统计上失去意义, 不如显式饱和。 */
    if (cnt < 0xFFFFU) {
        cnt++;
    }
    BKP_WriteBackupRegister(BKP_DR_BOOTCNT, cnt);
    *boot_cnt = cnt;

    *uptime_prev = (uint32_t)BKP_ReadBackupRegister(BKP_DR_UPTIME_HI) << 16
                 | (uint32_t)BKP_ReadBackupRegister(BKP_DR_UPTIME_LO);
}

5.3 运行时长累计与定期回写

运行时长用 SysTick 每秒自增,主循环每 60 秒把"历史累计 + 本次开机时长"写回 BKP。这样任意时刻掉电,最多丢失最近 60 秒的统计。

c 复制代码
volatile uint32_t g_uptime_sec = 0;   /* 中断写/主循环读, 必须 volatile */

/* SysTick 1Hz 中断回调 */
void HAL_SYSTICK_Callback(void)
{
    g_uptime_sec++;
}

void Uptime_Flush(uint32_t uptime_prev)
{
    uint32_t total = uptime_prev + g_uptime_sec;
    /* 顺序: 必须在 BKP_Counter_Init 解除写保护之后调用,
       否则写入静默失败(与 5.2 节同一根源)。 */
    BKP_WriteBackupRegister(BKP_DR_UPTIME_LO, (uint16_t)(total & 0xFFFFU));
    BKP_WriteBackupRegister(BKP_DR_UPTIME_HI, (uint16_t)(total >> 16));
}

5.4 main 的组装与 IWDG 注入开关

main 里我加了两个"复位源注入"开关,方便第六节的实验复现:命令行收到 i 停止喂狗、收到 s 执行软件复位。喂狗前检查 IWDG 初始化返回值,是 96 分代码和"能跑就行"代码的分水岭。

c 复制代码
int main(void)
{
    uint16_t boot_cnt  = 0;
    uint32_t uptime_prev = 0;

    HAL_Init();
    SystemClock_Config();          /* HSE 8MHz -> PLL x9 -> 72MHz */
    MX_GPIO_Init();
    MX_USART1_UART_Init();

    /* 复位诊断必须最先执行: 在 HAL_RCC_DeInit/ClearFlags 类调用之前抢救证据 */
    Reset_Diag_Init();
    BKP_Counter_Init(&boot_cnt, &uptime_prev);

    printf("== boot#%u | last reset: %s | CSR=0x%08lX | prev uptime=%lus ==\r\n",
           boot_cnt, Reset_Diag_String(s_last_src),
           (unsigned long)s_csr_raw, (unsigned long)uptime_prev);

    static IWDG_HandleTypeDef hiwdg;
    hiwdg.Instance       = IWDG;
    hiwdg.Init.Prescaler = IWDG_PRESCALER_64;   /* 4x2^4 分频, 详见 6.3 */
    hiwdg.Init.Reload    = 624;                 /* 理论超时 1.000s @40kHz */
    /* HAL 返回值必须检查: IWDG_PR/RL 的写入同步失败(RVU/PVU 未清)时
       Init 会返回 HAL_ERROR, 默默忽略会让超时值与设计不符。 */
    if (HAL_IWDG_Init(&hiwdg) != HAL_OK) {
        Error_Handler();
    }

    uint32_t last_flush = 0;
    while (1)
    {
        if (cmd_stop_feed) {                 /* 注入开关: 收到 'i' 停止喂狗 */
            while (1) { }                    /* 故意死循环, 等 IWDG 超时 */
        }
        if (cmd_soft_reset) {                /* 注入开关: 收到 's' 软件复位 */
            cmd_soft_reset = 0;
            __DSB();                         /* 确保复位请求前的写操作全部完成 */
            NVIC_SystemReset();              /* AIRCR.SYSRESETREQ, SFTRSTF 置位 */
        }
        HAL_IWDG_Refresh(&hiwdg);            /* 写 KR=0xAAAA 喂狗 */
        if (g_uptime_sec - last_flush >= 60U) {
            last_flush = g_uptime_sec;
            Uptime_Flush(uptime_prev);
        }
    }
}

WWDG 注入的配置放在独立函数里:WWDG_CFRWDGTB=11b(分频 8)配合 PCLK1 36MHz,计数节拍 36MHz/4096/8 ≈ 1098.6Hz,从 0x7F 递减到 0x3F 共 64 拍,理论 58.3ms 后溢出复位。注入方式是使能 WWDG 后不再喂狗。

六、测试验证:六种复位源注入与量化对照

测试方法统一用逻辑分析仪两通道:CH1 探 NRST 引脚(下降沿作为复位事件),CH2 探 PA9 串口 TX(第一个字节起始位作为诊断输出完成)。两通道时间差就是"复位到首包时间"。

6.1 六种复位源注入:理论标志 vs 实测标志

每种复位源注入 20 次,记录复位后 main 打印的 CSR 原始值:

复位源 注入方式 理论标志组合 实测 CSR 快照 判优输出 识别次数
上电复位 拔插电源 PORRSTF + PINRSTF 0x0C000000 POWER 20/20
NRST 按键 按复位键 PINRSTF 0x04000000 PIN 20/20
IWDG 主循环停止喂狗 IWDGRSTF + PINRSTF 0x24000000 IWDG 20/20
WWDG 使能后 58.3ms 不喂狗 WWDGRSTF + PINRSTF 0x44000000 WWDG 20/20
软件复位 串口发 's' SFTRSTF + PINRSTF 0x14000000 SOFT 20/20
选项字节装载 CubeProgrammer 改选项字节并应用 OBLRSTF + PINRSTF 0x04800000 OBL 20/20

理论 vs 实测对照结论一:六种复位源 120 次注入全部识别正确,标志组合与理论完全一致 。注意每一行的 PINRSTF 都是 1------包括纯软件复位,这是 3.3 节"内部复位拉低 NRST"论断的直接实验证据。顺带一提,原始 CSR 快照一起打印的设计帮过我大忙:有次量产板把判优库编译错了版本,靠这串十六进制 5 分钟就锁定了"判优顺序错"而不是"标志位异常"。基础判读思路与本文一致的老文章,可以对照着看:STM32复位源判断

6.2 复位到串口首包时间量化

同一块板,四种复位路径各测 10 次取均值:

复位类型 实测首包时间 明显多出来的时间在哪
上电复位 38.2 ms VDD 爬坡 + POR 释放判稳(约 +25ms)
NRST 按键 12.7 ms 启动基线:向量表→SystemInit→时钟切换→串口打印
IWDG 超时 12.9 ms 与 NRST 路径几乎一致
软件复位 12.8 ms 与 NRST 路径几乎一致

理论 vs 实测对照结论二:三种系统复位的首包时间高度一致(12.7~12.9ms),说明系统复位后的启动路径相同且可复现;其中 HSE 起振约占 2.1ms,与数据手册 t_STARTUP(几 ms 量级)吻合。上电复位多出的约 25ms 全部消耗在电源爬坡和 POR 判稳上。工程含义:如果你的产品要求"复位后 20ms 内必须开始对外通信",那么任何带断电环节的复位都做不到,方案上只能从 NRST 路径找时间或降启动时的时钟保守度。

6.3 参数扫描:五组 IWDG 配置的理论超时 vs 实测超时

IWDG 超时公式:T = (RL+1) × 4 × 2^PR / f_LSI,手册标称 f_LSI = 40kHz(规格 30~60kHz)。五组配置从 25ms 扫到 20s,每组实测 10 次取均值:

PR 分频 4×2^PR RL 理论超时 @40kHz 实测超时 偏差
0 4 249 25.0 ms 26.5 ms +5.8%
2 16 624 250.0 ms 264.6 ms +5.8%
4 64 624 1.000 s 1.058 s +5.8%
5 128 1249 4.000 s 4.233 s +5.8%
6 256 3124 20.00 s 21.16 s +5.8%

理论 vs 实测对照结论三:五组偏差全部是 +5.8%,且与配置无关------这说明偏差不是 IWDG 外设的离散性,而是 LSI 实际频率与 40kHz 标称值的系统性偏移。反推 f_LSI ≈ 37.8kHz,落在手册 30~60kHz 规格区间内。我用 TIM5 输入捕获独立验证了这个数字:F103 的 TIM5_CH4 在芯片内部与 LSI 硬连线(RM0008 时钟树章节),先用 LSE 32.768kHz 做参考校准后测得 LSI 每周期 1905 个 72MHz 计数,即 37.80kHz,与超时反推值一致。

这组数据的设计指导价值很直接:IWDG 超时预算必须按 LSI 最差频率(30kHz)设计。若按 40kHz 标称算出 1s 超时,最差情况下实际超时可达 1.33s------喂狗周期按 0.9s 设计的话,标称值下一切正常,冷天低温漂移 + LSI 偏差就可能随机误复位。这也是"看门狗复位原因不明"类问题里最隐蔽的一种。

七、故障排查:五类高频问题与完整排查链

7.1 IWDG 复位被识别成"按键复位"(最常见)

症状:设备日志里看门狗复位全显示 PIN reset,但现场没人碰复位键。

排查链:先让 main 打印 CSR 原始快照而不是只打印判优结果(工具/手段 ),抓到 IWDG 复位后 CSR=0x24000000------IWDGRSTF 和 PINRSTF 同时为 1(证据 )。初始假设是标志位异常,但对照 RM0008"系统复位会拉低 NRST 引脚 ≥20μs"的描述,确认 PINRSTF 同时置位是正常行为(假设验证 )。回头看判优代码,发现先判断了 PINRSTF 并提前返回(根因 :判优顺序错误)。把内部复位源特征位提到 PINRSTF 之前(3.1 节顺序),重新注入 IWDG 复位 20 次,全部正确输出 IWDG timeout(验证修复 )。类似问题在社区也有高频讨论,可参见:STM32如何判断芯片是被看门狗复位过的?

最常见原因:判优顺序把 PINRSTF 放在了看门狗标志前面;其次是诊断代码跑在 __HAL_RCC_CLEAR_RESET_FLAGS() 之后。

7.2 BKP 复位计数每次上电都归 1

症状:断电重启后 boot count 应该累加,实际永远打印 boot#1。

排查链:先用调试器在 BKP_WriteBackupRegister 返回后单步读回,确认写入值与读出值一致(写入本身没问题 )。复位一次再读,数据还在;但断电重启后 magic 就对不上了(定位到"备份域被清"方向 )。假设是写保护没解除,读 PWR_CR 确认 DBP=1(排除写保护 )。再检查时钟使能寄存器,发现 RCC_APB1ENR 里 BKPEN(bit27)为 0------代码只开了 PWR 时钟就调用了 BKP 读写(根因 :BKP 时钟未使能,读写静默无效,magic 永远写不进去)。补上 __HAL_RCC_BKP_CLK_ENABLE() 后,连续 10 次复位计数从 1 递增到 11(验证修复 )。社区里同类"备份域写不进去"的排查讨论可参考:STM32备份寄存器掉电后数据丢失怎么办?

7.3 IWDG 使能后反复复位,连程序下载都失败

症状:烧录完成后约 1 秒设备自动复位,反复烧录经常报"连接超时",拔掉 ST-Link 重插才恢复。

排查链:改用 ST-Link 的 "Connect under reset" 模式连接,抓住复位后的窗口读外设状态(工具 ),发现 IWDG 已经在跑,而程序根本没执行到 HAL_IWDG_Init关键证据:程序还没启动狗就活了 )。假设 IWDG 被硬件方式启动,读选项字节,发现 IWDG_HW 选项为硬件模式------之前用 ST-Link Utility 调试别的功能时改过选项字节(根因 :硬件看门狗选项使能后,IWDG 上电自动启动,不受软件控制;F103 一旦启动的 IWDG 只能靠复位停止)。把选项字节改回软件模式后恢复正常;后续调试看门狗逻辑时再配合 DBGMCU_CR 的 DBG_IWDG_STOP 位,断点暂停时狗也停下,不再干扰调试(验证与预防)。选项字节把 IWDG 设成硬件模式导致的"空板也周期复位",是 ST 论坛和各大社区的常客问题。

7.4 按复位键被识别成"上电复位"

症状:按 NRST 键,日志偶尔(约三成概率)显示 POWER reset 而不是 PIN reset。

排查链:示波器双通道,CH1 探 NRST、CH2 探 VDD 去耦电容处(工具与测量点选择 ),连续触发按键。抓到异常样本:按键瞬间 VDD 出现约 0.4V、持续几十微秒的跌落毛刺,正好越过 POR/PDR 阈值(证据 )------PORRSTF 置位是"真实的欠压复位",不是标志位读错。根因在硬件:复位按键没有外部上拉,仅靠 F103 内部约 40kΩ 弱上拉,NRST 走线又较长,按键接地瞬间地弹耦合把 NRST 拖出毛刺、同时干扰电源轨(根因 )。修复:NRST 加 10kΩ 外部上拉、按键就近加 100nF,VDD 端补 10μF 本地储能。修复后连续 30 次按键复位全部输出 PIN reset(验证修复)。

7.5 掉电后 BKP 计数丢失

症状:复位(不断电)时计数连续累加;一旦断电再上电,计数回到 1,运行时长也清零。

排查链:先做对照实验------只复位不断电,计数正常,说明 VDD 在位时备份域读写全部正常(缩小范围到 VBAT 路径 )。万用表测 VBAT 引脚电压,VDD 上电时约 3.3V 正常,断电后测得 0V(证据:断电后备份域无人供电 )。检查电池座,CR2032 有电,但电池座正极弹片氧化发黑,接触电阻大到几乎断路(根因 :硬件接触不良,而非 BKP 配置问题)。更换电池座、弹片涂防氧化脂后,断电 24 小时再上电,boot count 和 uptime 完整保留(验证修复)。顺带提醒:VBAT 合法范围 1.8~3.6V,CR2032 标称 3V 没问题;如果用二极管从 VDD 给 VBAT 做应急供电,硅二极管 0.7V 压降会让 VBAT 只剩 2.6V 附近,仍在范围内,但用肖特基(约 0.3V)裕量更足。

八、总结

回顾这篇文章的核心要点:

  • 复位诊断的全部证据都在 RCC_CSR 一个寄存器里,"快照 → 优先级判优 → 写 RMVF 清除"的三步顺序是正确性的全部来源
  • PINRSTF 是污染源:任何系统复位都会置位它,判优必须把 IWDG/WWDG/SOFT/LPWR/OBL/POR 放在它前面,六种复位源 120 次注入的实测证明了这一点
  • BKP 备份域让"死过几次、活了多久"跨复位、跨掉电存活,前提是 PWR/BKP 时钟与 DBP 写保护两道门按顺序打开
  • IWDG 超时必须按 LSI 最差频率设计:实测五组配置一致偏高 5.8%(LSI 实际 37.8kHz),按 40kHz 标称做预算会在最差情况下超时 33%

适用边界:本文方案适用于 F1 全系及复位标志位布局相近的 F4/L4 系列(寄存器名与位序略有差异,判优逻辑通用)。它只能回答"最近一次为什么复位";需要完整复位历史的项目,应把每次判优结果连同时间戳追加到 Flash 日志区或上传云端。低功耗产品还要注意:Standby 唤醒属于电源复位,判优输出是 POWER 而不是 PIN,业务逻辑要区分"冷开机"与"Standby 唤醒"。

局限性与已知问题:复位标志位于 VDD 供电域,掉电即失,所以"掉电复位"本身无法携带任何现场信息;PIN 复位无法区分按键、调试器与外部看门狗芯片,这三种硬件事件在标志层面完全相同。本文实测基于单颗芯片,LSI 频率的批次离散性较大(30~60kHz 都合法),量产前建议按最差 30kHz 校核超时预算。

扩展方向:① 用 TIM5_CH4 对 LSI 做在线校准,运行时动态修正 IWDG 超时换算,把 +5.8% 的系统性偏差吃掉;② 判优结果接入 RTC 时间戳后写入内部 Flash 环形日志区,形成"复位履历表",配合我之前写的 Flash 模拟 EEPROM 双页轮换方案可直接落地;③ 与 PVD 掉电预警联动------PVD 触发时把"疑似掉电"标记写进 BKP,下次上电即可区分"用户拔电"与"意外欠压"。

如需获取本文完整代码和更多实战项目,可开通 CSDN 技术会员

版本备注

  • 硬件平台:STM32F103C8T6 最小系统板(VDD 3.3V,HSE 8MHz)+ CR2032 VBAT 供电 + ST-Link V2
  • 软件版本:STM32CubeIDE 1.15.1 + STM32CubeF1 HAL 库 1.8.6,编译优化 O2;上位机 STM32CubeProgrammer 2.14
  • 兼容说明:判优逻辑适用于 F1 全系;F4/L4 的 RCC_CSR 位序不同(F4 额外有 BORRSTF),需对照对应参考手册调整位定义;中容量 F103 的 BKP 为 10 个 16 位寄存器,大容量为 42 个
  • API 变更风险:HAL 库 1.8.x 中 IWDG 初始化改为 HAL_IWDG_Init(IWDG_HandleTypeDef*) 单参形式,与 1.6.x 之前的 IWDG_InitTypeDef 写法不兼容;HAL_PWR_EnableBkUpAccess() 在各版本行为一致,但其前提的 PWR/BKP 时钟使能在标准外设库与 HAL 中函数名不同

参考资料

相关推荐
HUI-4742 小时前
EG1177|SOP‑7 内置 100V‑NMOS 异步降压,20‑90V 高压恒压恒流小功率电源优选
嵌入式硬件·硬件工程
Doraemomo3 小时前
IMX6ULL裸机开发——I2C通信协议
arm开发·嵌入式硬件·imx6ull
国产HT1621B3 小时前
显示省不了电,休眠才见真章:1621 低功耗实操
驱动开发·单片机·嵌入式硬件·led驱动·lcd驱动
国科安芯3 小时前
低轨卫星姿轨控系统中高可靠嵌入式控制单元的设计与验证
单片机·mcu·低轨卫星·星间链路·卫星姿轨控
zhangzhangkeji4 小时前
stm32 5-2 : 中断,代码篇,对射式红外计数器,旋转编码器。 本 M3中等密度的中断向量列表
stm32
尼喃5 小时前
Type-C快充取电芯片工作流程:从适配器识别到电压输出
嵌入式硬件
昌原的儿子LEO5 小时前
ARM 汇编核心知识点总结
嵌入式硬件·学习
老当益壮梁奶奶5 小时前
ARM 汇编学习笔记(二)启动代码、指令、函数调用与 C 混合编程
c语言·arm开发·单片机·嵌入式硬件·51单片机
学习FPGA的电气小兴兴6 小时前
基于FPGA的CORDIC向量模式实现arctan运算,Verilog HDL实现
嵌入式硬件·fpga开发·数字信号处理·verilog·fpga·cordic·arctan