Why should you NEVER feed the watchdog inside a timer ISR?
Feeding the watchdog in a timer ISR is one of the most dangerous design mistakes in embedded firmware because it completely defeats the watchdog's purpose while giving the false appearance of safety. The timer interrupt is driven by hardware — it fires at a fixed rate based on the timer peripheral's clock and configuration, completely independent of what the application code is doing. If the main loop is stuck in an infinite loop, if a task is deadlocked, if the stack has overflowed and corrupted the program counter so the CPU is executing random instructions, or if a critical peripheral driver has hung waiting for a status bit that will never be set — the timer ISR still fires on schedule (because it runs at a higher priority than the stuck code) and faithfully refreshes the watchdog. The system appears healthy to the watchdog but is completely non-functional.
The fundamental principle is: the watchdog feed must prove that the application logic is progressing correctly, not merely that the CPU is executing instructions. A timer ISR proves only that the interrupt controller and timer peripheral are working — it says nothing about the application's functional state. The watchdog feed must be placed at a point in the code that is only reached if all critical functions have executed successfully during the current cycle.
In a super-loop architecture, the correct placement is at the end of the main loop, after all critical subsystems have been serviced — sensor reads completed, communication processed, control outputs updated. If any subsystem hangs, the main loop stalls before reaching the feed point, and the watchdog fires. For additional safety, implement execution path verification: maintain a bitmask where each critical function sets its bit when it completes, and only feed the watchdog if all bits are set (then clear the mask for the next cycle). This detects not just hangs but also skipped subsystems:
#define TASK_SENSOR (1 << 0)#define TASK_COMMS (1 << 1)#define TASK_CONTROL (1 << 2)#define ALL_TASKS (TASK_SENSOR | TASK_COMMS | TASK_CONTROL)volatile uint32_t task_complete_mask = 0;while (1) {read_sensors(); task_complete_mask |= TASK_SENSOR;process_comms(); task_complete_mask |= TASK_COMMS;update_control(); task_complete_mask |= TASK_CONTROL;if (task_complete_mask == ALL_TASKS) {IWDG_feed();task_complete_mask = 0;}}
Source: Watchdog Q&A
