Arduino ADC: Muestreo alta velocidad

.



El objetivo de este mini tutorial es configurar el módulo del convertidor analógico-digital a mayor velocidad que la que viene por defecto configurada en Arduino, tras las diferentes pruebas que se fueron haciendo en este post de foro Arduino entre aero_yo, CesarCarlos y un servidor. A su vez, amplia el anterior tutorial sobre mostrar gráficamente datos recibidos de Arduino a tiempo real usando el  software libre KST publicado anteriormente aquí.

El programa de Arduino, usa mayoritariamente C y diréctamente registros del microcontrolador, intentando conseguir el máximo rendimiento.

Como se puede observar en el datasheet, Atmel recomienda que la velocidad de reloj del ADC esté entre 50-200 kHz, para una resolución de 10 bits. El oscilador de nuestro Arduino (16 MHz) alimenta también el módulo del conversor, pasando a través de un divisor de frecuencia (prescaler).
Dicho prescaler se configura mediante tres bits del registro ADCSRA. Las combinaciones nos permiten dividir entre 2, 4,8,16,32,64 y 128.
Por ejemplo, un prescaler de 64 nos daría una frecuencia de alimentación al ADC de 250 kHz (16 Mhz/64=250 kHz), lo cual esta un poco por encima de la recomendación.

Se puede trabajar a mayores frecuencias, pero perdiendo resolución, que es lo que haremos aquí. Se trabajará con una resolución de 8 bits, para poder tener mayores velocidades de conversión y a su vez, minimizar el tiempo necesario para enviar el dato convertido mediante serie para poder disponerlo a la mayor frecuencia posible (sin tener que bajar a ensamblador).

Una conversión normal cuesta 13 ciclos de reloj, y usaremos una sóla entrada analógica (5). El resultado generado tras la conversión es almacenado en los regitros ADCH y ADCL, pero si sólamente se desea 8 bits, bastará con leer el registro ADCH (justificación a la izquierda).

Dicho resultado de la conversión (ADC) será enviado mediante serie (UART),sin convertir el dato a ASCII, para minimizar al máximo los tiempos de programa.

Un script en phyton (soft libre y multiplataforma) será el encargado de recoger dicho byte, convertirlo en milivoltios y guardarlo en un fichero. En concreto, el script utilizado es el siguiente:

Código:

#! /usr/bin/env python

import serial
import sys 


ser = serial.Serial('/dev/ttyUSB0', 1000000);
while 1:
    try:
        n=ser.read();
        n=ord(n)*5000/255;  #Convierto en mV
        print n;
    except KeyboardInterrupt:
        ser.close();        
        sys.exit(0)
Para poder usar este código, se necesita tener instalado python y la librería pySerial. Como se puede observar, estamos trabajando a 1 Mbps. Se ha de cambiar el nombre del puerto serie en el que tenemos nuestro Arduino conectado, por ejemplo en caso Windows, 'COM3', 'COM4', etc.
Es decir, cambiar la línea de la forma ser = serial.Serial('COM2', 1000000);
Si disponemos dicho script en un fichero, por ejemplo llamado adquisicion.py, ejecutaremos desde el terminal:

./adquisicion.py >> mifichero.csv
El fichero que contiene el script, debe tener permisos de ejecución (linux).
Cuando deseemos terminar la ejecución del script, en el terminal pulsamos CRTL+C.
Este script ha sido probado en Ubuntu y Windows, funcionando en ambos perfectamente.

En la parte Arduino, el programa necesario es el siguiente:

//------------------------------------------------
// Fast Acquisition
// By: Igor R.
// 03/09/2011
//------------------------------------------------


#define CHRONO  0


void setup()
{
  Serial.begin(1000000);  //1 Mbps
  
  //Prescaler
  //ADPS2 - ADPS1 - ADPS0 - Division Factor
  //0        - 0       - 0        ->2
  //0        - 0       - 1        ->2
  //0        - 1       - 0        ->4
  //0        - 1       - 1        ->8
  //1        - 0       - 0        ->16
  //1        - 0       - 1        ->32
  //1        - 1       - 0        ->64
  //1        - 1       - 1        ->128
  //Configure to Prescaler=16 (11793.57 Hz a 115200)
  //Configure to Prescaler=16 (66418.71 Hz a 1000000)

  bitWrite(ADCSRA,ADPS2,1);
  bitWrite(ADCSRA,ADPS1,0);
  bitWrite(ADCSRA,ADPS0,0);
  
  //Analog Input A5
  ADMUX=(1<<ADLAR)|(0<<REFS1)|(1<<REFS0)|(0<<MUX3)|(1<<MUX2)|(0<<MUX1)|(1<<MUX0);
}


void loop()
{

  #if CHRONO==1
  
    MeasureTime();
    for (;;)
    {
    }

  #else
  
    int i;

    for (;;)
    {
      while (!(UCSR0A & (1 << UDRE0)));
      UDR0 = analogReadFast();
      i++;
      if (i==-1);
    }
    
  #endif
  
}

//Read ADC
int analogReadFast()
{
 ADCSRA|=(1<<ADSC);
 // ADSC is cleared when the conversion finishes
 while (bit_is_set(ADCSRA, ADSC));
        return ADCH;
}


//Chrono function
void MeasureTime()
{
  unsigned int i=1;
  unsigned long tStart;
  unsigned long tEnd;
  
  //--------------------------------------------
  //CHRONO
  tStart=micros();
  for (;;)
  {
    while (!(UCSR0A & (1 << UDRE0)));
    UDR0 = analogReadFast();
    i++;
    if (i== 1000)  break;
  }
  tEnd=micros();  
  // END CHRONO
  //--------------------------------------------
  
  Serial.begin(115200);
  delay(100);
  Serial.println("");
  
  Serial.print("tStart=");
  Serial.println(tStart);
  
  Serial.print("tEnd=");
  Serial.println(tEnd);
  
  Serial.print("Puntos=");
  Serial.println(i);
  
  Serial.print("Frecuecy=");
  Serial.print((float)1000000000.0/((float)tEnd-(float)tStart));
  Serial.println(" Hz");
  
}


Se observa que el programa dispone de la variable CHRONO. Para un funcionamiento normal, enviando constantemente el valor adquirido, debe valer cero (0). Cuando vale uno (1), lo que hace es ejectuar el código utilizado para adquirir y enviar por serie, durante 1000 veces y cronometrar el resultado. Dicho resultado lo podemos leer mediante la consola serie del IDE de Arduino, ya que aunque tengamos configurado el puerto a 1 Mbps, cuando termina el cronometraje, el resultado lo envia a 115200 bps.

Transmitiendo a 1Mbps, y con un prescaler de 16 para el ADC, me sale una frecuencia de 66417.71 Hz (aprox 66.5 kHz). Es importante disponer este valor para cálculos frecuenciales o tener las unidades en tiempo.

Resumiendo,  necesitamos disponer cargado en Arduino el programa expuesto anteriormente, ejecutar el script python grabando la salida en el fichero deseado,  lanzar el KST y abrir dicho fichero. Los datos se irán actualizando a "tiempo real".


A continuación, unas capturas de pantalla resumiendo los pasos usando Ubuntu (click para agrandar las imagenes):

Paso 1:



Paso 2:



Paso 3:



Paso 4:




Paso 5:




Y ahora solo esperar a ver como las gráficas se van actualizando con los datos recibidos de nuestro Arduino. En ejemplo de a continuación, los datos obtenidos de la captura de la onda senoidal de la red eléctrica (proveniente de un transformador), en los que se pueden ver el resultado de la PSD (Power Spectral Density):



A continuación el esquema de conexión entre la red eléctrica y el Arduino mediante un trafo:


El divisor de tensión creado con las resistencias de 1K, añaden un offset de 2.5v sobre la señal senoidal proveniente del trafo. La salida del trafo es una senoidal de 12v AC, por lo que añadimos este offset para poder entrar la señal a nuestro Arduino.
Con el segundo divisor, creado utilizando un potenciometro, ajustamos para tener la amplitud de la señal entre 0 y 5 voltios (centrada sobre 2.5v).
Cuidado con este montaje, que puede ser peligroso.



Veamos otro ejemplo: un tono de 4 kHz generado con la tarjeta de sonido, gracias al Audacity. Es decir, he usado la tarjeta de sonido, como un generador de ondas (conectándolo al ADC del microcontrolador).  El propio Audacity dispone de una herramienta de análisis frecuencial, por lo que fácilmente se puede ver cómo deberían ser los resultados obtenidos de Arduino en KST.




Se debería disponer de una etapa de filtraje para evitar antialiasing.


No dejes de visitar el post del foro original así como este otro post, en el cual encontrarás diversas pruebas creadas por aero_yo y CesarCarlos, como medidas de armónicos de una guitarra, captura luz de una lámpara incandescente mediante un ldr, medida de la frecuencia de aleteo de una abeja, adquisición con micrófono, etc.








17 comentarios:

  1. Hola Igor,
    He probado el muestreo en Mac y te paso el .py para que funcione:

    #! /usr/bin/env python

    import serial
    import sys


    ser = serial.Serial('/dev/arduino', 115200);
    while 1:
    try:
    n=ser.read();
    n=ord(n)*5000/255; #Convierto en mV
    print n;
    except KeyboardInterrupt:
    ser.close();
    sys.exit(0)



    el puerto /dev/arduino lo he creado con "sudo ln -s /dev/tty.usbserial-A9005daY /dev/arduino" más fácil de escribir :)
    ¿La velocidad de transmisión del serial en el .py debe ser 1000000? porque más velocidad de 115200bps en Mac se queja de que no hay puerto abierto...

    ResponderEliminar
  2. Gracias!

    En Win y Ubuntu me va bien 1 Mbps. Cuanto mas velocidad, mejor frecuencia conseguiras, ya que le costara menos al programa enviar los datos.

    :-)

    ResponderEliminar
  3. Hola Igor.

    Solo comentarte que en el esquema eléctrico de la conexión entre la red eléctrica y Arduino hay un error. La parte de abajo del potenciómetro no debe ir conectada a tierra, debe ir al punto medio del divisor de tensión que forman las dos resistencias de 1K.

    Hace tiempo que con Arduino y a ratos libres estoy trabajando en un analizador de la red eléctrica con un circuito de medición de Tensión de Red que empecé con operacionales para adaptar la señal, pero que poco a poco se ha ido reduciendo en componentes hasta llegar a un circuito muy parecido al que has dibujado. Está casi acabado, asi que supongo que pronto daré señales de vida y lo publicaré, por si a alguién le interesa o quiere aportar mejoras.

    Aprovecho para agradecerte la publicación periódica de estos artículos tan interesantes sobre tus experimentos.

    Un saludo.

    ResponderEliminar
  4. Hola Igor.

    Yo he modificado tu programa, capturo 800 muestras y luego las mando todas seguidas
    También he hecho un programa en gambas que representa gráficamente la onda.
    He probado el divisor de 4 y 8, con 2 no me funciona el conversor, he puesto las ondas que me salen.
    Mi página es:
    http://www.seta43.netau.net/ardu_os.html
    Agradecido por tus artículos.

    Un saludo

    ResponderEliminar
    Respuestas
    1. Hola,

      Tiene muy buena pinta tu programa de Gambas. De todas formas, con un buffer, la frecuencia debería ser mayor que los 66kHz,ya que no estas mandando a tiempo real como se hace aquí (evitas la parte de enviar por serie mientras vas recogiendo y acumulando en tu buffer).

      Saludos y gracias!


      ;)

      Eliminar
    2. Exacto, no va en tiempo real como bien has dicho.
      Como ves en las pruebas que pongo, si pongo un divisor por 8 me salen  123200 muestras/s, y si pongo el divisor por 4 me salen 198000 muestras/s. Pero yo para el montaje final lo he puesto a un divisor de 16.

      Saludos
      SETA43

      Eliminar
    3. has encontrado alguna forma practica de almacenar mas de 800 muestras?? saludos!

      Eliminar
  5. Hola, yo no acabo de entender que se pueda configurar el puerto serie del ordenador a más de 115200. No se supone que esa es la velocidad máxima? A mi el windows no me deja seleccionar más de 128000.

    ResponderEliminar
  6. Hola, ¿ podrías explicar los cálculos que haces para saber que con el prescaler de 16 obtienes la frecuencia de 66417.71 Hz?

    ResponderEliminar
    Respuestas
    1. El código lo calcula para ti. Si cambias CHRONO a 1, te sacará la frecuencia por serie.
      Saludos

      Eliminar
    2. Ah ok, ya entendí!
      Gracias esta buenísimo esto!

      Eliminar
  7. hola tengo una duda la mas basica de las que puede haber, pero no se como crear el fichero .py en windows y de ahi pasarlo a .csv? emmmm me van a mandar a buscar a google verdad..... aunque sea un enlace donde pueda aprender

    ResponderEliminar
  8. Hola, seria posible transmitir de un Arduino Uno a otro Arduino Uno a 1mbps?.. es decir que no sea el pc el que reciba datos, sino otro Arduino?.. si sincronizarían o seria inestable la comunicación?..lo digo por la velocidad de recepción del Arduino receptor.

    ResponderEliminar
  9. Hola, hay alguna forma de hacer funcionar el analogReadFast en un pin que no sea el numero 5 ? saludos

    ResponderEliminar
    Respuestas
    1. Hola,
      Tienes que modificar el registro ADMUX.

      Saludos,

      Eliminar
  10. Hola oye mira estoy haciendo un electrocardiograma con el arduino uno, lo que sucede es que los puertos analogos no toman las muestras necesarias para poder graficar esto. tu me podrias ayudar explicandome como hago para aumentarle la velocidad ha una entrada analoga para que tome mayor numero de valores no me importa el numero de bits de estos gracias ;)

    ResponderEliminar