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.

05 – Aplicación de ADC.h – Voltímetro digital

Anteriormente, hemos visto como utilizar ADC.h para configurar y utilizar el módulo de conversión analógico-digital del PIC18F4550. Ahora, vamos a darle una aplicación práctica a las librerías ADC.h y XLCD.h construyendo un sencillo voltímetro digital de 0 a 5V cuyos valores se muestren en un display LCD 16×2.

Inicialmente vamos a definir nuestro diagrama esquemático:

lcd16x2withRW

Como puede observarse, utilizaremos AN0 (RA0) como canal para la conversión, esto significa que podemos utilizar el puerto B para controlar nuestro display, de manera que podemos utilizar la configuración por defecto de la librería XLCD.h. Si tiene dudas sobre como utilizar XLCD.h y ADC.h puede consultar la entrada 01 y 03 respectivamente de este blog.

Conversión de entero a ASCII

Sabemos que el módulo A/D de nuestro microcontrolador nos dará como resultado un número entero de 10 bits equivalente a la señal analógica de entrada. Esto significa que si nuestro voltaje de entrada puede variar en el rango de 0V a 5V, tendríamos valores enteros que van desde 0 a 1023. Entonces ¿Porqué es necesario convertir de entero a ASCII? Simple, nuestro display funciona con caracteres ASCII o mejor dicho con valores enteros que tengan un equivalente en ASCII, de manera que si por ejemplo queremos mostrar el número 1, debemos sumar 48, debido a que 49 en ASCII equivale al número 1; Y así debemos proceder con cualquier otro valor. Ahora los invito a revisar la siguiente tabla de códigos ASCII.

Al observar la tabla de códigos ASCII podemos determinar que los números del 0 al 9, se pueden representar en ASCII sumando siempre la constante 48. Entonces, una manera de mostrar nuestros valores enteros generados por el módulo A/D consiste en convertirlo en un valor que represente el voltaje de entrada y posteriormente descomponer cada uno de sus dígitos para convertirlos en ASCII enviándolos al display  consecutivamente:

  1. Imaginemos que nuestro canal AN0 recibe un voltaje de entrada de 1.75V, si aplicamos la ecuación correspondiente, sabremos que a la salida del módulo A/D tendremos 358.0928994. Ahora, tenemos que convertirlo en un valor equivalente al voltaje de entrada multiplicando primero por 5000 lo cual nos da: 1,790,464.498 y dividiendo posteriormente por 1023 cuyo resultado será: 1,750.209675, si tomamos sólo la parte entera podemos apreciar que representa los 1.75V de entrada.
  2. Ahora que tenemos la cifra 1750, procedemos a descomponerlo en sus dígitos individuales, posteriormente a cada uno se le suma 48 y ya los podemos enviar al display LCD.

Como se puede apreciar los dos procedimientos anteriores no son nada complicados a nivel de programación. Aclaro que estos algoritmos son fáciles de encontrar en libros e Internet, por lo que no son de mi autoría. El siguiente segmento de código muestra la implementación del algoritmo:


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

 //Algoritmo para descomponer el resultado en digitos enteros
 vAnalogico = (long)resultado * 5000;
 vAnalogico = vAnalogico / 1023;
 digito = vAnalogico / 1000;
 SetDDRamAddr(0x09);
 putcXLCD(digito + 48);
 SetDDRamAddr(0x0A);
 putrsXLCD(".");
 digito = (vAnalogico / 100) % 10;
 SetDDRamAddr(0x0B);
 putcXLCD(digito + 48);
 digito = (vAnalogico / 10) %10;
 SetDDRamAddr(0x0C);
 putcXLCD(digito + 48);
 SetDDRamAddr(0x0D);
 putrsXLCD("V");

 ADIF = 0;
 ConvertADC();
 }
}

El algoritmo es muy simple y mostrará en el display un valor de un entero y dos cifras decimales. Se utiliza para este ejemplo la conversión A/D por interrupción ya que es el método mas eficiente.

El código completo es el siguiente:

#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <plib/xlcd.h>
#include <plib/delays.h>
#include "../plib/adc.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

//Funciones requeridas por la librería XLCD
void DelayFor18TCY(void);
void DelayPORXLCD(void);
void DelayXLCD(void);

int resultado;
long int vAnalogico;
char digito;

void main()
{
 //Configurando LCD 4 bits mutilínea
 OpenXLCD(FOUR_BIT & LINES_5X7);
 //Esperar hasta que el display esté disponible.
 while(BusyXLCD());
 //Mover cursor a la derecha...
 WriteCmdXLCD(0x06);
 //Desactivando el cursor.
 WriteCmdXLCD(0x0C);

 SetDDRamAddr(0x00);
 putrsXLCD("VOLTAJE: ");
 
 //==========================================
 /*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();

 //Algoritmo para descomponer el resultado en digitos enteros...
 vAnalogico = (long)resultado * 5000;
 vAnalogico = vAnalogico / 1023;
 digito = vAnalogico / 1000;
 SetDDRamAddr(0x09);
 putcXLCD(digito + 48);
 SetDDRamAddr(0x0A);
 putrsXLCD(".");
 digito = (vAnalogico / 100) % 10;
 SetDDRamAddr(0x0B);
 putcXLCD(digito + 48);
 digito = (vAnalogico / 10) %10;
 SetDDRamAddr(0x0C);
 putcXLCD(digito + 48);
 SetDDRamAddr(0x0D);
 putrsXLCD("V");

 ADIF = 0;
 ConvertADC();
 }
}


void DelayFor18TCY(void)
{
 Delay10TCYx(120);
}

void DelayPORXLCD(void)
{
 Delay1KTCYx(180);
 return;
}

void DelayXLCD(void)
{
 Delay1KTCYx(60);
 return;
}

Acá podemos ver la simulación:

demo ADC_XLCD

El siguiente video muestra la implementación del circuito:

Volveré posteriormente con ejemplos de uso de interrupciones, lo cual nos servirá como preámbulo para la implementación de temporizadores. !!!Hasta la pronto¡¡¡

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.

03 – Conversión de Analógico a Digital con librería ADC.h de C18

Continuando con la implementación de librerías C18, ha llegado el turno de adc.h, esta librería nos facilita el trabajo de conversión A/D ahorrándonos bastante código. Como siempre nos basaremos en el documento de Microchip: MPLAB_C18_Libraries_51297f.

Circuito de aplicación

Partimos describiendo el circuito de aplicación que es el siguiente:

Circuito de aplicación

El circuito es sencillo ya que únicamente tiene como finalidad demostrar el funcionamiento del módulo AD del microcontrolador. En el circuito, se utiliza el canal AN0 (RA0), los niveles analógicos son proporcionados por el potenciómetro de 10K y monitoreados por el voltímetro, el microcontrolador realizará la conversión y mostrará el valor digital (en binario) en el puerto D por medio de un conjunto de leds.

Consideraciones sobre el módulo AD del PIC18F4550

  • El PIC18F4550 posee un modulo AD de 13 canales, esto significa que solamente podemos utilizar un canal por vez (no es posible la conversión simultánea).
  • La resolución de la conversión es de 10 bits (2∧10 = 1024), por lo tanto tendremos 1024 – 1 = 1023 posibles valores.
  • El valor del escalón de la conversión si el voltaje de entrada máximo es de 5V será de: 5V/1023 = 0.004887V. Por ejemplo, si se ajusta el potenciómetro para entregar un voltaje de 3.5V tendremos que: 3.5V /0.004887V = 716 (valor digital), al convertir 716 a binario tenemos:  1011001100 (Este será el resultado que se mostrará en el puerto D).

Librería adc.h de C18

Originalmente creada para C18, puede utilizarse con XC8 y MPLAB X. Las funciones que la librería proporciona son las siguientes:

funciones libreria adc

Las funciones que utilizaremos son:

OpenADC(), se utiliza para configurar el módulo AD, requiere tres parámetros de configuración: origen de la señal de reloj para el módulo AD, la alineación de la conversión, y el tiempo de adquisición de datos (xTAD). El segundo parámetro se utiliza para indicar: el canal a utilizar (AN0 – AN12), estado de la interrupción AD, y los voltajes de referencia positivo y negativo. Finalmente, el tercer parámetro es un valor decimal que representa la configuración del registro ADCON1.

El siguiente es el ejemplo de aplicación:

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

**Para información sobre el origen de los valores de configuración se sugiere leer la hoja de especificaciones del PIC18F4550 pág. 259.

Cabe aclarar, que los parámetros de OpenADC() cambian de acuerdo al PIC que se está utilizando.

ConvertADC(), inicia el proceso de conversión.

ReadADC(), devuelve un número entero que representa el valor decimal de la conversión efectuada.

BusyADC(), hace una espera mientras el proceso de conversión AD se lleva a cabo.

Código de aplicación

Creamos entonces, un nuevo proyecto en MPLAB X e incorporamos el siguiente código:

#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 deshabilitada
 * 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_OFF &
 ADC_VREFPLUS_VDD &
 ADC_VREFMINUS_VSS,
 14);

 //Retardo de 50 Tcy
 Delay10TCYx(5);

 while(1)
 {
 //Iniciar la conversión
 ConvertADC();

 //Espera para que se complete la conversión
 while(BusyADC());

 //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;
 }
}

Bastará con compilar y verificar el funcionamiento en Proteus para ver los resultados, sería interesante comprobar que matemáticamente los valores digitales corresponden al nivel del voltaje entrada analógico (no olvidar que el resultado de 10 bits se está convirtiendo a 8 bits).

El siguiente video muestra la implementación en una placa de desarrollo:

Volveré posteriormente con un ejemplo de conversión A/D utilizando interrupciones.

Saludos a tod@s