ADC




El ADC (Analog to Digital Converter)  convierte un valor de tensión analógico en su equivalente valor digital expresado en unos y ceros en uno o varios registros del microcontrolador. El ATMEGA 1280 dispone de un ADC de 10 bits, con una tensión de referencia de 5V podemos obtener una resolución de 5/1024 V, es decir el ADC detecta cambios en la señal analógica cada 4.88 mV y por tanto el número en binario del registro o registros donde guardemos la conversión representará incrementos de este valor de tensión (si vale 500 pues la tensión de entrada sera 500*4.88 mV).

Este micro tiene hasta 16 entradas multiplexadas al ADC, con bastante opciones y configuraciones, en este caso sólo se van a ver las necesarias para hacer una conversión analógica digital simple desde una V de referencia de 5V comparada con tierra (single ended mode en el datasheet) que es lo que usaremos en el proyecto.

El pin de alimentación es AVcc que no debe tener una diferencia de mas de 0.3V con Vcc, lo normal es conectarlo a Vcc y así viene en la placa del Arduino Mega. El ADC toma una muestra de la tensión de entrada y lo mantiene contante mientras se realiza la conversión . Como referencia de voltaje podemos usar una externa en la patilla AREF, nuestra resolución será Vref/número de bits de ADC, y una vez alcanzada esta tensión en el pin de entrada tendremos el valor máximo ((Vref/2^n)*(-1+2^n) n=número bits adc) en el registro digital del ADC. También nos permite seleccionar un voltaje de referencia interno de 1.1V ,2.56V o AVcc, en este caso no debemos conectar nada a AREF.

El ADC para su funcionamiento necesita una señal de reloj con una frecuencia comprendida entre 50 KHz y 200 KHz para una resolución de 10 bits, si menos resolución es requerida se le puede proporcionar hasta una frecuencia de 1MHz para realizar las conversiones en menos tiempo, para proporcionar esta señal de reloj el ADC cuenta con un prescaler que genera la señal requerida a patir de la señal de reloj. La señal de reloj de nuestro micro es el cristal externo y éste es de 16 MHz, para generar una señal de reloj válida para el ADC a través del prescaler tendremos que usar los 7 bits (128 en decimal) lo que nos proporcionara una frecuencia de reloj de ADC de 16MHz/128 = 125 KHz.



Una conversión analógica digital requiere de 13 ciclos de reloj del ADC que con un prescaler de 128 serían 1664 ciclos de reloj (por lo que tenemos la opción de habilitar una interrupción que nos avise de cuando se ha finalizado esta conversión),  la primera conversión que realizamos tras activar el módulo del ADC ésta requiere de 25 ciclos de reloj ya que tiene que inicializar todo el hard. 



Todo esto para circuitos con una impedancia de salida de 10K o menos, para circuitos con una impedancia mayor el tiempo de muestreo dependerá del tiempo que tarde en cargarse el condensador que retiene la magnitud de la señal a través de esa impedancia. Señales con una frecuencia mayor que Fadc/2 (Nyquist) deben de ser evitadas ya que se distorsionan.

Circuito de entrada, muestreo y mantiene la señal durante la conversión.



Registros ADC.

Los registros del ADC en el microcontrolador son los siguientes:


Los bits 7 y 6 REFS1 y REFS0: se encargan de seleccionar el voltaje de referencia del ADC, si un voltaje de referencia externo es aplicado en AREF no se pueden usar los voltajes de referencia internos.



Cuando se selecciona las referencias internas se recomienda poner un condensador en el pin de AREF a tierra para eliminar ruido.

Bit 5 ADLAR (ADC Left Adjust Result):  los 10 bits de la conversión del adc se deben de guardar en dos registros de 8 bits destinados a éste fin, que son los máximos bits que podemos tener por dirección de memoria. Poniendo 0 como viene por defecto en este bit tenemos 8 bits en el registro de menor peso y 2 bits en el de mayor peso, para sacar el resultado debemos leer los dos registro y valor máximo será 3FF (1024). Poniendolo a uno tenemos 8 bits en el registro de mayor peso y dos en el de menor, esto lo hacemos cuando no necesitamos una precisión de 10 bits, leemos sólo los 8 bits de mayor peso en un sólo registro obteniendo una resolución de Vref/256, más que suficiente para la mayoría de los casos y de esta forma evitamos tener que leer más de un registro en el programa.

Bits 4:0 MUX4:0: estos bits se encargan (junto a otro bit MUX5 en otro registro) de seleccionar el pin de nuestro microcontrolador cuya entrada está conectada al ADC, tenemos 16 pines con la opción de ADC, aquí seleccionamos cual de ellos estamos leyendo. Si los bits son cambiados mientras se realiza una conversión el cambio no tiene efecto hasta que ésta esté completa.


Bit Mux5: junto a los 4 anteriores selecciona que entradas y que funciones de la electrónica están seleccionadas. Siempre debemos cambiar de canal antes de iniciar la conversión, si no el resultado será el del canal anterior

Bit 2:0 ADTS2:0: seleccionan la fuente de disparo automático del ADC.





En nuestro caso nos interesan las opciones de la primera columna (single ended input) que son las que vamos a utilizar.



Bit 7 ADEN (ADC Enable): enciende (1) y apaga (0) el adc.

Bit 6 ADSC (ADC Start Conversion):  cuando estamos "in single conversion mode" poniendo este bit a 1 iniciamos cada conversión, este bit está a 1 mientras se realiza la conversión y se pone a cero cuando se finaliza. En "Free running mode" ponemos este bit a uno para iniciar sucesivas conversiones, escribir un cero en este bit no tiene efecto. La conversión también se puede iniciar de forma automática disparada desde varias fuentes seleccionables. Para seleccionar el Free Running Mode debemos poner un 1 en ADATE y 000 en sus bits de selección (explicado más abajo).

Bit 5 ADATE (ADC Auto Trigger Enable):  cuando está a 1 el disparo automático del adc se encuentra habilitado y éste iniciara una conversión cuando una de las fuentes seleccionada le proporciones un flanco de subida en su entrada. Si un flanco de subida ocurre cuando se está realizando una conversión éste es ignorado. La fuente es seleccionada mediante los bits ADTS2:0 de ADCRSB Si cambiamos de una fuente con su entrada a cero a una fuente con entrada con la entrada a 1 tenemos un flanco de subida y la conversión se inicia.





Bit 4 ADIF (ADC Interrupt Flag): este bit se pone a uno cuando la conversión se ha realizado y los registros donde se guarda el resultado han sido actualizados, mediante este bit y habilitando su interrupción en los registros correspondientes podemos iniciar la conversión e irnos a hacer otras cosas, así no tenemos que comprobar por soft si ya ha finalizado la conversión, ya nos avisará la interrupción.

Bit 3 ADIE (ADC Interruptio Enable): habilita la interrupción del ADC indicada por el bit anterior.

Bits 2:0 ADPS2:0: (ADC Prescaler Select Bits): seleccionan por cuanto se va a dividir la señal de reloj del micro para generar la señal de reloj del ADC que debe estar comprendida entre 50 Khz y 200 KHz para 10 bits de resolución.






Estos son los registros donde se almacena el resultado de la conversión, ADCH es el de mayor peso y mediante ADLAR seleccionamos si vamos a usar una resolución de 8 ó 10 bits, explicado más arriba. Con ADLAR=0 ADCL se debe de leer primero y luego ADCH, el resultado de una conversión no es actualizado si hemos leído ADCL hasta que no hayamos leído ADCH.






En estos registros se deshabilitan la entrada digital del pin escribiendo un 1 en el bit correspondiente, cuando vamos a usar la entrada como analógica y la entrada digital no es necesaria se debe escribir un uno en el registro ya que de esta forma reducimos el consumo de corriente del micro.


ADC en C.


En el AVR-gcc no tenemos ninguna librería específica para usar el ADC, la única que tendremos que añadir al programa es el avr/interrupt.h si se va a usar la interrupción del ADC.

Se va a realizar un programa sencillo para leer un potenciometro que forma un divisor de tensión y sacar el valor de la lectura por el LCD, se compara con el valor leído en el polímetro para comprobar su correcto funcionamiento. El ADC se configura en Free Running Mode,  la resolución es de 8 bits que para el ejemplo es suficiente.


#include <avr/io.h>
#define F_CPU 16000000UL
#include <util/delay.h>
#include "lcd.h"

//#define Potenciometro PORTF0  //ADC0

void inicializar_adc();
void escribir_valor_decimal(unsigned char registro);
void escribir_registro(unsigned char registro);
void valor_real(unsigned char registro);

int main(void)
{
    inicializar_adc();
    lcd_inicializar();

    //Líneas para escribir en el LCD
    char linea1[]="Reg. ADCH:";
    char linea2[]="Caracter LCD:";
    char linea3[]="Valor Tension:";
    char linea4[]="V. decimal ADCH:";
    char linea5[]="Proyecto FireFly";
    char linea6[]="www.jmnlab.com";
    char linea7[]="Prueba ADC";
    char linea8[]="Arduino Mega";

    //Escribir en LCD
    posicionar_cursor(1,3);

    lcd_escribir(linea5);
    posicionar_cursor(2,5);
    lcd_escribir(linea6);
    posicionar_cursor(3,6);
    lcd_escribir(linea7);
    posicionar_cursor(4,4);
    lcd_escribir(linea8);
    _delay_ms(2500);

    lcd_comando(0x01);

     posicionar_cursor(1,1);
    lcd_escribir(linea1);
    posicionar_cursor(3,1);
    lcd_escribir(linea2);
    posicionar_cursor(2,1);
    lcd_escribir(linea4);
    posicionar_cursor(4,1);
    lcd_escribir(linea3);

    ADCSRA|= (1<<ADSC);    //Comenzar conversiones.

    while(1)
    {
        posicionar_cursor(1,12);
        escribir_registro(ADCH);
        posicionar_cursor(3,15);
        lcd_escribir_c(ADCH);
        posicionar_cursor(2,18);
        escribir_valor_decimal(ADCH);
        posicionar_cursor(4,16);
        valor_real(ADCH);
        _delay_ms(100);
    }
}

void inicializar_adc()
{
    ADMUX |=(1<<REFS0);          //Tensión de referencia AVCC 5 voltios.
    ADMUX |=(1<<ADLAR);        //8 bits de resolución.
     //Canal ADC por defecto ADC0, no tenemos que cambiarlo.
    ADCSRA |=(1<<ADATE);    //Habilitamos el Free Running Mode, por defecto seleccionado
    ADCSRA |=((1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0));  //Prescaler 128, Fadc 125 KHz @ 16MHz.
    DIDR0 |=(1<<ADC0D);           //Apagamos la parte digital del puerto.
    ADCSRA |= (1<<ADEN);       //Enable ADC
}
void escribir_registro(unsigned char registro)
{
    unsigned char mascara = 0b10000000;
    unsigned char cont=0;

    for(cont=0;cont<8;cont++)
    {
        if((registro & mascara)==0)
            lcd_escribir_c('0');
        else
            lcd_escribir_c('1');
//        mascara>>1;
        mascara = mascara/2;
    }
}

void escribir_valor_decimal(unsigned char registro)
{
    char centenas = 0;
    char decenas = 0;
    char unidades = 0;

    while(registro>=100)
    {
        registro= registro -100;
        centenas++;
    }
    while(registro>=10)
    {
        registro=registro -10;
        decenas++;
    }
    while(registro>0)
    {
        registro=registro-1;
        unidades++;
    }   
    lcd_escribir_c(centenas | 0b00110000);
    lcd_escribir_c(decenas | 0b00110000);
    lcd_escribir_c(unidades | 0b00110000);
}

void valor_real(unsigned char registro)
{
    unsigned int v_real = (int)registro;
    char unidades = 0;
    char decimas = 0;
    char centesimas = 0;

    v_real=v_real*195;

    while(v_real>10000)
    {
        v_real=v_real - 10000;
        unidades++;
    }

        while(v_real>1000)
    {
        v_real=v_real - 1000;
        decimas++;
    }

        while(v_real>100)
    {
        v_real=v_real - 100;
        centesimas++;
    }

    lcd_escribir_c(unidades | 0b00110000);
    lcd_escribir_c('.');
    lcd_escribir_c(decimas | 0b00110000);
    lcd_escribir_c(centesimas | 0b00110000);
    lcd_escribir_c('V');
}


Se configura el ADC de forma que éste realiza conversiones de manera continua y guarda su salida en el registro ADCH que es el que usamos en las distintas funciones, hubiese sido más acertado crear un char copia_ADCH = ADCH; para operar siempre con el mismo valor de ADCH en cada ciclo del búcle, por si entre función y función cambia el valor de ADCH, pero bueno para el ejemplo vale de esta forma que ya he subido el video y no lo cambio, se puede ver como alguna vez oscila alguna línea mientras que otra no y ésto es debido a lo anterior.

En la pantalla se representa el valor del registro ADCH en la primera línea binario, en la segunda línea su valor pasado a decimal, en la tercera el caracter del LCD que escribiriamos con ese valor (no tiene nada que ver pero queda bien...) y por último el valor de tensión real a la entrada del adc que es el que nos interesa y comparamos con el polímetro para saber que estamos realizando la conversión de manera correcta, la resolución es de 5/256 V, por lo que en la cuarta línea tenemos el valor decimal de ADCH multiplicado por 0.0195V.

El resto de funciones sirven para sacar el registro ADCH a la pantalla del LCD en binario, decimal y tensión real.

Con ésto y las dos actualizaciones anteriores (LCD e Interrupciones externas) ya se puede empezar con el primer módulo del hexápodo, el interfaz físico, que consta de un LCD, 3 pulsadores y un potenciometro y sirve para que el hexápodo pueda comunicarse con una persona de manera autónoma sin la necesidad de un PC por ejemplo. Por lo que el siguiente paso es meter el código de esta actualización y las dos últimas en su cabecera todo junto y crear el esqueleto del programa inicial de desarrollo para el  hexápodo que se usará para ir añadiendo módulos y probar su correcto funcionamiento. Dotar a un proyecto de un LCD es algo muy útil.

Video del funcionamiento.


Para cualquier comentario, sugerencia, correción:

blog comments powered by Disqus