【stm32】WWDG(窗口看门狗)

🔥铅笔小新z:个人主页

🎬博客专栏:stm32学习

💫滴水不绝,可穿石;步履不休,能至渊。


STM32 WWDG 窗口看门狗:从结构框图理解喂狗窗口、时间计算与 EWI 中断

适用平台与版本 :STM32F103ZET6,Cortex-M3,STM32CubeF1 HAL;寄存器以 RM0008 中适用于 STM32F103 的描述为准。

外设与技术点:窗口看门狗 WWDG、PCLK1、预分频器、CR/CFR/SR 寄存器、窗口比较、EWI 与 NVIC 联动。

最终效果:配置 PCLK1=36 MHz、预分频 8、窗口值 96、重装计数 127,分别观察 15 ms 过早喂狗、40 ms 正常喂狗、60 ms 超时复位,以及复位前的 EWI 回调。

适合读者:掌握 C 语言、GPIO 和基本中断概念,能够建立 HAL 工程,希望理解窗口看门狗内部机制并独立完成实验的开发者。

看门狗可以在程序无法正常运行时复位单片机,但"程序一直在喂狗"并不一定意味着程序正常。窗口看门狗增加了一个条件:喂狗必须发生在指定的时间窗口内,太早和太晚都不行。

理解这件事,需要把"时间"与"寄存器中的计数值"联系起来。下面先认识窗口看门狗,再从片上外设、时钟输入和复位输出入手,逐步拆开预分频器、控制寄存器和配置寄存器;随后沿结构框图推导过晚、过早与正常喂狗,计算时间,最后完成中断与代码实验。

全文围绕六个核心知识点展开:窗口概念、内部结构与寄存器、窗口及复位逻辑、喂狗时间计算、EWI 中断、配置与实验验证。完整工程应用代码、运行观察和排错方法放在实验部分,便于把每个现象与前面的机制对应起来。

验证说明:文中的数值是按参数计算的结果,运行输出和波形是预期现象。本次未在目标板上实际编译、烧录或测量,不把推导写成实测结论。

1. 窗口看门狗概念

1.1 什么是窗口看门狗

窗口看门狗的英文是 Window Watchdog,简称 WWDG 。它和独立看门狗 IWDG 一样,都是用来检测程序异常的片内外设。

两者都有递减计数机制:硬件不断把计数值减小,软件需要在规定条件下重新写入计数值,这个动作通常称为"喂狗"或"刷新"。如果软件没有及时完成刷新,硬件就会产生系统复位。

窗口看门狗还会检查刷新是否过早。它关注的不只是"有没有定期执行某条语句",还关注刷新动作是否符合预期的执行时序。

1.2 独立看门狗与窗口看门狗有什么区别

先把时钟和寄存器细节放到后面,只比较喂狗规则。

对于 STM32F103 的 IWDG,只要在超时前刷新,提前一些通常没有问题。例如允许最长约 1 s 不刷新,程序每隔 0.1 s 或 0.9 s 刷新,都能避免超时。

WWDG 则同时限定最早和最晚时机。假设一个便于理解的时间窗口是 0.5~1 s,那么:

刷新时刻 判断 原因
0.4 s 过早 窗口还没有开放
0.75 s 正常 刷新发生在窗口内部
超过 1 s 过晚 超时条件已经成立,程序可能已经复位

这里的秒数只是解释概念,不是本文 STM32F103、36 MHz 配置能够达到的 WWDG 时间参数。实际实验的窗口是毫秒级。

比较还必须限定型号:本文讨论的是 STM32F103。其他 STM32 系列的 IWDG 可能具有窗口功能,不能把这里的差异推广到所有芯片。

1.3 "窗口"究竟是什么

这里的窗口是时间窗口,即允许完成某个动作的一段连续时间。

可以把它理解为按预约时段取件:来早了还不能取,超过截止时间也不能取,只有在允许的时段内才行。回到硬件,WWDG 不会直接保存"第几毫秒允许刷新",而是让计数器递减,再用计数值判断经过了多少时间。
#mermaid-svg-kx6V4Ir25njmI7Ud{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-kx6V4Ir25njmI7Ud .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kx6V4Ir25njmI7Ud .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kx6V4Ir25njmI7Ud .error-icon{fill:#552222;}#mermaid-svg-kx6V4Ir25njmI7Ud .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kx6V4Ir25njmI7Ud .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kx6V4Ir25njmI7Ud .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kx6V4Ir25njmI7Ud .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kx6V4Ir25njmI7Ud .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kx6V4Ir25njmI7Ud .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kx6V4Ir25njmI7Ud .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kx6V4Ir25njmI7Ud .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kx6V4Ir25njmI7Ud .marker.cross{stroke:#333333;}#mermaid-svg-kx6V4Ir25njmI7Ud svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kx6V4Ir25njmI7Ud p{margin:0;}#mermaid-svg-kx6V4Ir25njmI7Ud .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kx6V4Ir25njmI7Ud .cluster-label text{fill:#333;}#mermaid-svg-kx6V4Ir25njmI7Ud .cluster-label span{color:#333;}#mermaid-svg-kx6V4Ir25njmI7Ud .cluster-label span p{background-color:transparent;}#mermaid-svg-kx6V4Ir25njmI7Ud .label text,#mermaid-svg-kx6V4Ir25njmI7Ud span{fill:#333;color:#333;}#mermaid-svg-kx6V4Ir25njmI7Ud .node rect,#mermaid-svg-kx6V4Ir25njmI7Ud .node circle,#mermaid-svg-kx6V4Ir25njmI7Ud .node ellipse,#mermaid-svg-kx6V4Ir25njmI7Ud .node polygon,#mermaid-svg-kx6V4Ir25njmI7Ud .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kx6V4Ir25njmI7Ud .rough-node .label text,#mermaid-svg-kx6V4Ir25njmI7Ud .node .label text,#mermaid-svg-kx6V4Ir25njmI7Ud .image-shape .label,#mermaid-svg-kx6V4Ir25njmI7Ud .icon-shape .label{text-anchor:middle;}#mermaid-svg-kx6V4Ir25njmI7Ud .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kx6V4Ir25njmI7Ud .rough-node .label,#mermaid-svg-kx6V4Ir25njmI7Ud .node .label,#mermaid-svg-kx6V4Ir25njmI7Ud .image-shape .label,#mermaid-svg-kx6V4Ir25njmI7Ud .icon-shape .label{text-align:center;}#mermaid-svg-kx6V4Ir25njmI7Ud .node.clickable{cursor:pointer;}#mermaid-svg-kx6V4Ir25njmI7Ud .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kx6V4Ir25njmI7Ud .arrowheadPath{fill:#333333;}#mermaid-svg-kx6V4Ir25njmI7Ud .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kx6V4Ir25njmI7Ud .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kx6V4Ir25njmI7Ud .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kx6V4Ir25njmI7Ud .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kx6V4Ir25njmI7Ud .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kx6V4Ir25njmI7Ud .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kx6V4Ir25njmI7Ud .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kx6V4Ir25njmI7Ud .cluster text{fill:#333;}#mermaid-svg-kx6V4Ir25njmI7Ud .cluster span{color:#333;}#mermaid-svg-kx6V4Ir25njmI7Ud 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-kx6V4Ir25njmI7Ud .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kx6V4Ir25njmI7Ud rect.text{fill:none;stroke-width:0;}#mermaid-svg-kx6V4Ir25njmI7Ud .icon-shape,#mermaid-svg-kx6V4Ir25njmI7Ud .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kx6V4Ir25njmI7Ud .icon-shape p,#mermaid-svg-kx6V4Ir25njmI7Ud .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kx6V4Ir25njmI7Ud .icon-shape .label rect,#mermaid-svg-kx6V4Ir25njmI7Ud .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kx6V4Ir25njmI7Ud .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kx6V4Ir25njmI7Ud .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kx6V4Ir25njmI7Ud :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 上次装入计数值
过早阶段
窗口开放
窗口内允许刷新
达到超时条件并复位

从左到右表示时间推移。实际硬件中的计数值随时间减小,后面会把这条时间轴对应到具体寄存器。

1.4 已经有 IWDG,为什么还需要 WWDG

先保留这个问题。仅知道"一个能早喂,一个不能早喂",还不足以判断它们在项目中的用途。理解窗口比较和 EWI 之后,第 6 节会用程序提前返回的例子说明两者为什么可以互补。

知识小结

结论 :WWDG 要求刷新发生在指定时间段内,过早刷新和超时都可能复位。

原因 :它不仅监控计数器是否超时,还检查软件写入是否早于窗口开放。

关键参数 :后面需要确定计数时钟、预分频、初始计数值和窗口值。

常见误区:把看门狗理解成"喂得越频繁越安全",或者把用于类比的秒级时间当成实际配置。

2. 窗口看门狗原理

2.1 宏观理解:WWDG 是一个片上外设

WWDG 并不是必须接在单片机外面的器件。它已经集成在 STM32F103 内部,连接在 APB1 总线上,有自己的寄存器地址空间。

对 STM32F103,WWDG 的基地址为 0x40002C00。CPU 对相应地址进行读写,就能配置和刷新这个外设。CMSIS 中的 WWDG 指针与 HAL 句柄,本质上都是把软件访问引向这些寄存器。


#mermaid-svg-RyXjF5rGTJsP7dq0{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-RyXjF5rGTJsP7dq0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RyXjF5rGTJsP7dq0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RyXjF5rGTJsP7dq0 .error-icon{fill:#552222;}#mermaid-svg-RyXjF5rGTJsP7dq0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RyXjF5rGTJsP7dq0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RyXjF5rGTJsP7dq0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RyXjF5rGTJsP7dq0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RyXjF5rGTJsP7dq0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RyXjF5rGTJsP7dq0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RyXjF5rGTJsP7dq0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RyXjF5rGTJsP7dq0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RyXjF5rGTJsP7dq0 .marker.cross{stroke:#333333;}#mermaid-svg-RyXjF5rGTJsP7dq0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RyXjF5rGTJsP7dq0 p{margin:0;}#mermaid-svg-RyXjF5rGTJsP7dq0 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-RyXjF5rGTJsP7dq0 .cluster-label text{fill:#333;}#mermaid-svg-RyXjF5rGTJsP7dq0 .cluster-label span{color:#333;}#mermaid-svg-RyXjF5rGTJsP7dq0 .cluster-label span p{background-color:transparent;}#mermaid-svg-RyXjF5rGTJsP7dq0 .label text,#mermaid-svg-RyXjF5rGTJsP7dq0 span{fill:#333;color:#333;}#mermaid-svg-RyXjF5rGTJsP7dq0 .node rect,#mermaid-svg-RyXjF5rGTJsP7dq0 .node circle,#mermaid-svg-RyXjF5rGTJsP7dq0 .node ellipse,#mermaid-svg-RyXjF5rGTJsP7dq0 .node polygon,#mermaid-svg-RyXjF5rGTJsP7dq0 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RyXjF5rGTJsP7dq0 .rough-node .label text,#mermaid-svg-RyXjF5rGTJsP7dq0 .node .label text,#mermaid-svg-RyXjF5rGTJsP7dq0 .image-shape .label,#mermaid-svg-RyXjF5rGTJsP7dq0 .icon-shape .label{text-anchor:middle;}#mermaid-svg-RyXjF5rGTJsP7dq0 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RyXjF5rGTJsP7dq0 .rough-node .label,#mermaid-svg-RyXjF5rGTJsP7dq0 .node .label,#mermaid-svg-RyXjF5rGTJsP7dq0 .image-shape .label,#mermaid-svg-RyXjF5rGTJsP7dq0 .icon-shape .label{text-align:center;}#mermaid-svg-RyXjF5rGTJsP7dq0 .node.clickable{cursor:pointer;}#mermaid-svg-RyXjF5rGTJsP7dq0 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RyXjF5rGTJsP7dq0 .arrowheadPath{fill:#333333;}#mermaid-svg-RyXjF5rGTJsP7dq0 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RyXjF5rGTJsP7dq0 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RyXjF5rGTJsP7dq0 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RyXjF5rGTJsP7dq0 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RyXjF5rGTJsP7dq0 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RyXjF5rGTJsP7dq0 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RyXjF5rGTJsP7dq0 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RyXjF5rGTJsP7dq0 .cluster text{fill:#333;}#mermaid-svg-RyXjF5rGTJsP7dq0 .cluster span{color:#333;}#mermaid-svg-RyXjF5rGTJsP7dq0 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-RyXjF5rGTJsP7dq0 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RyXjF5rGTJsP7dq0 rect.text{fill:none;stroke-width:0;}#mermaid-svg-RyXjF5rGTJsP7dq0 .icon-shape,#mermaid-svg-RyXjF5rGTJsP7dq0 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RyXjF5rGTJsP7dq0 .icon-shape p,#mermaid-svg-RyXjF5rGTJsP7dq0 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RyXjF5rGTJsP7dq0 .icon-shape .label rect,#mermaid-svg-RyXjF5rGTJsP7dq0 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RyXjF5rGTJsP7dq0 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RyXjF5rGTJsP7dq0 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RyXjF5rGTJsP7dq0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CPU 执行配置或刷新代码
经总线访问 WWDG 寄存器
WWDG 硬件按时钟递减并判断条件
正常继续运行
条件成立时请求系统复位

CPU 负责配置和刷新,倒计时与复位条件判断由硬件完成。因此,程序卡在死循环里不代表 WWDG 也会停止工作。

2.2 先读懂结构框图

结构框图可以分成两条主线:一条是时钟如何驱动计数器 ,另一条是计数状态与写入动作如何触发复位。

先看计数链路:
#mermaid-svg-XWrI49YUc2tdVeoP{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-XWrI49YUc2tdVeoP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XWrI49YUc2tdVeoP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XWrI49YUc2tdVeoP .error-icon{fill:#552222;}#mermaid-svg-XWrI49YUc2tdVeoP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XWrI49YUc2tdVeoP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XWrI49YUc2tdVeoP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XWrI49YUc2tdVeoP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XWrI49YUc2tdVeoP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XWrI49YUc2tdVeoP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XWrI49YUc2tdVeoP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XWrI49YUc2tdVeoP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XWrI49YUc2tdVeoP .marker.cross{stroke:#333333;}#mermaid-svg-XWrI49YUc2tdVeoP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XWrI49YUc2tdVeoP p{margin:0;}#mermaid-svg-XWrI49YUc2tdVeoP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XWrI49YUc2tdVeoP .cluster-label text{fill:#333;}#mermaid-svg-XWrI49YUc2tdVeoP .cluster-label span{color:#333;}#mermaid-svg-XWrI49YUc2tdVeoP .cluster-label span p{background-color:transparent;}#mermaid-svg-XWrI49YUc2tdVeoP .label text,#mermaid-svg-XWrI49YUc2tdVeoP span{fill:#333;color:#333;}#mermaid-svg-XWrI49YUc2tdVeoP .node rect,#mermaid-svg-XWrI49YUc2tdVeoP .node circle,#mermaid-svg-XWrI49YUc2tdVeoP .node ellipse,#mermaid-svg-XWrI49YUc2tdVeoP .node polygon,#mermaid-svg-XWrI49YUc2tdVeoP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XWrI49YUc2tdVeoP .rough-node .label text,#mermaid-svg-XWrI49YUc2tdVeoP .node .label text,#mermaid-svg-XWrI49YUc2tdVeoP .image-shape .label,#mermaid-svg-XWrI49YUc2tdVeoP .icon-shape .label{text-anchor:middle;}#mermaid-svg-XWrI49YUc2tdVeoP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XWrI49YUc2tdVeoP .rough-node .label,#mermaid-svg-XWrI49YUc2tdVeoP .node .label,#mermaid-svg-XWrI49YUc2tdVeoP .image-shape .label,#mermaid-svg-XWrI49YUc2tdVeoP .icon-shape .label{text-align:center;}#mermaid-svg-XWrI49YUc2tdVeoP .node.clickable{cursor:pointer;}#mermaid-svg-XWrI49YUc2tdVeoP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XWrI49YUc2tdVeoP .arrowheadPath{fill:#333333;}#mermaid-svg-XWrI49YUc2tdVeoP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XWrI49YUc2tdVeoP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XWrI49YUc2tdVeoP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XWrI49YUc2tdVeoP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XWrI49YUc2tdVeoP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XWrI49YUc2tdVeoP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XWrI49YUc2tdVeoP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XWrI49YUc2tdVeoP .cluster text{fill:#333;}#mermaid-svg-XWrI49YUc2tdVeoP .cluster span{color:#333;}#mermaid-svg-XWrI49YUc2tdVeoP 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-XWrI49YUc2tdVeoP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XWrI49YUc2tdVeoP rect.text{fill:none;stroke-width:0;}#mermaid-svg-XWrI49YUc2tdVeoP .icon-shape,#mermaid-svg-XWrI49YUc2tdVeoP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XWrI49YUc2tdVeoP .icon-shape p,#mermaid-svg-XWrI49YUc2tdVeoP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XWrI49YUc2tdVeoP .icon-shape .label rect,#mermaid-svg-XWrI49YUc2tdVeoP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XWrI49YUc2tdVeoP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XWrI49YUc2tdVeoP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XWrI49YUc2tdVeoP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} PCLK1 时钟
固定除以 4096
WDG 预分频器:1、2、4、8
7 位递减计数器 T
CPU 写控制寄存器
配置寄存器中的 W
与当前 T 比较

然后看复位逻辑。为方便解释,把最终控制复位的与逻辑称为 A,把过早刷新路径中的与逻辑称为 B:
#mermaid-svg-icBLGwga7m4XGJis{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-icBLGwga7m4XGJis .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-icBLGwga7m4XGJis .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-icBLGwga7m4XGJis .error-icon{fill:#552222;}#mermaid-svg-icBLGwga7m4XGJis .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-icBLGwga7m4XGJis .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-icBLGwga7m4XGJis .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-icBLGwga7m4XGJis .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-icBLGwga7m4XGJis .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-icBLGwga7m4XGJis .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-icBLGwga7m4XGJis .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-icBLGwga7m4XGJis .marker{fill:#333333;stroke:#333333;}#mermaid-svg-icBLGwga7m4XGJis .marker.cross{stroke:#333333;}#mermaid-svg-icBLGwga7m4XGJis svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-icBLGwga7m4XGJis p{margin:0;}#mermaid-svg-icBLGwga7m4XGJis .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-icBLGwga7m4XGJis .cluster-label text{fill:#333;}#mermaid-svg-icBLGwga7m4XGJis .cluster-label span{color:#333;}#mermaid-svg-icBLGwga7m4XGJis .cluster-label span p{background-color:transparent;}#mermaid-svg-icBLGwga7m4XGJis .label text,#mermaid-svg-icBLGwga7m4XGJis span{fill:#333;color:#333;}#mermaid-svg-icBLGwga7m4XGJis .node rect,#mermaid-svg-icBLGwga7m4XGJis .node circle,#mermaid-svg-icBLGwga7m4XGJis .node ellipse,#mermaid-svg-icBLGwga7m4XGJis .node polygon,#mermaid-svg-icBLGwga7m4XGJis .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-icBLGwga7m4XGJis .rough-node .label text,#mermaid-svg-icBLGwga7m4XGJis .node .label text,#mermaid-svg-icBLGwga7m4XGJis .image-shape .label,#mermaid-svg-icBLGwga7m4XGJis .icon-shape .label{text-anchor:middle;}#mermaid-svg-icBLGwga7m4XGJis .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-icBLGwga7m4XGJis .rough-node .label,#mermaid-svg-icBLGwga7m4XGJis .node .label,#mermaid-svg-icBLGwga7m4XGJis .image-shape .label,#mermaid-svg-icBLGwga7m4XGJis .icon-shape .label{text-align:center;}#mermaid-svg-icBLGwga7m4XGJis .node.clickable{cursor:pointer;}#mermaid-svg-icBLGwga7m4XGJis .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-icBLGwga7m4XGJis .arrowheadPath{fill:#333333;}#mermaid-svg-icBLGwga7m4XGJis .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-icBLGwga7m4XGJis .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-icBLGwga7m4XGJis .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-icBLGwga7m4XGJis .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-icBLGwga7m4XGJis .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-icBLGwga7m4XGJis .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-icBLGwga7m4XGJis .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-icBLGwga7m4XGJis .cluster text{fill:#333;}#mermaid-svg-icBLGwga7m4XGJis .cluster span{color:#333;}#mermaid-svg-icBLGwga7m4XGJis 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-icBLGwga7m4XGJis .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-icBLGwga7m4XGJis rect.text{fill:none;stroke-width:0;}#mermaid-svg-icBLGwga7m4XGJis .icon-shape,#mermaid-svg-icBLGwga7m4XGJis .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-icBLGwga7m4XGJis .icon-shape p,#mermaid-svg-icBLGwga7m4XGJis .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-icBLGwga7m4XGJis .icon-shape .label rect,#mermaid-svg-icBLGwga7m4XGJis .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-icBLGwga7m4XGJis .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-icBLGwga7m4XGJis .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-icBLGwga7m4XGJis :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 当前计数值 T
比较器:T 大于 W
窗口值 W
与逻辑 B
发生写 WWDG_CR 动作
计数器最高位 T6
取反
或逻辑
与逻辑 A
启动位 WDGA
复位请求

这是一张功能逻辑图,用来说明判断关系,不是要求读者按它搭建外部电路。图中的取反表示 1 变 0、0 变 1;与逻辑要求输入同时为真,或逻辑只要一路为真即可。

暂时不急着推导所有情况,先按顺序明确输入时钟、复位输出和各个寄存器,再回到这张图分析。

2.3 WWDG 的输入时钟

WWDG 的输入时钟是 PCLK1,也就是 APB1 外设总线时钟。它不像 IWDG 那样由独立的 LSI 驱动。

在本文的 STM32F103ZET6 配置中:

text 复制代码
外部 HSE 8 MHz → PLL ×9 → HCLK 72 MHz
HCLK / 2 → PCLK1 36 MHz → WWDG

这里必须区分 PCLK1 与 APB1 定时器时钟。STM32F103 的部分定时器在 APB 分频不为 1 时可使用倍频后的时钟,但 WWDG 不能直接套用这个定时器倍频规则。本例应使用 36 MHz 计算,不能用 72 MHz。

计时精度也取决于真实时钟源。使用稳定外部晶振的 PCLK1 通常比 LSI 更适合精确计时,但"来自 APB1"并不自动保证某个固定误差值。与此同时,WWDG 的计数依赖这个时钟域工作。

2.4 WWDG 如何复位 MCU

WWDG 的复位请求接入芯片内部的复位控制逻辑。条件成立时,由硬件产生系统复位,程序重新经过启动流程并进入 main()。


#mermaid-svg-7RaiGFk3ktivkKHt{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-7RaiGFk3ktivkKHt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7RaiGFk3ktivkKHt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7RaiGFk3ktivkKHt .error-icon{fill:#552222;}#mermaid-svg-7RaiGFk3ktivkKHt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7RaiGFk3ktivkKHt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7RaiGFk3ktivkKHt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7RaiGFk3ktivkKHt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7RaiGFk3ktivkKHt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7RaiGFk3ktivkKHt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7RaiGFk3ktivkKHt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7RaiGFk3ktivkKHt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7RaiGFk3ktivkKHt .marker.cross{stroke:#333333;}#mermaid-svg-7RaiGFk3ktivkKHt svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7RaiGFk3ktivkKHt p{margin:0;}#mermaid-svg-7RaiGFk3ktivkKHt .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7RaiGFk3ktivkKHt .cluster-label text{fill:#333;}#mermaid-svg-7RaiGFk3ktivkKHt .cluster-label span{color:#333;}#mermaid-svg-7RaiGFk3ktivkKHt .cluster-label span p{background-color:transparent;}#mermaid-svg-7RaiGFk3ktivkKHt .label text,#mermaid-svg-7RaiGFk3ktivkKHt span{fill:#333;color:#333;}#mermaid-svg-7RaiGFk3ktivkKHt .node rect,#mermaid-svg-7RaiGFk3ktivkKHt .node circle,#mermaid-svg-7RaiGFk3ktivkKHt .node ellipse,#mermaid-svg-7RaiGFk3ktivkKHt .node polygon,#mermaid-svg-7RaiGFk3ktivkKHt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7RaiGFk3ktivkKHt .rough-node .label text,#mermaid-svg-7RaiGFk3ktivkKHt .node .label text,#mermaid-svg-7RaiGFk3ktivkKHt .image-shape .label,#mermaid-svg-7RaiGFk3ktivkKHt .icon-shape .label{text-anchor:middle;}#mermaid-svg-7RaiGFk3ktivkKHt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7RaiGFk3ktivkKHt .rough-node .label,#mermaid-svg-7RaiGFk3ktivkKHt .node .label,#mermaid-svg-7RaiGFk3ktivkKHt .image-shape .label,#mermaid-svg-7RaiGFk3ktivkKHt .icon-shape .label{text-align:center;}#mermaid-svg-7RaiGFk3ktivkKHt .node.clickable{cursor:pointer;}#mermaid-svg-7RaiGFk3ktivkKHt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7RaiGFk3ktivkKHt .arrowheadPath{fill:#333333;}#mermaid-svg-7RaiGFk3ktivkKHt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7RaiGFk3ktivkKHt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7RaiGFk3ktivkKHt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7RaiGFk3ktivkKHt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7RaiGFk3ktivkKHt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7RaiGFk3ktivkKHt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7RaiGFk3ktivkKHt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7RaiGFk3ktivkKHt .cluster text{fill:#333;}#mermaid-svg-7RaiGFk3ktivkKHt .cluster span{color:#333;}#mermaid-svg-7RaiGFk3ktivkKHt 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-7RaiGFk3ktivkKHt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7RaiGFk3ktivkKHt rect.text{fill:none;stroke-width:0;}#mermaid-svg-7RaiGFk3ktivkKHt .icon-shape,#mermaid-svg-7RaiGFk3ktivkKHt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7RaiGFk3ktivkKHt .icon-shape p,#mermaid-svg-7RaiGFk3ktivkKHt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7RaiGFk3ktivkKHt .icon-shape .label rect,#mermaid-svg-7RaiGFk3ktivkKHt .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7RaiGFk3ktivkKHt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7RaiGFk3ktivkKHt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7RaiGFk3ktivkKHt :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} WWDG 满足复位条件
片内复位控制逻辑
外部 NRST 复位输入
CPU 和相关外设进入复位流程
重新执行启动代码与 main

上图强调两类复位来源进入片内逻辑,并不表示软件需要先拉低 NRST 才能让 WWDG 生效。WWDG 的工作不依赖应用程序去操作复位引脚。

实际实验中可以观察 NRST 辅助判断复位时序,再读取 RCC 的复位标志确认来源。看到程序重新启动,不能单凭这一点断定一定是 WWDG 导致,因为掉电、手动复位和其他复位源也会重新启动程序。

2.5 看门狗预分频器

PCLK1 进入 WWDG 后,并不是每来一个时钟,计数器就减 1。中间有两级分频:

text 复制代码
PCLK1 → 固定 /4096 → 可配置 /P → 计数器每次减 1

其中 P 可以选择 1、2、4、8。它由配置寄存器 WWDG_CFR 中的 WDGTB[1:0] 决定:

WDGTB 编码 实际分频系数 P 计数时钟
00 1 PCLK1 / 4096
01 2 PCLK1 / 4096 / 2
10 4 PCLK1 / 4096 / 4
11 8 PCLK1 / 4096 / 8

编码与实际系数不是同一个数。设两位编码的数值为 N,则 P=2^N。HAL 使用 WWDG_PRESCALER_8 这类宏表示正确的寄存器位组合,不应把裸数字 8 当成相同的编码写进去。

分频越大,计数越慢,同样次数的递减消耗时间越长。固定的 4096 是芯片规定的分频值,不能由用户修改,也不必把其设计原因推测成已经确认的事实。

2.6 看门狗控制寄存器 WWDG_CR

CR 是 Control Register,控制寄存器。它的两个关键部分是启动位和当前计数值。

字段 位号 作用
WDGA bit 7 设置为 1,启用 WWDG 复位监控
T6:0 bits 6:0 7 位递减计数值,写入它完成刷新

WWDG_CR 的偏移为 0x00,复位值为 0x7F。保留位应按手册要求处理,实际代码使用 CMSIS/HAL 定义访问。

WDGA:只能由软件开启,不能由软件写 0 关闭。 启动后需要系统复位才会清零。这能防止程序误操作把已经启用的监督机制关掉。

T6:0:硬件递减、软件重新装入。 7 位可以表示 0x00~0x7F,但启动后的正常工作范围是 0x40~0x7F。为什么不是一直减到 0,后面会从 T6 的复位逻辑推出。

喂狗的本质是向 T6:0 写入新的计数值。WWDG 没有另设一个独立的重装载寄存器;例如 HAL 中的 hwwdg.Init.Counter 是软件保存的配置值,调用刷新函数时才会写回硬件。

2.7 看门狗配置寄存器 WWDG_CFR

CFR 是 Configuration Register,配置寄存器。偏移为 0x04,复位值为 0x7F。

字段 位号 配置内容
W6:0 bits 6:0 窗口比较值,用来判断是否过早刷新
WDGTB1:0 bits 8:7 预分频编码,决定递减速度
EWI bit 9 提前唤醒中断使能,第 3 节展开

把 CR 与 CFR 对照起来,就不容易混淆:CR 里的 T 是正在变化的计数值,CFR 里的 W 是用来比较的窗口阈值。 W 本身不随时间递减。

当当前 T 大于 W 时,如果软件执行刷新,就会触发过早刷新复位。W 因此决定了"要等到什么时候才可以喂",而计数速度还要由 PCLK1 和分频决定。

注意寄存器名称:窗口值在 WWDG_CFR,不是 WWDG_CR,也不是稍后介绍的状态寄存器 WWDG_SR。

知识小结

结论 :WWDG 的基本结构是 PCLK1 经两级分频驱动计数器,CPU 通过 CR 刷新、通过 CFR 配置。原因 :时钟决定计数速度,T 表示当前进度,W 提供窗口比较阈值,WDGA 决定复位监控是否启用。关键参数 :固定 /4096、P=1/2/4/8、CR 的 WDGA 与 T、CFR 的 W 与 WDGTB。常见误区:把 W 当作递减计数器、把 HAL 的 Counter 当成独立硬件重装寄存器,或误用 APB 定时器的倍频时钟。

2.8 窗口与喂狗:沿着逻辑图推导

2.8.1 先看使能:最终的与逻辑 A

在上面的结构图中,WDGA 接在最终与逻辑 A 的一路输入上,另一路来自两类异常条件的合并。

因此,复位条件可以先概括为:

text 复制代码
WWDG 复位请求 = WDGA 与 异常条件

当 WDGA 为 0 时,这条 WWDG 复位路径被关闭;当 WDGA 为 1 时,只要异常条件成立,就能输出复位请求。这里讨论的是 WWDG 路径,不影响芯片其他复位源。

需要区分"复位监控未开启"和"计数链不工作"。WWDG 具有自由运行计数特性,在外设时钟有效时,计数器可以在 WDGA 未置位时继续递减。启动位决定的是是否允许这些条件引发 WWDG 复位。

2.8.2 为什么计数值可以表示时间窗口

假设每次刷新把 T 写成 127,随后硬件定期减 1:

text 复制代码
127 → 126 → 125 → ...... → 96 → 95 → ...... → 64 → 63

经过的时间越长,T 越小。如果一次递减耗时固定,那么"已经递减了多少次"就能换算成"已经过去了多久"。窗口比较因此可以用数值范围表达。

对本例 W=96=0x60:

当前 T 从时间上理解 刷新结果
0x7F~0x61 还太早 已启动时写 CR 会触发复位
0x60~0x40 进入有效计数窗口 按比较逻辑可接受有效刷新
0x40 → 0x3F 超过允许期限 无需软件再写,硬件就触发复位

数值区间按从小到大写为 [0x40, W],实际计数过程却是从 W 向 0x40 递减。不要把数值大小的方向与时间推移方向混在一起。

按结构图中"大于 W 才复位"的比较条件,T 等于 W 是边界;参考手册部分文字使用"低于窗口值"的说法。工程上直接选择窗口内部刷新,不依赖等号边界。T 等于 0x40 虽然尚未超时,但已接近最后期限,不能理解为仍有充分执行时间。

2.8.3 过晚喂狗:为什么到 0x3F 就复位

先看 T6,也就是 7 位计数值的最高位:

text 复制代码
0x40 = 100 0000₂,T6 = 1
0x3F = 011 1111₂,T6 = 0

从 0x40 减到 0x3F 时,T6 从 1 变成 0。沿复位逻辑往后走:

  1. T6=0,经过取反后得到 1。
  2. 这个 1 送到或逻辑,不管早喂路径是什么值,或逻辑输出都为 1。
  3. WDGA 已经为 1,最终与逻辑 A 的两路输入均为 1。
  4. 输出复位请求,MCU 重新启动。

#mermaid-svg-SIs1cSR5ffHFLGGu{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-SIs1cSR5ffHFLGGu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SIs1cSR5ffHFLGGu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SIs1cSR5ffHFLGGu .error-icon{fill:#552222;}#mermaid-svg-SIs1cSR5ffHFLGGu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SIs1cSR5ffHFLGGu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SIs1cSR5ffHFLGGu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SIs1cSR5ffHFLGGu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SIs1cSR5ffHFLGGu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SIs1cSR5ffHFLGGu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SIs1cSR5ffHFLGGu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SIs1cSR5ffHFLGGu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SIs1cSR5ffHFLGGu .marker.cross{stroke:#333333;}#mermaid-svg-SIs1cSR5ffHFLGGu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SIs1cSR5ffHFLGGu p{margin:0;}#mermaid-svg-SIs1cSR5ffHFLGGu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-SIs1cSR5ffHFLGGu .cluster-label text{fill:#333;}#mermaid-svg-SIs1cSR5ffHFLGGu .cluster-label span{color:#333;}#mermaid-svg-SIs1cSR5ffHFLGGu .cluster-label span p{background-color:transparent;}#mermaid-svg-SIs1cSR5ffHFLGGu .label text,#mermaid-svg-SIs1cSR5ffHFLGGu span{fill:#333;color:#333;}#mermaid-svg-SIs1cSR5ffHFLGGu .node rect,#mermaid-svg-SIs1cSR5ffHFLGGu .node circle,#mermaid-svg-SIs1cSR5ffHFLGGu .node ellipse,#mermaid-svg-SIs1cSR5ffHFLGGu .node polygon,#mermaid-svg-SIs1cSR5ffHFLGGu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-SIs1cSR5ffHFLGGu .rough-node .label text,#mermaid-svg-SIs1cSR5ffHFLGGu .node .label text,#mermaid-svg-SIs1cSR5ffHFLGGu .image-shape .label,#mermaid-svg-SIs1cSR5ffHFLGGu .icon-shape .label{text-anchor:middle;}#mermaid-svg-SIs1cSR5ffHFLGGu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-SIs1cSR5ffHFLGGu .rough-node .label,#mermaid-svg-SIs1cSR5ffHFLGGu .node .label,#mermaid-svg-SIs1cSR5ffHFLGGu .image-shape .label,#mermaid-svg-SIs1cSR5ffHFLGGu .icon-shape .label{text-align:center;}#mermaid-svg-SIs1cSR5ffHFLGGu .node.clickable{cursor:pointer;}#mermaid-svg-SIs1cSR5ffHFLGGu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-SIs1cSR5ffHFLGGu .arrowheadPath{fill:#333333;}#mermaid-svg-SIs1cSR5ffHFLGGu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-SIs1cSR5ffHFLGGu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-SIs1cSR5ffHFLGGu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SIs1cSR5ffHFLGGu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-SIs1cSR5ffHFLGGu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SIs1cSR5ffHFLGGu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-SIs1cSR5ffHFLGGu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-SIs1cSR5ffHFLGGu .cluster text{fill:#333;}#mermaid-svg-SIs1cSR5ffHFLGGu .cluster span{color:#333;}#mermaid-svg-SIs1cSR5ffHFLGGu 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-SIs1cSR5ffHFLGGu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-SIs1cSR5ffHFLGGu rect.text{fill:none;stroke-width:0;}#mermaid-svg-SIs1cSR5ffHFLGGu .icon-shape,#mermaid-svg-SIs1cSR5ffHFLGGu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-SIs1cSR5ffHFLGGu .icon-shape p,#mermaid-svg-SIs1cSR5ffHFLGGu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-SIs1cSR5ffHFLGGu .icon-shape .label rect,#mermaid-svg-SIs1cSR5ffHFLGGu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-SIs1cSR5ffHFLGGu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-SIs1cSR5ffHFLGGu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-SIs1cSR5ffHFLGGu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 计数从 0x40 减到 0x3F
T6 变为 0
取反得到 1
或逻辑输出 1
WDGA 为 1
与逻辑 A 输出 1
系统复位

所以 WWDG 的超时点是 0x40 到 0x3F 的跳变,不是减到 0。

"过晚喂狗"是对时间条件的描述,不一定意味着真的执行了刷新语句。更常见的情况是软件还在等待,硬件就已经复位,后面的刷新代码根本执行不到。

2.8.4 过早喂狗:为什么只是 T 大于 W 还不够

再看比较器与写入动作组成的与逻辑 B。

比较器检查当前 T 与 W 的大小。当 T > W 时,比较结果为 1,表示时间还太早。但要把早喂路径送到复位输出,还需要另一个条件:软件正在写入 WWDG_CR。

text 复制代码
过早刷新条件 = 当前 T 大于 W,并且发生 CR 写入

因此,同样处于 T > W 的区间:不刷新,硬件只是继续递减;此时刷新,才会沿 B → 或逻辑 → A 触发复位。
#mermaid-svg-qM0BaUheg980X32q{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-qM0BaUheg980X32q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qM0BaUheg980X32q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qM0BaUheg980X32q .error-icon{fill:#552222;}#mermaid-svg-qM0BaUheg980X32q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qM0BaUheg980X32q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qM0BaUheg980X32q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qM0BaUheg980X32q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qM0BaUheg980X32q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qM0BaUheg980X32q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qM0BaUheg980X32q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qM0BaUheg980X32q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qM0BaUheg980X32q .marker.cross{stroke:#333333;}#mermaid-svg-qM0BaUheg980X32q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qM0BaUheg980X32q p{margin:0;}#mermaid-svg-qM0BaUheg980X32q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qM0BaUheg980X32q .cluster-label text{fill:#333;}#mermaid-svg-qM0BaUheg980X32q .cluster-label span{color:#333;}#mermaid-svg-qM0BaUheg980X32q .cluster-label span p{background-color:transparent;}#mermaid-svg-qM0BaUheg980X32q .label text,#mermaid-svg-qM0BaUheg980X32q span{fill:#333;color:#333;}#mermaid-svg-qM0BaUheg980X32q .node rect,#mermaid-svg-qM0BaUheg980X32q .node circle,#mermaid-svg-qM0BaUheg980X32q .node ellipse,#mermaid-svg-qM0BaUheg980X32q .node polygon,#mermaid-svg-qM0BaUheg980X32q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qM0BaUheg980X32q .rough-node .label text,#mermaid-svg-qM0BaUheg980X32q .node .label text,#mermaid-svg-qM0BaUheg980X32q .image-shape .label,#mermaid-svg-qM0BaUheg980X32q .icon-shape .label{text-anchor:middle;}#mermaid-svg-qM0BaUheg980X32q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qM0BaUheg980X32q .rough-node .label,#mermaid-svg-qM0BaUheg980X32q .node .label,#mermaid-svg-qM0BaUheg980X32q .image-shape .label,#mermaid-svg-qM0BaUheg980X32q .icon-shape .label{text-align:center;}#mermaid-svg-qM0BaUheg980X32q .node.clickable{cursor:pointer;}#mermaid-svg-qM0BaUheg980X32q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qM0BaUheg980X32q .arrowheadPath{fill:#333333;}#mermaid-svg-qM0BaUheg980X32q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qM0BaUheg980X32q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qM0BaUheg980X32q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qM0BaUheg980X32q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qM0BaUheg980X32q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qM0BaUheg980X32q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qM0BaUheg980X32q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qM0BaUheg980X32q .cluster text{fill:#333;}#mermaid-svg-qM0BaUheg980X32q .cluster span{color:#333;}#mermaid-svg-qM0BaUheg980X32q 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-qM0BaUheg980X32q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qM0BaUheg980X32q rect.text{fill:none;stroke-width:0;}#mermaid-svg-qM0BaUheg980X32q .icon-shape,#mermaid-svg-qM0BaUheg980X32q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qM0BaUheg980X32q .icon-shape p,#mermaid-svg-qM0BaUheg980X32q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qM0BaUheg980X32q .icon-shape .label rect,#mermaid-svg-qM0BaUheg980X32q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qM0BaUheg980X32q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qM0BaUheg980X32q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qM0BaUheg980X32q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 当前 T 大于 W
与逻辑 B
软件此时写 WWDG_CR
过早刷新条件成立
或逻辑输出 1
WDGA 已使能,触发复位

这正是"窗口"的意义:它不是不允许计数器停留在较大数值,而是不允许软件在这个阶段完成刷新。

比较器检查的是写入前正在运行的当前计数值,不是准备装入的新值。如果比较新值,正常把 T 重新写成 127 就会被误判,刷新机制也无法成立。

2.8.5 正常喂狗:让两条异常路径都不成立

当 0x40 <= T <= W 时:

  • T6=1,取反为 0,超时路径不成立。
  • T 不大于 W,比较器输出为 0,即使发生 CR 写入,过早刷新路径也不成立。
  • 或逻辑输出为 0,最终不会产生 WWDG 复位请求。
  • 软件写入新的有效计数值,计数器从新的值继续递减。

例如当前 T=83、W=96,软件把计数重新装入 127。当前值在窗口内部,所以允许刷新;装入后回到较大计数值,接下来又要等待下一次窗口开放。

新写入值也必须有效:WWDG 已启动时,如果主动把 T6 写成 0,即使写入前的 T 在窗口内,也会触发复位。正常应用使用 0x40~0x7F 的有效计数值,通常选择大于 0x40 的值留出时间。

把完整关系写在一起:

text 复制代码
复位请求 = WDGA 与 [非 T6 或 (写 CR 与 当前 T 大于 W)]

过早:当前 T > W 时执行刷新
正常:0x40 <= 当前 T <= W,并写入有效新计数值
过晚:计数继续递减,从 0x40 变为 0x3F

知识小结

结论 :WWDG 有"写得太早"和"等得太久"两条复位路径。原因 :早喂由比较器与 CR 写入共同触发,超时由 T6 清零直接触发,最后均受 WDGA 控制。关键参数 :窗口上边界 W、固定有效下边界 0x40,以及写入的新计数值。常见误区:把 T 大于 W 本身当成复位条件、以为必须执行刷新才会超时复位,或把当前计数与新装入值混为一谈。

2.9 喂狗时间计算

到这里已经知道"在哪些计数值下允许刷新",接下来要把这些计数换成毫秒。思路始终只有两步:先算每次递减耗时,再数经过多少次递减。

2.9.1 先算时钟频率

本例 PCLK1=36 MHz。先经过固定 4096 分频:

text 复制代码
36,000,000 / 4096 = 8789.0625 Hz ≈ 8.789 kHz

这里的 kHz、MHz 使用十进制前缀,分别是 1000 Hz 和 1,000,000 Hz,不按 1024 换算。Hz 表示每秒发生的周期数,频率的倒数才是一次周期所需的时间。

经过实际预分频系数 P 后:

text 复制代码
f_counter = PCLK1 / 4096 / P
2.9.2 计数递减一次要多久

把一次递减的时间记为 Δt:

text 复制代码
Δt = 1 / f_counter = 4096 × P / PCLK1             单位:秒
Δt_ms = 1000 × 4096 × P / PCLK1                 单位:毫秒

在 36 MHz 下,四种分频对应如下:

WDGTB 编码 实际系数 P 计数频率 递减一次 Δt
00 1 8789.0625 Hz 0.113778 ms
01 2 4394.53125 Hz 0.227556 ms
10 4 2197.265625 Hz 0.455111 ms
11 8 1098.6328125 Hz 0.910222 ms

注意控制这两位的是 CFR 的 bits 8:7。CR 的 bits 6:0 是计数值,两者不要写反。

2.9.3 最晚喂狗时间:先看极值,再看一般情况

这里先用"每次递减都是完整 Δt"的简化模型推导名义超时。它帮助理解计数关系,实际软件必须在超时前留足余量。

最大装入值为 0x7F=127,复位在减到 0x3F=63 时发生:

text 复制代码
最大递减次数 = 0x7F - 0x3F = 64 次
名义最大超时 = 64 × Δt

最小有效装入值是 0x40=64,到 0x3F 只需一次递减:

text 复制代码
最小递减次数 = 0x40 - 0x3F = 1 次
名义最小超时 = Δt

因此不同分频下的名义范围为:

实际 P 装入 0x40 的名义超时 装入 0x7F 的名义超时
1 0.113778 ms 7.281778 ms
2 0.227556 ms 14.563556 ms
4 0.455111 ms 29.127111 ms
8 0.910222 ms 58.254222 ms

第一行的计算是 0.113778 ms × 64 ≈ 7.28 ms,注意保留毫秒单位,不能误写成 113 ms × 64。

再看一般情况。把每次写回的计数值记为 T_reload,它与运行时不断变化的 T 不同:

text 复制代码
t_reset = (T_reload - 0x3F) × Δt

例如 T_reload=100,经过 100-63=37 次递减后到达复位点。

为什么公式里有时会看到低 6 位加 1?因为有效计数值的 T6 为 1:

text 复制代码
T_reload = 0x40 + T[5:0]
T_reload - 0x3F = T[5:0] + 1

这是同一个计数关系的两种写法,不代表计数器只有 6 位。

2.9.4 最早喂狗时间:从装入值减到窗口值

通常选择 T_reload >= W。刚装入时处于较大计数值,至少要递减到 W 附近,窗口才开放。需要的递减次数为:

text 复制代码
T_reload - W

于是名义最早时间是:

text 复制代码
t_open = (T_reload - W) × Δt

W 存在配置寄存器 CFR 中,不是状态寄存器中的数。若选择 T_reload < W,装入后已经在窗口内部,最早时间按 0 理解,不能算出一个负的等待时间。

对于常用的 T_reload >= W,名义窗口持续时间为:

text 复制代码
t_window = t_reset - t_open
         = (W - 0x3F) × Δt

为什么不是 (W-0x40)×Δt?因为计数到 0x40 时仍有最后一个计数状态,下一次减到 0x3F 才复位,不能把这一段漏掉。

2.9.5 把公式连起来,并区分理论边界与实际刷新
要计算什么 公式 对应的硬件过程
递减一次耗时 Δt=4096×P/PCLK1 计数器减 1
名义开放时间 max(T_reload-W, 0)×Δt 从装入值减到 W
到达 0x40 的名义时间 (T_reload-0x40)×Δt 到达最后有效计数状态,也是 EWI 阈值
名义超时上界 (T_reload-0x3F)×Δt 再减一次到 0x3F,触发复位
名义窗口长度 (W-0x3F)×Δt 从 W 状态持续到复位前,前提为 T_reload 不小于 W

实际还要考虑分频器相位。这个计数器是自由运行的,写 CR 重新装入计数值,并不意味着分频链也从零开始。设写入后距离第一次递减还剩 δ,满足 0 < δ <= Δt,对于 T_reload > W 可按计数过程推得:

text 复制代码
到达 W:    δ + (T_reload - W - 1) × Δt
到达 0x40: δ + (T_reload - 0x40 - 1) × Δt
发生复位: δ + (T_reload - 0x40) × Δt

这个补充说明的是一个计数周期内的相位差异,不是本次测得的波形。主线仍是数递减次数,但工程上不能把名义超时上界当作恰好可以执行刷新语句的期限。相关自由运行和预分频相位说明可参见 RM0008 的 WWDG 功能描述。

此外,HAL_Delay() 的 tick 量化、业务耗时、阻塞日志和中断抢占都会改变实际刷新间隔。正确做法是在窗口中间选时间点,再按最坏执行时间检查两侧余量。第 4 节会用 127、96、8、36 MHz 这一组参数完成计算和实验。

知识小结

结论 :时间计算应从一次递减周期出发,再乘以到目标状态的递减次数。原因 :W、0x40 和 0x3F 分别对应窗口开放、最后有效状态和复位,三者不能互换。关键参数 :PCLK1、实际分频 P、T_reload、W,以及公式中的 0x3F。常见误区:把到达 0x40 当成复位、漏掉最后一个周期,或把名义边界当作无余量的软件截止时间。

3. WWDG 与中断

3.1 从 CFR 的 EWI 位认识提前唤醒

在 CFR 中还留着一个没有展开的 bit 9:EWI,Early Wakeup Interrupt,提前唤醒中断使能位。

当它被置位且 WWDG 已启动时,计数到 0x40 可以提出中断请求。这个时刻还没有达到超时复位点,但只差下一次递减。

text 复制代码
...... → 0x41 → 0x40 → 0x3F
                 │       │
                 EWI     复位

"提前唤醒"在这里表示超时前的预警,不代表收到中断后自动延长计时,也不意味着 CPU 一定有时间完成任意操作。

EWI 使能位置 1 后,不能再依靠软件清零来关闭该外设使能,需要复位。它与下面的事件标志 EWIF 是两个不同的位。

3.2 状态寄存器 WWDG_SR:EWIF 记录事件

最后再认识状态寄存器 SR(Status Register),偏移为 0x08,复位值为 0x00。本主题只需关心 bit 0:EWIF。

名称 所在位置 表达什么
EWI CFR bit 9 是否允许提前唤醒中断请求
EWIF SR bit 0 计数到达 0x40 的事件标志

硬件到达 0x40 后设置 EWIF,软件可以写 0 清除。清标志只是确认处理了事件,不会把 T 重新写成 127,也不会阻止下一次递减发生。

从到达 0x40 算起,到复位大约只有一个 Δt。实际进入中断回调时,还要扣除中断响应和分发耗时。因此适合执行短小、耗时可控的动作,例如改变一个观察引脚或执行一个已证明能及时完成的输出动作;不能把耗时未知的数据保存操作默认放进去。

3.3 EWI 如何与 NVIC 联动

外设提出中断请求之后,还需要经过 NVIC,才能由 CPU 执行中断函数。
#mermaid-svg-wI4Zg7hanaKvf8Fi{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-wI4Zg7hanaKvf8Fi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wI4Zg7hanaKvf8Fi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wI4Zg7hanaKvf8Fi .error-icon{fill:#552222;}#mermaid-svg-wI4Zg7hanaKvf8Fi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wI4Zg7hanaKvf8Fi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wI4Zg7hanaKvf8Fi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wI4Zg7hanaKvf8Fi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wI4Zg7hanaKvf8Fi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wI4Zg7hanaKvf8Fi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wI4Zg7hanaKvf8Fi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wI4Zg7hanaKvf8Fi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wI4Zg7hanaKvf8Fi .marker.cross{stroke:#333333;}#mermaid-svg-wI4Zg7hanaKvf8Fi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wI4Zg7hanaKvf8Fi p{margin:0;}#mermaid-svg-wI4Zg7hanaKvf8Fi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wI4Zg7hanaKvf8Fi .cluster-label text{fill:#333;}#mermaid-svg-wI4Zg7hanaKvf8Fi .cluster-label span{color:#333;}#mermaid-svg-wI4Zg7hanaKvf8Fi .cluster-label span p{background-color:transparent;}#mermaid-svg-wI4Zg7hanaKvf8Fi .label text,#mermaid-svg-wI4Zg7hanaKvf8Fi span{fill:#333;color:#333;}#mermaid-svg-wI4Zg7hanaKvf8Fi .node rect,#mermaid-svg-wI4Zg7hanaKvf8Fi .node circle,#mermaid-svg-wI4Zg7hanaKvf8Fi .node ellipse,#mermaid-svg-wI4Zg7hanaKvf8Fi .node polygon,#mermaid-svg-wI4Zg7hanaKvf8Fi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wI4Zg7hanaKvf8Fi .rough-node .label text,#mermaid-svg-wI4Zg7hanaKvf8Fi .node .label text,#mermaid-svg-wI4Zg7hanaKvf8Fi .image-shape .label,#mermaid-svg-wI4Zg7hanaKvf8Fi .icon-shape .label{text-anchor:middle;}#mermaid-svg-wI4Zg7hanaKvf8Fi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wI4Zg7hanaKvf8Fi .rough-node .label,#mermaid-svg-wI4Zg7hanaKvf8Fi .node .label,#mermaid-svg-wI4Zg7hanaKvf8Fi .image-shape .label,#mermaid-svg-wI4Zg7hanaKvf8Fi .icon-shape .label{text-align:center;}#mermaid-svg-wI4Zg7hanaKvf8Fi .node.clickable{cursor:pointer;}#mermaid-svg-wI4Zg7hanaKvf8Fi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wI4Zg7hanaKvf8Fi .arrowheadPath{fill:#333333;}#mermaid-svg-wI4Zg7hanaKvf8Fi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wI4Zg7hanaKvf8Fi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wI4Zg7hanaKvf8Fi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wI4Zg7hanaKvf8Fi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wI4Zg7hanaKvf8Fi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wI4Zg7hanaKvf8Fi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wI4Zg7hanaKvf8Fi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wI4Zg7hanaKvf8Fi .cluster text{fill:#333;}#mermaid-svg-wI4Zg7hanaKvf8Fi .cluster span{color:#333;}#mermaid-svg-wI4Zg7hanaKvf8Fi 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-wI4Zg7hanaKvf8Fi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wI4Zg7hanaKvf8Fi rect.text{fill:none;stroke-width:0;}#mermaid-svg-wI4Zg7hanaKvf8Fi .icon-shape,#mermaid-svg-wI4Zg7hanaKvf8Fi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wI4Zg7hanaKvf8Fi .icon-shape p,#mermaid-svg-wI4Zg7hanaKvf8Fi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wI4Zg7hanaKvf8Fi .icon-shape .label rect,#mermaid-svg-wI4Zg7hanaKvf8Fi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wI4Zg7hanaKvf8Fi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wI4Zg7hanaKvf8Fi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wI4Zg7hanaKvf8Fi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
是
否
否
计数到达 0x40,设置 EWIF
外设 EWI 使能了吗
向 NVIC 提出 WWDG 请求
NVIC 已使能且当前允许响应吗
进入 WWDG_IRQHandler
无论是否进入中断,计数继续
未刷新则下一次递减触发复位

STM32F103 的中断枚举中,WWDG_IRQn = 0 表示 WWDG 是外部中断编号 0。这不是优先级,也不是说它占用了整个向量表的第 0 项;Cortex-M3 的外部中断向量排列在前 16 个系统向量之后。

要让 EWI 回调正常执行,需要同时满足:外设 EWI 开启、NVIC 使能、中断入口正确、CPU 没有被相关屏蔽或更高优先级处理长期阻塞。即使中断无法执行,WWDG 的硬件超时复位仍可以发生。

实际配置与回调代码放到下一节,与 60 ms 超时实验一起观察。

知识小结

结论 :EWI 在计数到 0x40 时预警,SR 中的 EWIF 记录事件,NVIC 决定 CPU 是否接收请求。原因 :外设事件、外设使能、CPU 中断接收是不同环节,进入中断也不会暂停计数。关键参数 :CFR.EWI、SR.EWIF、WWDG_IRQn,以及从 0x40 到 0x3F 的一个递减周期。常见误区:只配置 NVIC 或只开启 EWI、把 IRQ 编号当优先级、把清 EWIF 当成刷新。

4. 案例:配置、代码与实验现象

4.1 参数预设与计算

4.1.1 实验平台与观察条件

这一节把前面的公式落到一组具体参数上。实验仍围绕三个目标展开:验证正常喂狗、验证过早与超时复位、观察超时前的 EWI。

项目 本文条件 需要确认的地方
MCU STM32F103ZET6,LQFP144 切换型号时重新核对引脚与器件宏
HSE 配置示例为外部 8 MHz 晶体 必须核对实物晶体或板卡原理图
系统时钟 HSE 8 MHz,经 PLL ×9 得到 72 MHz HSE_VALUE 必须与实物一致
APB1 HCLK / 2,36 MHz 不能误用 APB1 定时器的 72 MHz
下载调试 ST-Link,经 SWD 电源、GND、SWDIO、SWCLK;需要时连接 NRST
日志串口 本文补充配置:USART1,115200、8N1,无流控 默认映射 PA9 TX、PA10 RX,避免重映射
EWI 观察点 PF8,推挽输出,初始高,EWI 时拉低 板载 LED 接法和有效电平需自行确认

WWDG 自身没有外部接线。PF8 只是观察输出,并非 WWDG 的硬件输出脚。

使用 USB 转串口时,接 PA9 → 转串口 RX,双方共地。本文只发送日志,PA10 可以不接;需要接收时再接 转串口 TX → PA10。使用与 MCU 电平匹配的 TTL 串口设备。

PF8 可以直接接示波器或逻辑分析仪观察。若使用板载 LED,先核对其是否低电平点亮、是否已有限流电阻;原始截图不足以确认电阻阻值和整个 LED 原理图,不把这些硬件条件假定为所有开发板通用。

4.1.2 先把窗口算清楚

这组参数的含义如下:

参数 预设值 对应位置与作用
PCLK1 36 MHz WWDG 计数时钟的来源
实际预分频 P 8 CFR.WDGTB 编码为 11
窗口 W 96,即 0x60 CFR.W6:0,控制窗口开放时机
装入计数 T_reload 127,即 0x7F 每次向 CR 写回的有效计数值

逐项计算:

text 复制代码
递减一次:
Δt = 4096 × 8 / 36,000,000 × 1000
   ≈ 0.910222 ms

从装入值 127 减到窗口值 96:
t_open = (127 - 96) × Δt
       ≈ 28.216889 ms

从 127 减到 64,到达 EWI 阈值:
t_EWI = (127 - 64) × Δt
      = 57.344000 ms

从 127 减到 63,超时复位:
t_reset = (127 - 63) × Δt
        ≈ 58.254222 ms

名义有效窗口长度:
t_window = (96 - 63) × Δt
         ≈ 30.037333 ms

W=96 时,96、95、......、64 一共有 33 个有效计数状态,所以窗口长度按 33 个 Δt 计算,不是只算 96-64=32。

根据前面补充的分频相位,窗口开放约在 27.307~28.217 ms,超时约在 57.344~58.254 ms;这些是计数模型推导的范围,仍需考虑实际时钟误差与软件开销。实际刷新不依赖边界,选择中间的 40 ms。

4.1.3 选择三组延时
请求延时 预期结果 对应的原理
15 ms 调用刷新时因过早而复位 当前计数仍大于 96,写 CR 触发早喂路径
40 ms 正常刷新,持续运行 当前计数进入窗口内部
60 ms 延时尚未结束就复位 硬件已从 0x40 减到 0x3F,执行不到刷新

HAL_Delay(40) 表示请求延时 40 个毫秒单位,不保证恰好在 40.000 ms 完成 CR 写入。实际周期还包括调用前后的工作和中断耗时。

特别注意阻塞串口。115200 波特率、8N1 时每字节通常占 10 bit,发送 100 字节约需:

text 复制代码
100 × 10 / 115200 ≈ 8.68 ms

如果延时前后都打印,实际刷新间隔就不再只是 15、40 或 60 ms,而是延时、打印和其他工作的总和。后面的完整示例在 WWDG 启动前打印启动信息,运行时通过变量和 PF8 观察,保留实验余量。

4.2 配置

先在 CubeMX 建立 STM32F103ZET6 工程,开启 SWD 调试。下面按 WWDG、时钟、串口与生成工程的顺序完成配置;实际代码初始化时会把 WWDG 放在最后启动。

4.2.1 开启 WWDG

在 System Core → WWDG 勾选 Activated,填写:

CubeMX 项目 取值 对应效果
Counter clock prescaler 8 每次递减约 0.910222 ms
Window value 96 当前计数降到窗口附近才可刷新
Free-running downcounter value 127 启动和刷新时装入 0x7F
Early wakeup interrupt Enable 允许计数到 0x40 时提出中断请求
NVIC:Window watchdog interrupt Enabled 允许 WWDG 的中断请求进入 CPU

中断需要两道门都打开:外设的 CFR.EWI 决定能否提出请求,NVIC 的 WWDG_IRQn 使能决定 CPU 是否接收。只打开一处不能完成整个链路。

本文为便于观察,把 WWDG 的抢占优先级设为 0。数值 0 表示本例中的高优先级,不代表所有工程都应该这么设置。WWDG_IRQn = 0 指外部中断编号,不是优先级;在 Cortex-M3 向量表中,外部中断排在前 16 个系统向量之后。

4.2.2 配置 HSE 时钟源

在 System Core → RCC 中开启 HSE,选择 Crystal/Ceramic Resonator。本文时钟树使用外部 8 MHz 晶体;必须先核对实物晶体和原理图。

如果板上输入的是外部有源时钟,应按硬件选择 Bypass,不能沿用晶体模式。HSE_VALUE 也要与真实输入一致,本例为 8000000U。

4.2.3 设置时钟频率
  1. 在 Clock Configuration 中填写 HSE=8 MHz。
  2. PLL 输入选择 HSE,输入预分频 /1,倍频 ×9,得到 72 MHz。
  3. AHB 选择 /1,HCLK 为 72 MHz。
  4. APB1 选择 /2,PCLK1 为 36 MHz;APB2 选择 /1。
  5. 检查生成后的 HAL_RCC_GetPCLK1Freq() 计算结果应为 36000000。

HAL_RCC_GetPCLK1Freq() 按寄存器和配置常量计算,不是实测仪器。即使它返回正确数值,也仍需确保 HSE_VALUE 与实物一致。

4.2.4 配置串口与观察引脚

USART1 使用 115200、8N1、无流控,默认映射 PA9/PA10。PF8 设置为推挽输出、无上下拉、Low Speed,初始电平为 High;EWI 回调将它拉低。

PF8 的低电平是否对应 LED 点亮,取决于板卡接线。完整示例只把它视为可测量的电平变化,避免把所有板卡都假设为同一种 LED 电路。

4.2.5 生成工程并确认依赖

在生成选项中让外设初始化集中在 main.c,便于接入后文的三个应用文件;若你的工程已拆成 wwdg.c、usart.c 等文件,则将相应实现合并进去,每个全局句柄和函数只保留一份。

确认 stm32f1xx_hal_conf.h 启用了 RCC、CORTEX、GPIO、FLASH、UART、WWDG 等所需模块;保持 USE_HAL_WWDG_REGISTER_CALLBACKS=0,使用普通弱回调覆盖方式。精确 HAL 包版本未给出,导入已有工程时以本机头文件和生成代码为准,尤其要检查回调函数名称。

初始化顺序要明确 :时钟、GPIO、串口、复位标志读取和启动日志都在 WWDG 启动前完成。HAL_WWDG_Init() 会启动计数与复位监控,不能把它当成"只是填写配置、以后才开始计时"。

4.3 代码编写:先验证窗口看门狗基本效果

先使用 CubeMX 生成的句柄、初始化函数、MSP 与默认中断文件。WWDG 参数按第 4.1、4.2 节设置,其他外设初始化先完成,再调用 MX_WWDG_Init()。下面是 main() 的核心组织方式,只替换对应位置,不再叠加第二次 WWDG 初始化:

c 复制代码
/* 这里使用 CubeMX 已生成的函数、句柄与错误处理函数。 */
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();

    /* 其他必要初始化应放在这里,启动后不能再长时间阻塞。 */
    MX_WWDG_Init();

    while (1)
    {
        /* 40 ms 位于当前参数的窗口内部,给边界留出余量。 */
        HAL_Delay(40U);

        /* 刷新会把 Init.Counter 写回 CR,并不会等待窗口开放。 */
        if (HAL_WWDG_Refresh(&hwwdg) != HAL_OK)
        {
            Error_Handler();
        }
    }
}

这一段用于验证刷新机制,不代表真实业务任务已经受到了有效监督。改成 15 ms 或 60 ms 后可以观察故障;完整示例将进一步给出复位来源和 EWI 观察点。

4.3.1 三种延时对应的基本现象

先使用 40 ms,让程序自由运行。预期不会因 WWDG 复位。然后把最小示例中的延时改为 15 ms,重新构建并烧录,预期在刷新语句处触发复位;改为 60 ms,预期尚未执行到刷新语句就已超时。

如果使用"板子重新启动"的串口提示,重复打印只能说明又进入了启动流程。为了确认来源,完整示例会额外读取 RCC_CSR.WWDGRSTF,避免把其他原因的重启也当成窗口看门狗现象。

4.3.2 怎样理解"喂狗前、喂狗后"的计数打印

观察计数时,语句位置本身也是实验条件。下面这种组织方式中,第一条打印发生在延时之前:

c 复制代码
/* 说明采样位置的示意片段,假设工程已完成 printf 串口重定向。 */
printf("等待前 T=%lu\r\n",
       (unsigned long)(hwwdg.Instance->CR & WWDG_CR_T));
HAL_Delay(40U);
HAL_WWDG_Refresh(&hwwdg);
printf("刷新后 T=%lu\r\n",
       (unsigned long)(hwwdg.Instance->CR & WWDG_CR_T));

因此,第一行可能看到 125、124 一类接近 127 的值,它反映的是上一轮刷新后到本次采样之间的短暂递减,并不是"已经等了 40 ms 以后"的计数。第二行也不保证恒为 127,因为计数器一直在运行。

如果目标是观察实际刷新之前的 T,应把采样放在延时之后、刷新之前,再通过变量观察或在后续受控时机输出。不要为追求日志而把阻塞打印加入边界附近,否则观测手段本身就会改变刷新时机。

4.4 EWI 中断处理

4.4.1 为什么参数设置和 NVIC 设置都要打开

参数页的 Early Wakeup Interrupt 控制 CFR.EWI,NVIC 页控制 WWDG 中断请求能否进入 CPU。对应的软件动作不同:

c 复制代码
/* 字段先保存到句柄,HAL_WWDG_Init 随后才将它写入 CFR。 */
hwwdg.Init.EWIMode = WWDG_EWI_ENABLE;

/* NVIC 负责 CPU 侧的优先级与请求使能。 */
HAL_NVIC_SetPriority(WWDG_IRQn, 0U, 0U);
HAL_NVIC_EnableIRQ(WWDG_IRQn);

两处设置分别对应外设和 CPU 接收端,缺少任一环节都可能看不到回调。

4.4.2 从中断入口追到用户回调

用户回调 HAL_WWDG_IRQHandler WWDG_IRQHandler NVIC WWDG 硬件 用户回调 HAL_WWDG_IRQHandler WWDG_IRQHandler NVIC WWDG 硬件 #mermaid-svg-jVKQ3KXJClywd6ye{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-jVKQ3KXJClywd6ye .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jVKQ3KXJClywd6ye .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jVKQ3KXJClywd6ye .error-icon{fill:#552222;}#mermaid-svg-jVKQ3KXJClywd6ye .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jVKQ3KXJClywd6ye .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jVKQ3KXJClywd6ye .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jVKQ3KXJClywd6ye .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jVKQ3KXJClywd6ye .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jVKQ3KXJClywd6ye .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jVKQ3KXJClywd6ye .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jVKQ3KXJClywd6ye .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jVKQ3KXJClywd6ye .marker.cross{stroke:#333333;}#mermaid-svg-jVKQ3KXJClywd6ye svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jVKQ3KXJClywd6ye p{margin:0;}#mermaid-svg-jVKQ3KXJClywd6ye .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jVKQ3KXJClywd6ye text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-jVKQ3KXJClywd6ye .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-jVKQ3KXJClywd6ye .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-jVKQ3KXJClywd6ye .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-jVKQ3KXJClywd6ye .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-jVKQ3KXJClywd6ye #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-jVKQ3KXJClywd6ye .sequenceNumber{fill:white;}#mermaid-svg-jVKQ3KXJClywd6ye #sequencenumber{fill:#333;}#mermaid-svg-jVKQ3KXJClywd6ye #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-jVKQ3KXJClywd6ye .messageText{fill:#333;stroke:none;}#mermaid-svg-jVKQ3KXJClywd6ye .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jVKQ3KXJClywd6ye .labelText,#mermaid-svg-jVKQ3KXJClywd6ye .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-jVKQ3KXJClywd6ye .loopText,#mermaid-svg-jVKQ3KXJClywd6ye .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-jVKQ3KXJClywd6ye .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-jVKQ3KXJClywd6ye .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-jVKQ3KXJClywd6ye .noteText,#mermaid-svg-jVKQ3KXJClywd6ye .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-jVKQ3KXJClywd6ye .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jVKQ3KXJClywd6ye .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jVKQ3KXJClywd6ye .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-jVKQ3KXJClywd6ye .actorPopupMenu{position:absolute;}#mermaid-svg-jVKQ3KXJClywd6ye .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-jVKQ3KXJClywd6ye .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-jVKQ3KXJClywd6ye .actor-man circle,#mermaid-svg-jVKQ3KXJClywd6ye line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-jVKQ3KXJClywd6ye :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} T 从 0x41 减到 0x40,设置 EWIF EWI 使能时提出请求 若未被屏蔽且满足优先级条件 传入 hwwdg 检查来源、清除 EWIF 调用 EarlyWakeupCallback 拉低 PF8,立即返回 计数继续运行 未刷新则 0x40 减到 0x3F,复位

从硬件到达 0x40 算起,到下一次递减约剩一个 Δt,本例约为 0.910222 ms。CPU 真正进入回调时,可用时间已经被中断响应、抢占和 HAL 分发消耗了一部分。

因此回调只做一个 GPIO 写入。不要在这里 HAL_Delay()、阻塞打印或执行不能保证耗时的 Flash 擦写。以 115200、8N1 为例,哪怕只发送 10 字节也要约 0.868 ms,几乎耗尽整个理论预算。

清除 EWIF 只确认了事件,不会重新装载计数器,也不会取消即将发生的复位。反过来,如果在 EWI 中无条件刷新,主循环即使已经死锁,中断仍可能不断喂狗,使监督失效。确有恢复设计时,应单独论证健康条件与时间预算,不能把它作为默认写法。

HAL 的默认回调使用 __weak 声明。需要实现功能时,在用户源文件中定义同名、同参数的非 weak 函数,由链接器选择用户实现,不必直接修改 HAL 驱动文件。

例如最小的观察逻辑是:

c 复制代码
/* 用于讲解的回调实现;与完整代码二选一,不要重复定义。 */
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *handle)
{
    if (handle == &hwwdg)
    {
        /* PF8 初始为高,在回调里拉低,便于观察短暂电平变化。 */
        HAL_GPIO_WritePin(GPIOF, GPIO_PIN_8, GPIO_PIN_RESET);
    }
}
4.4.3 为什么使用 60 ms 观察 EWI

40 ms 模式下,在到达 0x40 之前已经刷新,通常不会触发 EWI。15 ms 模式是在过早刷新时立即走复位路径,也不会等到递减到 0x40。

60 ms 模式故意不及时刷新,才会自然经历"计数到 0x40 → EWI → 再减一次 → 复位"。如果板载 LED 低电平有效,回调可能使它短暂点亮;随后复位与重新初始化改变引脚状态,循环重复。

这一现象不保证一定能用肉眼看清。低电平脉冲短、复位期间 GPIO 状态与外部接线都会影响观感,建议结合示波器或逻辑分析仪验证。

4.5 源码阅读:把配置字段对应回寄存器

4.5.1 初始化结构体的四个字段

下面按作用重新标注与本文相关的字段:

c 复制代码
typedef struct
{
    uint32_t Prescaler; /* 使用 HAL 分频宏,最终写 CFR.WDGTB。 */
    uint32_t Window;    /* 窗口阈值,最终写 CFR.W[6:0]。 */
    uint32_t Counter;   /* 软件保存的装入值,启动及刷新时写 CR。 */
    uint32_t EWIMode;   /* 是否开启提前唤醒,最终对应 CFR.EWI。 */
} WWDG_InitTypeDef;

本例分别填 WWDG_PRESCALER_8、0x60、0x7F 和 WWDG_EWI_ENABLE。Window 与 Counter 在所用 HAL 接口中的有效范围都是 0x40~0x7F,但作用完全不同:Window 是比较阈值,Counter 是装入计数值。

4.5.2 初始化、刷新与中断接口
c 复制代码
HAL_StatusTypeDef HAL_WWDG_Init(WWDG_HandleTypeDef *hwwdg);
HAL_StatusTypeDef HAL_WWDG_Refresh(WWDG_HandleTypeDef *hwwdg);
void HAL_WWDG_IRQHandler(WWDG_HandleTypeDef *hwwdg);
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg);
接口 参数与返回值 实际职责
HAL_WWDG_Init 传入指向有效句柄的指针;返回 HAL 状态 调用 MSP,写启动计数值和配置;函数返回时 WWDG 已运行
HAL_WWDG_Refresh 传入句柄;本例核对的实现写完后返回 HAL_OK 把 Init.Counter 写入 CR,不判断实际时间窗口
HAL_WWDG_IRQHandler 传入相同句柄;无返回值 检查 EWI 使能与 EWIF,清标志并调用回调
HAL_WWDG_EarlyWakeupCallback HAL 传入触发中断的句柄;无返回值 用户实现的短小处理逻辑

句柄中 Instance=WWDG 指向寄存器,Init 保存 Prescaler、Window、Counter 和 EWIMode。分频字段应填 WWDG_PRESCALER_8 这种编码常量,不能把裸数字 8 当作同一件事写入。

刷新函数的核心可以用下面的等价逻辑理解:

c 复制代码
/* 说明用的核心语句,不要在工程里重新定义 HAL 库函数。 */
hwwdg->Instance->CR = hwwdg->Init.Counter;
/* 硬件判断写入时机;HAL 函数本身不替你验证窗口。 */

初始化时 HAL 会设置 WDGA;刷新时即使写入值没有置 WDGA,也不会关闭已启动的 WWDG,因为该位不能被软件清零。因此看到刷新源码只写 Counter,并不意味着它把看门狗关闭了。

HAL_OK 只能说明这个函数按其实现走到了返回语句。错误时机的写入由硬件复位处理,不能期望 HAL 返回 HAL_ERROR 再让应用补救。字段与函数实现可参照 ST 官方 HAL 头文件 和 WWDG 驱动源码。

4.5.3 初始化过程为什么意味着已经开始计时

对本文核对的 HAL 实现,普通回调模式下的主干可以简化为:

text 复制代码
检查句柄与配置参数
    → HAL_WWDG_MspInit:开时钟、配置 NVIC
    → CR 写入 WDGA 与 Counter:启动复位监控并装入计数
    → CFR 写入 EWIMode、Prescaler、Window
    → 返回 HAL_OK

这是一段用于理解的简化流程,省略可选回调注册分支,不作为跨版本不变的写入顺序承诺。移植时应检查本机 HAL 文件。

关键结论是:HAL_WWDG_Init() 本身就会启动 WWDG,不能在返回后再执行一大段不计入预算的初始化。对于先写 CR、再写 CFR 的实现,也不要在两次寄存器操作之间设置长时间断点。

4.6 完整实验工程的应用代码

下面给出 main.c、stm32f1xx_hal_msp.c 与 stm32f1xx_it.c 三个应用文件的实现,代码全部嵌在本篇 Markdown 中。它们依赖第 4.2 节生成的 CubeF1 工程,继续使用厂商提供的 HAL/CMSIS、system_stm32f1xx.c、启动文件和链接配置,不是脱离这些依赖的单文件裸机程序。

接入前确认:

  • 器件宏为 STM32F103xE,启动文件与 STM32F103 高密度器件一致。
  • HSE_VALUE=8000000U;链接配置按 ZET6 的实际容量生成,不手工套用其他型号。
  • 工程包含所需 HAL 源文件,WWDG 与 UART 模块已启用,使用默认 SysTick HAL 时基。
  • 以下实现各保留一份。若仍编译原有 wwdg.c、usart.c 或原来的中断/MSP 定义,应先合并,不能让同一函数、句柄重复定义。

实验模式只修改 TEST_MODE:0 为正常,1 为过早,2 为超时。代码不在 EWI 里刷新。

文件一:main.c
c 复制代码
#include "stm32f1xx_hal.h"
#include <stdint.h>
#include <string.h>

#define MODE_NORMAL  0U
#define MODE_EARLY   1U
#define MODE_LATE    2U
#define TEST_MODE    MODE_NORMAL

#if TEST_MODE == MODE_NORMAL
#define FEED_DELAY_MS 40U
#elif TEST_MODE == MODE_EARLY
#define FEED_DELAY_MS 15U
#elif TEST_MODE == MODE_LATE
#define FEED_DELAY_MS 60U
#else
#error "TEST_MODE must be 0, 1 or 2"
#endif

UART_HandleTypeDef huart1 = {0};
WWDG_HandleTypeDef hwwdg = {0};

/* 供观察使用。普通 RAM 变量复位后重新初始化,不承担跨复位存储。 */
volatile uint32_t g_boot_csr;
volatile uint32_t g_last_counter;
volatile uint32_t g_after_counter;
volatile uint32_t g_refresh_count;
volatile uint32_t g_ewi_seen;

static void SystemClock_Config(void);
static void GPIO_Init(void);
static void USART1_Init(void);
static void WWDG_Start(void);
static void BootLog(const char *text);
void Error_Handler(void);

int main(void)
{
    /* 尽早保存所有复位标志,避免后续清标志丢失来源信息。 */
    g_boot_csr = RCC->CSR;

    if (HAL_Init() != HAL_OK)
    {
        Error_Handler();
    }
    SystemClock_Config();
    GPIO_Init();
    USART1_Init();

    /* WWDG 尚未启动,启动日志的发送耗时不会挤占刷新窗口。 */
    if ((g_boot_csr & RCC_CSR_WWDGRSTF) != 0U)
    {
        BootLog("BOOT WWDG=1\r\n");
    }
    else
    {
        BootLog("BOOT WWDG=0\r\n");
    }

#if TEST_MODE == MODE_NORMAL
    BootLog("MODE=NORMAL delay=40ms\r\n");
#elif TEST_MODE == MODE_EARLY
    BootLog("MODE=EARLY delay=15ms\r\n");
#else
    BootLog("MODE=LATE delay=60ms\r\n");
#endif

    /* 启动前检查软件计算的 PCLK1;实物晶体还需单独核对。 */
    if (HAL_RCC_GetPCLK1Freq() != 36000000U)
    {
        BootLog("ERROR PCLK1\r\n");
        Error_Handler();
    }

    /* 清除历史复位标志,让下一次启动的标志有新的观察基准。 */
    __HAL_RCC_CLEAR_RESET_FLAGS();

    WWDG_Start();

    while (1)
    {
        /* 实验只模拟任务耗时,不在循环内进行阻塞打印。 */
        HAL_Delay(FEED_DELAY_MS);

        /* 这里采样的才是本轮刷新前的计数,不是延时前的计数。 */
        g_last_counter = WWDG->CR & WWDG_CR_T;

        /* 故障模式故意保留直接刷新,以显示硬件的早喂保护。 */
        if (HAL_WWDG_Refresh(&hwwdg) != HAL_OK)
        {
            Error_Handler();
        }

        g_after_counter = WWDG->CR & WWDG_CR_T;
        ++g_refresh_count;
    }
}

static void SystemClock_Config(void)
{
    RCC_OscInitTypeDef osc = {0};
    RCC_ClkInitTypeDef clk = {0};

    /* 此配置仅适用于实物 HSE 为 8 MHz 晶体的板卡。 */
    osc.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    osc.HSEState = RCC_HSE_ON;
    osc.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    osc.PLL.PLLState = RCC_PLL_ON;
    osc.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    osc.PLL.PLLMUL = RCC_PLL_MUL9;
    if (HAL_RCC_OscConfig(&osc) != HAL_OK)
    {
        Error_Handler();
    }

    clk.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
                    RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
    clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    clk.AHBCLKDivider = RCC_SYSCLK_DIV1;
    clk.APB1CLKDivider = RCC_HCLK_DIV2;  /* WWDG 使用这里的 36 MHz。 */
    clk.APB2CLKDivider = RCC_HCLK_DIV1;

    /* 72 MHz 下设置匹配的 Flash 等待周期,不使用低频配置。 */
    if (HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_2) != HAL_OK)
    {
        Error_Handler();
    }
}

static void GPIO_Init(void)
{
    GPIO_InitTypeDef gpio = {0};

    __HAL_RCC_GPIOF_CLK_ENABLE();

    /* 先预置高电平,再切换为输出,减少初始化时的低电平毛刺。 */
    HAL_GPIO_WritePin(GPIOF, GPIO_PIN_8, GPIO_PIN_SET);
    gpio.Pin = GPIO_PIN_8;
    gpio.Mode = GPIO_MODE_OUTPUT_PP;
    gpio.Pull = GPIO_NOPULL;
    gpio.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOF, &gpio);
}

static void USART1_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200U;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    if (HAL_UART_Init(&huart1) != HAL_OK)
    {
        Error_Handler();
    }
}

static void BootLog(const char *text)
{
    /* 仅发送很短的固定字符串,不依赖 printf 重定向。 */
    if (HAL_UART_Transmit(&huart1, (uint8_t *)text,
                          (uint16_t)strlen(text), 20U) != HAL_OK)
    {
        Error_Handler();
    }
}

static void WWDG_Start(void)
{
    hwwdg.Instance = WWDG;
    hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
    hwwdg.Init.Window = 0x60U;
    hwwdg.Init.Counter = 0x7FU;
    hwwdg.Init.EWIMode = WWDG_EWI_ENABLE;

    /* 此调用会启动 WWDG,因此它后面立即进入实验循环。 */
    if (HAL_WWDG_Init(&hwwdg) != HAL_OK)
    {
        Error_Handler();
    }
}

void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *handle)
{
    if (handle == &hwwdg)
    {
        /* 直接拉低观察点;不打印、不延时、不在这里刷新。 */
        GPIOF->BRR = GPIO_PIN_8;
        g_ewi_seen = 1U;
    }
}

void Error_Handler(void)
{
    /* 初始化失败时停在这里;若 WWDG 已启动则由其超时复位。 */
    __disable_irq();
    while (1)
    {
    }
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
    /* 可在调试器中查看 file 和 line,不在这里引入长耗时日志。 */
    (void)file;
    (void)line;
    Error_Handler();
}
#endif
文件二:stm32f1xx_hal_msp.c
c 复制代码
#include "stm32f1xx_hal.h"

void HAL_MspInit(void)
{
    __HAL_RCC_AFIO_CLK_ENABLE();
    __HAL_RCC_PWR_CLK_ENABLE();
    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
}

void HAL_UART_MspInit(UART_HandleTypeDef *handle)
{
    GPIO_InitTypeDef gpio = {0};

    if (handle->Instance == USART1)
    {
        __HAL_RCC_USART1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();

        /* 默认映射:PA9 为 USART1_TX,使用复用推挽输出。 */
        gpio.Pin = GPIO_PIN_9;
        gpio.Mode = GPIO_MODE_AF_PP;
        gpio.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA, &gpio);

        /* PA10 为 USART1_RX;本文日志实验不要求连接外部 TX。 */
        gpio.Pin = GPIO_PIN_10;
        gpio.Mode = GPIO_MODE_INPUT;
        gpio.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &gpio);
    }
}

void HAL_WWDG_MspInit(WWDG_HandleTypeDef *handle)
{
    if (handle->Instance == WWDG)
    {
        __HAL_RCC_WWDG_CLK_ENABLE();

        /* 避免自由运行阶段留下的 EWIF 使能后立即形成请求。 */
        __HAL_WWDG_CLEAR_FLAG(handle, WWDG_FLAG_EWIF);
        HAL_NVIC_ClearPendingIRQ(WWDG_IRQn);

        /* 本实验优先响应 EWI;实际系统要统一规划中断优先级。 */
        HAL_NVIC_SetPriority(WWDG_IRQn, 0U, 0U);
        HAL_NVIC_EnableIRQ(WWDG_IRQn);
    }
}
文件三:stm32f1xx_it.c
c 复制代码
#include "stm32f1xx_hal.h"

extern WWDG_HandleTypeDef hwwdg;

void NMI_Handler(void)
{
    /* 本最小实验未开启时钟安全系统 CSS。 */
}

void HardFault_Handler(void)
{
    /* WWDG 已启动时,停止刷新后会超时复位。 */
    while (1) { }
}

void MemManage_Handler(void)
{
    while (1) { }
}

void BusFault_Handler(void)
{
    while (1) { }
}

void UsageFault_Handler(void)
{
    while (1) { }
}

void SVC_Handler(void) { }
void DebugMon_Handler(void) { }
void PendSV_Handler(void) { }

void SysTick_Handler(void)
{
    /* HAL_Delay 依赖 HAL tick;遗漏后会卡住并被 WWDG 复位。 */
    HAL_IncTick();
    HAL_SYSTICK_IRQHandler();
}

void WWDG_IRQHandler(void)
{
    /* 由 HAL 清 EWIF 并调用用户回调,不在这里重复刷新。 */
    HAL_WWDG_IRQHandler(&hwwdg);
}

这三个文件面向无 RTOS 的实验。若移植到已有 RTOS 或已有复杂中断处理的项目,应合并相关函数,不能用本例的空 SVC、PendSV 等入口覆盖系统已有实现。

4.7 从实验代码走向业务监督

实验里的 HAL_Delay() 只是让不同刷新周期容易比较。真实项目应有一个明确的刷新责任点,在它之前确认关键任务确实完成,且完成时间落在预期范围内。

例如采集、控制计算、输出更新都完成一轮,监督逻辑才考虑刷新。多任务系统可以使用每轮进度标记或序号,检查各任务是否产生了新的进展,不能把一个上次已经置位的标志永久当作健康状态。

不要简单地在任意地方添加"太早就等到窗口开放再刷新"。若被监督的任务异常提前返回,这样的等待可能掩盖时序异常。应区分由设计决定的固定周期调度,与本应被检测的异常提前结束。

生产实现还应测算最坏执行时间:正常任务最慢耗时、最高优先级中断占用、临界区时长和必要通信开销都必须能放进刷新预算。如果预算本来就不满足,应该调整架构或参数,而不是不断添加补救喂狗的位置。

4.8 验证

4.8.1 构建与启动前检查

在 CubeIDE 中执行 Build,或在所选 IDE 中重新构建工程。检查编译器已识别 STM32F103xE,HAL 模块与启动文件正确,三个应用文件没有重复定义,且工程采用 C 语言编译。

烧录后先使用正常模式。串口终端设为 115200、8N1、无流控。为了观察接近实际运行的行为,优先让芯片自由运行,不在 WWDG 启动后长时间单步停住。

修改 TEST_MODE 后必须重新构建并下载。早喂和超时模式会刻意进入重复复位;需要结束实验时下载正常模式或不启动 WWDG 的程序。若复位循环影响连接,可使用 ST-Link 的 Connect Under Reset,并确保 NRST 连接符合板卡要求。

下列日志、计数范围与波形均为预期结果,不是本次已经取得的板上运行记录。

4.8.2 方法一:启动日志与复位原因
模式 预期行为 启动日志应如何解读
NORMAL,40 ms 连续刷新,不因本实验发生 WWDG 复位 每次人工启动只打印一次启动信息;第一行可能为 0 或带有历史标志
EARLY,15 ms 在过早调用刷新时复位,然后重新启动 第一次故障复位后,应反复看到 BOOT WWDG=1
LATE,60 ms 尚未从延时返回就超时复位 同样重复出现 BOOT WWDG=1,不能仅凭该标志区分早喂与超时

例如超时模式在 WWDG 复位后的日志可能如下:

text 复制代码
BOOT WWDG=1
MODE=LATE delay=60ms
BOOT WWDG=1
MODE=LATE delay=60ms

WWDGRSTF 表示发生过窗口看门狗复位。复位标志可能同时存在,不能假定只有一个位为 1;示例先保存整个 CSR,再检查 WWDGRSTF,最后统一清除。单纯看到"启动日志又打印一次"只证明程序重新启动,若没有复位来源检查,还无法排除手动复位、电源问题或其他复位原因。

4.8.3 方法二:计数器与刷新进度

正常模式下,g_refresh_count 应不断增长。g_last_counter 在延时结束、刷新之前采样,通常会在约 0x52~0x54 附近,具体值随 tick 相位和执行开销变化;它应位于窗口内部。g_after_counter 应接近 0x7F,但不能要求每次恰好等于 127,因为采样时硬件仍可能发生递减。

如果在循环顶部、延时之前打印"刷新前计数",它可能只反映上一轮刷新后很短时间的状态,并不能表示本轮等待 40 ms 后的计数。观测语句放在哪里,决定了数据能证明什么。

观察这些变量时,优先使用支持运行中读取的调试功能,并减少侵入性。如果必须暂停 CPU,应先明确调试冻结策略:

c 复制代码
/* 仅调试时按需使用,放在 WWDG 启动前,不加入本例默认运行代码。 */
__HAL_DBGMCU_FREEZE_WWDG();

该宏使 CPU 被调试器暂停时冻结 WWDG。冻结后的断点观察不能代表自由运行的时间行为;不要通过断点停在刷新前的方式证明"窗口足够大"。

4.8.4 方法三:PF8 与复位时序

将示波器或逻辑分析仪接到 PF8 和 GND,在超时模式下以 PF8 下降沿触发,采样速度应足以分辨亚毫秒波形;例如使用不低于 1 MS/s 的采样率即可获得更直观的观察。

预期过程是:

text 复制代码
初始化:PF8 被配置为高电平输出
计数到 0x40:进入 EWI 回调,PF8 被拉低
下一次递减:WWDG 请求系统复位,GPIO 返回复位状态
重新初始化:PF8 再次配置为高电平输出

真正需要比较的是PF8 拉低到复位事件之间的时间,而不是简单把整个低电平宽度等同于 Δt。复位期间 GPIO 处于复位默认状态,PF8 电平由外部上拉、下拉、LED 电路和测量输入共同决定;重新配置 GPIO 前还包含启动时间。

有条件时可同时观察 NRST,并结合 WWDGRSTF 确认来源。正常参数下,从到达 0x40 到硬件复位大约一个计数周期;从软件拉低 PF8 到复位应更短,因为回调有响应延迟。

板载 LED 可能出现很短的闪动,也可能肉眼几乎看不见。其亮度还取决于有效电平、脉冲宽度与复位间隔,所以"肉眼没看见闪灯"不能证明 EWI 没有执行。

4.9 排错

4.9.1 现象、原因、确认与修复
现象 可能原因 如何确认 修复方法
工程无法识别 EWIMode 或回调名 HAL 接口版本或系列不匹配 查看本机 stm32f1xx_hal_wwdg.h 的结构体和声明 使用对应 CubeF1 接口,按本机版本适配
链接报句柄或函数重复定义 旧的外设文件、MSP 或中断文件仍同时编译 在工程中搜索 hwwdg、WWDG_IRQHandler 等定义 合并实现,每个符号只留一份
HSE 配置失败,程序停在错误处理 晶体频率、晶体/Bypass 模式或硬件不匹配 启动前观察 HAL_RCC_OscConfig 返回值,核对原理图 按实物修改 HSE 模式、HSE_VALUE 与 PLL
没有串口启动信息 接线、端口、波特率、PA9 映射错误,或初始化失败 WWDG 暂不启动,单独检查发送和终端配置 共地、交叉 TX/RX、选择正确端口与 115200 8N1
请求 40 ms 仍发生 WWDG 复位 PCLK1 错、阻塞日志、业务超时、HAL tick 异常 检查 PCLK1 和 CFR,测量实际刷新间隔,查看 SysTick 是否运行 修正时钟和时基,移出阻塞操作,重新核算最坏耗时
15 ms 居然不复位 软件未写 CR、WDGA 未启动,或窗口/时钟不是预期值 核对 CR.WDGA、CFR.W、分频和实际执行分支 确保 WWDG_Init 已调用且只调用一次,核对当前镜像
60 ms 仍正常运行 EWI 或其他中断在刷新,或实际计数时钟过慢 全工程搜索所有 Refresh/CR 写入,读取 RCC 与 CFR 删除无条件补救刷新,按真实时钟重算参数
超时复位了但没有 EWI 波形 EWI 或 NVIC 未开、全局中断屏蔽、入口错误、优先级阻塞 核对 CFR.EWI、NVIC 使能、PRIMASK/BASEPRI、向量与回调符号 补全中断链,缩短临界区,改进优先级或测量方式
EWI 执行后仍复位 这是未刷新时的正常行为 确认回调只清标志/改 GPIO,没有重装计数 按设计接受复位,不把清 EWIF 当刷新
调试时反复复位,自由运行却正常 CPU 暂停而 WWDG 没冻结 核对 DBGMCU 冻结位与断点位置 调试阶段按需冻结,时间验证用自由运行
刷新返回 HAL_OK,却仍复位 HAL 不做硬件窗口合法性检查 查看刷新实现,对照写入前计数与实际时间 修正刷新位置、参数和时间预算
4.9.2 按顺序定位问题

#mermaid-svg-XuoZFBasSP8FjRHc{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-XuoZFBasSP8FjRHc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XuoZFBasSP8FjRHc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XuoZFBasSP8FjRHc .error-icon{fill:#552222;}#mermaid-svg-XuoZFBasSP8FjRHc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XuoZFBasSP8FjRHc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XuoZFBasSP8FjRHc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XuoZFBasSP8FjRHc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XuoZFBasSP8FjRHc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XuoZFBasSP8FjRHc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XuoZFBasSP8FjRHc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XuoZFBasSP8FjRHc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XuoZFBasSP8FjRHc .marker.cross{stroke:#333333;}#mermaid-svg-XuoZFBasSP8FjRHc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XuoZFBasSP8FjRHc p{margin:0;}#mermaid-svg-XuoZFBasSP8FjRHc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XuoZFBasSP8FjRHc .cluster-label text{fill:#333;}#mermaid-svg-XuoZFBasSP8FjRHc .cluster-label span{color:#333;}#mermaid-svg-XuoZFBasSP8FjRHc .cluster-label span p{background-color:transparent;}#mermaid-svg-XuoZFBasSP8FjRHc .label text,#mermaid-svg-XuoZFBasSP8FjRHc span{fill:#333;color:#333;}#mermaid-svg-XuoZFBasSP8FjRHc .node rect,#mermaid-svg-XuoZFBasSP8FjRHc .node circle,#mermaid-svg-XuoZFBasSP8FjRHc .node ellipse,#mermaid-svg-XuoZFBasSP8FjRHc .node polygon,#mermaid-svg-XuoZFBasSP8FjRHc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XuoZFBasSP8FjRHc .rough-node .label text,#mermaid-svg-XuoZFBasSP8FjRHc .node .label text,#mermaid-svg-XuoZFBasSP8FjRHc .image-shape .label,#mermaid-svg-XuoZFBasSP8FjRHc .icon-shape .label{text-anchor:middle;}#mermaid-svg-XuoZFBasSP8FjRHc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XuoZFBasSP8FjRHc .rough-node .label,#mermaid-svg-XuoZFBasSP8FjRHc .node .label,#mermaid-svg-XuoZFBasSP8FjRHc .image-shape .label,#mermaid-svg-XuoZFBasSP8FjRHc .icon-shape .label{text-align:center;}#mermaid-svg-XuoZFBasSP8FjRHc .node.clickable{cursor:pointer;}#mermaid-svg-XuoZFBasSP8FjRHc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XuoZFBasSP8FjRHc .arrowheadPath{fill:#333333;}#mermaid-svg-XuoZFBasSP8FjRHc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XuoZFBasSP8FjRHc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XuoZFBasSP8FjRHc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XuoZFBasSP8FjRHc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XuoZFBasSP8FjRHc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XuoZFBasSP8FjRHc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XuoZFBasSP8FjRHc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XuoZFBasSP8FjRHc .cluster text{fill:#333;}#mermaid-svg-XuoZFBasSP8FjRHc .cluster span{color:#333;}#mermaid-svg-XuoZFBasSP8FjRHc 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-XuoZFBasSP8FjRHc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XuoZFBasSP8FjRHc rect.text{fill:none;stroke-width:0;}#mermaid-svg-XuoZFBasSP8FjRHc .icon-shape,#mermaid-svg-XuoZFBasSP8FjRHc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XuoZFBasSP8FjRHc .icon-shape p,#mermaid-svg-XuoZFBasSP8FjRHc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XuoZFBasSP8FjRHc .icon-shape .label rect,#mermaid-svg-XuoZFBasSP8FjRHc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XuoZFBasSP8FjRHc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XuoZFBasSP8FjRHc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XuoZFBasSP8FjRHc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
否
是
是
确认真的发生了复位
启动时保存 RCC_CSR 并检查来源
包含 WWDGRSTF 吗
检查电源、手动复位与其他来源
核对实际时钟与 CR、CFR
搜索所有刷新位置并测实际间隔
间隔落在窗口内部吗
修正业务耗时、阻塞操作或参数
检查重复初始化、时基与采样方式
需要确认 EWI 吗
依次核对 EWI、NVIC、向量与回调
用 PF8 波形观察,避免 ISR 打印

如果在中断长期屏蔽的情况下依然看到 WWDG 超时复位,这是正常的:硬件复位不依赖 CPU 成功执行 EWI。反之,EWI 能执行也不代表程序完成了正常业务。

知识小结

结论 :本例用同一套参数验证 15 ms 早喂、40 ms 正常和 60 ms 超时,并通过 EWI、复位标志和波形解释现象。原因 :不同延时分别落在窗口前、窗口内与复位期限之后,CR 写入和硬件递减触发了不同路径。关键参数 :36 MHz、P=8、W=96、Counter=127、EWI Enable、115200 8N1,以及启动时保存的 WWDGRSTF。常见误区:以 HAL_OK 证明时机正确、用延时前的打印代替刷新前采样、在回调里长时间打印,或只看到重启文字就断定复位来源。

5. 总结:把 IWDG 与 WWDG 放在一起看

到这里可以把两种看门狗的差异与机制对应起来,而不只是记住"一个能早喂,一个不能早喂"。

对比项 STM32F103 IWDG STM32F103 WWDG
喂狗要求 超时前刷新;本芯片不提供可配置的早喂窗口 指定窗口内刷新,太早写入也复位
计数器 12 位递减计数器 7 位递减计数器,T6 参与复位判断
时钟源 独立 LSI PCLK1 经 4096 和预分频
时间尺度 可用于较长超时监控,具体由 LSI 与参数决定 本文 36 MHz 条件下,最大名义超时约 58.25 ms
启动方式 可由软件启动,或由选项字节选择硬件启动 本文芯片通过软件设置 WDGA 启动
预警中断 不提供本文所述 EWI 计数到 0x40 可请求 EWI
主要监控角度 是否长时间没有正常刷新 刷新是否满足预期时间窗口

这些差异都限定在本文芯片,不能直接覆盖其他系列。两者也都需要正确安排刷新责任点:如果刷新语句与业务健康没有关系,硬件无法凭空知道某项任务是否已经出错。

WWDG 的基本工作过程可以复述为:PCLK1 经固定与可配置分频驱动计数器,软件设置 WDGA 后开启复位监控;当当前 T 大于 W 时写 CR,会因早喂复位;没有及时刷新而从 0x40 减到 0x3F,则因超时复位;在窗口内部写入有效计数值,才是正常刷新。

6. 补充说明

6.1 递减计数器是 7 位,为什么又出现低 6 位

部分旧版框图可能把计数器标成 6 位。判断时应把具体寄存器字段、T6 的作用和所用版本一起核对,不要只看某张图上的一句标注。

WWDG_CR.T[6:0] 共 7 位,表示的是一个 7 位计数值。正常运行让最高位 T6 保持 1,低 6 位用于表示从有效起点到复位点之间还能经过多少次递减:

text 复制代码
完整有效值:0x40 + 低 6 位
到复位所需次数:低 6 位 + 1

因此,超时公式里出现 T[5:0]+1,与计数器有 7 位并不矛盾。最高位既属于计数器,也承担复位判断条件。

遇到图文不一致,应核对适用于目标器件的参考手册修订版、寄存器定义与勘误。不能简单推断某一种语言的所有版本一定都更新、更准确。

6.2 为什么有了 IWDG 还需要 WWDG

现在回到第 1 节保留的问题。设某个关键流程正常需要一段时间,最后执行刷新。因为错误条件判断,它没有完成预定工作就提前返回,原本几十毫秒的循环突然缩短到 1 ms,并不断执行刷新。

对本芯片的 IWDG 而言,只要一直在超时前刷新,就可能不会复位。WWDG 则可能发现计数器还没降到 W 就被再次写入,于是触发过早刷新复位。
#mermaid-svg-caxRpYltbsgRJDBV{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-caxRpYltbsgRJDBV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-caxRpYltbsgRJDBV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-caxRpYltbsgRJDBV .error-icon{fill:#552222;}#mermaid-svg-caxRpYltbsgRJDBV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-caxRpYltbsgRJDBV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-caxRpYltbsgRJDBV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-caxRpYltbsgRJDBV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-caxRpYltbsgRJDBV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-caxRpYltbsgRJDBV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-caxRpYltbsgRJDBV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-caxRpYltbsgRJDBV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-caxRpYltbsgRJDBV .marker.cross{stroke:#333333;}#mermaid-svg-caxRpYltbsgRJDBV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-caxRpYltbsgRJDBV p{margin:0;}#mermaid-svg-caxRpYltbsgRJDBV .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-caxRpYltbsgRJDBV .cluster-label text{fill:#333;}#mermaid-svg-caxRpYltbsgRJDBV .cluster-label span{color:#333;}#mermaid-svg-caxRpYltbsgRJDBV .cluster-label span p{background-color:transparent;}#mermaid-svg-caxRpYltbsgRJDBV .label text,#mermaid-svg-caxRpYltbsgRJDBV span{fill:#333;color:#333;}#mermaid-svg-caxRpYltbsgRJDBV .node rect,#mermaid-svg-caxRpYltbsgRJDBV .node circle,#mermaid-svg-caxRpYltbsgRJDBV .node ellipse,#mermaid-svg-caxRpYltbsgRJDBV .node polygon,#mermaid-svg-caxRpYltbsgRJDBV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-caxRpYltbsgRJDBV .rough-node .label text,#mermaid-svg-caxRpYltbsgRJDBV .node .label text,#mermaid-svg-caxRpYltbsgRJDBV .image-shape .label,#mermaid-svg-caxRpYltbsgRJDBV .icon-shape .label{text-anchor:middle;}#mermaid-svg-caxRpYltbsgRJDBV .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-caxRpYltbsgRJDBV .rough-node .label,#mermaid-svg-caxRpYltbsgRJDBV .node .label,#mermaid-svg-caxRpYltbsgRJDBV .image-shape .label,#mermaid-svg-caxRpYltbsgRJDBV .icon-shape .label{text-align:center;}#mermaid-svg-caxRpYltbsgRJDBV .node.clickable{cursor:pointer;}#mermaid-svg-caxRpYltbsgRJDBV .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-caxRpYltbsgRJDBV .arrowheadPath{fill:#333333;}#mermaid-svg-caxRpYltbsgRJDBV .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-caxRpYltbsgRJDBV .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-caxRpYltbsgRJDBV .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-caxRpYltbsgRJDBV .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-caxRpYltbsgRJDBV .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-caxRpYltbsgRJDBV .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-caxRpYltbsgRJDBV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-caxRpYltbsgRJDBV .cluster text{fill:#333;}#mermaid-svg-caxRpYltbsgRJDBV .cluster span{color:#333;}#mermaid-svg-caxRpYltbsgRJDBV 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-caxRpYltbsgRJDBV .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-caxRpYltbsgRJDBV rect.text{fill:none;stroke-width:0;}#mermaid-svg-caxRpYltbsgRJDBV .icon-shape,#mermaid-svg-caxRpYltbsgRJDBV .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-caxRpYltbsgRJDBV .icon-shape p,#mermaid-svg-caxRpYltbsgRJDBV .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-caxRpYltbsgRJDBV .icon-shape .label rect,#mermaid-svg-caxRpYltbsgRJDBV .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-caxRpYltbsgRJDBV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-caxRpYltbsgRJDBV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-caxRpYltbsgRJDBV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 正常完成
错误地提前返回
阻塞不返回
关键流程开始
按预期完成了吗
按正常周期到达刷新点
窗口内部刷新
过早到达刷新点
当前 T 仍大于 W
WWDG 识别早喂并复位
长时间不能刷新
超时复位

这个例子成立的前提是刷新位置能反映关键流程的进展。如果系统另有一个不问业务状态、每隔固定时间就刷新的中断,流程提前返回未必会改变刷新节奏,WWDG 也就不能检测出这类问题。

WWDG 还有 EWI 预警,能够在真正超时前执行短小动作。基于稳定 PCLK1 的计数也适合毫秒级时序监督。但它依赖该时钟域,无法代替独立时钟的全部价值。

两者可以互补:WWDG 关注关键任务的时间窗口,IWDG 使用独立 LSI 提供超时监督。当系统时钟域失效而 LSI 和相关复位路径仍正常时,IWDG 仍可发挥作用。若同时使用,两个刷新机制都应基于明确的健康条件,不能互相用无条件刷新掩盖故障。

7. 一页纸总结

WWDG 通过一个由 PCLK1 驱动的递减计数器监督软件时序。它不认识业务内容,只有当刷新责任点与关键流程的真实完成绑定时,才可能识别任务提前返回、阻塞和部分流程失常。

计数器 T 与窗口值 W 是两个不同角色:T 不断递减,W 是固定比较阈值。启动后,在 T > W 时写 CR 会因过早刷新复位;在窗口内部写入合法计数值可以继续运行;不刷新时,从 0x40 减到 0x3F 会触发超时复位。

计算时先求一次递减周期:Δt = 4096 × P / PCLK1。然后分别数到 W、到 0x40、到 0x3F 的递减次数,避免遗漏最后一个周期。对本文的 127/96/8/36 MHz 配置,名义窗口开放约 28.217 ms,到达 EWI 阈值约 57.344 ms,超时上界约 58.254 ms。分频相位与软件开销要求刷新安排在窗口内部,40 ms 是本实验合适的选择。

EWI 在计数到 0x40 时发出预警,必须同时开启外设 EWI 和 NVIC。进入中断不会暂停计数,清 EWIF 也不等于刷新;回调时间不足一个完整计数周期,适合短小的状态动作,不适合阻塞打印或长操作。

复现时先做好时钟、GPIO、串口与启动日志,再启动 WWDG。正常、早喂和超时三组测试应同时结合启动复位标志、计数进度和 GPIO 波形分析。最终设计应让关键任务的每轮进度与刷新绑定,并用最坏执行时间验证窗口预算。

关键参数表

参数 本文值 / 范围 直接影响与注意事项
MCU STM32F103ZET6 不把其他 STM32 系列特性直接套用
HSE / HCLK / PCLK1 8 / 72 / 36 MHz 硬件 HSE 和 HSE_VALUE 必须一致,WWDG 用 PCLK1
固定分频 4096 芯片规定,不能软件修改
WDGTB / 实际 P 编码 3 / 系数 8;可选 1、2、4、8 HAL 填 WWDG_PRESCALER_8,不用裸数字 8
T_reload 0x7F=127;HAL 有效范围 0x40~0x7F 决定每轮计数起点,T6 应保持 1
W 0x60=96;HAL 有效范围 0x40~0x7F 决定早喂边界,是计数值而非毫秒
Δt 约 0.910222 ms 一次递减时间,也是 EWI 到复位的硬件时间预算
名义开放 / 超时上界 约 28.216889 / 58.254222 ms 含相位与软件余量后选择窗口内部刷新
本文保守参考区间 晚于约 28.217 ms,早于约 57.344 ms,并另留执行余量 用于解释安全余量,不是已测得的精确边界
测试延时 15 / 40 / 60 ms 分别对应早喂、正常、超时
EWI / EWIF 使能位在 CFR bit 9;事件位在 SR bit 0 清标志不刷新;EWI 使能置位后需复位清除
WWDG_IRQn 0,外部 IRQ 编号 不等于中断优先级
PF8 初始高,回调拉低 LED 极性与复位期间电平取决于实物电路
串口 USART1,PA9/PA10,115200 8N1 仅启动前打印,减少对刷新周期的干扰
复位来源 RCC_CSR.WWDGRSTF 先保存,再统一清除;不能单靠它区分早喂和超时

6 个自测问题

  1. 一个任务正常应执行几十毫秒,异常时却每 1 ms 刷新一次。为什么 STM32F103 的 IWDG 可能无法识别,而 WWDG 可以?怎样安排刷新责任点才有意义?
  2. 当前 T 大于 W 时,为什么不刷新可以继续运行,一刷新却可能复位?比较器检查的是旧 T 还是新写入值?
  3. 给定 PCLK1=36 MHz、P=8、T_reload=0x7F、W=0x60,如何从递减次数算出开放、EWI 和超时的名义时间?为什么不能在 58.25 ms 才开始刷新?
  4. CubeMX 中已启用 EWI,为什么仍需要启用 NVIC?为什么 IRQ 编号 0 不等于优先级 0?
  5. 60 ms 模式不断重启,但肉眼看不到 LED 闪烁。你会依次检查哪些标志、配置和波形?清 EWIF 为什么不会停止复位?
  6. 40 ms 模式加入几条串口打印后出现复位,HAL_WWDG_Refresh 又没有返回错误。应如何从实际刷新周期、时钟、时基与函数实现定位原因?

展开参考答案

1. 本芯片 IWDG 主要检查是否超时,持续提前刷新仍会使其保持运行。WWDG 在窗口尚未开放时写 CR 会复位。刷新应由监督逻辑在关键工作确实完成后执行;如果定时中断不问业务进展就刷新,两种看门狗都可能被错误使用。

2. 早喂复位要求"当前 T 大于 W"与"发生 CR 写入"同时成立。没有写入时,硬件只是继续倒计时。比较器检查写入前的当前值;随后才装入新的 T_reload。新值本身还必须保持 T6 为 1。

3. 一次递减是 4096×8/36000000 秒,约 0.910222 ms。从 127 到 96、64、63 分别是 31、63、64 次,名义时刻约 28.217、57.344、58.254 ms。自由运行分频器相位可能让复位更早发生,软件和中断也会消耗时间,所以应在窗口内部完成刷新。

4. EWI 控制外设能否提出请求,NVIC 控制 CPU 是否接收该中断请求。IRQ 编号用于标识和定位中断,优先级用于决定抢占与响应顺序,是不同的配置维度。

5. 先通过启动时保存的 CSR 检查 WWDGRSTF,再核对 PCLK1 与 CR/CFR;然后检查 EWI、NVIC、全局中断屏蔽、向量入口和回调是否正确。用 PF8 下降沿触发观察亚毫秒事件,必要时关联 NRST。清 EWIF 只是确认事件,计数器仍在运行,未刷新就会从 0x40 减到 0x3F。

6. 实际周期包括延时、打印、业务和中断开销。先核对 36 MHz 的真实配置与 HAL tick,再测量两次 CR 写入之间的时间,并估算串口阻塞耗时。HAL 刷新实现直接写 CR,不替应用判断时机,不能依靠 HAL_OK 排除窗口违规。移出阻塞日志后重新核算最坏执行时间,再验证正常与故障模式。


相关推荐
comyccccc2 小时前
平衡车底板设计:精准布局与高效布线学习笔记
单片机·嵌入式硬件
La Pulga2 小时前
【FreeRTOS源码学习笔记】任务创建
笔记·stm32·mcu·学习·嵌入式·freertos·实时操作系统
芯片人0073 小时前
GEK100系列芯片进入上市公司上海移远通信供应链
人工智能·单片机·嵌入式硬件·物联网·芯片
fly_sunnn4 小时前
ADC模数转换器
单片机·嵌入式硬件
我叫洋洋4 小时前
I2C 为什么必须用开漏输出?从推挽短路、线与,到上拉电阻取值
单片机·嵌入式硬件
无敌贵点大王4 小时前
从寄存器层面重学STM32(5)——串口USART理论部分
stm32·单片机·嵌入式硬件
女神下凡5 小时前
芯参谋(83):AliOS Things_驱动开发指南
arm开发·驱动开发·单片机·嵌入式硬件·设计规范·1024程序员节
女神下凡5 小时前
芯参谋(84):Linux eMMC 驱动开发指南
linux·运维·arm开发·驱动开发·单片机·嵌入式硬件
szxinmai主板定制专家17 小时前
X86 工控机 vs RK3588 异构主控:AMR 控制器 BOM、散热、长期运维对比
运维·stm32·单片机·嵌入式硬件·fpga开发·am62x