3.2. Timer interrupts


1.    Introduction

In many applications with signal processing involved, you want the computation loop to be trigged at regular time intervals providing a stable sampling period. A common way to do this is to implement a timebase with a timer that fires an interrupt every update event.

2. Using the timer to generate periodic interrupts

 

2.1. Setting up the peripheral

In bsp.c, adjust the TIM6 timebase settings in order to achieve a 200ms period between update events, and enable update interrupts:

/*
 * BSP_TIMER_Timebase_Init()
 * TIM6 at 48MHz
 * Prescaler   = 48000 -> Counting period = 1ms
 * Auto-reload = 200   -> Update period   = 200ms
 */

void BSP_TIMER_Timebase_Init()
{
	// Enable TIM6 clock
	RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;

	// Reset TIM6 configuration
	TIM6->CR1 = 0x0000;
	TIM6->CR2 = 0x0000;

	// Set TIM6 prescaler
	// Fck = 48MHz -> /48 = 1kHz counting frequency
	TIM6->PSC = (uint16_t) 48000 -1;

	// Set TIM6 auto-reload register for 200ms
	TIM6->ARR = (uint16_t) 200 -1;

	// Enable auto-reload preload
	TIM6->CR1 |= TIM_CR1_ARPE;

	// Enable Interrupt upon Update Event
	TIM6->DIER |= TIM_DIER_UIE;

	// Start TIM6 counter
	TIM6->CR1 |= TIM_CR1_CEN;
}

Then, set the priority and enable TIM6 interrupts in the BSP_NVIC_Init() function:

/*
 * BSP_NVIC_Init()
 * Setup NVIC controller for desired interrupts
 */

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);

	// Set priority level 1 for TIM6 interrupt
	NVIC_SetPriority(TIM6_DAC_IRQn, 1);

	// Enable TIM6 interrupts
	NVIC_EnableIRQ(TIM6_DAC_IRQn);
}

At this point, we wrote all the required code to have an interrupt signal out of TIM6 peripheral every 200ms, that the NVIC controller will propagate.

 

2.2. Writing the interrupt handler (ISR)

As said before, when the interrupt signal is propagated by the NVIC controller, the program execution will automatically branch to a specific code memory address. Looking in the assembler file startup_stm32f0xx.S will tell you that the function name corresponding to the TIM6 interrupt handler (or ISR) is: TIM6_DAC_IRQHandler(). If you wonder why the name includes ‘DAC’, the reason is that TIM6 has a specific feature associated with the DAC that other timers have not. We’re not using it so far, so you may just consider TIM6 as a regular basic timer.

 

Let us write something simple in stm32f0xx_it.c as TIM6 Interrupt Service Routine (ISR):

/*
 * This function handles TIM6 interrupts
 */

extern uint8_t timebase_irq;

void TIM6_DAC_IRQHandler()
{
	// Test for TIM6 update pending interrupt
	if ((TIM6->SR & TIM_SR_UIF) == TIM_SR_UIF)
	{
		// Clear pending interrupt flag
		TIM6->SR &= ~TIM_SR_UIF;

		// Do what you need
		timebase_irq = 1;
	}
}

 
Timers can be used to fire interrupts in many situations. Therefore, and it should be an automatic practice each time you write an interrupt handler, we start by testing which event lead us to the interrupt service routine. Because we only turned on the interrupt based on “update” event, there should be no other reason to be there… but still, it has to be done by verifying that the UIF flag is set, and then clear it to re-enable next interrupt signal

Remember that ISR must be short. Here we only set to ‘1’ a global variable timebase_irq. This variable will be tested into the main() function.

 

2.3. The main() function

The main loop now keeps polling global variables to test whether interrupts occurred. Based on the value of these variables, actions are performed:

// Global variables

uint8_t	button_irq = 0;
uint8_t 	timebase_irq = 0;

// Main program

void main()
{
	// Configure System Clock
	SystemClock_Config();

	// Initialize Button pin
	BSP_PB_Init();

	// Initialize Console
	BSP_Console_Init();
	my_printf("Console Ready!\r\n");

	// Initialize timebase
	BSP_TIMER_Timebase_Init();

	// Initialize NVIC
	BSP_NVIC_Init();

	// Main loop
	while(1)
	{
		// Process button upon interrupt
		if (button_irq == 1)
		{
			my_printf("#");
			button_irq = 0;
		}

		// Process main task upon timebase interrupt
		if (timebase_irq == 1)
		{
			my_printf(".");
			timebase_irq = 0;
		}
	}
}

Build and flash the project into the target. Open the console and see if you have a ‘.’ printing every 200ms.

image001.png
 

The system is now very responsive to the push-button pressure because is it spending most of the time looping through the test of the two global variables waiting for an event to process. In the same time, the main task is reliably processed every 200ms.

 

  Commit name "Timer periodic interrupt"
  Push onto Gitlab

 

3. Summary

In many situations, the MCU will be asked to process data at regular time intervals. Using delays in loops must be avoided because a delay is something that keeps the MCU busy (counting). Using timers together with interrupt must be your first choice. The setup shown in this tutorial can be used as a basic configuration for many applications.