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.