Sie sind auf Seite 1von 9

Interrupciones (conceptos bsicos)

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

------------------------------------------------------------------------------------
------------------------

Habilitacin de interrupciones:

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):

// Global flags (without priority levels)
#define enable_global_ints INTCONbits.GIE=1
#define enable_perif_ints INTCONbits.PEIE=1
#define disable_global_ints INTCONbits.GIE=0
#define disable_perif_ints INTCONbits.PEIE=0

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:

#define enable_TMR0_int INTCONbits.TMR0IE=1
#define disable_TMR0_int INTCONbits.TMR0IE=0
#define TMR0_flag INTCONbits.TMR0IF
#define set_TMR0_high INTCON2bits.TMR0IP=1
#define set_TMR0_low INTCON2bits.TMR0IP=0

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:

#define enable_INT1_int INTCON3bits.INT1IE=1
#define disable_INT1_int INTCON3bits.INT1IE=0
#define INT1_low2high INTCON2bits.INTEDG1=1
#define INT1_high2low INTCON2bits.INTEDG1=0
#define INT1_flag INTCON3bits.INT1IF
#define set_INT1_high INTCON3bits.INT1IP=1
#define set_INT1_low INTCON3bits.INT1IP=0


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:

Temporizadores/Contadores (TMR0, TMR1, TMR2, TMR3)

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;
}
}

// Code @ 0x0008 -> Jumps to ISR routine
#pragma code high_vector = 0x0008
void code_0x0008(void) {_asm goto high_ISR _endasm}
#pragma code

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:

void main() {

TRISC=0; PORTC=0x00; // PORTC output
T0CON = 0b10000110; // Starts TMR0, rolls over every 128x65536 cycles = 1.67 sec @ 20 Mhz

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.

Das könnte Ihnen auch gefallen