Translate

viernes, 22 de junio de 2012

Niveles de prioridad en interrupciones

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

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

 Manejo simultáneo de varias interrupciones sin priorizar

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




6 comentarios:

  1. como configurar las interrupciones?

    ResponderEliminar
  2. faltan los archivos #include "..\int_defs_C18.h"

    ResponderEliminar
    Respuestas
    1. Está en el primer capitulo

      http://picfernalia.blogspot.com.es/2012/06/interrupciones-conceptos-basicos.html

      Eliminar
  3. Excelente aporte y aclaración, sin duda es de mucha ayuda.
    Mil gracias

    ResponderEliminar