工业嵌入式设备的看门狗编程:实现软件跑飞后的自动恢复与故障日志记录
看门狗不是“主循环里加一句喂狗”就完事。工业现场要的是两层能力:跑飞能硬复位恢复 + 复位后知道上次为什么死。只喂狗不记日志,设备天天重启却查不出根因;只记日志不喂狗,程序卡死就真卡死。正经做法是 IWDG 兜底 + WWDG 监控关键周期 + 复位标志溯源 + 故障日志落非易失区。
一、IWDG 与 WWDG 分工
独立看门狗走 LSI 低速时钟,主时钟炸了它还在跑,适合做最后防线;只要在计数归零前喂就行。
窗口看门狗走 APB 时钟,必须在“窗口”内喂,太早太晚都复位,专门防“主循环没死但时序已经乱了”的伪活状态。
工业设备常见搭配:
IWDG:超时 1~2s,主调度任务喂
WWDG:关键控制任务周期监控,比如 10ms 控制环
外部 CPLD/看门狗芯片:MCU 彻底死机时硬复位
二、喂狗别放中断里
最典型误区:定时器中断里 IWDG_Reload()。主循环已经死循环,定时器中断还在跑,看门狗永远不触发。
正确是“系统级健康”才喂:
主循环/最低优先级任务喂 IWDG
各业务任务维护心跳计数
监控任务检查所有心跳,异常时不喂狗
typedef struct {
const char*name;
uint32_t tick; // 每次正常跑自增
uint32_t last_chk;
}task_hb_t;
task_hb_t hb[4];
void task_mark(int id){ hb[id].tick++; }
void watchdog_guard_1s(void){
uint8_t alive=1;
for(int i=0;i<4;i++){
if(hb[i].tick == hb[i].last_chk){
alive=0;
log_fault("task stuck:", hb[i].name);
}
hb[i].last_chk = hb[i].tick;
}
if(alive) iwdg_feed();
/* 不alive:不喂,IWDG超时复位 */
}
WWDG 可在 EWI 提前唤醒中断里存“临终现场”:当前任务ID、关键状态字,再让它在窗口外自然复位。
三、启动第一段:读复位标志
复位原因全在 RCC_CSR / SRS 里,上电第一时间读,读完清掉,不然下次叠加旧标志。
void boot_check_reset(void){
uint32_t csr = RCC->CSR;
uint8_t reason;
if(csr & RCC_CSR_IWDGRSTF) reason = RST_IWDG;
else if(csr & RCC_CSR_WWDGRSTF) reason = RST_WWDG;
else if(csr & RCC_CSR_SFTRSTF) reason = RST_SOFT;
else if(csr & RCC_CSR_PORRSTF) reason = RST_POR;
else reason = RST_PIN;
fault_log_write(reason); // 写备份区/EEPROM
RCC->CSR |= RCC_CSR_RMVF; // 清标志
}
读到 IWDG/WWDG 复位 → 上一次有任务卡死;读到 POR/BOR 频繁 → 先查电源别怪软件。
四、故障日志:RAM 环形 + 严重落盘
别每条 printf 写 Flash,擦写寿命和实时性都崩。架构:
正常日志进 RAM 环形缓冲
WARN/ERROR 以上才考虑持久化
看门狗复位、HardFault、掉电事件 → 写备份寄存器/EEPROM/FRAM
typedef struct {
uint32_t magic; // 0xFA55
uint16_t seq;
uint8_t reason;
uint8_t task_id;
uint32_t ts;
uint16_t crc;
}fault_rec_t;
void fault_log_write(uint8_t reason){
fault_rec_t r;
r.magic=0xFA55;
r.seq = nv_read16(ADDR_SEQ)+1;
r.reason=reason;
r.task_id = current_task_id();
r.ts = rtc_ms();
r.crc = crc16((uint8_t*)&r, sizeof(r)-2);
nv_write(ADDR_FAULT_LAST, &r, sizeof(r));
nv_write16(ADDR_SEQ, r.seq);
}
FRAM/MRAM 不用擦除,掉电瞬间几十微秒能写完;用 NOR Flash 必须靠掉电中断+储能电容短窗口提交。
五、分级自愈(可选)
不是所有超时都直接复位:
第一次任务看门狗超时 → 记日志、删任务重启
连续 N 次 → IWDG 不管了,系统复位
关键安全设备 → 先断输出再复位
void wdg_timeout_cb(int task_id){
fault_log_write(RST_TASK_STUCK);
if(++fault_cnt > MAX_RETRY){
NVIC_SystemReset();
}else{
task_restart(task_id);
}
}
六、落坑清单
中断里喂狗 → 主循环死循环被掩盖
不读 RCC_CSR → 永远不知道为啥重启
读完不清 RMVF → 标志叠加误判
每帧日志写 Flash → 寿命耗尽+卡业务
WWDG 窗口配错 → 正常喂狗也复位
LSI 精度±30%当精确计时 → 超时时间留余量
HardFault 里还死等串口 → 看门狗又超时嵌套
备份区不写 CRC → 读到半截脏数据
只开 IWDG 不监控任务 → 伪活发现不了
调试阶段忘关狗 → 单步必复位
七、落地顺序
开 IWDG,超时按最长任务周期 1.5~2 倍
主调度任务统一喂狗,中断不喂
各任务心跳计数,监控任务巡检
启动首行读复位标志、存备份区、清 RMVF
RAM 环形日志跑正常输出
严重故障(看门狗/HardFault/掉电)写 FRAM/EEPROM
关键任务加 WWDG 或任务看门狗分级重启
外部 CPLD 看门狗兜底 MCU 死机
工业看门狗的成熟形态不是“重启就行”,是重启能恢复 + 重启有记录。IWDG 兜住跑飞,WWDG/任务心跳兜住伪活,复位标志区分电源问题和软件卡死,故障日志落非易失区留 seq+crc——下次现场设备自己复位了,串口拉出来能看到“WWDG复位 / 任务=can_poll / 第37次”,不用靠猜。日志写盘频率压住、喂狗点放对地方,这两点守住,看门狗才是可靠性资产而不是掩盖 bug 的遮羞布。





