news 2026/8/31 8:04:52

STM32独立看门狗IWDG详解:原理、超时计算与代码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32独立看门狗IWDG详解:原理、超时计算与代码实现

简介:本资源是一套面向嵌入式初学者与STM32开发者的独立看门狗(IWDG)实战例程,聚焦于STM32F103单片机系统可靠性设计,解决程序跑飞、死循环等常见异常场景下的自动复位问题。压缩包共85个文件,含39个头文件(.h,用于外设配置与函数声明)、38个源文件(.c,涵盖IWDG初始化、喂狗逻辑、SysTick定时调度及LED/按键/蜂鸣器等配套功能模块),另有Keil工程文件(.uvprojx/.uvoptx)、编译输出(.hex)、实验截图(.png)及批处理脚本(.bat)等,总大小326KB,结构清晰、开箱即用。已有147人学习下载,配套代码完整实现了IWDG与SysTick协同计时、DS1302实时时钟读写、HS0038红外信号解码等多外设联动逻辑,特别适合理解看门狗参数配置(预分频/重载值)、中断服务流程及HAL/标准库混合编程实践。 先说个我自己踩过的坑。早几年调一块STM32F103的板子,程序跑着跑着偶尔就整个卡死,重新上电又能活过来。查了两三天,最后定位到是程序里某个分支进了死循环,没有任何机制能把这个状态拉回来。后来我把独立看门狗(IWDG)正式加到每个工程里,这个“系统跑飞没人管”的问题才算彻底解决。今天这篇文章就围绕这份基于STM32F103的独立看门狗实验例程源代码,把IWDG的原理、参数计算、代码实现到工程化踩坑一次讲透。

这个例程适合两类人:一类是刚开始学STM32、想做看门狗实验又不知道从哪下手的同学;另一类是已经会点单片机、但在实际项目里对“到底该在哪里喂狗”这件事拿不准的开发者。看完你至少能回答三个问题:超时时间怎么算、初始化代码每行在干什么、为什么调试时一打断点就复位。

1. 独立看门狗到底是什么?先搞清楚它解决什么问题

1.1 单片机会死机,谁来兜底

单片机在真实环境里跑,不像开发板上那么安逸。强电磁干扰、电源毛刺、软件逻辑漏洞、指针意外越界,都可能导致程序跳出正常流程,陷入某个死循环里出不来。如果这时候系统正在控制电机、加热器、通信总线,发生什么后果就很难预料了。

看门狗就是为这种情况设计的“最后一道兜底机制”。它的本质是一个独立运行的硬件计数器:一旦启动,就自动从设定值往0递减,减到0就强制复位整个单片机。正常工作时,软件必须在计数器减到0之前“喂狗”,也就是把计数器重新加载回设定值。如果程序跑飞了,喂狗动作自然就中断了,计数器一路减到0,芯片复位,程序从头开始跑。

注意,这里的关键词是“独立”。在STM32F103里,IWDG的时钟源是芯片内部的LSI低速RC振荡器,不依赖主时钟。哪怕外部晶振停振、主程序卡死、SysTick中断失效,只要LSI还在振,IWDG就还会工作。这也是它跟后面要说的窗口看门狗最大的区别之一。

1.2 IWDG内部结构和关键寄存器

STM32F103的独立看门狗内部结构并不复杂,核心由三部分组成:

  • LSI时钟源:频率约40kHz,实际范围在30kHz到60kHz之间,芯片手册里会给不同温度下的变化范围。
  • 8位预分频器:对LSI时钟进行分频,支持4、8、16、32、64、128、256这几种分频系数。
  • 12位递减计数器:从重装载值(最大4095)开始递减,减到0触发系统复位。

控制IWDG只需要操作4个寄存器,了解透这4个寄存器,整个例程就等于是读代码了。

寄存器作用关键点
IWDG_KR键寄存器写入0x5555解锁PR/RLR,写入0xAAAA喂狗,写入0xCCCC启动
IWDG_PR预分频寄存器3位有效位,配置分频系数
IWDG_RLR重装载寄存器12位有效位,配置喂狗时加载的计数值
IWDG_SR状态寄存器PVU/RVU位,分别表示PR和RLR是否正在更新,写寄存器前最好等这两个位清零

IWDG的地址映射在APB1外设总线,基地址0x40003000。这个细节在你用寄存器直接操作时很重要,但平时开发用标准外设库或HAL库封装好的函数就够了。

关于键寄存器,官网文档有个词叫“write protection”,但这里的保护跟平时说的写保护还不是一回事。IWDG_PR和IWDG_RLR在默认状态下是被“锁”住的,必须先往IWDG_KR写0x5555才能修改它们。而0xAAAA和0xCCCC则分别承担喂狗和启动两个完全不同的动作,写错顺序或者遗漏任何一步,看门狗都不会按你预期的工作。

顺便提一句,很多初学者会把IWDG和WWDG(窗口看门狗)搞混。两者的区别很大,简单对比一下:

  • IWDG用LSI时钟,WWDG用系统时钟分频,所以WWDG精度高,IWDG精度低。
  • IWDG只要在超时前任意时间喂狗就行,WWDG规定了一个“喂狗窗口”,窗口没开就喂狗也会复位。
  • IWDG喂狗逻辑简单,适合做独立兜底;WWDG能检测程序运行时序是否异常,适合更严格的时序控制。

在大多数入门例程里,IWDG是首选,因为它逻辑简洁,配置起来也不容易出错。

2. 超时周期怎么算:这个例程的1秒是如何来的

2.1 计算公式与LSI时钟误差

看门狗超时时间是一个必须自己动手算的参数,例程里一般会给一个固定的配置,比如预分频64、重装载值624,实际超时时间大概是1秒。这1秒是怎么来的,一定要算明白。

IWDG超时时间公式:

Tout = ((4 × 2^PRER) / LSI_freq) × (RLR + 1)

其中4 × 2^PRER是预分频系数,PRER是IWDG_PR寄存器里的值。举例说明:

  • 如果PRER=4,对应的分频系数是4 × 2^4 = 64。
  • LSI频率取典型值40kHz,分频后的时钟频率是40kHz / 64 = 625Hz,也就是每个计数脉冲的周期是1.6ms。
  • 重装载值RLR=624,由于计数器从624递减到0需要625个脉冲,所以总时间 = 625 × 1.6ms = 1秒。

这就是例程里“1秒超时”的由来。同样的配置,如果你把RLR改成499,超时时间就变成500 × 1.6ms = 800ms。

这里有一个非常容易被忽略的坑:LSI不是精确时钟。STM32F103手册里写的LSI典型值是40kHz,但实际范围是30kHz到60kHz,具体数值跟温度和制造工艺有关。也就是说,你的程序如果是按40kHz算的超时时间,实际跑起来可能偏差很大,比如按30kHz算,原本1秒的超时就变成1.33秒;按60kHz算就只有0.67秒。所以在给实际项目定超时时间时,千万不要卡在临界值上,比如任务正常周期是950ms,你把看门狗超时设成1秒,看似没问题,其实一点余量都没有,LSI稍微偏一点系统就会误复位。

2.2 喂狗周期与超时周期的配合原则

超时时间定好了,接下来要决定的就是什么时候喂狗。很多例程演示时会把喂狗放在主循环的末尾,这样最简单,也符合实验逻辑。但放到真实项目里,这个策略要再细想。

我的经验是,喂狗周期一般取超时时间的三分之一到二分之一。比如看门狗超时1秒,那么设计上最好每300ms到500ms就喂一次狗。这样做的原因是:

  • 给执行中的长任务留出足够时间,不至于因为一个任务偶尔超过正常耗时就被误判为死机。
  • 又能保证一旦程序真的跑飞,最多几百毫秒内就会被复位,不至于长时间无响应。

另外,如果主循环里有一个特别耗时的业务分支,比如某个网络通信的超时等待能跑800ms,而你配置了1秒的超时时间,那风险就很高。正确做法是先梳理所有任务的最长执行路径,再拿这个时间乘以1.5到2倍去配置看门狗超时时间。如果最长路径变化很大,就要考虑是不是该调整喂狗的位置,而不是无脑放大超时时间。

我见过有人把超时时间配到10秒甚至更长,理由是“别让看门狗误复位”,但实际上,10秒不喂狗意味着系统若跑飞,10秒才能恢复,在很多设备里这个时间根本无法接受。看门狗设计本质上是在“防误杀”和“快速恢复”之间找平衡,偏向哪边都会出问题。

3. 例程代码逐段解析:从初始化到喂狗的完整链路

3.1 标准外设库初始化代码:解锁、配置、启动

这个例程用的是STM32标准外设库(StdPeriph Library),初始化函数通常长这样:

void IWDG_Configuration(void) { // 写0x5555解锁PR和RLR寄存器 IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); // 配置预分频系数为64 IWDG_SetPrescaler(IWDG_Prescaler_64); // 配置重装载值为624 IWDG_SetReload(624); // 将重装载值加载到计数器,这一步也叫"喂狗" IWDG_ReloadCounter(); // 启动看门狗 IWDG_Enable(); }

每一步都要理解它在干什么:

IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable)这一行对应的是往IWDG_KR写0x5555。如果不做这一步,后面两个配置语句其实是不生效的,寄存器写不进去。

IWDG_SetPrescaler(IWDG_Prescaler_64)设置PR寄存器,64分频。之前算过,LSI=40kHz时,分频后是625Hz。

IWDG_SetReload(624)设置RLR寄存器,配置递减计数器的起始值624。

IWDG_ReloadCounter()这一行很容易被忽略,但它非常关键。它实际上就是往IWDG_KR写0xAAAA,把刚才配置好的RLR数值正式加载到递减计数器里。如果跳过这一步,计数器加载的可能是复位后默认的值,而不是你期望的624。

IWDG_Enable()往IWDG_KR写0xCCCC,启动看门狗。一旦执行完这一句,看门狗就开始工作了,而且之后软件上没有任何办法让它停止,除非整个芯片复位。

标准库封装已经把这些寄存器操作封装得很干净了,但如果将来要移植到寄存器直接写的场景,底层动作其实就是这样的:

void IWDG_Init_Reg(void) { // 1. 解锁PR和RLR IWDG->KR = 0x5555; // 2. 配置PR,0x04对应64分频 IWDG->PR = 0x04; // 3. 配置RLR IWDG->RLR = 624; // 4. 等待寄存器更新完成(检查PVU和RVU位) while (IWDG->SR & 0x03); // 5. 重载计数器 IWDG->KR = 0xAAAA; // 6. 启动看门狗 IWDG->KR = 0xCCCC; }

这里多出来的第4步,标准库实际上也在内部做了类似处理,只不过封装之后你未必关注得到。用寄存器方式操作的话,写PR或者RLR之后如果立刻去改另一个寄存器,可能会因为硬件还没来得及更新而上一次写入失败,所以等待SR寄存器里的PVU和RVU位清零是个好习惯。

3.2 喂狗的位置与主循环结构

初始化写好之后,喂狗的位置直接影响看门狗能不能起到“兜底”作用。实验例程的main函数一般长这样:

int main(void) { // 系统时钟、GPIO、串口等初始化 SystemInit(); LED_Init(); UART_Init(); // 看门狗初始化,放在所有耗时初始化之后 IWDG_Configuration(); printf("System Start...\r\n"); while (1) { // 业务逻辑 LED_Toggle(); // 任务执行完毕,喂狗 IWDG_ReloadCounter(); } }

这个结构在例程里没问题,但要注意一点:看门狗初始化放在所有耗时初始化之后。如果芯片上电后先初始化看门狗,再去做一个耗时1秒的外部设备复位等待,那么这1秒内看门狗可能已经超时并复位了,系统就会一直卡在“启动-复位-启动-复位”的死循环里。

喂狗放在主循环末尾,代表“主循环完整跑完一圈,证明系统还活着”。如果主循环里某个分支卡死,喂狗就执行不到,看门狗在超时后复位系统,逻辑上是通的。这也是最基本的看门狗使用方式。

但实际项目里会比这个复杂。比如你有一个大循环同时处理按键、显示、通信,其中通信模块的阻塞等待时间过长,或者某个函数内部有自己的while等待标志位,这些代码都会让喂狗操作被推迟。所以在复杂工程里,不要简单地把喂狗放在主循环末尾就完事,还要考虑每个可能卡住的代码段的最大耗时。

3.3 复位原因判断:验证看门狗是否真的工作

写完代码,你肯定想验证看门狗到底生效没有。最简单的验证方式有两种。

第一种是故意不喂狗。注释掉main循环里的IWDG_ReloadCounter(),然后观察LED或者串口打印。如果LED不停地快闪复位,说明看门狗确实在强制复位,程序一直在跑“启动-死循环-被复位-再启动”。串口如果能看到反复打印“System Start...”日志,效果更直观。

第二种是读取复位标志。STM32F103的RCC模块里有一个复位标志寄存器,其中有一个位专门记录“复位原因是否为独立看门狗”。标准库对应的读取方式:

if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET) { printf("上次复位原因:独立看门狗复位\r\n"); } RCC_ClearFlag();

把这个判断放在main函数的最开始,上电后串口先打印复位原因,就能判断本次启动是正常上电还是被看门狗拉起来的。这一步在实际排查问题时有奇效,特别是设备偶尔复位时,能快速区分是看门狗触发复位还是外部引脚复位。

注意,读取完标志之后最好调用RCC_ClearFlag()把它清掉,否则下次上电时看到的是上一次的残留状态,容易误判。

4. 调试中一定会踩的坑

4.1 断点调试时系统反复复位

这个坑我几乎每次带新人都会遇到。在MDK里用J-Link或者ST-Link调试,设一个断点,全速运行到断点停下来,CPU暂停了,但IWDG还在走。LSI不受调试停止信号影响,计数器继续递减,减到0就复位,然后调试会话就乱了,断点根本停不住。

解决办法是把IWDG接到调试停止信号上。STM32F103有一个调试模块DBGMCU,它的控制寄存器里有一个DBG_IWDG_STOP位,置1之后,只要内核调试器发出停止信号,IWDG的计数器也跟着停止。标准库里可以直接调用:

DBGMCU_Config(DBGMCU_IWDG_STOP, ENABLE);

或者直接用寄存器:

DBGMCU->CR |= 0x00000100;

这一行代码放在初始化早期,建议放在IWDG启动之前。这样调试时打断点就不会被打扰了。但请注意,这只是调试阶段的手段,正式发布程序前可以保留也无妨,它对正常运行没有任何影响。

4.2 IWDG一旦启动就关不掉

IWDG最让人头疼的特性是:一旦启动,软件上没有任何办法停止它。你想要关闭,只能通过系统复位,包括上电复位、引脚复位、看门狗复位等。问题就来了——如果你在例程里启用了IWDG,烧录后它就会一直运行,你哪怕在调试器里暂停程序想慢慢看变量,只要忘了配DBGMCU那一位,就会被反复复位。

更麻烦的是另一类场景:你在开发板上下载了带看门狗的程序,然后重新下载一个不带看门狗的程序。下载动作本身会触发芯片复位,复位后新程序没有IWDG配置,那IWDG自然就不工作了,所以一般不用担心“旧代码的看门狗关不掉”这种事。但如果你的新程序里初始化了IWDG但忘了喂狗,就会出现“烧录后系统不息工作,只能再次烧录程序才能恢复”的迷惑现象。

因此,在开发调试阶段,我习惯用条件编译把IWDG打包起来,比如:

#define IWDG_ENABLE_DEBUG 1 #if IWDG_ENABLE_DEBUG void IWDG_Configuration(void) { // ... } #endif

功能联调没问题之后,再打开宏,真正把看门狗纳入系统。这样能少踩很多烦人的坑。

4.3 喂狗过快或过慢的隐患

喂狗过快的隐患其实很隐蔽。举个例子,你在一个定时器中断里喂狗,每1ms喂一次,主循环就算彻底卡死,中断还在正常触发,看门狗永远不超时,兜底机制形同虚设。

很多人下意识觉得“只要喂狗执行了,程序就是好的”,这种认知在定时器中断喂狗的设计里是错的。看门狗要守护的是整个程序的健康度,而不是某个中断是否还在跑。如果喂狗逻辑被放在中断里,主循环各种逻辑全都卡死了,喂狗依然执行,那看门狗就完全失去了意义。

反过来,喂狗过慢则会导致频繁的误复位。尤其是代码里有耗时操作时,比如等待一个外部设备响应、写Flash、做IAP升级,这些操作动辄几秒,如果看门狗超时时间设置得太短,系统会在正常工作的途中被复位,数据还会丢失。

所以正确定位是:**喂狗动作应该放在能代表“主程序整体运行正常”的位置,比如主循环底部,或者任务调度器的主控循环里。**中断里可以喂,但前提是你清楚知道自己是在做什么,别让中断喂狗掩盖了主循环卡死的事实。

5. 从例程到项目:工程化设计建议

5.1 多任务场景下的喂狗策略

在裸机开发里,所谓“多任务”其实就是主循环里调用各种任务函数、中断触发各种标志位。这个场景下,一个比较稳的喂狗策略是“分任务登记,主循环统一喂”。

思路很简单:

  • 定义一个全局的喂狗计分板,每个关键任务在完成自己的核心逻辑后,把自己的标志位置1。
  • 主循环每次转一圈,检查所有关键任务的标志位是否都置位了。
  • 如果全部正常,清空标志位并喂狗;如果有任何一个任务没完成,就跳过这次喂狗。

这样设计的价值在于,看门狗不再只是“主循环还活着”的指示器,而是“所有关键任务都在按预期运行”的指示器。任何一个任务卡死、某一步没执行到,喂狗就会被跳过,超时后系统自动复位。这个思路在稍微大一点的裸机项目里非常实用。

简化版的实现大概长这样:

volatile uint8_t task1_done = 0; volatile uint8_t task2_done = 0; void Task1(void) { // 业务逻辑... task1_done = 1; } void Task2(void) { // 业务逻辑... task2_done = 1; } void FeedDogCheck(void) { if (task1_done && task2_done) { task1_done = 0; task2_done = 0; IWDG_ReloadCounter(); // 所有任务都完成,才真正喂狗 } }

当然,这个方案也有代价:如果某个任务的执行时间本身就不固定,比如偶尔会有一次长任务跑到几百毫秒,需要给这个任务设置超时上限,否则看门狗超时参数很难选。我的经验是按“任务的最长正常耗时”加30%-50%余量去配,既不会误杀,又不至于让复位来得太慢。

5.2 看门狗与低功耗的取舍

有些设备为了省电会进入STOP或者待机模式,这两种模式下IWDG的行为不一样。如果在低功耗模式里IWDG还在跑时间会变长;如果它停了你又期望它在唤醒后还能兜底,那逻辑就要重新设计。每个芯片的行为不完全相同,最稳妥的做法是去查对应型号参考手册里有关LSI和IWDG在低功耗模式下行为的章节。

我个人的一条原则是:低功耗场景下,看门狗要慎用。如果产品对休眠功耗极其敏感,IWDG的超时和唤醒策略会变得很拧巴——超时太短可能把设备从休眠中复位,超时太长又失去了兜底作用。这种情况下,我通常会选择“只在运行态喂狗,进入休眠前,看门狗保持现有状态;唤醒后判断唤醒原因,如果是看门狗复位就重新初始化,再做数据恢复”。

注意,这里说的是“保持现有状态”而不是“关闭”,因为前面说过IWDG软件上根本关不掉,所以只能让它继续跑,同时把超时时间调得足够大,或者利用芯片的调试冻结功能让它在休眠期间暂停。这个部分每个型号的差异很大,没有统一的代码模板,用之前一定去手册里查清楚。

6. 从这份例程出发,还能扩展什么

学完独立看门狗,下一步建议去研究窗口看门狗(WWDG)。IWDG只能判断“超时没超时”,WWDG会进一步约束“喂狗时间是否在一个合法窗口内”,对程序时序异常更敏感。很多车规级、工控级的裸机程序都会同时使用两者:IWDG做全局兜底,WWDG做任务时序校验。

另外,IWDG和备份寄存器经常搭配使用。看门狗复位后程序重新启动,在main开头读取备份寄存器里的数据,就能知道复位前系统运行到了哪个阶段,从而决定是重新执行任务还是恢复到某个断点继续跑。这个组合在设备需要掉电记忆状态的场景是很实用的。

我个人在实际操作中的体会是:看门狗这东西,配置起来十分钟,但想用好它,靠的是对整个系统运行节拍的透彻理解。每次写喂狗代码前,先把主循环所有分支的耗时列出来,再决定超时时间和喂狗位置,这样踩坑最少。最后再分享一个小技巧:产品量产前,把所有喂狗代码独立抽出来做一个模块,跟业务逻辑解耦。这样后续要调整看门狗参数、增加喂狗检查项,都不用去动业务代码,维护起来省心得多。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 8:02:18

STM32H7驱动480x1280 MIPI触摸屏的LVGL V9.4移植与优化方案

这次我们看的是一个比较典型的嵌入式 GUI 落地组合:6.86 英寸 4801280 分辨率 MIPI 接口电容触摸屏,搭配 STM32H757XIH6 主控,图形框架使用 LVGL V9.4。和常见的 3.5 寸 SPI 屏、4.3 寸 RGB 屏相比,这套方案的核心区别在于屏幕分辨…

作者头像 李华
网站建设 2026/8/31 8:00:39

Termux上跑10层SQLite Agent Mesh:热节流感知调度实践

在实际开发中,把多级 Agent Mesh 跑在 Termux 里,和跑在云服务器上最大的差别不在 Python 语法,也不在 SQLite 的能力,而在热节流(thermal throttling)。SQLite 在移动端通常几十 GB 的数据规模下性能绰绰有…

作者头像 李华
网站建设 2026/8/31 7:58:59

MATLAB Simulink仿真与嵌入式代码生成完整指南

如果你已经用 MATLAB 做过一段时间仿真,大概率会遇到这样一个问题:Simulink 里模型跑得好好的,波形、数据、控制效果都符合预期,但一提到“生成代码”,就不知道从哪下手。选什么求解器、配置什么系统目标文件、TLC 是什…

作者头像 李华
网站建设 2026/8/31 7:55:28

OpenClaw U盘部署后工具调不动?从权限排查到环境修复全流程

把 OpenClaw 放到 U 盘里跑,图的是“插哪台机器都能用”。但很多人在这一步踩了同一个坑:启动界面正常、模型加载也正常,真正调用工具的时候却一直失败。这时候第一反应往往是换模型、重装依赖,其实更大概率是权限问题。 OpenCla…

作者头像 李华