ARM: Temporizadores

Los temporizadores son unos registros internos en la memoria del microcontrolador capaces de realizar retardos de precisión o tareas periódicas, valiéndose de la entrada de reloj para contar el número de veces que le llega un pulso y, tras dividir por el preescaler (para que no vaya demasiado rápido), sumarlo al registro.

Una vez dicho registro se “desborda”, es decir, excede el máximo o mínimo número posible, puede crear una interrupción.

Como conocemos con precisión la frecuencia a la que trabajan y nosotros le asignamos el preescaler, somos capaces de hacer un cálculo del tiempo aproximado que necesita para desbordar.

Un contador funciona de la misma forma, pero utilizando como reloj un pin externo, de forma que podemos contar eventos que ocurren en ése pin.

Además de crear retardos, el timer puede averiguar la frecuencia o periodo de algunos sensores y señales externas como códigos infrarrojos o de radiofrecuencia.

Uno de los pocos problemas de usar timers es que tienen que “hacer cola” a la hora de crear una interrupción por el tema de las prioridades asignadas. Aunque la solución es darles la prioridad máxima cuando sea totalmente necesario.

Entre las ventajas, la CPU no gasta recursos ni tiene que prestar atención a un timer hasta que éste desborda. Los timers tienen funciones listas para arrancar y parar la cuenta cuando sea necesario.

Los ARM Cortex-M tienen un timer principal de 24 bits (systick) y otros 14 de distintas capacidades y frecuencias.

¿Qué es el preescaler? Como he mencionado anteriormente, sirve para ralentizar la cuenta del timer. Si ponemos un preescaler de 2, por ejemplo, al registro del timer sólo se sumará 1 cuando hayan llegado 2 pulsos de reloj. En la tabla anterior podemos ver que llega hasta los 65536 pulsos, por lo que se puede alargar mucho más un sólo registro de timer.

El timer SysTick

Es el temporizador principal y más simple de hacer funcionar, no tiene preescaler. Éste timer, al contrario que los demás, no puede pararse. Sin embargo, sólo es necesaria una función para configurarlo al tiempo que necesitemos.

Por defecto, tiene la prioridad más alta entre las interrupciones.

El valor con el que se debe cargar la función del timer se calcula mediante la fórmula:

Valor_Systick = Systick_Clock (168 MHz) * Tiempo (s)

Teniendo en cuenta que no puede exceder 0xFFFFFF. Las funciones a utilizar son:

  • SysTick_Config(uint16_t Valor_Systick); //Inicializa el contador con el valor, y lo habilita junto a su interrupción
  • NVIC_SetPriority(SysTick_IRQn, Número_prioridad); //Si se desea cambiar la prioridad
  • En stm32f4xx_it.c se crea la función void SysTick_Handler(void) que es la ISR de éste timer

El resto de timers

Éstos ya no son tan simples de configurar, si bien tienen varias ventajas sobre el SysTick. Veamos un ejemplo donde se configura el TIM3 (16 bit, 84 MHz) para hacer una cuenta de 2 segundos, y cada vez que se desborde que cambie el estado de un LED:

Podemos hacer la configuración tanto en el archivo Main como en cualquier otra librería.

void main(){
   //Inicialización de los LED (no voy a dar detalles)
   LED_Init();
   TIM3_Config(); //Configura el timer 3
   TIM_Cmd(TIM3, ENABLE); //Habilita la cuenta e interrupción del timer 3

   while(1); //Se espera al desbordamiento
}

void TIM3_Confif(){
   //En primer lugar se decide el prescaler que se le va a asignar
   //Lo que busco es una frecuencia de reloj de forma que multiplicada
   //por un número menor que 2^16 - 1 de 2 segundos
   //Pongamos por ejemplo 10 KHz:
   //TIM_Pre = (84MHz / 10 KHz) - 1 = 8399
   //TIM_Value = 10 KHz * 2 s = 20000

   TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //La variable correspondiente

   TIM_TimeBaseStructure.TIM_Prescaler = 8399; //Asignamos prescaler
   TIM_TimeBaseStructure.TIM_Period = 20000; //Y el tiempo correspondiente
   TIM_TimeBaseStructure.TIM_ClockDivision = 0; //No divide el reloj del bus
   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//La cuenta es hacia arriba

   TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //Se configura
   
   //Estructura de NVIC para la interrupción
   NVIC_InitTypeDef NVIC_InitStructure;

   // Habilitar el reloj del TIM3
   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

   //Configuración de la interrupción
   NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
   NVIC_Init(&NVIC_InitStructure);

   //Habilitar la interrupción
   TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);
} 

Y ahora en el stm32f4xx_it.c:

void TIM3_IRQHandler (void)  {

   if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
   {
      TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
      LED_Toggle(LED4);	
   }	
}