4.3. Watchdog timer
1. Preparing the project
In this tutorial, we address the use of the Watchdog timer. Watchdog timer should always be used, in any project, as it is a very efficient weapon against unexpected hangs, which are inherent of most software-based applications (whether related to bad coding practice or physical disturbances).
Practically, the Watchdog timer working principle is very simple. You can see it as a free running and independent down-counter that generates a hardware reset on the MCU when it reaches zero. Of course, the game is to prevent it from reaching zero using a clever placement of the reload code. As long as the watchdog reloads, you then know that the program is executing fine.
Think about a dog, who bites when hungry. Don’t want to get bitten? Then keep feeding the dog at regular time intervals.
Same thing applies to the Watchdog timer. Don’t want a reset? Then keep reloading the timer somewhere in your main loop. If the timer is not reloaded for a while, that means that you left the main loop for abnormal reason. A reset will be triggered as soon as the watchdog time has elapsed, taking you back into the main loop. You see how important it is?
In order to illustrate the Watchdog behavior, we will first cause a software hang.
1.1. The main() application
The main() function in this example comes from RTC introduction:
// Main function
int main()
{
time_t now;
// Configure System Clock
SystemClock_Config();
// Peripheral Inits
BSP_LED_Init();
BSP_Console_Init();
my_printf("\r\n\n --- Program Starting Over ---\r\n");
// If this is a Power ON reset
if ( (RCC->CSR & RCC_CSR_PORRSTF_Msk) == RCC_CSR_PORRSTF )
{
my_printf("This is a Power ON reset\r\n");
// Setup RTC clock
BSP_RTC_Clock_Config();
// Setup RTC time to 12:00:00
now.hours = 12;
now.minutes = 00;
now.seconds = 00;
BSP_RTC_SetTime(&now);
my_printf("RTC time has been reset to 12:00:00\r\n");
}
// If reset pin was held low
else if ( (RCC->CSR & RCC_CSR_PINRSTF_Msk) == RCC_CSR_PINRSTF )
{
my_printf("Reset pin was low\r\n");
my_printf("RTC continues...\r\n");
}
// Clear reset flags for next reset
RCC->CSR |= RCC_CSR_RMVF;
// Main loop
while(1)
{
BSP_RTC_GetTime(&now);
my_printf("RTC Time is %02d:%02d:%02d\r", now.hours,
now.minutes, now.seconds);
BSP_LED_Toggle();
BSP_DELAY_ms(100);
}
}
Save all , build , and make sure that the application works as expected :
1.2. User button as external interrupt
Now, we want to use the (blue) user button to “hang” the software into an infinite loop. Check that your push-button initialization function BSP_PB_Init() is correctly setting the corresponding pin to generate an EXTI interrupt on line 13 (if you did not change this function since it was first written, that should be OK).
/*
* BSP_PB_Init()
* Initialize Push-Button pin (PC13) as input without Pull-up/Pull-down
*/
void BSP_PB_Init()
{
// Enable GPIOC clock
RCC->AHBENR |= RCC_AHBENR_GPIOCEN;
// Configure PC13 as input
GPIOC->MODER &= ~GPIO_MODER_MODER13_Msk;
GPIOC->MODER |= (0x00 <<GPIO_MODER_MODER13_Pos);
// Disable PC13 Pull-up/Pull-down
GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR13_Msk;
// Enable SYSCFG clock
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
// Select Port C as interrupt source for EXTI line 13
SYSCFG->EXTICR[3] &= ~ SYSCFG_EXTICR4_EXTI13_Msk;
SYSCFG->EXTICR[3] |= SYSCFG_EXTICR4_EXTI13_PC;
// Enable EXTI line 13
EXTI->IMR |= EXTI_IMR_IM13;
// Disable Rising / Enable Falling trigger
EXTI->RTSR &= ~EXTI_RTSR_RT13;
EXTI->FTSR |= EXTI_FTSR_FT13;
}
Update NVIC settings if necessary:
void BSP_NVIC_Init()
{
// Set maximum priority for EXTI line 4 to 15 interrupts
NVIC_SetPriority(EXTI4_15_IRQn, 0);
// Enable EXTI line 4 to 15 (user button on line 13) interrupts
NVIC_EnableIRQ(EXTI4_15_IRQn);
...
}
1.3. Something wrong in the interrupt handler
Now let us edit the EXTI interrupt handler, with an intended mistake that will produce a Hard Fault exception in the microcontroller at runtime.
/**
* This function handles EXTI line 13 interrupt request.
*/
extern uint8_t button_irq;
void EXTI4_15_IRQHandler()
{
// Test for line 13 pending interrupt
if ((EXTI->PR & EXTI_PR_PR13_Msk) != 0)
{
// Clear pending bit 13 by writing a '1'
EXTI->PR = EXTI_PR_PR13;
// Writing to an non-existing address produces a Hardfault exeption
// that will hang the program into an infinite loop
*(__IO uint32_t *) 0x00040001 = 0xFF;
button_irq = 1;
}
}
1.4. Testing the “bug”
Make sure that BSP_PB_Init() function is called somewhere in the beginning of main():
// Main function
int main()
{
time_t now;
// Configure System Clock
SystemClock_Config();
// Peripheral Inits
BSP_LED_Init();
BSP_Console_Init();
BSP_PB_Init();
// NVIC Init
BSP_NVIC_Init();
my_printf("\r\n\n --- Program Starting Over ---\r\n");
// If this is a Power ON reset
if ( (RCC->CSR & RCC_CSR_PORRSTF_Msk) == RCC_CSR_PORRSTF )
{
...
}
// If reset pin was held low
else if ( (RCC->CSR & RCC_CSR_PINRSTF_Msk) == RCC_CSR_PINRSTF )
{
...
}
// Clear reset flags for next reset
RCC->CSR |= RCC_CSR_RMVF;
// Enable interruptions
BSP_NVIC_Init();
...
}
Build the project and start the debugger . Then start the program execution .
You should see the clock working as expected (i.e. incrementing every second):
Then, press the User (blue) button once. The clock should stop incrementing.
If not done automatically, suspend the debugger to see where the program is stuck. You should find the program looping into the Hardfault Handler infinite loop, written in the stm32f0xx_it.c file:
Terminate the debug session .
- Commit name "Hardfault !" - Push onto Gitlab |
2. Release the Dog !
Edit the main() function:
// Main function
int main()
{
...
// If this is a Power ON reset
if ( (RCC->CSR & RCC_CSR_PORRSTF_Msk) == RCC_CSR_PORRSTF )
{
...
}
// If reset pin was held low
else if ( (RCC->CSR & RCC_CSR_PINRSTF_Msk) == RCC_CSR_PINRSTF )
{
if ((RCC->CSR & RCC_CSR_IWDGRSTF_Msk) == RCC_CSR_IWDGRSTF )
{
my_printf("The watchdog did his job!\r\n");
}
my_printf("Reset pin was low\r\n");
my_printf("RTC continues...\r\n");
}
// Clear reset flags for next reset
RCC->CSR |= RCC_CSR_RMVF;
// Watchdog setup
IWDG->KR = 0x5555; // Enable write access
IWDG->PR = (0x03 <<IWDG_PR_PR_Pos); // Prescaler /32
IWDG->RLR = 1250; // 1s period
IWDG->KR = 0xAAAA; // Reload Watchdog
IWDG->KR = 0xCCCC; // Enable Watchdog
// Enable interruptions
BSP_NVIC_Init();
// Main loop
while(1)
{
BSP_RTC_GetTime(&now);
my_printf("RTC Time is %02d:%02d:%02d\r", now.hours,
now.minutes, now.seconds);
BSP_LED_Toggle();
BSP_DELAY_ms(100);
IWDG->KR = 0xAAAA; // Reload Watchdog
}
}
Within the Reset Pin startup test, we also test for the RCC IWDGRST flag. If this flag is set, then that means that the Watchdog has produced the reset. It is an interesting thing to report
Configure the Watchdog clock and period. The watchdog receives an internal clock of about 40kHz from LSI oscillator. By setting the prescaler to /32, and the reload value to 1250, we have approximately 1s before the watchdog bites if not fed
Feed the dog (i.e. reload the counter value) somewhere in the main loop
As you can see Watchdog operations are done thru ‘keys’ in the KR register to prevent Watchdog registers to be accessed by accident.
0x5555 → Enables Watchdog register writing
0xCCCC → Enables Watchdog
0xAAAA → Reloads Watchdog timer
Build the project, flash the target and experiment by pressing user-button:
You can see that the watchdog restarts code execution after the application get hang into the hardfault handler. Very nice indeed!
Well that's not a reason for being lazy writing robust code now...
- Commit name "Watchdog timer saves me" - Push onto Gitlab |
3. Summary
In this tutorial, you learned how to setup and use the Watchdog Timer.