17-WDG看门狗.md 10 KB

17-WDG看门狗

原理

看门狗工作模式

                    ┌──────────────┐
                    │  计数器递减   │
                    │  CNT ──▶ 0   │
                    └──────┬───────┘
                           │
                  ┌────────▼────────┐
                  │   CNT == 0 ?    │
                  └────────┬────────┘
                           │
              ┌────────────┴────────────┐
              │ 是                      │ 否
              ▼                         ▼
        ┌───────────┐              ┌───────────┐
        │ 系统复位   │              │ 继续递减   │
        └───────────┘              └───────────┘
              ▲
              │
        在 CNT 到 0 前
        "喂狗" (重载计数器)

核心概念

  • 看门狗是一个递减计数器
  • 计到 0 时触发系统复位
  • 软件需要在溢出前"喂狗"(重载计数器值)
  • 防止程序跑飞后无法恢复

IWDG (独立看门狗)

时钟源:LSI (40kHz RC 振荡器,独立于主时钟)

LSI 40kHz ──▶ 预分频器 (4/8/16/.../256) ──▶ 12位递减计数器 ──▶ 复位
                    ▲                                │
                    │                                │
                IWDG_SetPrescaler()             IWDG_ReloadCounter()

溢出时间公式

T_ovf = (4 × 2^PR) × RL / 40kHz
= 预分频系数 × 重载值 / 40kHz
预分频 (PR) 系数 4×2^PR 取值
0 4 4
1 8 8
2 16 16
3 32 32
4 64 64
5 128 128
6 256 256
7 256 保留 (不推荐)

示例:PR=4 (64分频), RL=625

T = 64 × 625 / 40000 ≈ 1.0 秒

WWDG (窗口看门狗)

时钟源:APB1 (36MHz),经 4096 分频后

APB1 ──▶ /4096 ──▶ 预分频器 (/1/2/4/8) ──▶ 7位递减计数器 ──▶ 复位
                          ▲                      │
                          │                      │
                     WWDG_SetPrescaler()    WWDG_SetCounter()
                          │                      │
                          └──────────────────────┘
                          窗口期喂狗条件:
                          0x40 < CNT ≤ W[6:0]
计数器值
  │
0x7F ─── 最大计数值
  │     ┌───────────────┐
  │     │  允许喂狗区域   │  ← 窗口期 (CNT > W[6:0])
  W ────┤               │
  │     │  禁止喂狗      │  ← 在此喂狗 → 复位
  │     │  (必须继续减)   │
0x40 ───┤               │  ← EWIF 置位 (早期唤醒中断)
  │     └───────────────┘
0x3F ─── 复位阈值
  │
  ▼  复位

WWDG 特性

  • 7 位递减计数器 (0x7F ~ 0x3F)
  • 只能在 CNT > W[6:0] 时喂狗
  • CNT 到 0x40 时触发早期唤醒中断 (EWI)
  • CNT 到 0x3F 时触发复位
  • 窗口外喂狗也会复位 (这是与 IWDG 最大的区别)

API 表格

IWDG

函数 说明
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess) 使能写访问 (必须先调用)
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler) 设置预分频系数
void IWDG_SetReload(uint16_t Reload) 设置重载值 (12位, 0~0xFFF)
void IWDG_ReloadCounter(void) 喂狗 (重载计数器)
void IWDG_Enable(void) 使能 IWDG (一旦使能无法软件关闭)
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG) 获取状态标志 (PVU/RVU)

IWDG_Prescaler 可选值

分频系数
IWDG_Prescaler_4 4
IWDG_Prescaler_8 8
IWDG_Prescaler_16 16
IWDG_Prescaler_32 32
IWDG_Prescaler_64 64
IWDG_Prescaler_128 128
IWDG_Prescaler_256 256

WWDG

函数 说明
void WWDG_Enable(uint8_t Counter) 使能 WWDG 并设置初始计数值
void WWDG_SetWindowValue(uint8_t WindowValue) 设置窗口值 (W[6:0])
void WWDG_SetCounter(uint8_t Counter) 喂狗 (重载计数器)
void WWDG_EnableIT(void) 使能早期唤醒中断 (EWI)
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler) 设置预分频
FlagStatus WWDG_GetFlagStatus(void) 获取 EWIF 标志
void WWDG_ClearFlag(void) 清除 EWIF 标志

WWDG_Prescaler 可选值

分频系数
WWDG_Prescaler_1 1
WWDG_Prescaler_2 2
WWDG_Prescaler_4 4
WWDG_Prescaler_8 8

完整代码示例

IWDG 独立看门狗

/*------------------------------------------------
 * IWDG_Example.c
 * 溢出时间 ≈ 1 秒, 主循环 500ms 喂狗
 *------------------------------------------------*/

#include "stm32f10x.h"

static void Delay_ms(uint32_t ms);

/* IWDG 初始化:溢出约 1 秒 */
void IWDG_Init(void)
{
    /* 计算:LSI ≈ 40kHz, PR=4 (64分频), RL=625 */
    /* T = 64 × 625 / 40000 = 1.0 秒 */
    IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);   // 允许写
    IWDG_SetPrescaler(IWDG_Prescaler_64);            // 64 分频
    IWDG_SetReload(625);                            // 重载值 625
    IWDG_ReloadCounter();                           // 喂第一次
    IWDG_Enable();                                  // 使能 IWDG
}

int main(void)
{
    IWDG_Init();

    while (1)
    {
        /* 用户代码 */

        IWDG_ReloadCounter();                       // 500ms 喂一次狗

        Delay_ms(500);                              // 模拟工作延时
        /* 如果在 1 秒内没有喂狗,系统复位 */
    }
}

WWDG 窗口看门狗

/*------------------------------------------------
 * WWDG_Example.c
 * 窗口值 0x5F, 计数器 0x7F, 早期唤醒中断
 *------------------------------------------------*/

#include "stm32f10x.h"

static void Delay_ms(uint32_t ms);

void WWDG_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);

    /* 窗口值 = 0x5F, 计数值 = 0x7F, 预分频 = /8 */
    /* 超时时间 ≈ 4096 × 8 × (0x7F - 0x3F) / 36MHz ≈ 5.8ms */
    /* 窗口上限 ≈ 4096 × 8 × (0x7F - 0x5F) / 36MHz ≈ 2.9ms */
    WWDG_SetPrescaler(WWDG_Prescaler_8);
    WWDG_SetWindowValue(0x5F);                      // 窗口值
    WWDG_EnableIT();                                // 使能 EWI 中断

    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = WWDG_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);

    WWDG_Enable(0x7F);                              // 使能 WWDG,计数器=0x7F
}

/* WWDG 早期唤醒中断处理 */
void WWDG_IRQHandler(void)
{
    if (WWDG_GetFlagStatus())
    {
        WWDG_SetCounter(0x7F);                      // 喂狗
        WWDG_ClearFlag();                           // 清标志
    }
}

int main(void)
{
    WWDG_Init();

    while (1)
    {
        /* 用户代码 */
        /* WWDG 在中断中喂狗 */
    }
}

IWDG + WWDG 同时使用

/*------------------------------------------------
 * DualWatchdog_Example.c
 * IWDG: 长超时 (1秒), 主循环喂狗
 * WWDG: 短超时 (5.8ms), 中断喂狗
 *------------------------------------------------*/

#include "stm32f10x.h"

void Watchdog_Init(void)
{
    /* ---- IWDG: 1秒 ---- */
    IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
    IWDG_SetPrescaler(IWDG_Prescaler_64);
    IWDG_SetReload(625);
    IWDG_ReloadCounter();
    IWDG_Enable();

    /* ---- WWDG: 5.8ms ---- */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
    WWDG_SetPrescaler(WWDG_Prescaler_8);
    WWDG_SetWindowValue(0x5F);
    WWDG_EnableIT();
    WWDG_Enable(0x7F);

    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = WWDG_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
}

void WWDG_IRQHandler(void)
{
    if (WWDG_GetFlagStatus())
    {
        WWDG_SetCounter(0x7F);
        WWDG_ClearFlag();
    }
}

int main(void)
{
    Watchdog_Init();

    while (1)
    {
        IWDG_ReloadCounter();           // 主循环喂 IWDG

        /* 如果主循环卡死超过 1 秒,IWDG 复位 */
        /* 如果中断卡死超过 5.8ms,WWDG 复位 */
        /* 如果喂狗太早 (窗口期外),WWDG 复位 */
    }
}

常见坑点

  1. IWDG 一旦使能无法软件关闭

    • 除非发生系统复位,否则 IWDG 不可停止
    • 调试时注意:芯片会不断复位
    • 调试期间可注释掉 IWDG_Enable() 或使用 DBGMCU_Config
  2. WWDG 窗口期外喂狗也复位

    • 与 IWDG 不同,WWDG 不是任意时刻都能喂狗
    • 必须在 CNT > W[6:0] 时喂狗
    • 太早喂狗也会触发复位!
  3. IWDG 预分频和重载寄存器写保护

    • 修改 PR 和 RL 前必须先调 IWDG_WriteAccessCmd(ENABLE)
    • 且必须在 IWDG_Enable() 之前
  4. IWDG 的 LSI 精度差

    • LSI 典型 40kHz,实际 30~60kHz
    • 溢出时间不精确,不适用于精确计时
  5. WWDG 时序依赖 APB1 时钟

    • 修改 APB1 时钟会影响 WWDG 超时时间
    • 如果 APB1 不是 36MHz,需重新计算
  6. 早期唤醒中断 (EWI)

    • WWDG_EnableIT() 使能后,CNT=0x40 时触发 WWDG_IRQHandler
    • 在中断中喂狗可避免复位
    • 但如果在中断中没喂狗,CNT=0x3F 时仍复位
  7. 调试期间看门狗干扰

    • 调试暂停时看门狗仍在走
    • 可用 DBGMCU_Config(DBGMCU_IWDG_STOP | DBGMCU_WWDG_STOP, ENABLE)
    • 这样调试暂停时看门狗停止计数
  8. IWDG 寄存器更新需要时间

    • 设置 PR 或 RL 后,硬件需要几个 LSI 周期更新
    • 可通过 IWDG_GetFlagStatus(IWDG_FLAG_PVU/RVU) 轮询等待