07 – Interrupciones externas del PIC18F4550

Las computadoras y equipos electrónicos modernos son gobernados por microprocesadores y microcontroladores, estos equipos poseen una gran cantidad de dispositivos periféricos que por lo general requieren ser atendidos por el microcontrolador alterando el orden natural del programa. Para atender las peticiones de los dispositivos periféricos, se utilizan las interrupciones. Continuando el tema de las interrupciones, en esta ocasión veremos cómo utilizar las interrupciones externas INTx del PIC18F4550.

Interrupciones externas

Se configuran por medio de los registros INTCON, INTCON2 e INTCON3, los cuales poseen bits de habilitación, prioridad y banderas de estado. El registro INTCON es el más importante para la configuración e implementación de interrupciones externas, la estructura del registro se muestra en la siguiente figura:

Registro INTCON

En el contexto de INT0, los siguientes bits de INTCON son los mas importantes:

  • GIE, se utiliza para habilitar las interrupciones en general, debe ser = 1.
  • INT0IE, se utiliza para habilitar la interrupción INT0, debe ser = 1.
  • INT0IF, es la bandera de interrupción de INT0, se colocará en 1, cada vez que se produzca una interrupción INT0, posteriormente debe ser inicializada “manualmente” con 0.

Adicionalmente, en el registro INTCON2, el bit INTEDG0 se utiliza para definir si la interrupción se activará con un flanco de subida (Rising edge) o flanco de bajada (Falling edge) de la señal. Por defecto INTEDG0 posee un valor de 1, lo que equivale a una activación en flanco de subida.

NOTA: Las interrupciones INT1 e INT2 se configuran por medio del registro INTCON3.

Existen otras configuraciones como, la definición de prioridad de interrupciones, pero este tema no lo abordaremos aquí.

Ejemplo de implementación

A continuación, veremos una aplicación sencilla de la interrupción INT0. El circuito es el siguiente:

Circuito INT0

El microcontrolador tiene una rutina permanente que es hacer un blink en el led conectado a RA0 a intérvalos de 0.25 segundos. Cuando el botón haga una transición de 0 a 1 (Rising edge) se activará INT0, la cual activará una rutina que encenderá o apagará el led conectado a RA1 durante 1 segundo. Posteriormente, el microcontrolador continuará con la rutina del blink hasta que se produzca una nueva interrupción INT0.

El código del ejemplo es el siguiente:


#include <xc.h>

//BITS DE CONFIGURACION.....
#pragma config PLLDIV = 5, CPUDIV = OSC1_PLL2, USBDIV = 2
#pragma config FOSC = HSPLL_HS, FCMEN = OFF, IESO = OFF
#pragma config PWRT = OFF, BOR = OFF, VREGEN = OFF
#pragma config WDT = OFF, WDTPS = 32768
#pragma config MCLRE = ON, LPT1OSC = OFF, PBADEN = OFF
#pragma config STVREN = ON, LVP = OFF, ICPRT = OFF, XINST = OFF

#define _XTAL_FREQ 48000000

void retardo(int t);

void main()
{
 //Todas las entradas ANx son digitales
 ADCON1 = 0x0F;
 
 //RA0 y RA1 son salidas
 TRISAbits.RA0 = 0;
 TRISAbits.RA1 = 0;
 
 //INT0 (RB0) es entrada
 TRISBbits.RB0 = 1;
 
 //Leds apagados
 PORTBbits.RB0 = 0;
 PORTBbits.RB1 = 0;
 
 //Configuración de INT0
 INTCONbits.GIE = 1; //Habilitando interrupciones
 INTCONbits.INT0IE = 1; //Habilitar INT0
 INTCON2bits.INTEDG0 = 1; //Interrupción se activa en flanco de subida
 
 while(1)
 {
 //Blink
 PORTAbits.RA0 = 1;
 retardo(25);
 PORTAbits.RA0 = 0;
 retardo(25); 
 } 
}

void interrupt high_isr()
{
 if(INT0IF) //Si la bandera de interrupción es 1
 {
 PORTAbits.RA1 = ~PORTAbits.RA1; //Se invierte el estado del led
 retardo(100);
 //inicializar bandera
 INT0IF = 0;
 }
}

void retardo(int t)
{
 for(int i = 0; i <= t; i++)
 {
 __delay_ms(10);
 }
}

En el siguiente video se muestra la simulación del ejemplo:

Como se puede apreciar, la implementación de interrupciones externas es muy simple pero de gran utilidad para nuestros proyectos. En futuras publicaciones abordaremos el tema de las interrupciones periféricas de los módulos internos del PIC, iniciaremos con los temporizadores. ¡¡¡Un saludo a tod@s!!!

06 – Interrupción de Cambio en Puerto B del PIC18F4550

Las interrupciones son muy importantes en los sistemas electrónicos microcontrolados ya que proveen un canal a través del cual los dispositivos periféricos solicitan la atención del microprocesador (que se encuentra dentro del encapsulado) ante eventos tales como: finalización de conversión A/D, desbordamiento de un Timer, click o movimiento de un puntero (ej. ratón), presión de teclas de un teclado, etc.

Todos los dispositivos de hardware de una computadora tienen asignada una interrupción (IRQ) para comunicar al microprocesador que deben ser atendidos. De esta manera el CPU puede pausar momentáneamente la ejecución de un proceso para atender otros y así sucesivamente.

El PIC18F4550 cuenta con las siguientes interrupciones:

  • Externas: en este tipo de interrupciones, el microcontrolador utiliza uno de sus pines I/O para recibir la interrupción desde un dispositivo externo. Estas a su vez pueden ser: INT: denominadas “Edge-Triggered” debido a que se pueden programar para activarse en su flanco de subida o flanco de bajada. Son las interrupciones idóneas para controlar dispositivos externos. De cambio en PORTB: “Interrupt-On-Change”, es un tipo de interrupción que utiliza los bits RB4:RB7 del PIC, la interrupción se activará cuando en alguno de estos pines se produzca un cambio de estado de 1 a 0 o viceversa.
  • Periféricas: son interrupciones que se originan en los módulos periféricos internos (dentro del encapsulado) del microcontrolador, tales como: TIMER, ADC, EUSART, CCPx, etc.

 

Interrupciones de Cambio de PORTB.

La interrupciones más simples son las de “Cambio en PORTB”, cuando están habilitadas el microcontrolador monitorea constantemente los pines KBI0, KBI1, KBI2 y KBI3 que corresponden a RB4, RB5, RB6 y RB7 respectivamente. Cualquier cambio de estado en estos pines activará una interrupción.

El registro INTCON, se utiliza para configurar y habilitar la interrupción de cambio en PORTB, su estructura es la siguiente:

INTCON

  • El bit RBIE: habilita la interrupción de cambio en PORTB al asignarle un valor de 1.
  • RBIF: es la bandera de interrupción de PORTB, se pondrá en 1 cada vez que se produzca un cambio en PORTB. Después de atender la interrupción debe ser inicializada en 0.
  • GIE: Es el bit de habilitación de interrupciones globales, debe activarse asignándole un valor de 1.

Cada vez que se produzca una interrupción, el microcontrolador ejecutará un método, cuya firma típica es: void interrupt isr(void) {}. Inicialmente, dentro de este método se debe verificar que bandera de interrupción está activada para así poder atenderla por medio del código correspondiente. El siguiente segmento de código muestra la estructura básica del método.


void interrupt isr(void)
{
 if(RBIF) //Si hay cambio de estado en PORTB
 {
 //Código de atención de la interrupción
 RBIF = 0; //Desactivar bandera...
 }
}

Siempre se debe verificar si la bandera de interrupción deseada está activada ya que en una aplicación real se tienen varias interrupciones habilitadas.

Ejemplo de implementación

Para demostrar cómo utilizar la interrupción de cambio en puerto B, utilizaremos el siguiente circuito:

10_INT_PORTB

El funcionamiento es simple, el microcontrolador mostrará constantemente un conteo de 0 a 255 en el puerto B. Si se produce un cambio en el puerto B, se detendrá el conteo y se mostrará en los leds en que bit se ha producido el cambio. Ej. Si se cambió el estado de KBI0, se mostrará 00000011, si el cambio sucedió en KBI1 se mostrará 00001100 y así sucesivamente. Pasado un segundo, se continuará con el conteo. El código es el siguiente:


#include <xc.h>;

//BITS DE CONFIGURACION.....
#pragma config PLLDIV = 5, CPUDIV = OSC1_PLL2, USBDIV = 2
#pragma config FOSC = HSPLL_HS, FCMEN = OFF, IESO = OFF
#pragma config PWRT = OFF, BOR = OFF, VREGEN = OFF
#pragma config WDT = OFF, WDTPS = 32768
#pragma config MCLRE = ON, LPT1OSC = OFF, PBADEN = OFF
#pragma config STVREN = ON, LVP = OFF, ICPRT = OFF, XINST = OFF

#define _XTAL_FREQ 48000000

void retardo(int t);
int KBIx;
int old_PORTB = 0B11110000;

void main()
{
 ADCON1 = 0x0F;
 TRISB = 0xFF;
 TRISD = 0;
 PORTD = 0;


 INTCONbits.RBIE = 1; //Habilitar interrupciones de puerto B.
 INTCONbits.RBIF = 0; //Bandera desactivada.
 INTCONbits.GIE = 1; //Interrupciones globales habilitadas.

 while(1)
 {
 for(int i = 0; i <= 255;i++)
 {
 PORTD = i;
 retardo(10);
 }
 }
}

void interrupt isr(void)
{
 if(RBIF) //Si hay cambio de estado en PORTB
 {
 //==============================================
 //Según datasheet debe incorporarse esto...
 if(PORTB)
 {
 asm("nop");
 }
 //==============================================
 KBIx = PORTB ^ old_PORTB; //Hacemos una operación XOR
 old_PORTB = PORTB;
 KBIx = KBIx >> 4;
 switch(KBIx)
 {
 case 1:
 PORTD = 0B00000011;
 break;
 case 2:
 PORTD = 0B00001100;
 break;
 case 4:
 PORTD = 0B00110000;
 break;
 case 8:
 PORTD = 0B11000000;
 break;
 }
 retardo(100);
 RBIF = 0; //Desactivar bandera...
 }
}

void retardo(int t)
{
 for(int i = 0; i <= t; i++)
 {
 __delay_ms(10);
 }
}

El microcontrolador detectará cambios en el puerto B cuando se hace una transición de 0 a 1 y viceversa, esto último podemos comprobarlo manteniendo presionado el botón por más de un segundo y posteriormente soltarlo.

En conclusión, este tipo de interrupción es muy simple y nos ha servido como base para que en futuros artículos podamos abordar las interrupciones de los módulos internos del microcontrolador (nuestro principal objetivo). Volveré posteriormente abordando el tema de las interrupciones externas. Saludos a tod@s desde El Salvador.

04 – Conversion de Analógico a Digital por Interrupción

En el post 03 de este blog expliqué como utilizar la librería ADC.h para realizar conversiones A/D mostrando el ejemplo  “tradicional”. Sin embargo, dado que el módulo A/D del microcontrolador es un periférico externo integrado a la estructura del microcontrolador (dentro del encapsulado del PIC), posee una interrupción asociada denominada ADCInterrupt que se indica por medio de la bandera ADIF del registro PIR1. En el microcontrolador, cada vez que se produce una interrupción se ejecuta un método asociado a esta o el método general interrupt high_isr().

El funcionamiento es simple, cada vez que el módulo A/D termine de realizar una conversión lo indicará poniendo en 1 la bandera ADIF, utilizaremos el método ADCInterrupt para capturar esta interrupción y evaluar el estado de la bandera. Siempre que sea posible, es conveniente utilizar interrupciones ya que es una manera mas eficiente de realizar varias actividades con el microcontrolador.

void interrupt ADCInterrupt()
{
 if(PIR1bits.ADIF)
 {
   /*Acciones a ejecutar
   .
   .
   .*/

   /*Siempre se debe colocar la bandera
   en 0 antes de salir.*/
   PIR1bits.ADIF = 0;
 }
}

Por otra parte, debemos activar la interrupción de la conversión con el parámetro ADC_INT_ON en la función OpenADC():

 OpenADC(ADC_FOSC_64 &
 ADC_RIGHT_JUST &
 ADC_16_TAD,
 ADC_CH0 &
 ADC_INT_ON &
 ADC_VREFPLUS_VDD &
 ADC_VREFMINUS_VSS,
 14);

Utilizaremos el mismo circuito:

Circuito de aplicación

El código es el siguiente:

#include <xc.h>
#include <plib/adc.h>
#include <plib/delays.h>
#include "stdlib.h"

//Bits de configuración para Fosc = 48Mhz
#pragma config PLLDIV = 5, CPUDIV = OSC1_PLL2, USBDIV = 2
#pragma config FOSC = HSPLL_HS, FCMEN = OFF, IESO = OFF
#pragma config PWRT = OFF, BOR = OFF, VREGEN = OFF
#pragma config WDT = OFF, WDTPS = 32768
#pragma config MCLRE = ON, LPT1OSC = OFF, PBADEN = OFF
#pragma config STVREN = ON, LVP = OFF, ICPRT = OFF, XINST = OFF

#define _XTAL_FREQ 48000000

int resultado;

void main()
{
 TRISD = 0;
 PORTD = 0;

 /*Configuración del módulo AD
 * Fosc = 64
 * Alineación = derecha
 * 16 TAD
 * Canal AN0
 * Interrupción habilitada
 * VREF+ y VREF- conectados a VDD y VSS respectivamente
 * Valor de ADCON1 = 14 (Canal AN0 analógico, el resto digitales)
 */
 OpenADC(ADC_FOSC_64 &
 ADC_RIGHT_JUST &
 ADC_16_TAD,
 ADC_CH0 &
 ADC_INT_ON &
 ADC_VREFPLUS_VDD &
 ADC_VREFMINUS_VSS,
 14);

 //Retardo de 50 Tcy
 Delay10TCYx(5);

 ADC_INT_ENABLE(); //Habilitación de la interrupción AD
 ei();

 //Iniciar la conversión
 ConvertADC();

 while(1)
 {}
}

void interrupt ADCInterrupt()
{
 if(ADIF)
 {
 //Capturando el resultado
 resultado = ReadADC();

 /*Como el resultado es de 10 bits
 * y sólo se dispone de un puerto de 8 bits
 * para mostrarlo. El resultado se convierte
 * en su equivalente de 8 bits dividiendo
 * por cuatro.
 */
 resultado = resultado / 4;

 //Mostrando el resultado
 PORTD = resultado;

 //Antes de salir se debe poner la bandera en 0
 ADIF = 0;

 //Continuar con la conversión
 ConvertADC();
 }
}

Como se puede apreciar, al bucle while(1) {} lo dejamos sin código ya que todo el proceso es controlado desde el método ADCInterrupt(), podemos entonces dejar funcionando automáticamente al módulo A/D mientras el microcontrolador se encarga de realizar otras tareas.

En el siguiente post, utilizaremos la conversión por interrupción para construir un voltímetro digital.