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.
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();
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.