En
cualquier aplicación tendremos que manejar varias fuentes de interrupciones. El
problema es que, en principio, una interrupción no puede ser a su vez
interrumpida por otra. En este primer ejemplo, mientras se este procesando una
interrupción, aunque se produzca otra, tendrá que esperar. Esta es una de las
razones por las que un código de interrupción debe ser lo más reducido posible.
No es
difícil pensar una situación donde esto pueda suponer un problema. Imaginad que
una de las interrupciones es critica e impredecible (p.e. contar el número de
pulsos que llegan a un pin), mientras que la otra, que se ejecuta cada cierto
tiempo, está asociada a una tarea menos importante (p.e. refrescar una pantalla con el número de
pulsos recibidos). El problema es que si los pulsos llegan muy rápidamente es
posible que algunos lleguen mientras estamos en la segunda tarea. Como dicha
tarea no se interrumpirá (ella misma es una interrupción) algunos pulsos no
serán contados y perderemos dicha información.
La solución será usar prioridades en las interrupciones.
La familia
PIC18 introdujo una importante novedad respecto a los PIC16 en el sentido de
que es posible definir 2 niveles de prioridad en las interrupciones. Una
interrupción de alto nivel puede interrumpir a otra de bajo nivel, pero lo
contrario no sucederá. Presentaremos como configurar un PIC en este modo de prioridades y usaremos lo
aprendido para escribir un sencillo programa que solucione el problema
anterior.
Archivos
de código asociado a esta entrada: test_int2.c test_low_high_ok.c
---------------------------------------------------------------------------------------------
Añadamos al
ejemplo anterior una segunda fuente de interrupción. La segunda interrupción
que usaremos será la asociada al timer TMR1. El código usado sería ahora:
void TMR0_ISR(void)
{
PORTCbits.RC0=1;
Delay10KTCYx(255); PORTCbits.RC0=0;
TMR0_flag=0;
}
void TMR1_ISR(void)
{
PORTCbits.RC1=~PORTCbits.RC1;
TMR1_flag=0;
}
// High priority interruption
#pragma interrupt high_ISR
void high_ISR (void)
{
if (TMR0_flag) TMR0_ISR(); //
ISR de la interrupcion de TMR0
if (TMR1_flag) TMR1_ISR(); //
ISR de la interrupcion de TMR1
}
// Code @ 0x0018 ->
Jump to ISR for Low priority interruption
#pragma code high_vector = 0x0008
void code_0x0008(void) {_asm goto high_ISR _endasm}
#pragma code
Ahora hay dos comprobaciones en
la función ISR para ver si es TMR0 o TMR1 el causante de la llamada. En cada
caso se ejecuta un código distinto y se pone a 0 la bandera (IF)
correspondiente. Notad que al contrario de lo que pasaba con TMR0, la ISR del
TMR1 no se demora apenas. Simplemente cambia el valor del pin RC1 y vuelve.
En el
main(), configuraremos TMR0 como antes.
Por el contrario programamos a TMR1 (usando el registro T1CON) para que
su interrupción salte mucho más a menudo que la de TMR0, unas 50 veces por
segundo. Posteriormente
habilitaremos las interrupciones del
TMR0 y TMR1, las interrupciones globales y también las interrupciones
periféricas, ya que la interrupción del TMR1 está definida como interrupción
periférica:
void main() {
TRISC=0; PORTC=0x00;
// Starts TMR0, rolls over every 128x65536 machine
cycles = 1.67 sec @ 20 Mhz
T0CON = 0b10000110;
//
Starts TMR1, rolls over every 2x65536 machine cycles = 0.02 sec @ 20 Mhz
T1CON= 0b10010001;
enable_global_ints; enable_perif_ints;
enable_TMR0_int; //
Enable Timer0 interrupt
enable_TMR1_int; //
Enable Timer1 interrupt
while(1);
}
Al
ejecutar el programa anterior veréis que ambos LEDS conectados a RC0/RC1
parpadean (el correspondiente a RC1 mucho más rápido que el otro). Eso nos
indica que ambas interrupciones están sucediéndose. Sin embargo notaréis que
cuando RC0 está en alto, el parpadeo cesa en RC1. Esto es debido a que cuando
la interrupción del TMR0 está ejecutándose (RC0=1) la otra interrupción no
puede entrar, por lo que deja de parpadear RC1.
En esta
pantalla del osciloscopio se aprecia claramente esta situación. El canal 1
(arriba) monitoriza RC1 (rápido). El canal 2 (abajo) refleja el comportamiento
de RC0 (lento). Vemos que cuando RC0 está activo no hay actividad en RC1.
No es
difícil pensar una situación donde esto pueda suponer un problema. Imaginad que
una de las interrupciones es critica e impredecible (p.e. contar el número de
pulsos que llegan a un pin), mientras que la otra, que se ejecuta cada cierto
tiempo, está asociada a una tarea menos importante (p.e. refrescar una pantalla con el número de
pulsos recibidos). El problema es que si los pulsos llegan muy rápidamente es
posible que algunos lleguen mientras estamos en la segunda tarea. Como dicha
tarea no se interrumpirá (ella misma es una interrupción) algunos pulsos no
serán contados y perderemos dicha información. La solución será usar
prioridades en las interrupciones.
Interrupciones con prioridades:
En la
familia PIC18 podemos definir dos modos de interrupciones. En el primero,
denominado modo de compatibilidad, todas las interrupciones tienen la misma
prioridad. La rutina de servicio de interrupciones (ISR) debe colocarse en la
posición 0x0008 de la memoria. En este modo, mientras se está ejecutando una
interrupción ninguna otra puede atenderse. Este es el modo que hemos usado
hasta ahora.
En el
segundo modo las interrupciones pueden asignarse a dos niveles de prioridad.
Las interrupciones de alta prioridad siguen saltando a la posición 0x0008,
mientras que las de baja van a la 0x0018. En este caso una interrupción de alta
prioridad puede interrumpir a una de baja, pero no al contrario.
La
elección de un modo u otro depende del valor del bit IPEN (Interrupt Priority
Enable) del registro RCON. Como siempre,
en el fichero int_defs_C18.h tenemos algunas definiciones para facilitarnos la
tarea:
#define enable_priority_levels RCONbits.IPEN=1
#define disable_priority_levels RCONbits.IPEN=0
En el modo de compatibilidad los bits
INTCON.GIE e INTCON.PEIE que
habilitaban/deshabilitaban de forma global las interrupciones pasan a
denominarse GIEH (Global Int Enable High) y GIEL (Global Int Enable Low) y gobiernan la activación en bloque de las
interrupciones de alta y baja prioridad respectivamente:
// Global flags (with
priority levels)
#define enable_high_ints
INTCONbits.GIEH=1
#define enable_low_ints
INTCONbits.GIEL=1
#define disable_high_ints INTCONbits.GIEH=0
#define disable_low_ints
INTCONbits.GIEL=0
Modifiquemos
el código anterior declarando a la interrupción TMR1 (rápida) como de alta
prioridad y bajando la interrupción asociada al TMR0 a baja prioridad:
// High priority
interruption
#pragma interrupt high_ISR
void high_ISR (void)
{
if (TMR1_flag) TMR1_ISR(); //
ISR de la interrupcion de TMR1
}
// Code @ 0x0018 ->
Jump to ISR for Low priority interruption
#pragma code high_vector = 0x0008
void code_0x0008(void) {_asm goto high_ISR _endasm}
#pragma code
// Low priority
interruption
#pragma interruptlow low_ISR
void low_ISR (void)
{
if (TMR0_flag)
TMR0_ISR(); // ISR de
la interrupcion de TMR0
}
// Code @ 0x0018 ->
Jump to ISR for Low priority interruption
#pragma code low_vector = 0x0018
void code_0x0018 (void){_asm goto low_ISR _endasm}
#pragma code
El código
de ambas interrupciones (TMR0_ISR y TMR1_ISR) no ha cambiado, pero ahora uno de
ellos se ejecuta como interrupción normal (TMR1) y el otro (TMR0) como
interrupción de baja prioridad. Al igual que se hizo antes, hay que definir una
rutina para la interrupción de baja prioridad y saltar a ella desde la
dirección 0x0018. Notad también la directiva #pragma interruptlow que se asocia a la rutina de baja prioridad.
También
habrá que hacer algunos (pocos) cambios en el programa principal para activar
el modo de prioridades:
void main() {
TRISC=0; PORTC=0x00;
// Starts TMR0, rolls over every 128x65536 machine
cycles = 1.67 sec @ 20 Mhz
T0CON = 0b10000110;
//
Starts TMR1, rolls over every 2x65536 machine cycles = 0.02 sec @ 20 Mhz
T1CON= 0b10010001;
enable_priority_levels;
enable_TMR0_int; set_TMR0_low; //
Enable TMR0 int with low priority
enable_TMR1_int; set_TMR1_high; // Enable TMR1 int with high priority
enable_high_ints;
enable_low_ints;
while(1);
}
Lo
fundamental es habilitar el modo de prioridades de interrupción
(enable_priority_levels). Después, tras habilitar cada interrupción se
establece su prioridad (en este caso baja para TMR0, alta para TMR1). Finalmente habilitamos tanto las prioridades
de bajo como de alto nivel.
Si ahora
corremos el nuevo programa veremos que el parpadeo de RC1 no se altera
independientemente de si RC0 esta encendido o apagado. La interrupción del TMR1
que causa el parpadeo en RC1 sigue sucediendo aunque se esté ejecutando la
interrupción del TMR0 (RC0 encendido) ya que es capaz de interrumpirla al ser
de alta prioridad.
La
siguiente captura de un osciloscopio refleja la nueva situación. La actividad
de RC1 (arriba) no se altera independientemente de si está o no activa la
interrupción que cambia el valor de RC0 (abajo):
como configurar las interrupciones?
ResponderEliminarfaltan los archivos #include "..\int_defs_C18.h"
ResponderEliminarEstá en el primer capitulo
Eliminarhttp://picfernalia.blogspot.com.es/2012/06/interrupciones-conceptos-basicos.html
Excelente aporte y aclaración, sin duda es de mucha ayuda.
ResponderEliminarMil gracias
Mil gracias fue de mucha ayuda
ResponderEliminarMUCHAS GRACIAS
ResponderEliminar