Skip to main content

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 .

gitlab- commit Commit name "Hardfault !"
- push 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...

gitlab- commit Commit name "Watchdog timer saves me"
- push Push onto Gitlab

 

3. Summary

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