30 de junio de 2015

EXTI STM32F4 Discovery

Como vimos en GPIO STM32F4 Discovery, el empleo del polling es una técnica muy sencilla, pero por contra, tiene el inconveniente de que puede generar retardos no deseados y también, que el evento o flanco no sea detectado debido a que en ese momento se esté ejecutando otra línea de código, ademas del consumo de recursos para realizar el sondeo de los periféricos.

Para solventar estos defectos, sin introducir una gran dificultad al código, recurrimos al empleo de interrupciones. Según Wikipedia una interrupción es una señal recibida por un procesador para indicarle que debe interrumpir la ejecución normal de este y pasar a ejecutar el código especifico para tratar la situación requerida por la interrupción. Dicho código se conoce como ISR (Interrupt Service Routine, rutina de atención a la interrupción).

Nuestro dispositivo en concreto, dispone de 23 lineas de interrupción externas:
  • 16 de ellas (EXTI_Line0 a EXTI_Line15) están disponibles para direccionarlas directamente a los puertos de entrada/salida. Por ejemplo, si la interrupción se genera en el pin PA0 (botón de usuario) se usará EXTI_Line0, si la interrupción se genera en el pin PD5 se usará EXTI_Line5 y así sucesivamente.
  • Las restantes 7 están conectadas a periféricos específicos, según vemos en la siguiente tabla:
EXTI_Line
Periférico
EXTI_Line16
PVD Output
EXTI_Line17
RTC Alarm event
EXTI_Line18
USB OTG FS Wakeup from suspended event
EXTI_Line19
Ethernet Wakeup event
EXTI_Line20
USB OTG HS Wakeup event
EXTI_Line21
RTC Tramper and Time Stamp events
EXTI_Line22
RTC Wakeup event

A continuación nos centraremos en las lineas de interrupción EXTI_Line0 a EXTI_Line15, que son las que conciernen a los pines de entrada/salida (por usar el botón de usuario).
Una vez seleccionada la linea (PA0 en nuestro caso) se habrá de configurar el manejador de interrupciones (interrupt handler) mediante el NVIC (Nested Vector Interrupt Controller), que se encarga de seleccionar la interrupción de mayor prioridad y de activar y desactivar estas (soporta hasta 256 vectores de interrupción diferentes).
En la siguiente tabla podemos ver el manejador de las interrupciones de los pines de entrada/salida para configurar el NVIC:

EXTI_Line
IRQn
ISR
EXTI_Line0
EXTI0_IRQn
EXTI0_IRQHandler
EXTI_Line1
EXTI1_IRQn
EXTI1_IRQHandler
EXTI_Line2
EXTI2_IRQn
EXTI2_IRQHandler
EXTI_Line3
EXTI3_IRQn
EXTI3_IRQHandler
EXTI_Line4
EXTI4_IRQn
EXTI4_IRQHandler
EXTI_Line5 a EXTI_Line9
EXTI9_5_IRQn
EXTI9_5_IRQHandler
EXTI_Line10 a EXTI_Line15
EXTI15_10_IRQn
EXTI15_10_IRQHandler

Y en las siguientes imágenes podemos ver lo hasta aquí descrito:



Vista esta pequeña introducción a la configuración de las interrupciones, pasamos a ver el ejemplo en el que se alterna la iluminación de los leds.

Las funciones de configuración (GPIO_Configure y SysTick_Configure) son las mismas, ademas de las utilizadas para generar los delays (Delay y TimingDelay_Decrement). En cambio, se ha introducido una nueva función de configuración para la interrupción del botón de usuario, como podemos ver a continuación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void EXTI_Configure (void)
{
    EXTI_InitTypeDef EXTI_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_APB2PeriphClockCmd (RCC_APB2Periph_SYSCFG, ENABLE);

    SYSCFG_EXTILineConfig (EXTI_PortSourceGPIOA, EXTI_PinSource0);

    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init (&EXTI_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init (&NVIC_InitStructure);
}

Lo primero es configurar SYSCFG para que sea consciente de que se usarán las interrupciones y a continuación especificamos la procedencia de estas. La estructura de inicialización de la interrupción externa se comenta a continuación:
  • Dado que usamos el pin cero, usaremos EXTI_Line0, que como se comentó, tiene correspondencia directa con el numero del pin que se escoja.
  • Se configura en modo interrupción (EXTI_Mode_Interrupt).
  • Se escoge como disparo de la interrupción el flanco de subida (EXTI_Trigger_Rising).
  • Se activa la linea de interrupción (ENABLE).
Seguidamente vemos la estructura del vector de interrupciones:
  • Se escoge el canal 0 de las interrupciones externas, que como vimos antes, corresponde en cierta medida con el pin escogido (EXTI0_IRQn).
  • Se escoge el nivel de prioridad (valor de 0 a 15), siendo 0 al no existir mas interrupciones.
  • Se escoge el nivel de subprioridad (valor de 0 a 15), que igualmente será 0 (0x0 en hexadecimal) al no existir mas interrupciones con el mismo nivel de prioridad.
  • Se activa el canal de interrupción (ENABLE).
Tras la configuración e inicialización de la interrupción, pasamos a ver la rutina de atención a la interrupción (ISR).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void EXTI0_IRQHandler (void)
{
    if (EXTI_GetITStatus (EXTI_Line0) != RESET)
    {
        i++;
        if (i == 3)
            i = 0;
        EXTI_ClearITPendingBit (EXTI_Line0);
    }
}

Cada vez que se pulse el botón, se accederá a la rutina anterior, en la cual se comprobará que la interrupción ha sido generada en la linea que esperamos y a continuación se incrementará el valor de i, en este caso declarada como variable global en el main a la que haremos referencia de la siguiente forma:

1
extern int i;

Las lineas descritas son las que finalmente sustituyen a la funcionalidad de las siguientes lineas que aparecían en el main del programa de la entrada anterior.

1
2
3
4
5
6
7
if (GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_0) == 1)
{
    while (GPIO_ReadInputDataBit (GPIOA, GPIO_Pin_0) == 1);
    i++;
    if (i == 3)
        i = 0;
}

Finalmente vuelvo a colocar un pequeño vídeo en el que esta funcionando en nuestro dispositivo el programa descrito. En este caso, se puede pulsar el botón en cualquier momento, que la rutina detectará esto e incrementará el contador i, pero si se pulsa varias veces antes de esperar a que finalice la secuencia actual de iluminación, saltará a la secuencia que indique i y no a la que iría justo después de la actual.


Para la siguiente entrada, nos centraremos en los Timers, primeramente en su uso mas sencillo, generar una interrupción cada x tiempo, contrastándola con la interrupción SysTick_Handler, seguido de otros empleos habituales de estos.

Y aquí está, como será siempre, el proyecto listo para importar: EXTI STM32F4 Discovery Dropbox