Gestor de interrupciones El gestor de interrupciones es quien se encarga de lidiar con eventos asincrónicos. La mayoría de estos eventos vienen de periféricos de hardware. Por ejemplo, un temporizador que alcanza un valor de período configurado, o un puerto UART que advierte sobre la llegada de datos. Otros son originados por el “mundo exterior”, por ejemplo, el usuario presiona un interruptor que hace que se genere una interrupción. Imagen 1. Diagrama de bloques de un núcleo Cortex-M. Una interrupción es un evento asíncrono que provoca detener la ejecución del código actual en base a una prioridad (cuanto más importante es la interrupción, mayor será su prioridad, lo que hará que una interrupción de menor prioridad se suspenda). La rutina que se ejecuta durante una interrupción se denomina “Rutina de servicio de interrupción” o “Interrupt Service Routine” por sus siglas en ingles ISR. Las interrupciones son una fuente de multiprogramación: el hardware es responsable de guardar el contexto de ejecución actual (es decir, los datos de la pila, el actual Contador de Programa y entre otras cosas) antes de cambiar al ISR. Esta funcionalidad es explotada por los sistemas operativos en tiempo real para introducir la noción de tareas. Sin ayuda del hardware es imposible contar con un verdadero sistema preventivo, que permita conmutar varios contextos de ejecución sin perder irreparablemente el flujo de ejecución actual. Las interrupciones pueden originarse tanto por el hardware como por el propio software. La arquitectura ARM distingue entre los dos tipos: interrupciones originadas por el hardware, interrupciones por el software (por ejemplo, un acceso a una ubicación de memoria no válida). En la terminología ARM, una interrupción es un tipo de excepción. Los procesadores Cortex-M proporcionan una unidad dedicada a la administración de interrupciones el cual se llama “Nested Vectored Interrupt Controller” (NVIC). NVIC (Administrador de vector de interrupciones anidadas). Es una unidad de hardware dentro de los microcontroladores basados en CortexM que es responsable de la administración de interrupciones. La imagen 2 muestra la relación entre la unidad NVIC, el núcleo del procesador y los periféricos. Aquí tenemos que distinguir dos tipos de periféricos: los externos al núcleo Cortex-M, pero internos a la MCU STM32 (por ejemplo, temporizadores, UARTS, etc.) y los periféricos externos a la MCU. La fuente de las interrupciones procedentes de la última clase de periféricos son puertos GPIO, que pueden configurarse como entrada o salida (por ejemplo, un botón conectado a un pin configurado como entrada) o para excitar un periférico externo avanzado (por ejemplo, un módulo USB). Un controlador programable dedicado, llamado “External Interrupt/Event Controller” (EXTI), es responsable de la interconexión entre las señales de E/S externas y el controlador NVIC, como veremos a continuación. Imagen 2. Relación entre NVIC, el núcleo Cortex-M y los perifericos del STM32. Como se indicó anteriormente, ARM distingue entre las excepciones del sistema, que se originan dentro del núcleo de la CPU, y las excepciones de hardware procedentes de periféricos externos, también llamadas “Solicitudes de Interrupción” (IRQ). Los programadores manejan las interrupciones mediante el uso de ISRs específicos, que están codificados en un nivel superior (a menudo usando el lenguaje C). El procesador sabe dónde ubicar estas rutinas gracias a una tabla indirecta que contiene las direcciones en memoria de las Rutinas de Servicio de Interrupción. Esta tabla se denomina comúnmente tabla del vector de interrupciones, y cada microcontrolador STM32 define su propia. Características principales. 82 canales de interrupciones. 16 niveles de prioridades programables. Re-priorización dinámica de las interrupciones. Agrupación de valores de prioridad y subprioridades. El NVIC y la interfaz del núcleo del procesador están estrechamente acoplados, lo que permite una baja latencia procesamiento de interrupciones y procesamiento eficiente. Todas las interrupciones, incluidas las excepciones básicas, son gestionadas por NVIC. El procesador guarda automáticamente el contexto durante la incidencia de una interrupción y retorna a ese estado al atender dicha interrupción. Tabla del Vector de interrupciones en un STM32. Esta tabla puede ser encontrada en el manual de referencia RM0090 en la Tabla número 61. En la imagen número 3 se muestra una parte de dicha tabla, la cual nos ofrece información de la interrupción como: Posición en el vector de interrupción Prioridad de la interrupción. El tipo de prioridad (Si es configurable o no). Nombre de la interrupción. Una pequeña descripción de la interrupción. Dirección en memoria del microcontrolador. Imagen 3. Tabla del vector de interrupciones para STM32F405xx/07xx. Incluso si la tabla vectorial contiene las direcciones de las rutinas del controlador, el núcleo de Cortex-M necesita una forma de encontrar la tabla vectorial dentro de la memoria. Por convención, la tabla vectorial comienza en la dirección de hardware 0x00000000 en todos los procesadores basados en Cortex-M. Si la tabla vectorial reside en la memoria no volátil interna, y dado que dicha memoria en todas los MCUs STM32 está asignado desde la dirección 0x08000000, como se muestra en la imagen 4, pero en cuanto el microcontrolador enciende, enmascara esta dirección y la ve como 0x00000000. Imagen 4. Fragmento del mapa de memoria de un STM32. La imagen 5 muestra cómo se organiza la tabla de vectores en la memoria. El cero de entrada de esta matriz es la dirección del puntero de la pila principal (MSP) dentro de la SRAM. Normalmente, esta dirección corresponde al final de la SRAM, es decir, su dirección base más su tamaño. A partir de la segunda entrada de esta tabla, podemos encontrar todas las excepciones y el manejador de interrupciones. Imagen 5. Vector de interrupciones de un microcontrolador STM32. Interrupciones externas. Como hemos visto en la imagen 2, los microcontroladores STM32 proporcionan un número variable de fuentes de interrupción externas conectadas al NVIC a través del controlador EXTI, que a su vez es capaz de gestionar varias líneas EXTI. El número de fuentes y líneas de interrupción depende de la familia STM32 específica. Los GPIO están conectados a las líneas EXTI, y es posible habilitar interrupciones para cada GPIO de MCU, incluso si la mayoría de ellos comparten la misma línea de interrupción. Por ejemplo, para una MCU STM32F4, hasta 114 GPIOs están conectados a 16 líneas EXTI. Sin embargo, sólo 7 de estas líneas tienen una interrupción independiente asociada con ellas. En la imagen 6 podemos ver que todos los pines Px0 están conectados a EXTI0, todos los pines Px10 están conectados a EXTI10 y todos los pines Px15 están conectados a EXTI15. Sin embargo, las líneas EXTI 10 y 15 comparten el mismo IRQ dentro del NVIC (y por lo tanto son atendidas por el mismo ISR). Esto significa que: Sólo un pin PxY puede ser una fuente de interrupción. Por ejemplo, no podemos definir PA0 y PB0 como pines de interrupción de entrada. Para las líneas EXTI que comparten el mismo IRQ dentro del controlador NVIC, tenemos que codificar el ISR correspondiente para que debamos ser capaces de discriminar qué líneas generaron la interrupción. Por ejemplo, si definimos PA10 y PA15, nesecitamos leer los dos pines para identificar cual genero el evento de interrupción. Imagen 6. Líneas EXTI 0, 10 y 15 en un MCU STM32F4. Para futuras referencias, puede consultar el manual de referencia RM0090 en la sección 12.2.5 “External interrupt/event line mapping”. Niveles de prioridad para las interrupciones. Un rasgo distintivo de la arquitectura ARM Cortex-M es la capacidad de priorizar interrupciones (excepto las tres primeras excepciones de software que tienen una prioridad fija, como se muestra en la imagen 7). La prioridad de interrupción permite definir dos cosas: Los ISR que se ejecutarán primero en caso de interrupciones simultáneas; Aquellas rutinas que se pueden optar opcionalmente para iniciar la ejecución de un ISR con una prioridad más alta. El mecanismo de prioridad de interrupción en Cortex-M4 es más avanzado que el disponible en los microcontroladores basados en Cortex-M0/0+. Los desarrolladores tienen un mayor grado de flexibilidad. En los núcleos CortexM3/4/7, la prioridad de cada interrupción se define a través del registro IPR. Se trata de un registro de 8 bits en la arquitectura de núcleo ARMv7-M que permite hasta 255 niveles de prioridad diferentes. Sin embargo, en la práctica, los MCUs STM32 que implementan estos núcleos utilizan solamente los cuatro bits superiores de este registro, viendo todos los otros bits igual a cero. Imagen 7. Registro IPR implementado en un STM32. La imagen 8 muestra claramente cómo se interpreta el contenido de los DPI. Esto significa que tenemos los únicos dieciséis niveles de prioridad máxima: 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0. Cuanto menor sea este número, mayor será la prioridad. Es decir, un IRQ que tiene una prioridad igual a 0x10 tiene una prioridad más alta que un IRQ con un nivel de prioridad igual a 0xA0. Si dos interrupciones disparan al mismo tiempo, el que tenga la mayor prioridad será atendido primero. Si el procesador ya está atendiendo una interrupción y una prioridad más alta interrumpe, entonces la ejecución de la actual interrupción se suspende y el control pasa a la interrupción de prioridad más alta. Cuando la ejecución de la interrupción de alta prioridad se completa, la ejecución vuelve a la interrupción anterior, mientras no se produzca otra interrupción con mayor prioridad. Hasta ahora, el mecanismo es sustancialmente el mismo de Cortex-M0/0+. La complicación surge del hecho de que el registro IPR puede subdividirse lógicamente en dos partes: una serie de bits que definen la prioridad de prioridad y una serie de bits que definen la sub-prioridad. El primer nivel de prioridad rige las prioridades de prioridad entre ISRs. Si un ISR tiene una prioridad más alta que otra, prevendrá la ejecución del ISR de menor prioridad en caso de que se active. La sub-prioridad determina qué ISR se ejecutará primero, en caso de un ISR múltiple pendiente. Imagen 8. Subdivisión del registro IPR entre prioridad y sub-prioridad. La imagen 9 muestra un ejemplo de interrupciones de diferentes prioridades. La letra A representa un IRQ con una prioridad baja (0x20) que se dispara en el tiempo t0. El ISR inicia la ejecución de A-ISR, pero el B-IRQ que tiene una prioridad más alta (0x10), se dispara en el instante t1. Entonces la ejecución de A-ISR se detiene y comienza la ejecución de B-ISR. Después en un tiempo t3 se dispara C-IRQ (prioridad 0x00), la ejecución de B-ISR se detiene y se inicia la ejecución de C-ISR. Cuando la ejecución de C-ISR termina, se reanuda la ejecución de B-ISR hasta que termina. Finalmente se reanuda la ejecución de AISR hasta el final. A este proceso de control de interrupciones se le llama: “anidado”. El mecanismo inducido por prioridades de interrupción conduce al nombre del controlador NVIC o Controlador de Interrupción Vectorial Anidado. Imagen 9. Interrupciones anidadas.