5.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.

Think about a dog, which 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 trigged 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 program

void 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:

image012.png
 
 

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 begining of main():

// Main program

void main()
{
	time_t		now;

	// Configure System Clock
	SystemClock_Config();

	// Peripheral Inits
	BSP_LED_Init();
	BSP_Console_Init();
	BSP_PB_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):

image017.png
 

Then, press the User (blue) button once. The clock should stop incrementing.

Now 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:

image018.png

 

  Commit name "Hardfault !"
  Push onto Gitlab

 

2. Release the Dog !

Edit the main() function:

  • 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.
-    0x5555   → Enables Watchdog register writing
-    0xCCCC  → Enables Watchdog
-    0xAAAA  → Reloads Watchdog timer

 

// Main program

void 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
	}
}

Build the project, flash the target and experiment by pressing user-button:

image020.png

 

You can see that the watchdog restarts code execution after the application get hang into the hardfault handler. Nice!

Well that's not a reason for being lazy writting robust code now...

 

  Commit name "Watchdog timer saves me"
  Push onto Gitlab

 

3. Summary

In this tutorial, you have learned how to setup and use the Watchdog Timer.