Generic timers

Providing a SysTick timer is not mandatory for low-end microcontrollers. Some targets may not have a system timer, but all of them expose some kind of interface to program a number of general-purpose timers for the program to be able to implement time-driven operations. Timers in general are very flexible and easy to configure, and are generally capable of triggering interrupts at regular intervals. The STM32F4 provides up to 17 timers, each with different characteristics. Timers are in general independent from each other, as each of them has its own interrupt line and a separate peripheral clock gate. On the STM32F4, for example, these are the steps needed to enable the clock source and the interrupt line for timer 2. The timer interface is based on a counter that is incremented at every tick. The interface exposed on this platform is very flexible and supports a number of features, including the selection of a different clock source for input, the possibility to concatenate timers, and even the internals of the timer implementation that can be programmed. It is possible to configure the timer to count up or down, and trigger interrupt events on different values of the internal counter. Timers can be one-shot or continuous.

An abstraction of the timer interface can usually be found in support libraries provided by the silicon vendor, or in other open source libraries. However, in order to understand the interface exposed by the microcontroller, the example provided here is once again directly communicating with the peripherals using the configuration registers.

This example mostly uses the default settings for a general-purpose timer on the STM32F407. By default, the counter is increased at every tick, up to its automatic reload value, and continuously generates interrupt events on overflow. A prescaler value can be set to divide the clock source to increase the range of the possible intervals. To generate interrupts spread at a constant given interval, only a few registers need to be accessed:

  • Control registers 1 and 2 (CR1, CR2)
  • The DMA/Interrupt enable register (DIER)
  • The status register (SR)
  • The prescaler counter (PSC)
  • The auto-reload register (ARR)

In general, the offsets for these registers are the same for all the timers, so that, given the base address, they can be calculated using a macro. In this case, only the register, for the timer in use are defined:

#define TIM2_BASE (0x40000000)
#define TIM2_CR1 (*(volatile uint32_t *)(TIM2_BASE + 0x00))
#define TIM2_DIER (*(volatile uint32_t *)(TIM2_BASE + 0x0c))
#define TIM2_SR (*(volatile uint32_t *)(TIM2_BASE + 0x10))
#define TIM2_PSC (*(volatile uint32_t *)(TIM2_BASE + 0x28))
#define TIM2_ARR (*(volatile uint32_t *)(TIM2_BASE + 0x2c))

Also, for readability, we define some relevant bit position in the registers that we are going to configure:

#define TIM_DIER_UIE (1 << 0)
#define TIM_SR_UIF (1 << 0)
#define TIM_CR1_CLOCK_ENABLE (1 << 0)
#define TIM_CR1_UPD_RS (1 << 2)

First of all, we are going to define a service routine. The timer interface requires us to clear one flag in the status register. In this simple case, all we do is increment a local variable, so that we can verify that the timer is being executed by inspecting it in the debugger. We mark the timer2_ticks variable as volatile so it does not get optimized out by the compiler, since it is never used in the code:

void isr_tim2(void)
{
static volatile uint32_t timer2_ticks = 0;
TIM2_SR &= ~TIM_SR_UIF;
timer2_ticks++;
}

The service routine must be associated, by including a pointer to the function in the right position within the interrupt vector defined in startup.c:

isr_tim2 , // TIM2_IRQ 28

If the timer is connected to a different branch in the clock tree, as in this case, we need to account for the additional scaling factor between the clock bus that feeds the timer, and the actual CPU clock frequency, while calculating the values for the prescaler and the reload threshold. Timer 2 on STM32F407 is connected to the APB bus, which runs at half of the CPU frequency.

This initialization is an example of a function that automatically calculates TIM2_PSC and TIM2_ARR values, and initializes a timer based on the given interval, expressed in milliseconds. The clock variable must be set to the frequency of the clock source for the timer, which may differ from the CPU frequency.

The following definitions are specific for our platform, mapping the address for the clock gating configuration and the interrupt number of the device we want to use:

#define APB1_CLOCK_ER (*(volatile uint32_t *)(0x40023840))
#define APB1_CLOCK_RST (*(volatile uint32_t *)(0x40023820))
#define TIM2_APB1_CLOCK_ER_VAL (1 << 0)
#define NVIC_TIM2_IRQN (28)

And, here is the function to invoke from main to enable a continuous timer interrupt at the desired interval:

int timer_init(uint32_t clock, uint32_t interval_ms)
{
uint32_t val = 0;
uint32_t psc = 1;
uint32_t err = 0;
clock = (clock / 1000) * interval_ms;
while (psc < 65535) {
val = clock / psc;
err = clock % psc;
if ((val < 65535) && (err == 0)) {
val--;
break;
}
val = 0;
psc++;
}
if (val == 0)
return -1;
nvic_irq_enable(NVIC_TIM2_IRQN);
nvic_irq_setprio(NVIC_TIM2_IRQN, 0);
APB1_CLOCK_RST |= TIM2_APB1_CLOCK_ER_VAL;
DMB();

APB1_CLOCK_RST &= ~TIM2_APB1_CLOCK_ER_VAL;
APB1_CLOCK_ER |= TIM2_APB1_CLOCK_ER_VAL;
TIM2_CR1 = 0;
DMB();
TIM2_PSC = psc;
TIM2_ARR = val;
TIM2_CR1 |= TIM_CR1_CLOCK_ENABLE;
TIM2_DIER |= TIM_DIER_UIE;
DMB();
return 0;
}

The example presented here is only one of the possible applications of system timers.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset