# Convertidor AD en MikroC PIC (Analógico-Digital)



## kal00 (Oct 24, 2010)

Amigos buen día, recurro a ustedes en busca de ayuda con un proyecto. Estoy aprendiendo a usar el ADC de los PIC bajo _MikroC_, sólo que en todos los ejemplos que he encontrado (incluyendo los de MikroC), no he encontrado información *detallada* acerca de lo que hace cada función, y los comentarios son muy vagos. Pongo un ejemplo:


```
char ch;
unsigned int adc_rd;
char *text;
long tlong;

void main() {
  INTCON = 0;                              // disable all interrupts
  ANSEL  = 0x04;                           // Configure AN2 pin as analog input
  TRISA  = 0x04;
  ANSELH = 0;                              // Configure other AN pins as digital I/O
  Lcd_Init(&PORTB);
  LCD_Cmd(LCD_CURSOR_OFF);
  LCD_Cmd(LCD_CLEAR);

  LCD_Out(1,1,"Temperature");
  LCD_Out(2,3,"LCD Example");

  ADCON1     = 0x82;                       // configure VDD as Vref, and analog channels
  TRISA      = 0xFF;                       // designate PORTA as input
  Delay_ms(2000);
  text  = "voltage=";
  while (1) {
    adc_rd  = ADC_read(2);                 // get ADC value from 2nd channel
    LCD_Out(2,1,text);                     // print string a on LCD, 2nd row, 1st column

    tlong = (long)adc_rd * 5000;           // covert adc reading to milivolts
    tlong = tlong / 1023;                  // 0..1023 -> 0-5000mV

    ch     = tlong / 1000;                 // extract volts digit
    LCD_Chr(2,9,48+ch);                    // write ASCII digit at 2nd row, 9th column
    LCD_Chr_CP('.');

    ch    = (tlong / 100) % 10;            // extract 0.1 volts digit
    LCD_Chr_CP(48+ch);                     // write ASCII digit at cursor point

    ch    = (tlong / 10) % 10;             // extract 0.01 volts digit
    LCD_Chr_CP(48+ch);                     // write ASCII digit at cursor point

    ch    = tlong % 10;                    // extract 0.001 volts digit
    LCD_Chr_CP(48+ch);                     // write ASCII digit at cursor point
    LCD_Chr_CP('V');

    Delay_ms(1);
  }
}//FIN
```

Por ejemplo, no entiendo el porque ponen dos veces _TRISA_, según la segunda es para hacer el PORTA como entradas, pero y la primera? Otra sería el funcionamiento y descripción de _ANSEL_, _ANSELH_, _ADCON1_, _ADC_READ_, *pero sobretodo *no entiendo lo que hace cuando empieza a multiplicar el adc_rd por 5000 y todas las operaciones con % que hace.

Espero me puedan ayudar, apenas estoy empezando en esto, al final después de entender este proceso quisiera hacer un medidor de temperatura básico con LM35 pero tampoco se como convertir los volts a ºC. Muchísimas gracias por su tiempo!!


----------



## ByAxel (Oct 24, 2010)

El módulo ADC de un PIC incluye los registros de configuración y donde se almacena el dato convertido (*Ojo*, que los nombres de los registros y configuración no suele ser el mismo, para eso siempre revisa la hoja de datos del pic respectivo).
Sobre el ejemplo que pones:


> no entiendo el porque ponen dos veces TRISA, según la segunda es para hacer el PORTA como entradas, pero y la primera?


No tiene sentido... se han confundido, es la única explicación...



> funcionamiento y descripción de ANSEL, ANSELH, ADCON1, ADC_READ,


ADC_READ > Es una función de las librerías de MikroC y requiere un dato de entrada que es el canal a leer.
ANSEL, ANSELH y ADCON1 son los registros de configuración del módulo ADC de un PIC  pero no menciona el modelo. Pero como dije antes, debes de revisar el datasheet del PIC que uses, ver los registros que involucran al ADC y configurar adecuadamente...



> no entiendo lo que hace cuando empieza a multiplicar el adc_rd por 5000 y todas las operaciones con % que hace


Hay otras formas pero este ejemplo usa la regla 'de tres simple' para obtener el voltaje y usando % obtiene el resto o residuo de la división, consiguiendo en una sola pasada los dígitos en BCD; luego les suma el ASCII cero '0' = '48' para representarlo en el LCD...

saludos


----------



## kal00 (Oct 24, 2010)

Hola *ByAxel *buen día, gracias por tomarte la molestia de contestar y explicar. Entonces lo de _TRISA_ está mal, ya me lo imaginaba. El programa que te muestro aquí es un ejemplo de un voltímetro para el_ PIC 16F887_, ya estube leyendo el datasheet y según yo los registros funcionan de la siguiente manera: _(Según lo que entiendo de las tablas en el datasheet de microhip)_

*ANSEL:* Se selecciona el canal, en este caso es AN2, en binario sería 0000 0100, por lo que se pone 0x04.
*ANSELH:* Son otros canales, pero como no se usan se pone 0x00, aunque no entiendo el por qué de declararla si de todas formas es cero y por qué el comentario dice 'all digital I/O'.
*ADCON1:* Aquí es donde no entiendo muy bien. En el programa ponen 0x82 que en binario sería 1000 0010, entonces viendo en el datasheet:


```
[B]ADCON1[/B]
[I]bit 7 ADFM: A/D Conversion Result Format Select bit[/I]
      1 = Right justified
      0 = Left justified

[I]bit 6 Unimplemented: Read as ‘0’[/I]

[I]bit 5 VCFG1: Voltage Reference bit[/I]
      1 = VREF- pin
      0 = VSS

[I]bit 4 VCFG0: Voltage Reference bit[/I]
      1 = VREF+ pin
      0 = VDD

[I]bit 3-0 Unimplemented: Read as ‘0’[/I]
```

_0x82_ quiere decir que: *Bit7* esta _'Right justified' _osea empezar el bit 0 en la derecha, lo normal. *Bit6* no se usa, por eso esta 0. *Bit5* y *Bit4* agarran _VSS_ y _VDD_ de referencia, que sería lo mas sencillo. *Bit3-0* no importan así que se ponen a cero, pero en el programa ponen 0010, cuando debería ser 0000 no?

Otra cosa que me di cuenta es que en el programa no usan _ADCON0_ cuando en el datasheet especifica que es (casi?) crucial.



```
ADCON0
bit 7-6 ADCS<1:0>: A/D Conversion Clock Select bits
      00 = FOSC/2
      01 = FOSC/8
      10 = FOSC/32
      11 = FRC (clock derived from a dedicated internal oscillator = 500 kHz max)

bit 5-2 CHS<3:0>: Analog Channel Select bits
      0000 = AN0
      0001 = AN1
      0010 = AN2
      0011 = AN3
      0100 = AN4
      0101 = AN5
      0110 = AN6
      0111 = AN7
      1000 = AN8
      1001 = AN9
      1010 = AN10
      1011 = AN11
      1100 = AN12
      1101 = AN13
      1110 = CVREF
      1111 = Fixed Ref (0.6V fixed voltage reference)

bit 1 GO/DONE: A/D Conversion Status bit
      1 = A/D conversion cycle in progress. Setting this bit starts an A/D conversion cycle. 
      This bit is automatically cleared by hardware when the A/D conversion has completed.
      0 = A/D conversion completed/not in progress

bit 0 ADON: ADC Enable bit
      1 = ADC is enabled
      0 = ADC is disabled and consumes no operating current
```

Se "supone" que debería haber una instruccion en el programa que diga:

```
ADCON0 = 0b10001011  //Podria ser tambien 0x8B
```

Y en este caso tendrían que coincidir ADCON0 y ADCON1 en escoger el mismo canal, en éste caso AN2 o CANAL2.

Perdón por abusar de tu ayuda, espero no haber hecho estoy muy largo. Muchas gracias por tu ayuda!!


----------



## ByAxel (Oct 24, 2010)

Que cosas no?, mejor cree lo que dice el Datasheet y sobre los ejemplos, lo peor puede ser es el mismo error humano.
El ADCON0 es usado internamente por la Función *ADC_READ()*, por eso pide una valor de entrada que es el canal a leer. Si modificas algo, descuida que el compilador solo cambia los valores de:

```
bit 5-2 CHS<3:0>: Analog Channel Select bits
bit 1 GO/DONE: A/D Conversion Status bit
bit 0 ADON: ADC Enable bit
```
el resto no lo toca...

Ahora, estamos viendo el mismo Datasheet?  ya que en ADCON1 no hay nada de canales... Son ANSEL, ANSELH para configurar cuantos o cuales canales ADC se van a usar y en ADCON0 se configura que canal se va a leer de los pines disponibles configurados como entrada análoga... este último no es necesario porque lo usa la función *ADC_READ()*.

saludos.


----------



## kal00 (Oct 24, 2010)

Tienes razón, _ADCON1_ no usa canales, yo mismo lo puse en la tablita pero me confundí jaja perdón. El que selecciona el canal sería *ANSEL*, aquí es donde tiene que coincidir con el canal que pones en ADC_READ() cierto? Aunque sigo sin entender muy bien *ANSELH*...

Me dices que la misma librería de *ADC_READ() *ya usa por defecto el _ADCON0_, está perfecto, pero que pasa con los bits 6 y 7 que seleccionan a _Fosc_? Como pongo esos bits sin afectar a los que ya modifico el _ADC_READ_? O cuál es la velocidad que toma por defecto? 

Lo único que me faltaría por entender sería esto:

```
tlong = (long)adc_rd * 5000;           // covert adc reading to milivolts
    tlong = tlong / 1023;                  // 0..1023 -> 0-5000mV

    ch     = tlong / 1000;                 // extract volts digit
    LCD_Chr(2,9,48+ch);                    // write ASCII digit at 2nd row, 9th column
    LCD_Chr_CP('.');

    ch    = (tlong / 100) % 10;            // extract 0.1 volts digit
    LCD_Chr_CP(48+ch);                     // write ASCII digit at cursor point

    ch    = (tlong / 10) % 10;             // extract 0.01 volts digit
    LCD_Chr_CP(48+ch);                     // write ASCII digit at cursor point

    ch    = tlong % 10;                    // extract 0.001 volts digit
    LCD_Chr_CP(48+ch);                     // write ASCII digit at cursor point
    LCD_Chr_CP('V');
```

Crees que me puedas explicar paso a paso como saca los digitos y las conversiones? El por qué le suma el 0 ASCII, o que es tlong  En verdad que no entiendo nada .

Gracias por tu valioso tiempo!!


----------



## ByAxel (Oct 24, 2010)

*ANSEL* y *ANSELH* hacen lo mismo... si revisas el Item de "I/O Ports" verás que los registros que intervienen en la configuración del PORTA son (TRISA Y ANSEL)... entonces para que un pin sea entrada ADC necesita configurar el pin respectivo en TRISA y el bit respectivo en ANSEL.

ANSEL:

```
bit 7-0 ANS<7:0>: Analog Select bits
        Analog select between analog or digital function on pins AN<7:0>, respectively.
        1 = Analog input. Pin is assigned as analog input
        0 = Digital I/O. Pin is assigned to port or special function.
```

Es decir para que pin RA0 sea entrada análoga se tiene que poner por ejemplo:
TRISA.B0 = 1;
ANSEL.B0 = 1; // Porque el pin cero del PORTA va a ser entrada análoga...

Como vez, no tiene nada que ver con ADCON0 y el canal a leer porque en ANSEL solo le indicas al PIC cuales pines van a ser de entrada análoga...
Al PIC se le tiene que indicar que canal debe de leer y eso se pone en ADCON0 "ADC_READ", se puede poner el canal que quieras, siempre y cuando el canal (pin) a leer esté configurado como entrada análoga en ANSEL.


----------



## kal00 (Oct 24, 2010)

Claro, y si no se ocupan los demás pines como análogos entonces no es necesario poner los demas _ANSEL_ y _ANSELH_ a cero o sí? Entonces sale sobrando esa línea de *ANSELH = 0 *en el programa. Pero entonces que pasa con los últimos dos bits de ADCON0 que controlan a *Fosc*? O el _ADC_READ _ya pone una velocidad por default?

Saludos y gracias de verdad.


----------



## ByAxel (Oct 24, 2010)

Sobre el ANSEL, si es necesario aunque no uses el ADC... por defecto cuando el PIC inicia, todos los pines son entrada y todos los bits de ANSEL son '1'; por eso es necesario su configuración...

Como mencione antes, la función ADC_READ solo modifica

```
bit 5-2 CHS<3:0>: Analog Channel Select bits
bit 1 GO/DONE: A/D Conversion Status bit
bit 0 ADON
```
el resto no lo toca...


----------



## kal00 (Oct 24, 2010)

Ohh ya, entonces sería bueno poner los _ANSEL_ a cero excepto los que uses como entrada analógica. Ahora respecto al _Fosc_, como le hago para poner los últimos dos bits sin modificar los demás? Yo pondría algo así:


```
ADCON0 = 0b11000000
```

Los dos unos serían los que seleccionan al _Fosc_, pero entonces los demás bits se ponen en cero haciendo la función del ADC_READ nula. Cómo se podría solucionar ésto?

Muchas gracias por la molestia q te tomas.


----------



## ByAxel (Oct 24, 2010)

No hay problema... cuando se usa la función, ésta solo modifica los que corresponden al canal a leer + el bit GO/DONE y el bit ADON; el resto, esos dos bit no los modifica.

saludos


----------



## kal00 (Oct 24, 2010)

Entonces *primero* sería la instruccion del _ADCON0 = 0b11000000_, *y después *la del ADC_READ. Ok ya entendí muchas gracias. Te puedo pedir otro favor? Podrías explicarme la parte de tlong y las operaciones matemáticas que hace? No le entiendo muy bien. Muchas gracias de antemano por tu tiempo.

Saludos!


----------



## ByAxel (Oct 24, 2010)

La señal análoga (supongamos que va de 0V a 5V) es convertida a digital con una resolución de 10bits (el valor es 0 cuando es 0V y 1023 cuando es 5V), para almacenar el valor usa la variable *adc_rd* que es de 16bits suficientes para alojar los 10bits del ADC.

*tlong* es una variable de 32bits, la usa para acumular las operaciones siguientes;

1) Usa la Regla de tres para obtener el voltaje actual.
tlong = (long)adc_rd * 5000;
tlong = tlong / 1023;

Es decir, ejemplo... si:

```
5V  ->  1023 (max. ADC)
?   ->  adc_rd
...
adc_rd = 512; // > Por ejemplo.
...
// > tlong = (adc_rd * 5000) / 1023
// > tlong ahora es 2502
```

2) Obtiene el entero de la división.

```
ch = tlong / 1000; // > 2502 / 1000 = 2.502; como se ve 512 equivale a 2.502 voltios.
```
obtiene en *ch* solo el entero *2* del resultado, luego para mostrar en el LCD necesita que el *2* sea un ASCII, por eso le suma (48 + ch) que en binario es:

```
00000010 +
00110000
---------
00110010 // > El resultado es el ASCII "2".
```

Con *LCD_Chr_CP('.');* muestra el punto (.) que separa decimales.
3) Usando el operador '*%*' obtiene el resto o residuo de la división para que sea el 1er decimal:

```
ch = (tlong / 100) % 10;
```
donde 2502 / 100 = 25 y el residuo de la división entre 10 es *5* y este valor es el 1er decimal.

4,5) Se repite el punto (3) pero el residuo sale de la división entre 10 para obtener el 2do y 3er decimal, luego lo muestra en el LCD sumando 48 para convertirlo en ASCII.

saludos.


----------



## MAAD98 (Nov 10, 2010)

Buenas a todos......
Soy nuevo en esto y tengo un pequeÑo problema ojala y me puedan ayudar...    
Mi problema es el siguiente...

Como puedo hacer multiplicar en una conversion analogica digital para visualizar de derecha a izquierda y de izquierda a derecha en el lcd......  

Por que de izquierda a derecha cambia el valor  multicado.....


----------



## ByAxel (Nov 10, 2010)

MAAD98 dijo:


> Buenas a todos......
> Soy nuevo en esto y tengo un pequeÑo problema ojala y me puedan ayudar...
> Mi problema es el siguiente...
> 
> ...


Que tal.
Un efecto animado en el LCD? o que...
sube tu programa...


----------



## Bistolf (Nov 15, 2010)

Que tal buenos dias. Queria aprobechar el tema para para ver si hay una respuesta simple para lo siguiente : He probado de todo y termine cayendo en programar un simple convertidor A/D que enciende 8 leds a las salida de ptob, y aun asi las cosas no se dan como espero. Entonces se me ocurre preguntar ¿hay manera de saber si el convertidor del pic esta dañado?...a este mismo pic le cargue programas como por ejemplo una rutina PWM y anda a la perfeccion, pero quizas una cosa no quita la otra. 


Muchas gracias.


----------



## gmgx (Feb 1, 2012)

Hola ByAxel te entendí muy bienb ciertas cosas en tu explicación, excepto cuando hiciste la regla de 3. se hacer reglas de 3, es algo muy sencillo, pero no te explicaste muy bien como salieron los valores que se meten en C


----------



## ByAxel (Feb 6, 2012)

gmgx dijo:


> Hola ByAxel te entendí muy bienb ciertas cosas en tu explicación, excepto cuando hiciste la regla de 3. se hacer reglas de 3, es algo muy sencillo, pero no te explicaste muy bien como salieron los valores que se meten en C



La variable es obtenida al leer un canal ADC del PIC, el resto es una forma de convertir y representar el valor del ADC en ASCII, puesto que se muestra en un LCD. Del ejemplo (512 del ADC 10bits equivale a 2.5V); el resto de constantes como 1024 o 5000 que supongo a lo que te refieres; 1024 es la resolucion maxima del ADC a 10bits y 5000 representa el valor maximo de la conversion a ASCII = 5Voltios.


----------

