Interrupciones



Una interrupción es un evento que cambia el curso del programa, cuando ésta ocurre el microcontrolador salta a una parte del programa para ejecutar código que atiende a esa interrupción/evento, y una vez atendida vuelve al punto de programa desde donde ha saltado. En este caso se van a utilizar para leer los botones del LCD, cuando un botón se pulse éste activará una interrupción que avisa al micro del suceso.

En el AVR tenemos diferentes fuentes de interrupciones, cada una teniendo su espacio de memoria de programa para el vector de interrupción, en esta dirección se indicará cual es la dirección de salto donde se encuentra el código que se debe ejecutar para esa interrupción. El contador de programa se carga con la direccuón del vector de interrupción una vez que ésta sucede y ahí tendremos una instrucción de salto a la dirección de memoria donde está el código que atiende a la interrupción.

En la parte más baja de la memoria de programa están estos vectores de interrupción, si dos interrupciones ocurren al mismo tiempo la interrupción con una posición de memoria más baja se ejecuta primero, estos vectores también pueden ser ubicados en la "Boot flash section" en el caso de que usemos un boot loader.




Notes: 1. When the BOOTRST Fuse is programmed, the device will jump to the Boot Loader address at
reset, see “Memory Programming” on page 337.
2. When the IVSEL bit in MCUCR is set, Interrupt Vectors will be moved to the start of the Boot
Flash Section. The address of each Interrupt Vector will then be the address in this table
added to the start address of the Boot Flash Section.
3. Only available in ATmega640/1280/2560

Todas las interrupciones tienen su bit en el registro correspondiente para ser habilitadas o deshabilitadas, también hay un bit (Global Interrupt Enable) para habilitar o deshabilitar todas las interrupciones en el registro de Status (SREG).

Hay dos tipos de interrupciones las que se disparan por un evento que cuando tiene lugar activa un bit (flag) en un registro del microcontrolador y cuando se habilita salta y las que se disparan mientras la condición de la interrupción está presente, no tienen que tener ningún flag como en el caso anterior, si la condición de la interrupción desaparece antes de que ésta esté habilitada no se dispara.

El registro de Status no es salvado en el stack cuando se dispara la interrupción, se debe salvar y restaurar por software, el registro que va al stack, éste se encuentra en la SRAM (el programa empieza por un lado y el stack por el otro,) es el contador de programa, y se restaura cuando se sale de la interrupción para que el micro vuelva al código que estaba ejecutando.

Registros interrupciones.

Status.



IVSEL (Interrupt Vector Select): situa los registros de interrupción en memoria, cuando es cero se colocan al principio de la Flash, cuando es 1 al principio de la sección del bootloader. Para nuestro caso va a ser cero.
IVCE (Interrupt Vector Change Enable): sirve para mover los vectores de interrupción junto con el bit anterior.



El bit 7 es Global Interrupt Enable, que habilita o deshabilita todas las interrupciones.


Interrupciones externas y sus registros.

Estas interrupciones se pueden disparar desde el exterior del micro cambiando el nivel de tensión de uno de sus pines, todas tienen asociadas una o varias patillas del microcontrolador y son las que usaremos para los botones.

Las interrupciones externas son disparados por los pines INT7:0 y PCINT23:0 incluso aunque estén configurados como salida, lo que permite disparar interrupciones por soft. Hay 10 vectores de interrupción (con el salto a la dirección de programa  donde se encuentra el código que atiende a la interrupción) externa en la memoria, 7 de ellos (los de external interrupt request) se corresponden con las interrupciones asociadas a los pines del registro EIMSK, un vector/interrupción por cada bit del registro. Los otros 3 vectores (Pin Change Interrupt Request 0,1 y 2) se corresponden con las interrupciones asociadas a los pines de los registros PCMSK2, PCMSK1 y PCSMK0, una interrupción por cada registro, es decir los 8 bits/pines del registro pueden hacer saltar la misma interrupción, luego habrá que determinar en la rutina de atención a la interrupción cual de esos bits dentro del registro es el responsable, leyendo su entrada uno a uno.

Pin Change Interrupt. Interrupciones que se disparan cuando cambia el estado del pin asociado a cada interrupción, no necesitan la presencia de la señal de reloj en el micro.



PCIE2 (Pin Change Interrupt Enable 2):  habilita las interrupciones de los pines entre PCINT23:16, los pines son habilitados uno a uno en el registro PCMSK2. Cuando una de estas interrupciones ocurre se activa el flag correspondiente y se salta al vector de interrupción $0016 si todo lo necesario está habilitado.
PCIE1: lo mismo con PCINT15:8, PCI1, PCMSK1. $0014.
PCIE0: PCINT7:0, PCI0, PCMSK0. $0012.


Estos bits son los flags de las interrupciones habilitadas en el registro anterior, cuando una interrupción ocurre se pone a 1 el bit de este registro que se corresponde con la interrupción para indicar al microcontrolador que la interrupción ha tenido lugar. EL flag es limpiado cuando la rutina de interrupción ha sido ejecutada. Para limpiar un flag debemos escribir un uno lógico en el bit.


La máscara para PCIE2. Si uno de estos bits está activado y el bit PCIE2 del registro PCICR está activado, la interrupción está habilitada en el pin de entrada/salida correspondiente, y cuando hay un cambio en éste se dispara la interrupción de PCIE2.

De la misma forma con PCIE1 y PCIE0:






External Interrupt.

En este registro cada bit tiene su vector de interrupción, no es como el caso anterior que todos los bits del registro hacían saltar la misma interrupción.


Los bits habilitan la interrupción externa del pin correspondiente.


Los flags de las interrupciones asociadas al registro anterior.






En estos registros se configura el tipo de evento que dispara las interrupciónes externas asociadas a EIMSK según la tabla de debajo, por ejemplo podemos configurar que la interrupción suceda cuando en el pin tenemos un flanco de bajado o de subida, cualquiera de los dos, o cuando tenemos un nivel bajo en el pin. Los flancos de subida y bajada de las interrupciones de EICRB necesitan la presencia de reloj en el microcontrolador, mientras que las de EICRA son asíncronas. Al cambiar la configuración hay que deshabilitar el bit correspondiente en EIMSK para evitar que la interrupción salte como consecuencia de este cambio.



Interrupciones en C.
Link.

En AVR GCC una rutina de atención a la interrupción se declara de la siguiente forma:

ISR(NOMBRE_vect)
{
//code
}

Donde NOMBRE_vect identifica el vector de interrupción, puede tener más argumentos además del nombre según lo que queramos que haga la ISR, las opciones están en el link anterior.

Debomos añadir el #include <avr/interrupt.h> al programa.

Con sei() y cli() de avr/interrupts.h habilitamos y deshabilitamos todas las interrupciones poniendo a 1 ó 0 el bit de Global Interrupt Enable. Al entrar en una ISR se deshabilita el bit GIE a no ser que indiquemos lo contrario y al salir se habilita.

En nuestro programa tenemos 3 botones, y por tanto deberemos configurar una interrupción para cada uno si usamos las interrupciones externas o también podemos seleccionar una misma interrupción para los tres utilizando las Pin On Change Interrupt, luego tendremos que leer los botones uno a uno para determinar cual ha sido pulsado. En este caso se van a usar las interrupciones externas.

Cuando se pulsa un botón no se pasa de uno a cero o viceversa en el momento, si no que tenemos rebotes, podemos tener una señal compuesta por varios pulsos de 1 y 0 que pueden hacer saltar a la interrupción varias veces, lo mismo al soltar el botón que mantenemos pulsado. Para ello lo mejor es añadir electrónica adicional para eliminar los rebotes, como puede ser un filtro RC o varios circuitos que se pueden encotrar buscando un poco diseñados para tal fin. En este caso no hay electrónica adicional, por lo que la única opción que tenemos es eliminarlos por soft, para ello se puede hacer un delay de 30-40 ms en la ISR de tal forma que cuando salgamos de la interrupción y ésta esté otra vez lista para ser disparada los rebotes ya han desaparecido. En los pcbs finales habrá electrónica adicional en los botones para eliminar los posibles rebotes, no cuesta nada hacerlo y facilitamos la programación, y cuando tenemos un programa complejo con varias interrupciones y muchos tiempos cuanto más facilitemos el programa mejor.

Para comprobar el funcionamiento de lo anterior un ejemplo sencillo:


#include <avr/io.h>
#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/interrupt.h>
#include "lcd.h"

//Botones del LCD, interrupciones externas.
//#define boton1 PORTD2   //INT2
//#define boton2 PORTD3   //INT3
//#define boton3 PORTE4    //INT4
   
void botones_inicializar();        //Inicializar Hard y configurar interrupciones.

int main(void)
{
    lcd_inicializar();
    botones_inicializar();

    char linea1[]="Proyecto FireFly";
    char linea2[]="Pulse un boton para";
    char linea3[]="comenzar";
    posicionar_cursor(1,3);
    lcd_escribir(linea1);
    posicionar_cursor(3,1);
    lcd_escribir(linea2);
    posicionar_cursor(4,7);
    lcd_escribir(linea3);

    while(1)
    {
    }
}

void botones_inicializar()        //Configurar registros del micro.
{
    //Tipo de evento que dispará la interrupción, 10, flanco de bajada.
    EICRA &= ~(1<<ISC20);    //INT2
    EICRA |= (1<<ISC21);
    EICRA &= ~(1<<ISC30);    //INT3
    EICRA |= (1<<ISC31);
    EICRB &= ~(1<<ISC40);    //INT4
    EICRB |= (1<<ISC41);

    EIMSK |= ((1<<INT2)|(1<<INT3)|(1<<INT4));        //Habilitar interrupciones externas 2,3 y 4.
    sei();        //Habilitar todas las interrupciones
}

//ISRs. Rutinas de atención a las interrupciones.
ISR(INT2_vect)
{
    char linea[]="Pulsado boton 1";
    lcd_comando(0x01);        //Limpiar pantalla LCD.
    posicionar_cursor(1,3);
    lcd_escribir(linea);
//  _delay_ms(40);    //Rebotes
}

ISR(INT3_vect)
{
    char linea[]="Pulsado boton 2";
    lcd_comando(0x01);        //Limpiar pantalla LCD.
    posicionar_cursor(2,3);
    lcd_escribir(linea);
//  _delay_ms(40);    //Rebotes
}

ISR(INT4_vect)
{
    char linea[]="Pulsado boton 3";
    lcd_comando(0x01);        //Limpiar pantalla LCD.
    posicionar_cursor(3,3);
    lcd_escribir(linea);
//  _delay_ms(40);    //Rebotes
}



El programa tiene un #include "lcd.h" en el que están las funciones necesarias y #defines para el control del LCD. Lo primero que debemos hacer es configurar el hardware del pic, de esto se encarga lcd_inicializar(); que configura los puertos como entrada/salida que necesita para funcionar, para los botones no tenemos que tocar ningún puerto ya que por defecto están todos configurados como entradas, lo que si tenemos que hacer es configurar los registros del microcontrolador para habilitar las interrupciones externas. En los bits de ECRIA y EICRB seleccionamos que las interruociones externas 2,3 y 4 (las que vamos a usar) se disparan cuando tenemos un flanco de bajada en la entrada, es decir cuando se pulsa el botón pasando de 1 a 0, si no configuramos estos bits por defecto estarían en 00, la interrupción se dispararía cuando tengamos un nivel bajo en el pin y de esta forma estaríamos entrando repetidamente a la interrupción mientras mantuvieramos el botón pulsado. Luego se habiltan las interrupciones en EIMSK poniendo a 1 el bit de las que queramos que estén activas y por último se habilitan todas las interrupciones del micro con las instrucción sei();, que pone a 1 el bit 7 (GIE) de SREG.

En el programa principal se escribe la pantalla de presentación en el LCD y pasa a un búcle infinito, del que sólo se puede salir mediante una interrupción externa disparada por los botones, cuando ocurre la interrupción saltamos a su ISR (aquí definimos su código) y ésta se encarga de limpiar la pantalla y escribir un mensaje en función del botón pulsado y cuando finaliza vuelve al bucle principal. Un ejemplo sencillo que sirve para comprobar que las interrupciones se están produciendo de forma correcta, en este caso no estamos haciendo nada fuera de las interrupciones pero en un programa normal debemos salvar todos los registros que se puedan modificar dentro de la ISR y usemos fuera de ella para luego restaurarlos y que el micro siga con su ejecución de instrucciones de forma correcta.

Hasta aquí con las interrupciones que aún queda mucho de ellas...

Video del funcionamiento.


Para cualquier comentario:
blog comments powered by Disqus