Bare-Metal STM32: preste atención al evento de interrupción

Las interrupciones no son solo la base de nuestra vida diaria. También son cruciales para los sistemas informáticos operativos, así como lo son, ya que permiten que un sistema responda inmediatamente a un evento. Si bien en las computadoras de escritorio estas interrupciones son menos prominentes que antes, cuando todavía teníamos que configurar manualmente la IRQ para el nuevo hardware con interruptores integrados en una tarjeta ISA, las IRQ junto con DMA (acceso directo a la memoria) siguen siendo lo que hace que el sistema parezca rápido. al usuario si se utiliza correctamente.

En los sistemas de microcontroladores como el STM32, las interrupciones son aún más importantes, ya que esto permite que la MCU reaccione con fuerza en tiempo real a un evento (externo). Especialmente en algo como un proceso industrial o en un automóvil moderno, hay muchos eventos que simplemente no se pueden procesar cuando el procesador se registra. Además, las interrupciones junto con los administradores de interrupciones proporcionan una forma conveniente de responder a eventos externos e internos.

En este artículo veremos lo que se necesita para configurar controladores de interrupción en las entradas GPIO, usando un ejemplo práctico de un codificador de incremento rotativo.

Se requiere algún ensamblaje

Diagrama de kernel Cortex-M4 en la familia de MCU STU32F4. (ST PM0214, sección 1.3).

Las interrupciones en las MCU STM32 tienen dos tipos: internas y externas. Ambos tipos de interrupciones usan el mismo kernel periférico en el kernel Cortex-M: el Controlador de interrupción vectorial anidadoo NVIC. Dependiendo del núcleo Cortex-M correcto, este periférico puede soportar cientos de interrupciones, con múltiples niveles de prioridad.

Sin embargo, estas interrupciones no son todas atribuibles libremente. Si miramos el manual de referencia para el MCU STM32F4xx (específicamente RM0090, sección 12), podemos ver que para las líneas de interrupción NVIC estamos reducidos a 82 a 91 canales de interrupción enmascarables de hasta 250 en total para el núcleo NVIC. periférico en el Cortex-M4.

Todos estos canales de interrupción tienen un propósito específico, como se define en la tabla de vectores (por ejemplo, RM0090, Tabla 62), que tiene más de 90 entradas. Algunas de estas interrupciones están reservadas para eventos del procesador, la memoria o el bus de datos (por ejemplo, fallas de funcionamiento), mientras que las que suelen ser de mayor interés para un programador son las relacionadas con los periféricos que no son del núcleo. Casi cualquier periférico, ya sea un punto de tiempo, USART, canal DMA, bus SPI o I2C, tiene al menos una interrupción relacionada con ellos.

Diagrama de bloques de periféricos (RM0090, 12.2.5).

Lo mismo ocurre con el periférico EXTI (Interruptor externo / Controlador de eventos). En las familias STM32F1, F4 y F7 STM32, el periférico EXTI tiene asociadas 7 interrupciones y 3 en el F0 (STM32F04x y otras). Para el primer grupo, estos se describen como:

  • EXTI línea 0
  • EXTI-línea 1
  • EXTI-línea 2
  • EXTI-línea 3
  • EXTI-línea 4
  • EXTI línea 5 a 9
  • Línea EXTI 10 a 15
  • Como puede ver, obtenemos 16 líneas en el periférico EXTI, que se pueden usar con pines GPIO, pero algunas de esas líneas están agrupadas, lo que requiere un poco más de trabajo en el administrador de conmutadores para determinar qué línea disparar si es deseable. Las líneas mismas están conectadas usando moscas a los pines GPIO como en el siguiente diagrama:

    STM32F4 EXTI a mapeo periférico GPIO. (RM0090, 12.2.5)

    Esto significa que en las familias F1 a F7, los pines GPIO 0 a 4 reciben una interrupción dedicada, que comparten con otros GPIO periféricos. Los 11 pines restantes de cada GPIO periférico se agrupan en las dos interrupciones restantes. En la familia STMF0xx, las líneas 0 y 1, así como las 2 y 3 y la 4 a la 15 se agrupan en un total de tres interrupciones.

    Las líneas EXTI restantes están conectadas a periféricos como RTC, Ethernet y USB para funciones como Wakeup y eventos de alarma.

    Tiempo de demostración: aumente los codificadores y las interrupciones

    Encoder incremental rotativo mecánico montado en PCB.

    La forma en que funcionan los codificadores de refuerzo rotativos mecánicos es que, alternativamente, crean contacto entre el pin de entrada único y el pin de salida A&B. El resultado es una salida pulsante de la que se pueden deducir el sentido de rotación y la velocidad. Se utilizan comúnmente en paneles de control, donde dos pines adicionales proporcionan una función de botón.

    Sin embargo, para detectar correctamente estos pulsos, nuestro código que funciona en la MCU debe ser consciente de cada pulso. La falta de pulsos dará como resultado efectos visibles en el usuario, como una respuesta lenta en el sistema, o incluso un cambio de dirección que no se inicia de inmediato.

    Para este ejemplo, usaremos un codificador rotatorio estándar, conectando su pin de entrada a tierra y conectando los pines A y B a las entradas GPIO. Esta puede ser cualquier combinación de pines GPIO en cualquier puerto, si recordamos que no nos superponemos con los números de pin: si usamos, por ejemplo, PB0 para la señal A, no podemos usar PA0 o PC0 para la señal B. Sin embargo, podemos utilice PB1, PB2, etc.

    Establecer interrupciones externas

    Los pasos necesarios para configurar una interrupción externa en un pin GPIO se pueden resumir de la siguiente manera:

    • Habilitar SYSCFG (excepto en F1).
    • Habilitar EXTI en RCC (excepto en F1).
    • Aro EXTI_IMR registrarse para el pin para habilitar la línea como una interrupción.
    • Aro EXTI_FTSR Y EXTI_RTSR registros para el pin de disparo en el flanco descendente y / o ascendente.
    • Aro NVIC prioridad a la interrupción.
    • Habilitar interrupción en el NVIC registros.
    • Por ejemplo, la familia de MCU STM32F4, primero habilitaríamos el periférico SYSCFG (controlador de configuración del sistema).

RCC->APB2ENR |= (1 << RCC_APB2ENR_SYSCFGCOMPEN_Pos);

La SYSCFG periférico gestiona las líneas de interrupción externas al GPIO periféricos, es decir, el mapeo entre un GPIO periférico y el EXTI línea. Digamos, si queremos usar PB0 y PB4 como los pines de entrada para las señales A y B de nuestro codificador, debemos configurar las líneas relevantes a la apropiada. GPIO periférico. Para el puerto B, esto se haría en SYSCFG_EXTICR1 y SYSCFG_EXTICR2, porque cada registro de 32 bits cubre un total de cuatro EXTI líneas:

Registre SYSCFG_EXTICR1 para MCU STM32F4. (RM0090, 9.2.3)

Aunque un poco confuso a primera vista, configurar estos registros es relativamente simple. P.ej Por PB0:

SYSCFG->EXTICR[0] |= (((uint32_t) 1) << 4);

Dado que la sección de cada línea en el registro es de cuatro bits, a la izquierda cambiamos el valor de puerto apropiado para alcanzar la posición requerida. Para PB4 hacemos lo mismo, pero en el segundo registro, y sin desplazamiento a la izquierda, porque ese registro comienza con la línea 4.

En este punto, estamos casi listos para configurar los registros EXTI y NVIC. Primero debemos habilitar el GPIO periférico que pretendemos usar y configurar los pines en el modo de entrada en la configuración de extracción, como aquí para PB0:

RCC->AHB1ENR |= (1 << RCC_AHBENR_GPIOBEN_Pos); 
GPIOB->MODER &= ~(0x3);
GPIOB->PUPDR &= ~(0x3);
GPIOB-&>PUPDR |= (0x1);

Digamos que queremos configurar PB0 para que comience en un flanco descendente, primero debemos habilitar la Línea 0 y luego configurar los registros de activación:

pin = 0;
EXTI->IMR |= (1 << pin); 
EXTI->RTSR &= ~(1 << pin); 
EXTI->FTSR |= (1 << pin);

Todos estos registros son bastante sencillos, y cada línea tiene su propio bit.

Con eso completo, simplemente necesitamos habilitar las interrupciones ahora y asegurarnos de que nuestros administradores de interrupciones estén en su lugar. Primero, el NVIC, que se hace más fácilmente con las funciones estándar de CMSIS, como aquí para PB0, con nivel de prioridad de interrupción 0 (el más alto):

NVIC_SetPriority(EXTI0_IRQn, 0);
NVIC_EnableIRQ(EXTI0_IRQn);

Los manejadores de interrupciones (ISR) deben coincidir con la firma de la función definida en la tabla de vectores, que se carga en la RAM durante el inicio. Cuando se utilizan los títulos de dispositivos ST estándar, estos tienen la siguiente firma:

void EXTI0_IRQHandler(void) {
// ...
}

Cuando use C ++, tenga en cuenta que los ISR necesitan absolutamente un símbolo de función de estilo C (es decir, sin abuso de nombre). O envuelva todo el ISR en extern "C" {} bloquear o reenviar declaraciones del ISR para eludir esto.

Envolvente

Con todo esto implementado y el codificador conectado a los pines correctos, necesitamos ver que los dos manejadores de interrupciones que hemos implementado se inicien cada vez que encendemos el codificador. Gran parte del código de este artículo se basó en el ejemplo "Eventful" del proyecto Nodate. Este ejemplo utiliza las API implementadas en la clase Interrupts de ese marco.

Si bien vale un poco de miedo, usar interrupciones e incluso configurarlas manualmente como se describe en este artículo no debería ser demasiado aterrador después de tener una descripción general básica de los elementos, su función y cómo configurar los registros individuales.

El uso de periféricos NVIC y EXTI para detectar entradas externas es, por supuesto, solo un ejemplo de interrupciones en la plataforma STM32. Como se mencionó anteriormente, sirven para muchos más propósitos incluso fuera del núcleo Cortex-M. Se pueden usar para despertar al MCU de una condición de suspensión o para que un programa de tiempo periférico active periódicamente una interrupción para que una función específica se pueda realizar con un alto determinismo en lugar de controlar una variable en un bucle o similar.

Espero que este artículo haya proporcionado una descripción general y una base sólida para futuras aventuras con interrupciones STM32.

  • Greg A. dice:

    este artículo solo me recuerda lo divertido que es el desarrollo integrado. una vez que obtenga la colección correcta de hojas de datos / manuales de usuario (un pequeño desafío para el STM32 en comparación con PIC, pero), realmente puede verlo todo. sin capas. Los tipos de preguntas de "seguridad de subprocesos" que enfrenta al interactuar con los ISR son muy concretos, no hay "referencia bla bla mientras que bla resulta en comportamiento indefinido". La limpieza puede ser un gran dolor en el trasero, pero sigo pensando que es divertido.

    Es triste que no tuviera un proyecto que necesitara un microcontrolador para cruzar mi radar durante algunos años.

Miguel Vidal
Miguel Vidal

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *