miércoles, 5 de abril de 2017

Interrupciones externas -ARM Cortex-M4-

Programa

/* Febrero de 2016
Tarjeta: STM32 Discovery
Microcontrolador: STM32F411VET6U
IDE: IAR Embedded Workbench 

Programa que, empleando una interrupción externa, cambia el estado de los LED
de la tarjeta Discovery cada vez que se presiona un pulsador conectado entre el
pin de 3V de la tarjeta y PE3 (el pulsador no está conectado a ningún circuito
anti-rebotes).
*/

#include "stm32f4xx.h"

#define LED_Discovery_on 0x0000F000;    // LED verde (PD12), LED naranja (PD13),
                                                                       // LED rojo (PD14), LED azul (PD15),
                                                                       // [Bits 12, 13, 14 y 15 de ODR] = 1
#define LED_Discovery_off 0xFFFF0FFF;  // [Bits 12, 13, 14 y 15 de ODR] = 0

int estado_LED = 0;                                       // Estado de los LED de la tarjeta


//--- Función que inicializa el puerto D y configura los pines de los LED
void ini_LED_Discovery (void){
  RCC->AHB1ENR   |= 0x00000008;    // Habilita el reloj para el puerto D
  GPIOD->MODER   |= 0x55000000;    // Config. PD12, PD13, PD14 y PD15 como sal. dig.
  GPIOD->OSPEEDR |= 0xFF000000;  // PD12, PD13, PD14 y PD15 funcionarán a alta
                                                                // velocidad
}

//--- Función que inicializa el puerto E y configura el pin del pulsador
void ini_pulsador(){
  RCC->AHB1ENR   |= 0x00000010;    // Habilita reloj puerto E
  //GPIOE->MODER |= 0x00000000;    // Configura puerto E como entrada
  GPIOE->PUPDR   |= 0x00000080;      // Pull-down para PE3
  GPIOE->OSPEEDR |= 0x000000C0;  // PE3 a alta velocidad
}

//---- Rutina de servicio de interrupción. Esta función usa un código anti-rebotes: 

// ejecuta retardo; comprueba si el pulsador está pulsado (*); si es así, se ejecuta la
// orden estado_LED ^= 1. Aunque sería mejor utilizar un circuito anti-rebotes
// para no emplear retardos dentro de la subrutina de servicio interrupción y para no comprobar el estado del pin.

void EXTI3_IRQHandler (void) {
  for (int i=0; i<30000; i++);              // Retardo
  if (GPIOE->IDR & 0x00000008){  // (*)
    for (int i=0; i<30000; i++);            // Retardo

    estado_LED ^= 1;                          // Conmuta de 0 a 1 o de 1 a 0
  }
  EXTI->PR |= 0x00000008;         // Borra el indicador de interr. para EXTI3
  if (estado_LED == 1) GPIOD->ODR |= LED_Discovery_on;     // Enciende los LED
  if (estado_LED == 0) GPIOD->ODR &= LED_Discovery_off;  // Apaga los LED
}

//--- Función principal del programa
void main (void)  {  
  ini_LED_Discovery();
  ini_pulsador();
 
  // Órdenes para configurar y habilitar la interrupción externa
  RCC->APB2ENR  |=  0x00004000;           // Habilita el reloj para SYSCFG
  EXTI->IMR   |=  0x00000008;                   // Hab. solicitud interrupción en EXTI3
  EXTI->RTSR  |=  0x00000008;                  // Hab. disparo de interrupción por flanco de subida
  SYSCFG->EXTICR[0]  |=  0x00004000;  // Selecciona PE3 para EXTI3
  NVIC->ISER[0]  |=  0x00000200;             // Hab. interrupción para EXTI3 en NVIC


  // Bucle infinito
  while (1) {
  }
}




Circuito

Se utiliza un pulsador conectado a PE3 sin ninguna resistencia pull-down externa, dado que el programa habilita una pull-down interna en el microcontrolador. Tampoco se le conecta al pulsador ningún circuito anti-rebotes, lo cual se subsana mediante unas líneas de código, situadas dentro de la rutina de servicio de interrupción.








Figuras










➤ Observaciones

- Para no complicar el programa, el retardo utilizado se realiza con un bucle for. Las cuestiones relativas a la señal de reloj del microcontrolador se explican en esta entrada del blog y la forma de generar un retardo, de una manera más adecuada, se detalla en esta otra.

- En el programa se accede a los siguientes registros: RCC_AHB1ENR, GPIOD_MODER, GPIOD_OSPEEDR, GPIOE_PUPDR, GPIOE_OSPEEDR, GPIOE_IDR, GPIOD_ODR, RCC_APB2ENR, EXTI_IMR, EXTI_RTSR, SYSCFG_EXTICR1, NVIC_ISER0.

- El programa, además de la función principal, emplea otras tres funciones: ini_LED_Discovery(), para inicializar el puerto D y para configurar los pines conectados a los LED de la tarjeta Discovery; ini_pulsador(), para inicializar el puerto E y configurar el pin conectado al pulsador; EXTI3_IRQHandler(), que es la rutina de servicio de interrupción y se ejecuta cuando se presiona el pulsador.

- Las órdenes contenidas en ini_LED_Discovery() e ini_pulsador() están explicadas en las primeras entradas del blog. En el programa está inactiva la orden GPIOE->MODER |= 0x00000000, debido a que es el valor que tiene el registro GPIOE_MODER por defecto, lo cual implica que todos los pines del puerto están configurados como entradas digitales. En este ejemplo, es necesario que PE3 sea una entrada digital.

- Dentro la función principal, están las órdenes para configurar y habilitar una interrupción interna (también podría haberse hecho escribiendo todas estas órdenes dentro de una función).

- Aunque en este ejemplo, para las salidas digitales, se ha empleado el registro GPIOD_ODR, es preferible usar BSRR.

- Ver apartado 10.2.4 de RM0383 Reference Manual. Para generar una interrupción, la línea de interrupción tiene que ser configurada y habilitada. La forma de hacerlo es programando los dos registros de disparo con el tipo de flanco deseado (de subida o de bajada) y habilitando la solicitud de interrupción; esto último se realiza escribiendo un ‘1’ en el correspondiente bit en el registro de máscara de interrupción. Se genera una solicitud de interrupción cuando se produce un flanco, del tipo seleccionado, en la línea de interrupción externa, . El pending bit correspondiente a la línea de interrupción también se habilita. Esta solicitud se puede borrar escribiendo un ‘1’ en el pending register. Una interrupción también puede ser generada escribiendo un ‘1’ en el registro de interrupciones/eventos. 

- Ver apartado 10.2.4 de RM0383 Reference Manual. Para configurar las 23 líneas como fuentes de interrupción hay que hacer lo siguiente: (1) Configurar el bit de máscara de las 23 líneas de interrupción (EXTI_IMR); (2) configurar los bits de selección de disparador de las líneas de interrupción (EXTI_RTSR y EXTI_FTSR); (3) configurar los bits de habilitación y de máscara que controlan el canal NVIC_IRQ asignado al controlador de interrupciones externas (EXTI), de manera que una interrupción proveniente de una de las 23 líneas pueda ser correctamente admitida.

- Todas las interrupciones son manejadas por NVIC (nested vectored interrupt controller). Los registros de NVIC están detallados en el documento PM0214 Programming manual.

- RCC->APB2ENR |= 0x00004000: pone a ‘1’ el bit 14 del registro RCC_APB2ENR para habilitar el reloj de SYSCFG (system configuration controller). SYSCFG es el controlador de configuración del sistema y se utiliza principalmente para reasignar la memoria accesible en el área de código y gestionar la conexión de la línea de interrupción externa de los pines GPIO del microcontrolador.

- EXTI->IMR |= 0x00000008: habilita la solicitud de interrupción en EXTI3, poniendo a ‘1' el bit 3 del registro EXTI_IMR.

- EXTI->RTSR |= 0x00000008: habilita el disparo de la interrupción por flanco de subida para EXTI3 y queda deshabilitado para las demás líneas de interrupción. Escribe un '1' en el bit 3. Para usar un flanco de bajada sería necesario utilizar el registro EXTI->FTSR.

- SYSCFG->EXTICR[0] |= 0x00004000: escribe ‘0100’ en los bits 15, 14, 13 y 12 (EXTI3[3:0]) del registro 1 de configuración de interrupciones externas (SYSCFG_EXTICR1). Con esta operación, el pin que se elige para EXTI3 es PE3. Para EXTI3, sólo es posible escoger los pines designados por el número 3 de cada puerto del microcontrolador (ver la figura 30, arriba).


- NVIC->ISER[0] |= 0x00000200: escribe un ‘1’ en el bit 9 del registro NVIC_ISER0, lo cual produce la habilitación de la interrupción asociada a EXTI3. El registro NVIC_ISER0 tiene que ver con las 32 primeras posiciones de la tabla de vectores de los microcontroladores STM32F411xC/E, dicha tabla enumera las fuentes de interrupción del microcontrolador. La interrupción correspondiente a EXTI3 ocupa la posición número 9 de la tabla, de ahí que haya que escribir un ‘1’ el bit 9 del registro ISER0 (ver tabla 27, arriba). La información sobre el registro NVIC_ISER0 se puede consultar en el documento PM0214 Programming manual.


- EXTI->PR |= 0x00000008: esta instrucción, que esta dentro de la rutina de servicio de interrupción, se encarga de borrar el indicador de interupción para EXTI3. El bit 3 (PR3) del registro EXTI_PR (pending register) queda habilitado cuando se produce un flanco de subida en la línea de interrupción externa EXTI3, es decir, cuando tiene lugar una petición de interrupción procedente de EXTI3. La forma de deshabilitar ese bit es poniéndolo a ‘1’.

- Los registros NVIC_ISERx sirven para habilitar interrupciones, pero también muestran qué interrupciones han sido habilitadas.

- También se puede establecer la prioridad de la interrupción mediante uno de los registros NVIC_IPRx, pero en este ejemplo no es necesario, dado que sólo se usa una interrupción.

- while (1) {}: es un bucle infinito. En este ejemplo, sirve para demostrar que aunque el programa esté permanentemente ocupado con alguna tarea, puede ir atendiendo las interrupciones que se produzcan.
 

➤ Registros empleados

Las siguientes imágenes representan una parte de los registros utilizados en el programa y en ellas están señalados los bits que éste modifica o consulta. Únicamente aparecen los registros relacionados con la interrupción externa y el registro GPIOE_PUPDR, los demás se pueden encontrar en las primeras entradas del blog.

Para más información, ver el documento RM0383 Reference Manual y PM0214 Programming manual.
















No hay comentarios: