¿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.
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 }