Nivel: Intermedio

¿Qué tengo que saber para este post?

  • Haber leído el post anterior.
  • Tener conocimientos sobre programación en C.
  • Saber qué son y como funcionan las interrupciones en un µControlador.

—————————————

El NVIC

Ya vimos como configurar el clock principal y los puertos de entrada/salida, utilizamos el SysTick y el debugger. Ahora, para poder seguir avanzando necesitamos hablar del NVIC (Nested Vectored Interrupt Control), que para decirlo mal y pronto de forma reducida, es el módulo o periférico que maneja las interrupciones en un Cortex-M. Cada fabricante aplica la cantidad de interrupciones y prioridades dependiendo de sus necesidades. En este caso tenemos hasta 43 fuentes de interrupción con 16 prioridades. Si tuvieramos que hablar de esto a fondo, ameritaría toda una sección, por eso no lo vamos a hacer.

Nuestro querido libOpenCM3 nos ofrece una abstracción lo suficientemente potente como para que necesitemos escencialmente estas tres funciones:

void nvic_enable_irq (uint8_t irqn);
void nvic_disable_irq (uint8_t irqn);
void nvic_set_priority (uint8_t irqn, uint8_t priority);

A éstas funciones hay que pasarle el número o vector de interrupción correpondiente, que porsupuesto tiene un define compuesto por NVIC_<periférico>_IRQ por ejemplo:

// Se habilita la interrupción de la USART1:
nvic_enable_irq(NVIC_USART1_IRQ);

En cuanto a las prioridades, si nunca las utilizaron, significa que si una interrupción se está ejecutando y llega otra con mayor prioridad, la primera queda a mitad de ejecución y se atiende la que tiene prioridad más alta (es uno de los casos). Siendo 0 (cero) la prioridad más alta, y 15 la más baja en este microcontrolador.

// Se baja la prioridad de la interrupción:
nvic_set_priority(NVIC_USART1_IRQ, 1);
Representación de una interrupción anidada

Cuando estamos usando muchas interrupciones al mismo tiempo se vuelve casi una obligación setear al menos un poco la prioridades (dependiendo de la implementación).

El NVIC es un módulo del Cortex-M, por lo tanto está presente en todos los micros de todos los fabricantes, y hay 16 vectores de interrupción que están reservados para este y tiene un máximo de 512 vectores (contando los 16), por lo tanto el fabricante podría tener hasta 496 interrupciones en su micro. Como anticipé, el STM32F103 tiene 43. Y donde y cómo se implementan depende del fabricante.

Las EXTI

Las EXTI o Iinterrupciones EXTernas, son las clásicas interrupciones que se ejecutan al alternar un pin del microcontrolador. En los microcontroladores de 8 bits más antiguos solo algunos pines tenían esta capacidad, pero en este cualquier pin puede ser fuente de interrupción. Como los GPIOs tiene 16 pines (del 0 al 15), tenemos del EXTI0 a EXTI15, pero debemos tener en cuenta que sólo las del 0 al 4 tienen vectores de interrupción independientes. Las que son del pin 5 al 9 comparten el mismo vector y ocurre lo mismo del 10 al 15. Pero todas pueden ser configuradas separadamente (flanco ascendente, descendente o ambos).

Cuando se configura el EXTI0, debemos seleccionar de qué puerto tomaremos la fuente de interrupción, del GPIOA o GPIOB, ya que no disponemos de otro GPIO que tenga disponible el pin 0 en la BluePill. Pero en otro modelo de la misma familia con más pines (este integrado tiene 48 pines, pero hay de 100 pines) se abre el abanico de posibilidades. Es importante que recordemos que antes de utilizar las EXTI debemos habilitar el periférico AFIO (Alternative Functions Input/Output) a través del RCC:

rcc_periph_clock_enable(RCC_AFIO);

La API para manejar las EXTI consta de las siguiente funciones básicas:

void exti_select_source(uint32_t exti, uint32_t gpioport);
void exti_set_trigger(uint32_t extis, enum exti_trigger_type trig);
void exti_enable_request(uint32_t extis);
void exti_disable_request(uint32_t extis);
void exti_reset_request(uint32_t extis);
uint32_t exti_get_flag_status(uint32_t exti);

Por ejemplo, si quisiéramos utilizar la EXTI1, una alternativa de configuración podría ser la siguiente:

// (...)
rcc_periph_clock_enable(RCC_AFIO);
// (...)
rcc_periph_clock_enable(RCC_GPIOB);
gpio_set_mode(GPIOB,GPIO_MODE_INPUT,GPIO_CNF_INPUT_FLOAT,GPIO0);
// Se configura la interrupción externa EXTI0:
exti_select_source(EXTI0, GPIOB);
exti_set_trigger(EXTI0, EXTI_TRIGGER_FALLING);
exti_enable_request(EXTI0);
// Se habilita la interrupción:
nvic_enable_irq(NVIC_EXTI0_IRQ);
// (...)

Las ISRs (Interruption Sub-Rutins)

En los ARM Cortex-M utilizando CMSIS se utilizaba una técnica que es darle el atributo de compilación WEAK a las funciones que servían de ISR, para poder redefinirlas en nuestro código. En libOpenCM3 hacen lo mismo, solo cambian los nombres para coincidir con su estilo snake_case y lo podemos encontrar en la documentación. Se componen del nombre como <vector/interrupción>_isr(), para las EXTI son las siguientes:

void exti0_isr (void);
void exti1_isr (void);
void exti2_isr (void);
void exti3_isr (void);
void exti4_isr (void);
void exti9_5_isr (void);
void exti15_10_isr (void);

Ejemplo de código

Tenemos toda la información necesaria para adentrarnos en un ejemplo concreto. En el siguiente vamos a hacer un programa que utilizando una interrupción externa cuenta (en milisegundos) la longitud del pulso que entra por el pin asociado a la misma. Luego, se hará titilar un LED conectado a PB6 una cantidad de veces igual a la cantidad de segundos que duró el pulso. Para ello, se reconfigurará la interrupción para que empiece a contar cuando llega el flanco descendente y termine de contar cuando llegue el ascendente, utilizando una variable que se incrementa dentro de la interrupción del SysTick.

El gráfico anterior ilustra cómo sería el comportamiento, NO ESTÁ A ESCALA, obviamente. El pin de interrupción que elegí fue el PB15, porque allí tengo un pulsador en mi placa básica de entradas y salidas, pero pueden utilizar cualquier otro:

#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/exti.h>
#include <libopencm3/cm3/nvic.h>
#include <libopencm3/cm3/systick.h>

#define FALLING false // Flanco descendente
#define RISING true   // Flanco ascendete

volatile uint32_t millis;      // cantidad de milisegundos
volatile bool trig;            // sentido del trigger
volatile uint32_t pulse_width; // cantidad de ms que dura el pulso

/**
 * @brief Demora bloqueante en milisegundos
 * 
 * @param ms cantidad de milisegundos a demorar.
 */void delay_ms(uint32_t ms);

int main(void)
{
    // Configuración del SYSCLK
    rcc_clock_setup_in_hse_8mhz_out_72mhz();

    // PC13 (LED de la BluePill) como salida
    rcc_periph_clock_enable(RCC_GPIOC);
    gpio_set_mode(GPIOC, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO13);
    gpio_set(GPIOC, GPIO13);

    // LED en PB6 (activo en alto), el PB15 como entrada flotante (default)
    rcc_periph_clock_enable(RCC_GPIOB);
    gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO6);
    gpio_set_mode(GPIOB, GPIO_MODE_INPUT, GPIO_CNF_INPUT_FLOAT, GPIO15);

    /* Para configurar las EXTI es necesario activar el 
       Alternative Functions Input/Output              */    rcc_periph_clock_enable(RCC_AFIO);

    /* Se configura la interrupción EXTI15 en el GPIOB con flanco
       inicialmente descendente.                                 */    exti_select_source(EXTI15, GPIOB);
    trig = FALLING;
    exti_set_trigger(EXTI15, EXTI_TRIGGER_FALLING);
    exti_enable_request(EXTI15);

    // Habilitación de la interrupción en el NVIC:
    nvic_enable_irq(NVIC_EXTI15_10_IRQ);

    // Se configura el SysTick para interrumpir cada 1ms:
    systick_set_frequency(1000, rcc_ahb_frequency);
    systick_counter_enable();
    systick_interrupt_enable();

    while (true)
    {
        if (pulse_width) // Si el pulso es distinto de 0:
        {
            uint32_t s = pulse_width / 1000; // Cantidad de segundos que duro el pulso
            s *= 2;                          // Doble para prender y apagar
            while (s--)                      // El LED PB6 titila la cantidad de
            {                                //    segundos que duró el pulso:
                gpio_toggle(GPIOB, GPIO6);
                delay_ms(300);
            }
            pulse_width = 0; // se resetea para que no se repita el "titileo"
        }
    }
}


void delay_ms(uint32_t ms)
{
    uint32_t tm = millis + ms;
    while (millis < tm);
}

void exti15_10_isr(void)
{
    static uint32_t lm = 0;
    exti_reset_request(EXTI15); // Se baja el flag
    if (trig == FALLING)        // Si fue un flanco descendente
    {
        lm = millis;               // Tomo la referencia de tiempo
        gpio_clear(GPIOC, GPIO13); // prende el LED de la BluePill
        trig = RISING;             // Se configura la interrupción por flanco ascendente
        exti_set_trigger(EXTI15, EXTI_TRIGGER_RISING);
    }
    else
    {
        pulse_width = millis - lm; // Se carga la longitud del pulso
        gpio_set(GPIOC, GPIO13);   // Se apaga el LED de la BluePill
        trig = FALLING;            // Se configura la interrupción por flanco descendente
        exti_set_trigger(EXTI15, EXTI_TRIGGER_FALLING);
    }
}

void sys_tick_handler(void)
{ // cada 1ms:
    millis++;
}

No lo he dicho, pero saben que ante cualquier duda pueden utilizar los comentarios. Les dejo el repo de GitLab y hasta la próxima.

https://gitlab.com/tute-avalos/libopencm3-04-extis

msavalos

Soy un hobbista: toco la guitarra, mandolina, descompongo en partituras; también soy aficionado a la fotografía, he hecho algún cursillo por ahí; me encanta la programación, sea de un programa de PC para procesar datos, una interfaz gráfica o un microcontrolador; evidentemente linuxero; y, cayéndose de maduro, geek. Hincha del más grande, técnico en electrónica, ex-estudiante de ingeniería en electrónica en la UTN FRBA, Técnico Superior en Informática y Profesor. Doy clases en el nivel medio en el Gobierno de la Ciudad de Buenos Aires e instituciones privadas y a nivel terciario en una sede dependiente de la UTN. Sobre mi afinidad política, bueno, podría decir que soy militante del Software Libre y medio zurdito (no me ofende).

Ver comentarios

Entradas recientes

Páginas embebidas con ESP8266 (WebServer)

Nivel: Básico ¿Qué tengo que saber para este post? Diseño básico de páginas con HTML…

3 años hace

Modificación de fuente ATX para laboratorio.

Nivel: Avanzado. La modificación de fuentes ATX para su utilización en laboratorio o comunicaciones es…

3 años hace

FLISoL Shield ARDUINO

El pasado 23 de abril se celebró la 18° edición del FLISoL en la que…

3 años hace

Regulador de tensión MOSFET para Moto.

Circuito del regulador, con los elementos de simulación. Típicamente, las motocicletas de baja cilindrada utilizan…

3 años hace

Review Analizador Lógico de menos de u$s10 en Linux

¿Qué puedo decir? Siempre quise tener un analizador lógico, había visto estos pequeños y baratos…

4 años hace

Oversampling con bajos recursos (+Bits ADC)

Nivel: Intermedio ¿Qué tengo que saber para este post? Entender el uso de un ADC.Programación…

4 años hace

Esta web usa cookies.