Conversión Análogo Digital (ADC)

A lo largo de este curso hemos venido usando el microcontrolador como un dispositivo que recibe y entrega datos digitales, en un ejemplo anterior aprendimos a leer una entrada a la cual conectamos un switch pulsador y a través del programa detectamos si estaba pulsado o no.

Pero sabemos que existen gran cantidad de variables a medir que tienen más de dos estados, es decir, no solo tienen un estado alto y un bajo como las digitales, sino que pueden tomar otros valores. Un ejemplo muy común es la temperatura, el voltaje, la presión, entre otros.

Este tipo de variables son análogas y los microcontroladores tienen una configuración especial para poder leerlas e interpretar su valor de entrada. Básicamente lo que hacen estas entradas el medir el valor de voltaje que se encuentra en ellas y representar su valor en una cantidad equivalente teniendo los voltajes de referencia y la cantidad de bits a los que se realice la conversión. Esta última parte la explicamos más abajo.

Los pines marcados como ANx (donde la x viene siendo el número con el que se identifica), pueden ser configurados como entradas análogas. El microcontrolador que venimos usando posee 12 de estas entradas, marcadas desde AN0 hasta AN11.

En la siguiente gráfica se muestra como están repartidas en el microcontrolador:

En el puerto A: AN0, AN1, AN2, AN3 Y AN4
En el puerto B: AN5, AN6, AN7, AN8, AN9, AN10 Y AN11

En la conversión análoga a digital siempre se toman dos valores de voltaje como referencia, un valor bajo y un valor alto. La diferencia que haya entre tales valores será determinante a la hora de la operación y entregar un resultado, para este primer ejemplo los valores que se usaran de referencia serán los de alimentación del microcontrolador, es decir, 0 voltios (o tierra) y 5 voltios.

Otro valor a tener en cuenta es la cantidad de bits a los cuales se realiza la conversión, el microcontrolador que usamos lo hace a 10 bits, es decir que el resultado final de la conversión análogo digital será un valor entre 0 y 1023.

Un término muy importante es el de resolución, la resolución es el mínimo valor representable que se obtiene del voltaje referencia y la cantidad de bits.

Dijimos que para este ejemplo los valores del voltaje de referencia serán los de alimentación, 0 y 5 voltios, y que el microcontrolador realiza la conversión a 10 bits, dado esto tenemos que para cuando en la entrada tengamos 0 voltios el resultado será 0 y para cuando tengamos 5 voltios el resultado que tendremos será 1023, ahora, basándonos en los valores máximos y mínimos podemos determinar el valor de la resolución con la siguiente formula:

Voltaje máximo – Voltaje mínimo
Valor máximo en bits

Tenemos que:

5 voltios – 0 voltios
1023

De aquí la resolución es 0,00489 voltios, esto quiere decir que cada unidad de las 1023 posibles, equivale a 0,00489 voltios.

Para casos prácticos si obtenemos como resultado de una conversión un 512, lo único que tendremos que hacer es multiplicarlo por el valor de la resolución y obtendremos el valor del voltaje medido.

512 * 0,00489 voltios = 2,5 voltios

Configuración del microcontrolador para usar el conversor análogo digital

Para empezar debemos indicarle al microcontrolador que usaremos una o varias entradas análogas, como aprendimos antes debemos configurar el registro TRIS correspondiente con 1 (uno) en los pines que decidamos usar, para que estos queden configurados como entradas.

Anteriormente vimos en muchos ejemplos el registro ANSEL, este nos permite escoger si la entrada será análoga o digital, en casos anteriores lo hemos configurado con 0 (cero), lo que indica que será usado como entrada digital, ahora configurando los bits correspondientes en 1 (uno), tendremos entradas análogas.

TRISA = 0x03	// Configuramos RA0 y RA1 como entradas, las demás son salidas (valor en hexadecimal).
ANSELA = 0x03	// Ahora configuramos AN0 y AN1 como análogas y las demás como digitales (valor en hexadecimal).

ADCON0 y ADCON1

ADCON0 es un registro donde podemos seleccionar: el canal por el cual realizaremos la lectura, habilitar o deshabilitar la conversión análoga a digital y por último, iniciar la conversión.

En esta tabla se describen los bits:

Bits Nombre Descripción Configuración
0 ADON Bit de habilitación de la conversión. 0 = La conversión análogo digital está deshabilitada
1 = La conversión análogo digital está habilitada
1 GO/DONE Bit de estado. 0 = La conversión se ha completado o no esta en progreso.
1 = Conversión en progreso, poniendo este bit en 1 (uno), se inicia la conversión, al finalizar automáticamente pasa a 0 (cero).
6-2 CHS Bits de selección de canal. 00000 = AN0
00001 = AN1
00010 = AN2
00011 = AN3
00100 = AN4
00101 = AN5
00110 = AN6
00111 = AN7
01000 = AN8
01001 = AN9
01010 = AN10
01011 = AN11
(Las siguientes configuraciones están reservadas para otras funciones).
7 Este bit no está implementado.

Con ADCON1 podemos: Seleccionar como queremos obtener el resultado, la frecuencia a la cual se realiza la conversión y seleccionar la referencia de voltaje positivo y negativo.

En la siguiente tabla se describen los bits:

Bits Nombre Descripción Configuración
1-0 ADPREF Voltaje de referencia positivo. 00 = Referencia conectada a alimentación positiva.
01 = (Reservado)
10 = Referencia externa conectada al pin VRef+
11 = Referencia interna por módulo FVR.
2 ADNREF Voltaje de referencia negativo. 0 = Referencia conectada a alimentación negativa.
1 = Referencia externa conectada al pin VRef-
3 Este bit no está implementado.
6-4 ADCS Frecuencia de reloj de conversión. 000 = Fosc/2 (Fosc = Frecuencia del oscilador)
001 = Fosc/8
010 = Fosc/32
011 = FRC (Frecuencia entregada por un oscilador RC, resistencia/condensador)
100 = Fosc/4
101 = Fosc/16
110 = Fosc/64
111 = FRC (Frecuencia entregada por un oscilador RC, resistencia/condensador)
7 ADFM Formato de resultado. 0 = Resultado justificado a la izquierda.
1 = Resultado justificado a la derecha.

Detengámonos un momento para explicar un poco los bits del ANCON1:

Como vimos más arriba, se requiere que haya una referencia positiva y una referencia negativa para realizar la conversión, con los bits ADPREF y ADNREF, le indicamos al microcontrolador donde están nuestras referencias.

Con ADCS, seleccionamos la frecuencia de reloj a la que se ejecuta la conversión, Fosc se refiere a la frecuencia del oscilador que tenemos en nuestro microcontrolador, en los ejemplos anteriores hemos usado uno de 4MHz, según la configuración que escojamos, esta frecuencia será divida por el valor correspondiente.

Por ultimo ADFM se encarga de “dar formato” al resultado que entrega la conversión, es útil en la medida de que el conversor es a 10 bits y los registros de nuestro microcontrolador son de 8 bits, es claro que el resultado no puede ser representado en un registro sino en dos, tales registros son ADRESH y ADRESL. En la siguiente tabla se muestra como se entrega el resultado según el tipo de justificación que se elija.

Asumiendo que el resultado de la conversión es 1023 (11111111 en binario).

Justificación a la izquierda (ADFM = 0)

ADRESH ADRESL
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |

Justificación a la derecha (ADFM = 1)

ADRESH ADRESL
| 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |

De este modo, entendiendo que siempre el bit de más peso se encuentra a la izquierda y el de menos peso a la derecha, se ubicara el resultado en los registros ADRESH y ADRESL, los restantes 6 bits en cada caso serán escritos en 0 (cero). En el próximo ejemplo mostramos como leer los resultados y unirlos en una sola variable para su posterior uso.

En resumen, los pasos para configurar y leer una entrada análoga son los siguientes:

  1. Configurar el o los bits correspondientes en el registro TRIS como entrada escribiendo un 1 en ellos.
  2. Configurar el o los bits correspondientes en el registro ANSEL como análogos escribiendo un 1 en ellos.
  3. Indicar las referencias de voltaje positivo y negativo en los bits ADPREF y ADNREF respectivamente en el registro ADCON1.
  4. Dar la frecuencia del reloj en los bits ADCS en el registro ADCON1.
  5. Determinar el formato para la entrega del resultado en el bit ADFM en el registro ADCON1.
  6. Seleccionar el canal que vamos a leer, escribiendo el número correspondiente al AN en los bits CHS en ADCON0
  7. Habilitar la conversión análogo digital en el registro ADCON0, escribiendo un 1 en el bit ADON.
  8. Iniciar la conversión análogo digital poniendo en 1 el bit GO/DONE en el registro ADCON1.
  9. Esperar a que el bit GO/DONE pase automáticamente a 0 indicando la finalización del proceso de conversión.
  10. Leer los registros ADRESH y ADRESL para obtener el resultado.
  11. Deshabilitar la conversión análogo digital poniendo en 0 el bit ADON del registro ADCON0.
  12. Repetir desde el paso 6, las veces que sea necesario y con las entradas que se hayan habilitado.

Nota: el paso 11 donde se deshabilita la conversión se recomienda para disminuir el consumo de energía en aplicaciones que se alimenten con batería, además de ser una buena práctica deshabilitar los módulos mientras no los usamos.

Veamos un ejemplo corto donde encendemos una secuencia de leds al tiempo que graduamos el voltaje que entra a nuestro microcontrolador a través de un potenciómetro.

Este es el código:

#include <xc.h> 

#pragma config FOSC = XT        // Oscilador con cristal de cuarzo de 4MHz conectado en los pines 15 y 16
#pragma config WDTE = OFF       // Perro guardian (WDT deshabilitado)
#pragma config MCLRE = ON       // Master clear habilitado (pin reset)

#define _XTAL_FREQ 4000000      // Oscilador a 4MHz

void main(){

    // Configuración de entradas y salidas

    TRISA = 0xFF;               // Puerto A como entradas
    ANSELA = 0x01;              // RA0 (AN0) del Puerto A como Análogo y restantes como digital
    TRISB = 0x00;               // Puerto B como salida
    ANSELB = 0x00;              // Puerto B como Digital

    // Configuración del conversor Análogo/Digital

    ADCON1bits.ADPREF = 0;      // Referencia voltaje positivo conectado a la alimentación
                                // del microcontrolador (VDD).
    ADCON1bits.ADNREF = 0;      // Referencia voltaje negativo conectado a la tierra
                                // del microcontrolador (VSS).
    ADCON1bits.ADCS = 1;        // Frecuencia del oscilador dividida entre 8.
    ADCON1bits.ADFM = 1;        // Justificación del resultado a la derecha.
  
    ADRESL = 0;                 // Limpiamos los registros ADRESL
    ADRESH = 0;                 // y ADRESH

    int resultado = 0;          // En esta variable reuniremos los resultados entregados
                                // en ADRESH y ADRESL

    while(1){                   // Programa que se ejecuta continuamente

        ADCON0bits.CHS = 0;             // Seleccionamos el canal AN0
        ADCON0bits.ADON = 1;            // Habilitamos la conversión Análogo Digital
        ADCON0bits.GO_nDONE = 1;        // Iniciamos el proceso de conversión
                                        // este cambia automáticamente a 0 (cero)
                                        // cuando el proceso finalice.
        while(ADCON0bits.GO_nDONE){     // En espera a que la conversión termine
                                        // cuando el bit pase a cero el ciclo while termina
        }
        
        // En este punto la conversión finalizo y el resultado esta en los registros
        // ADRESH y ADRESL
        
        resultado = ((ADRESH << 8) + ADRESL); // Pasamos el valor del resultado en ADRESH y ADRESL
                                               // a la variable resultado
        
        //********************************************
        // ¡MOMENTO!, ¿qué pasó?, ¿qué significa eso?
        // Al final te lo explicamos :D
        //********************************************
        
        ADCON0bits.ADON = 0;            // Deshabilitamos la conversión Análogo Digital
        
        // Ahora con el resultado almacenado vamos encender una
        // secuencia de leds cada que subamos el voltaje en la entrada AN0.
        
        // Cada que incremente el valor del resultado se encenderá un nuevo led
        // y se apagará conforme decremente.
        
        // Lo verificamos con estos condicionales if, else if
        
        if(resultado < 50){
            PORTB = 0b00000000;
        }else if(resultado < 128){
            PORTB = 0b00000001;
        }else if(resultado < 256){
            PORTB = 0b00000011;
        }else if(resultado < 384){
            PORTB = 0b00000111;
        }else if(resultado < 512){
            PORTB = 0b00001111;
        }else if(resultado < 640){
            PORTB = 0b00011111;
        }else if(resultado < 786){
            PORTB = 0b00111111;
        }else if(resultado < 896){
            PORTB = 0b01111111;
        }else if(resultado < 1023){
            PORTB = 0b11111111;
        }
    }
}

En el código tenemos la siguiente línea:

resultado = ((ADRESH << 8) + ADRESL);

¿Qué pasa en ella?, ¿Qué significa <<?

El signo << (doble menor que) y >> (doble mayor que), son operadores de corrimiento de bits, esto quiere decir que podemos correr los bits de un registro o una variable hacia la derecha o izquierda los espacios que le indiquemos.

Teniendo en cuenta que cuando realizamos un corrimiento en cualquier dirección los bits nuevos que entran son ceros, por ejemplo:

Tenemos una variable de 8 bits con el siguiente dato en binario:

variable = 10010011;

Si a variable le realizamos un corrimiento a la izquierda de 2 posiciones, tendremos el siguiente resultado:

variable = variable << 2;  // 01001100

Si por el contrario lo hacemos a la derecha:

variable = variable >> 2;  // 00100100

Ahora en nuestro caso puntual creamos una variable tipo int llamada resultado, recordemos que ese tipo de variable es de 16 bits, a ella llevaremos el resultado de la conversión almacenados en los registros de 8 bits ADRESH y ADRESL, recordemos que hemos justificado el resultado al derecha.

Supongamos que el resultado de la última conversión fue 953, su representación en binario es 1110111001, es un número de 10 bit, y debido a la justificación escogida queda dividido así:

ADRESH = 00000011;
ADRESL = 10111001;

La variable resultado (de 16 bits) la hemos iniciado en cero es decir que en binario:

resultado = 0000000000000000;

Cuando realizamos un corrimiento a la izquierda de 8 posiciones al ADRESH y almacenamos en resultado en nuestra variable, obtenemos:

resultado = ADRESH << 8;
resultado = 0000001100000000:

Luego al sumarle a resultado el valor en ADRESL, nos queda:

resultado = resultado + ADRESL;
resultado = 0000001110111001;

De este modo es que ahora ya podemos trabajar tranquilamente con un valor único en una variable y operar según la función que cumpla nuestro programa.

Todo ese proceso lo realizamos en una sola línea separando las operaciones por paréntesis, como en cualquier operación matemática, los paréntesis internos tienen prioridad y los cálculos se hacen desde el más interno hacia afuera, igual aplica el peso de los operadores.

El tema de los operadores será explicado más a fondo en un post dedicado a ello.