Una interrupcin es un aviso provocado por un mdulo del PIC, por un
cambio en el estado de un pin o un recordatorio de que ha pasado un cierto tiempo. Como su nombre indica este aviso interrumpir la tarea que se este haciendo en ese momento y pasaremos a ejecutar una rutina de servicio o gestin de la interrupcin.
Veremos un repaso de los bits y registros de control asociados a las diferentes interrupciones, como habilitarlas y como escribir rutinas de servicio (ISR). Crearemos definiciones (#define) que nos permitirn operar con las interrupciones sin tener que recordar los bits/registros asociados, a la vez que facilitarn la tarea de portar nuestro programa a otro compilador y/o microcontrolador.
Es importante familiarizarse con el manejo de interrupciones, ya que nos evita poder manejar muchos tipos de eventos sin estar pendientes de ello. En sucesivos tutoriales veremos como el uso de interrupciones nos permite aprovechar de forma mucho ms eficiente los recursos del PIC.
Archivos de cdigo asociado a esta entrada: int_defs.h test_int.c
Antes de entrar en detalles sobre cada interrupcin por separado hemos de describir un par de bits (bits 7 y 6 del SFR INTCON) que tienen un efecto global sobre la activacin de bloques de interrupciones.
INTCON.GIE -> habilita (1) o deshabilita (0) todas las interrupciones. INTCON.PEIE -> habilita (1) o deshabilita (0) las interrupciones asociadas a mdulos perifricos.
Por ejemplo, antes de poder usar la interrupcin del temporizador TMR0 debemos asegurarnos de que las interrupciones globales estn habilitadas (INTCON.GIE=1). Si lo que deseamos es usar la interrupcin asociada a la recepcin del puerto serie, tanto INTCON.GIE como INTCON.PEIE deben estar a 1, ya que dicha interrupcin est declarada como perifrica.
Para usar estos bits de una forma ms conveniente incluiramos los siguientes defines (en el programa principal o bien en un fichero .h incluido en el proyecto):
Hay varias razones para usar estas (o similares definiciones): Siempre es ms facil recordar enable_global_ints que acordarse de que hay que poner a 1 el bit GIE del registro INTCON. Si cambiamos a otro compilador donde la forma de direccionar los bits de los registros es diferente, basta cambiar las definiciones (esto es, usar un fichero .h distinto). En el caso p.e. del compilador MikroC Pro en vez de INTCONbits.GIE usaramos INTCON.GIE. Si cambiamos a otro controlador, puede que los bits correspondientes cambien de registro y/o nombre. De nuevo, un cambio en el fichero de encabezamiento hace que no sea preciso cambiar el resto del cdigo.
Bits asociados a cada interrupcin:
Adems de los bits anteriores que afectan de forma global a las interrupciones, para cada fuente de interrupcin hay tres bits asociados: IE (interrupt enable): determina si la interrupcin est o no habilitada. Si no lo est, aunque la condicin de la interrupcin se cumpla, la interrupcin no se producir. IF (interrupt flan): indica si la condicin de la interrupcin se ha producido. Es responsabilidad del usuario borrar dicho bit antes de regresar de la ISR. IP (interrupt priority): indica si la prioridad asociada a la interrupcin es alta (1) o baja (0). Obviamente, solo tiene efecto si est activado el modo de niveles de prioridad. Consideremos por ejemplo la interrupcin asociada la temporizador TMR0. Un temporizador es simplemente un contador que se incrementa con cada ciclo mquina (4 ciclos del oscilador). Dicho contador puede configurarse como de 8 o 16 bits. Cuando dicho contador rebosa y pasa de 0xFF a 0x00 (modo 8 bits) o de 0xFFFF a 0x0000 (modo 16 bits) la bandera IF asociada a la interrupcin del TMR0 se pone a 1. Para activar o desactivar la interrupcin, establecer su prioridad o acceder al valor de su bandera de interrupcin definiramos los siguiente macros:
Las dos primeras lneas activan o desactivan la interrupcin a travs del correspondiente bit de IE. La tercera define el flag (IF) asociado al temporizador como TMR0_flag. Finalmente las dos ltimas establecen la interrupcin del TMR0 como de alta o baja prioridad, modificando el correspondiente bit IP.
Algunas interrupciones pueden tener algunos bits extras dedicados. Por ejemplo, en la interrupcin INT0 (asociada a detectar cambios en el pin RB0) podemos especificar si la interrupcin salta al pasar de nivel alto a bajo o viceversa.
Como se observa en los ejemplos anteriores todos los bits que hemos visto hasta ahora estn en los registros especiales INTCON e INTCON2. Al ir aadiendo ms fuentes de interrupcin se han tenido que crear nuevos registros para interrupciones de perifricos (PIEx,PIRx), lugares donde almacenar los bits de prioridades (IPRx), etc.
Podemos repetir las definiciones anteriores para el resto de las interrupciones. Cada interrupcin tendra una serie de definiciones similares a las listadas anteriormente para TMR0. Por ejemplo, para la interrupcin INT1 tenemos definidas:
De esta forma tendramos una forma sencilla de activar una u otra interrupcin, consultar sus banderas y establecer su prioridad sin tener que recordar la posicin de los diferentes bits en los registros. Lo usual es guardar dichas definiciones en un fichero que incluiramos en nuestros proyectos. El fichero que usare en sucesivos programas es int_defs_C18.h.
Para el resto de las interrupciones tenemos similares definiciones, sin ms que cambiar TMR0 o INT1 por el cdigo de la interrupcin en cuestin. Entre las interrupciones que ms usaremos podemos destacar:
La interrupcin se produce al rebosar y pasar por 0 los contadores asociados.
Cambios en pines (INT0, INT1, INT2, RB)
INTx: La interrupcin se produce con un cambio en el nivel de los pines RB0, RB1 y RB2 respectivamente. Es posible establecer si la interrupcin se produce en el flanco ascendente o descendente (ver INT1_low2high e INT1_high2low en los ejemplos citados antes).
RB: se produce ante cualquier cambio en los pines RB4 a RB7. Al contrario que las anteriores no es posible especificar un pin o una transicin determinada.
Puerto serie (Rx,Tx)
RX, interrupcin producida con la recepcin de un carcter.
TX, interrupcin de transmisin que nos avisa cuando el buffer de transmisin est libre para mandar un nuevo carcter.
Conversor Analgico/Digital (AD)
AD, nos avisa cuando se ha completado una conversin Analgica-Digital.
Rutinas de servicio de interrupciones (ISRs)
Como hemos visto, para que una interrupcin se produzca, los siguientes bits deben estar a 1:
GIE -> Habilita todas las interrupciones
PEIE -> Necesario (adems de GIE) para las interrupciones perifricas.
El bit IE (int enable) de la interrupcin deseada, habilitando dicha interrupcin en particular.
El bit IF (int flag) de la interrupcin, indicando que la condicin de la interrupcin se ha producido.
Es responsabilidad del usuario activar los tres primeros bits con los correspondiente comandos enable que hemos definido. Por ejemplo para activar la interrupcin del timer 0, TMR0, haramos:
enable_global_ints; // Enable global ints enable_TMR0_int; // Enable TMR0 int
Si deseramos activar la interrupcin de recepcin del puerto serie (RX) haramos:
enable_global_ints; // Enable global ints enable_perif_ints; // Enable peripheral ints enable_RX_int; // Enable RX int
Con los tres primeros bits en 1, cuando se cumpla una condicin de interrupcin el microcontrolador pondr a 1 el correspondiente bit IF y la interrupcin se producir. El microcontrolador pasar el control a la posicin 0x0008.
Cuando esto suceda es fundamental que en dicha posicin tengamos un cdigo vlido para gestionar la interrupcin. A dicho cdigo se le denomina rutina de servicio de la interrupcin (Interrupt Service Routine o ISR).
Las funciones mnimas de una ISR son las siguientes:
Determinar que interrupcin se ha producido, ya que en principio todas las interrupciones se procesan en la misma rutina. Esto lo haremos chequeando que IF bit est a 1. Obviamente no ser necesario chequear las flags de todas las interrupciones, sino slo de aquellas que estn habilitadas.
Una vez determinada que interrupcin en particular ha sucedido, ejecutar el cdigo que sirve a dicha interrupcin.
Finalmente, antes de volver, poner a 0 la correspondiente bandera IF. Si no lo hacemos, en cuanto devolvamos el control a la rutina principal se volver a verificar la condicin de interrupcin y volveremos a entrar en la ISR.
Hemos dicho que ante una interrupcin el PIC saltar a una direccin determinada. Cmo podemos poner el cdigo de la ISR en la posicin de memoria adecuada? Eso va a depender del compilador. Usualmente los compiladores nos facilitan dicha tarea teniendo una rutina de nombre predefinido para las interrupciones. Si se define una rutina con dicho nombre el compilador la pondr en la posicin adecuada al compilar por lo que se ejecutar
Veremos como lo hacen el C18 de Microchip y el MikroC Pro de Mikroelektronica.
Compilador C18:
#pragma interrupt high_ISR void high_ISR (void) { if (TMR0_flag) // ISR de la interrupcion de TMR0 { PORTCbits.RC0=1; Delay10KTCYx(255); PORTCbits.RC0=0; TMR0_flag=0; } }
La primera parte del cdigo usa #pragma interrupt para indicarle al compilador que la rutina siguiente es una interrupcin (una interrupcin debe volver con una instruccin especial, Return from Interrupt, en vez de un Return normal). El nombre usado high_ISR() es arbitrario y podemos cambiarlo por el que queramos.
La rutina high_ISR() se ejecutar al producirse cualquier interrupcin. Por eso lo primero que hacemos es chequear que interrupcin ha causado la llamada a la ISR, consultando las posibles IF de las interrupciones posibles (las habilitadas). En este caso que tenemos una sola interrupcin habilitada podramos evitar dicha comprobacin.
Una vez verificado (TMR0_flag==1) que efectivamente estamos ah por una interrupcin del TMR0 simplemente escribimos el cdigo que deseemos ejecutar. Qu es lo que se hace en la ISR del TMR0? Poca cosa: levantamos el pin RC0, esperamos 255x10000 ciclos de reloj (unos 0.5 segundos) y ponemos de nuevo a 0 el pin RC0 antes de regresar. El delay podra representar el tiempo que estaramos ocupados haciendo lo que se supone que tendramos que hacer cada cierto tiempo. Mientras veamos encendido RC0 es que estamos dentro de la ISR. Muy importante: antes de regresar reseteamos la bandera (TMR0_flag=0) para evitar entrar de nuevo en la interrupcin.
La segunda parte del cdigo usa la directiva #pragma code que nos permite posicionar un cdigo en una direccin de memoria dada. Se define una nueva funcin code_0x0008 (de nuevo un nombre arbitrario) cuyo cdigo es muy sencillo, simplemente un salto a la rutina high_ISR anterior.
Se ve que C18 deja ver lo que realmente est pasando. En la posicin 0x0008 no podramos meter una rutina muy grande (al fin y al cabo en 0x0018 podramos tener otra rutina, la de la interrupcin de bajo nivel). Lo nico que metemos es un salto a la verdadera rutina de interrupcin high_ISR().
MikroC Pro:
En el caso del compilador MikroC Pro, hay una rutina de nombre reservado, interrupt( ). Veamos como escribiramos el cdigo anterior para MikroC Pro.
void interrupt(void) // High priority interrupt { if (TMR0_flag) // TMR0 ISR { PORTC.RC0=1; delay_ms(600); PORTC.RC0=0; TMR0_flag=0; } }
Vemos que el cdigo es ms sencillo. Basta declarar la funcin (reservada) interrupt y el compilador se ocupa de todo. Por debajo el compilador MikroC Pro har lo mismo que el C18 (escribir el cdigo en algn sitio y poner un salto en 0x0008 a esa direccin), pero la ventaja (o inconveniente segn se mire) es que hace que el usuario se despreocupe de los detalles.
Nosotros seguiremos a partir de ahora la programacin en C18. De hecho, las nicas diferencias eran en la forma de declarar las interrupciones, ya que el programa principal sera idntico en ambos compiladores.
Lo nico que tenemos que hacer en el main() es configurar de forma adecuada TMR0 para que salte en el tiempo deseado. Para ello damos cierto valor al registro T0CON (veremos detalles de cmo se hace en la entrada dedicada a los temporizadores). Tras programar el temporizador lo nico que queda es habilitar las interrupciones globales y la interrupcin del TMR0 en particular:
enable_TMR0_int; // Enable Timer0 interrupt enable_global_ints; // Enable global ints
while(1); }
Vemos que en el bucle principal no hacemos nada. Sin embargo, al ejecutar el programa veremos como el pin RC0 parpadea: cada 1.6 segundos entra la interrupcin en la que RC0 se pone en 1 (LED encendido) y permanece as durante 600 msec, apagndose hasta el prximo ciclo.
La moraleja: aunque nuestro programa principal no este haciendo nada, el microcontrolador puede estar haciendo cosas a travs del cdigo asociado a las interrupciones habilitadas. Veremos la aplicacin de este principio en otras aplicaciones.
Podis comprobar que si comentis cualquiera de las dos lneas enable el LED en RC0 permanece apagado, ya que la interrupcin no se ejecuta al no estar habilitada (o al no estar habilitadas la interrupciones de forma global).
Es importante que siempre que habilitemos una interrupcin tengamos definida la correspondiente funcin de manejo de interrupciones (posicionada en 0x0008). Si no es as, al producirse la interrupcin y saltar el programa a la direccin 0x0008, al no encontrar un cdigo valido en esa direccin el comportamiento es impredecible.