Uso de interrupciones MikroC

¿Qué son las interrupciones?

Imagina que estás cocinando, ya colocaste agua a hervir, ya licuaste todo lo necesario y de repente tocan el timbre de tu casa, te están interrumpiendo, entonces tu función en ese momento será ir a abrir la puerta, atender a la persona y cerrar la puerta, cuando terminas, regresas a cocinar, pero no vuelves a empezar, sino que sigues cocinando desde donde te quedaste.

Cuando se activa una interrupción, el microcontrolador dejará de hacer lo que está haciendo para realizar la función de interrupción que se haya declarado anteriormente, para después regresar a donde se había quedado.

Una interrupción puede verse como un aviso que puede ser activado tanto por algún proceso específico del microcontrolador (final de conversión del ADC, recepción de datos del módulo EUSART, desborde de timer, etc) o por un cambio externo al mismo (cambio en algún puerto específico, cambio de un pin, etc.).

Interrupciones

En la hoja de datos de nuestro microcontrolador podemos encontrar las interrupciones con las que cuenta, por ejemplo, en el caso de Miuva (PIC 18F4550), tendremos: en la sección 9, todo lo relacionado al tema, así como los registros asociados a las mismas. Es importante dar una lectura de esa sección para saber a detalle como se configuran y utilizan.

Las interrupciones tienen asociadas algunos bits de los registros RCON, INTCON, INTCON2, INTCON3, PIR1, PIR2, PIE1, PIE2, IPR1 e IPR2, será importante configurar cada uno de estos registros para adaptarlo a nuestro programa.

Bits de configuración para interrupciones

Tenemos que prestar atención a tres bits que serán parte de la configuración de todas las interrupciones:

  • XIE: Interrupt enable (sirve para habilitar o deshabilitar la interrupción)
  • XIF: Interrupt flag (nos indicará si se activo la interrupción)
  • XIP: Interrupt priority (podemos configurar de alta o baja prioridad)

Por ejemplo, para el caso de la interrupción externa 2 (INT2) la cual se activa cuando hay un cambio en el bit (RB2), estos bits serán:

  • INT2IE
  • INT2IF
  • INT2IP

Será necesario después de atender nuestras funciones de interrupción limpiar el bit de bandera que fue activado automáticamente.

Será importante localizar estos tres bits y configurarlos como se desee, y también revisar si la interrupción a utilizar requiere más bits de configuración, por ejemplo, en el caso de las interrupciones externas, tienen asociado otro bit de configuración llamado XEDGE, el cual nos indicará si la interrupción se activará en un flanco de subida o en un flanco de bajada.

En el caso de las interrupciones por periféricos (ADC, módulo EUSART, etc.) estos bits se pueden encontrar en los registros PIRx (Bits de banderas), PIEx (Bits de habilitación) y IPRx (Bits de prioridad).

También tendremos que prestar atención a tres bits de configuración importantes:

  • IPEN (RCON <7>): Habilita los niveles de interrupción
  • GIE (INTCON<7>): Activa las interrupciones/Activa las interrupciones de alta prioridad
  • PEIE (INTCON<6>): Activa las interrupciones por periféricos/Activa las interrupciones de baja prioridad

Interrupciones para MikroC

Será importante en nuestro código realizar toda la configuración de los registros necesaria para habilitar las interrupciones, posteriormente, necesitamos declarar la función de interrupción de la siguiente manera:

void int_EXT() iv 0x0008 ics ICS_AUTO {
     //Función de interrupción
}

Es importante notar que la función de interrupción la mandamos a la dirección 0x0008, esto se debe a que en nuestra hoja de datos encontraremos que la ubicación de las interrupciones en el mapa de memoria es esa (Imagen1) e int_EXT podemos modificarlo por el nombre que queremos que tenga nuestra función de interrupción.

Imagen1. Memoria de programa

Código de ejemplo:

Con esto podemos construir un código en el cual tengamos un contador en nuestra función principal del 0 al 255, en caso de que se detecte un flanco de bajada en el PIN RB1, se interrumpirá nuestro programa para que la función de interrupción sea atendida, en la cual encenderemos un LED un momento y después lo apagaremos, lo importante será notar que se interrumpe el avance del contador hasta que la función de interrupción haya sido totalmente atendida.

La configuración para activar la interrupción externa 1, será la siguiente:

GIE_bit = 1;       //Activamos interrupciones
INT1IE_bit = 1;    //Habilitamos interrupción externa 1
INTEDG1_bit = 0;   //Interrupción activada en flancos de bajada
TRISB1_bit = 1;    //Bit 1 del puerto B como entrada

Quedándo el siguiente código:

// Conexiones del módulo LCD
sbit LCD_RS at RD5_bit;
sbit LCD_EN at RD4_bit;
sbit LCD_D4 at RD0_bit;
sbit LCD_D5 at RD1_bit;
sbit LCD_D6 at RD2_bit;
sbit LCD_D7 at RD3_bit;

sbit LCD_RS_Direction at TRISD5_bit;
sbit LCD_EN_Direction at TRISD4_bit;
sbit LCD_D4_Direction at TRISD0_bit;
sbit LCD_D5_Direction at TRISD1_bit;
sbit LCD_D6_Direction at TRISD2_bit;
sbit LCD_D7_Direction at TRISD3_bit;
// Final de las conexiones del módulo LCD

unsigned int contador = 0;
int temp = 0;
long unsigned int contador2 = 0;
char texto[5];

void main(){
    ADCON1    = 0B00001111;             //Todos los pines del Pic son Digitales
    TRISE = 0B00000000;                 //Configura puerto E como salidas.
    LATE.F0=1;                          //LED VERDE de Miuva
    LATE.F1=1;                          //LED ROJO de Miuva
    LATE.F2=1;                          //LED AZUL de Miuva
    Lcd_Init();                         //Inicialización del visualizador LCD
    Lcd_Cmd(_LCD_CURSOR_OFF);           //Comando LCD (apagar el cursor)
    Lcd_Cmd(_LCD_CLEAR);                //Comando LCD (borrar el LCD)
    GIE_bit = 1;
    INT1IE_bit = 1;
    INTEDG1_bit = 0;
    TRISB1_bit = 1; 
    while(1){
       if (contador <= 255){            //Si el contador es menor a 255
          contador++;                   //Aumentamos su valor
       }
       else{                            //Si no
          contador = 0;                 //Lo reiniciamos a cero
       }
       //Escribimos en la LCD el valor del contador
       temp = (contador / 100) % 10;    // Extraer centenas
       Lcd_Chr(1,1,48+temp);            // Escribir resultado en formato ASCII
       temp = (contador / 10) % 10;     // Extraer decenas
       Lcd_Chr(1,2,48+temp);            // Escribir resultado en formato ASCII
       temp = contador % 10;            // Extraer unidades
       Lcd_Chr(1,3,48+temp);            // Escribir resultado en formato ASCII
   }
}

void int_EXT() iv 0x0008 ics ICS_AUTO {     //Definición de la función de interrupción
     LATE.F0 = 0;                      //Encendemos LED VERDE (Miuva)
     contador2 = 0;                    //Reiniciamos el valor del contador2
     while(contador2 < 300000){        //Retardo 
        contador2++;
     }
     LATE.F0 = 1;                      //Apagamos el LED VERDE (Miuva)
     INT1IF_bit = 0;                   //Limpiamos la bandera
}

Niveles de interrupción

En muchas ocasiones nuestros programas necesitan realizar varias tareas y algunas pueden ser más importantes que otras, es por eso que cuando trabajamos con interrupciones podemos declarar diferentes prioridades para ellas, entre más alta sea su prioridad más importante es la tarea a realizar, entonces si se activa una interrupción de alta prioridad, no importa lo que esté realizando nuestro programa (incluso aunque esté realizando otra función de interrupción de baja prioridad) saltará a la función correspondiente y después regresará a donde estaba.

Para activarlos será necesario habilitar los bits:

  • IPEN
  • GIE
  • PEIE

Dentro de nuestro código tendremos que especificar las funciones de interrupción tanto de baja como de alta prioridad, en la hoja de datos podemos ver la ubicación de la memoria de programa donde son almacenadas:

void int_EXT() iv 0x0008 ics ICS_AUTO {       //HIGH PRIORITY
      //Definimos función de interrupción de alta prioridad
}
                                              
void int_EXT2() iv 0x0018 ics ICS_AUTO {      //LOW PRIORITY
      //Definimos función de interrupción de baja prioridad
}

Código de ejemplo:

En este código habilitaremos las interrupciones tanto de alta como de baja prioridad, ambas serán externas, una habilitada por el pin RB1 y la otra por el pin RB2, nuestro programa principal tendrá un contador del 0 al 255 mostrado en la LCD, cada una de las prioridades encenderá un LED diferente durante un tiempo definido, lo importante será corroborar que la función principal puede ser interrumpida tanto por las interrupciones de alta como de baja prioridad, mientras que la interrupción de baja prioridad puede ser interrumpida por la de alta prioridad, y la de alta prioridad no puede ser interrumpida por nada.

// Conexiones del módulo LCD
sbit LCD_RS at RD5_bit;
sbit LCD_EN at RD4_bit;
sbit LCD_D4 at RD0_bit;
sbit LCD_D5 at RD1_bit;
sbit LCD_D6 at RD2_bit;
sbit LCD_D7 at RD3_bit;

sbit LCD_RS_Direction at TRISD5_bit;
sbit LCD_EN_Direction at TRISD4_bit;
sbit LCD_D4_Direction at TRISD0_bit;
sbit LCD_D5_Direction at TRISD1_bit;
sbit LCD_D6_Direction at TRISD2_bit;
sbit LCD_D7_Direction at TRISD3_bit;
// Final de las conexiones del módulo LCD

unsigned int contador = 0;
int temp = 0;
long unsigned int contador2 = 0;
long unsigned int contador3 = 0;
char texto[5];

void main(){

  ADCON1    = 0B00001111;           //Todos los pines del Pic son Digitales
    TRISE = 0B00000000;                 //Configura puerto E como salidas.
    LATE.F0=1;                          //VERDE
    LATE.F1=1;                          //ROJO
    LATE.F2=1;                          //
    Lcd_Init();                    // Inicialización del visualizador LCD
    Lcd_Cmd(_LCD_CURSOR_OFF);      // Comando LCD (apagar el cursor)
    Lcd_Cmd(_LCD_CLEAR);           // Comando LCD (borrar el LCD)
    GIE_bit = 1;                   //Habilitamos interrupciones de alta prioridad
    IPEN_bit = 1;                  //Habilitamos niveles de interrupción
    PEIE_bit = 1;                  //Habilitamos interrupciones de baja prioridad
    INT1IE_bit = 1;                //Habilitamos interrupción externa 1
    INTEDG1_bit = 0;               //Habilitamos en flanco de bajada
    INT1IP_bit = 1;                //Alta prioridad
    INT2IE_bit = 1;                //Habilitamos interrupcione externa 2
    INTEDG2_bit = 0;               //Habilitamos en flanco de bajada
    INT2IP_bit = 0;                //Baja prioridad
    TRISB1_bit = 1;                //Bit 1 de puerto B como entrada
    TRISB2_bit = 1;                //Bit 2 de puerto B como entrada
    
  while(1){                        //Ciclo infinito
    if (contador <= 255){          //Si contador es menor a 255
       contador++;                 //Aumentamos el valor del contador
    }
    else{                          //Si no
       contador = 0;               //Reiniciamos el contador
    }
    temp = (contador / 100) % 10;  // Extraer centenas
    Lcd_Chr(1,1,48+temp);          // Escribir resultado en formato ASCII
    temp = (contador / 10) % 10;   // Extraer decenas
    Lcd_Chr(1,2,48+temp);          // Escribir resultado en formato ASCII
    temp = contador % 10;          // Extraer unidades
    Lcd_Chr(1,3,48+temp);          // Escribir resultado en formato ASCII
  }
}

void int_EXT() iv 0x0008 ics ICS_AUTO {     //HIGH
     LATE.F2 = 0;                     //Encendemos el LED VERDE (Miuva)
     contador2 = 0;                   //Reiniciamos el contador
     while(contador2 < 300000){       //Retardo
                     contador2++;
     }
     LATE.F2 = 1;                     //Apagamos el LED VERDE (Miuva)
     INT1IF_bit = 0;                  //Limpiamos el bit de bandera
}

void int_EXT2() iv 0x0018 ics ICS_AUTO {         //LOW
     LATE.F1 = 0;                     //Encendemos el LED ROJO (Miuva)
     contador3 = 0;                   //Reiniciamo el contador
     while(contador3 < 300000){       //Retardo
                     contador3++;
     }
     LATE.F1 = 1;                     //Apagamos el LED ROJO (Miuva)
     INT2IF_bit = 0;                  //Limpiamos el bit de bandera
}

 

Menú