ACELERAR FRECUENCIA DE MUESTREO

HOLA BUENAS

Soy un estudiante de electrónica y estoy metido en un proyecto con arduino en el cual necesito tomar muchas muestras a una velocidad muy alta. Tengo que medir una señal de 5KHz en la cual hay variaciones en cuestion de unos 100ns. El arduino due tiene un maximo de frecuencia de muestreo de 40KHz (corregirme si me equivoco), por lo que no puedo medir tan rápido.
Necesitaria algo asi como 10MHz de muestreo, algo mas si es posible pero con eso podria apañarme.

Sabeis de algun periferico que puede hacer de medidor o algun modo de tomar muestras tan rapido?

Podria comunicarlo con algun osciloscopio para que el osciloscopio recoja los datos y el arduino solamente trabaje con ellos una vez cogidos?

Estoy abierto a cualquier solución y toda ayuda sera bienvenida,
gracias de antemano!

1 Like

Bueno no entiendo porque necesitas samplear una frecuencia de 5Khz a 10Mhz.

El teorema demuestra que la reconstrucción exacta de una señal periódica continua en banda base a partir de sus muestras, es matemáticamente posible si la señal está limitada en banda y la tasa de muestreo es superior al doble de su ancho de banda.

Según teorema de Nyquist con 10Khz estarás bien, pero como nunca conseguirás un filtro pasabajos perfecto conviene usar una frecuencia 10% mayor o sea 11Khz. La mejor demostración es que para lograr calidad CD 20Khz se muestrea a 44Khz

Espero que expliques porque necesitas 10Mhz o has escrito mal la frecuencia que debes samplear?

a ver surbyete una cosa es las muestras para conocer la frecuencia de una señal (entonces me parece correcto aplicar el teorema) y otra cosa diferente es pretender conocer su forma, encontrar maximos, minimos o los puntos de inflexión para actuar en consecuencia

en ese caso es correcto unas 10x Fmax y aqui propone algo menos, si no es una cosa critica me parece perfecto

Hay ociloscopios de calidad que se conectan al Labview (el puerto se llama GPIB) así que seguro que se puede conectar a arduino, pero a esa velocidad no se si el arduino los pordra recibir hay que investigar

La frecuencia del ADC depende de la velocidad del reloj del micro (normalmente 16MHz) dividida por el preescaler, por defecto 128, y por el numero de ciclos que necesita el adc para trabajar que eso es fijo

Podemos actuar fácilmente sobre el preescaler y cambiar su valor, si miras el datasheet del micro podrás encontrar esta tabla

si lees un poco veras que pone que hasta 1Mhz de reloj la conversión no se ve demasiado afectada: " However, frequencies up to 1 MHz do not reduce the ADC resolution significantly."

(te preguntaras entonces por que hay opciones para preescaler mas pequeñas si con 16 ya llegamos a 1Mhz, pues bien es para usar con micros que lleven el reloj mas lento, por tema de consumo, pero que necesiten igualmente una conversión adc rápida)

tipico ejemplo para comprobar la velocidad de muestreo y tambien ves como se cambia a 16

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

void setup() {
 int start ;
 int i ;
 

 // set prescale to 16
 sbi(ADCSRA,ADPS2) ;
 cbi(ADCSRA,ADPS1) ;
 cbi(ADCSRA,ADPS0) ;


 Serial.begin(9600) ;
 Serial.print("ADCTEST: ") ;
 start = millis() ;
 for (i = 0 ; i < 1000 ; i++)
   analogRead(0) ;
 Serial.print(millis() - start) ;
 Serial.println(" msec (1000 calls)") ;
}

void loop() {
}

lo ejecutas asi y apuntas, luego borras los sbi y cbi y vuelves a ejecutar (obtienes la velocidad normal de muestreo)

saludos

1 Like

RAYOZUD:
El arduino due tiene un maximo de frecuencia de muestreo de 40KHz (corregirme si me equivoco), por lo que no puedo medir tan rápido.

Segun el Datasheet del procesador del Due la velocidad puede ser hasta de 1 MHz, te lo comento por si te interesa.

En su momento estudie utilizar arduino en la medición de un transitorio, para utilizarlo en un buscador de metales de modulación de pulso. Concluí que la mejor forma de hacerlo era no utilizar el ADC del arduino, consumía muchos ciclos de reloj , y usar un chip independiente ADC de 8 bits.
Utilizando los registros PORT y DDR se podía llegar a frecuencias de muestreos de Mhz (puedo equivocarme porque nunca llegue a montarlo).

Saludos

BUENAS!

Muchas gracias a todos por contestar.
Efectivamente como bien dice GO_zalo, necesito medir puntos maximos y minimos con mucha precision. de hecho necesito saber tiempos de subida con margen de 0,6us y con mucha precision por eso necesito una frecuencia de muestreo tan alta, para que esa medidas sean exactas.

Ya habia mirado lo del labview, me parecio la idea mas interesante ya que puedo comunicar el osciloscopio con el ordenador para recibir los datos y luego pasarlos a arduino para que los procese y haga el resto del proceso.
El problema de esta opcion era el presupuesto por el osciloscopio pero es la que tenia en mente y de hecho ahora iba a ponerme con el labview a ver que consigo hacer.

La idea del ADC externo parece muy interesante. Ya habia leido que era el tiempo del ADC lo que limitaba la velocidad asi que podria ser una solucion.

Voy a intentar la idea del labview ya que ahora tengo acceso a un osciloscopio y a ver si consigo hacer algo. No obstante la idea del ADC puede ser interesante, si la monto en un futuro o no consigo hacerlo con el labview ya os contare.

Muchas gracias y sigo aceptando ideas o si alguien se le ocurre alguna otra cosa

Hombre yo probaría primero a aumentar la frecuencia del reloj como te he explicado en mi ejemplo y a ver si ya tienes suficiente

Es infinitamente más fácil y económico (gratis)

Y puestos a hacerlo con el osciloscopio y labview, nunca haría los cálculos en el arduino sino en el propio pc y luego enviando las acciones resultantes al arduino, comandos para actuar o lo que sea

Perdona que se me habia olvidado esa parte y ademas veo que no la he entendido bien.
Pensaba que el programa que habias puesto era solo para ver la frecuencia no que podia subirla de manera tan simple.

No entiendo bien el programa y lo que quieres que haga.
He metido el programa en mi arduino mega (no tengo el due aqui ahora mismo) y me ha puesto 18msec (1000call), imagino que eso quiere decirme que hace 1000 pruebas por cada 18ms.
Despues he eliminado

sbi(ADCSRA,ADPS2) ;
cbi(ADCSRA,ADPS1) ;
cbi(ADCSRA,ADPS0) ;

del programa y el resultado a sido 11msec (1000call). No se si era eso lo que decias que hiciera pero no entiendo el programa ni como hacerlo bien, podrias explicarlo de alguna otra manera por favor.
La verdad que parece una solucion muy interesante e infinitamente mas economica claro esta si consigo esas frecuencias.
Muchas gracias de antemano!!!!

RAYOZUD, recuerda que si no necesitas resolucion en la medida, solo velocidad de muestreo puedes alinear a la Izquierda el resultado de la conversion y leer solo el registro ADCH con ello aumentas la velocidad de lectura a costa de tener solo 8 bits como valor medido de la conversion, te lo comento por si te interesa.

Abajo te dejo un codigo para que observes tiene lo que comentao ahi arriba de reducir la resolucion leyendo solo la parte alta y ps te lo puse a que hiciera pruebas con cada valor del prescaler a mi me arrojo los siguientes resultados en mi mega, como observas con el minimo de prescaler x2 se llega a una velocidad de 227 kHz sin embargo recuerda que el comerciante no recomienda usar prescalers por debajo de 16 para el Mega este codigo lastimosamente no servivira para el Due ya que me imagino que los registros son diferentes si quieres hacer algo similar en el te tocaria leer el datasheet del AtSam

ADCTEST:
Valor Leido:255
4408 usec (1000 calls)
Valor Leido:255
6288 usec (1000 calls)
Valor Leido:255
9536 usec (1000 calls)
Valor Leido:255
16004 usec (1000 calls)
Valor Leido:255
30124 usec (1000 calls)
Valor Leido:255
56100 usec (1000 calls)
Valor Leido:255
112000 usec (1000 calls)

// Inclucion de cbi y sbi para el prescaler del adc
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif 

enum DivisionFactor {
  prescalerX2,
  prescalerX4,
  prescalerX8,
  prescalerX16,
  prescalerX32,
  prescalerX64,
  prescalerX128
};

void setPrescaler(DivisionFactor prescaler)
{
  //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
  
  switch(prescaler)
  {  
    case prescalerX2:
      cbi(ADCSRA,ADPS2);cbi(ADCSRA,ADPS1);sbi(ADCSRA,ADPS0);
    break;
    case prescalerX4:
      cbi(ADCSRA,ADPS2);sbi(ADCSRA,ADPS1);cbi(ADCSRA,ADPS0);
    break;
    case prescalerX8:
      cbi(ADCSRA,ADPS2);sbi(ADCSRA,ADPS1);sbi(ADCSRA,ADPS0);
    break;
    case prescalerX16:
      sbi(ADCSRA,ADPS2);cbi(ADCSRA,ADPS1);cbi(ADCSRA,ADPS0);
    break;
    case prescalerX32:
      sbi(ADCSRA,ADPS2);cbi(ADCSRA,ADPS1);sbi(ADCSRA,ADPS0);
    break;
    case prescalerX64:
      sbi(ADCSRA,ADPS2);sbi(ADCSRA,ADPS1);cbi(ADCSRA,ADPS0);
    break;
    case prescalerX128:
      sbi(ADCSRA,ADPS2);sbi(ADCSRA,ADPS1);sbi(ADCSRA,ADPS0);
    break;
    
  }
}

uint8_t analogReadFast(uint8_t pin)
{
    // the MUX5 bit of ADCSRB selects whether we're reading from channels
    // 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
    ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin   >> 3) & 0x01) << MUX5);
  
    // set the analog reference (high two bits of ADMUX) and select the
    // channel (low 4 bits).  
    ADMUX = (ADMUX & ~(0x7)) | (pin & 0x07);
  
    // without a delay, we seem to read from the wrong channel
    //delay(1);
  
    sbi(ADCSRA, ADSC);
  
    // ADSC is cleared when the conversion finishes
    while (bit_is_set(ADCSRA, ADSC));
  
    return ADCH;
}

void setup() {
 unsigned long start,end;
 int i,lectura ;
 Serial.begin(250000) ;
 Serial.println("ADCTEST: ");

for(int n=0;n<7;n++){
 setPrescaler((DivisionFactor)((int)prescalerX2+n));
 //setPrescaler(prescalerX16);
 sbi(ADMUX,ADLAR); // Seteamos el bit ADLAR en el Registo ADMUX para que el resultado
                      // de la conversion quede alineado a la Izquierda
 cbi(ADMUX,REFS1);sbi(ADMUX,REFS0); // Referencia  AVCC with external capacitor at AREF pin
 delay(10);
 start = micros() ;
 for (i = 0 ; i < 1000 ; i++)
   analogReadFast(0) ;
 end = micros();
 lectura=analogReadFast(0);
 Serial.print("Valor Leido:");Serial.println(lectura);
 Serial.print(end - start) ;
 Serial.println(" usec (1000 calls)") ;
 }
}

void loop() {
}
1 Like

Con este tema empecé mal pero como siempre me motiva a estudiar/aprender.
He hecho algunas pruebas con mi DUE que empezaron muy mal.
puse una señal de 1KHz y me encuentro con que cada analogRead requiere 40useg.
Asi que dije.. noo algo estoy haciendo mal. Investigo a ver y encuentro un artículo que sugiere esto:
El valor por defecto del ADC Mode Register del DUE es 0x0c y eso da un tiempo de conversión de 36.6 useg.
Pero ese artítulo sugiere cambiar ese valor por 2 usando esta línea de código (perdonen no encuentro el artículo ahora).

REG_ADC_MR = (REG_ADC_MR & 0xFFF0FFFF) | 0x00020000;

Con esta linea mi tiempo de conversión bajó a 3.98useg ( aprox 250KS/seg).
Una mejora de 9 veces.
En el manual del SAM3X en la pagina 1334 dice que

Asi que me dije: no está habilitado.
El registro ADC Mode Register debe ponerse en 1 en su bit 7

REG_ADC_MR = (REG_ADC_MR & 0xFFFFFF0F) | 0x00000080; //habilita FREERUN mode

El tiempo de conversión bajó a 3.15uSeg.
Luego encontré en un comentario del foro Arduino que esto

analogReadResolution(12);

mejoraba la velocidad de sample y si lo hizo apenas.
Medí 3.12uSeg, pero lo mas sorprendente es que recuperé la resolución a 12 bits.

Pero en ese mismo tema surge que debe comentarse una linea del

/adc_disable_channel(ADC, ulChannel);

que esta en
\arduino-1.XXX\hardware\arduino\sam\cores\arduino\wiring_analog.c

Exactamente dice:

Totv:
Careful examination of the file c:\arduino-1.5.1r2\hardware\arduino\sam\cores\arduino\wiring_analog.c showed that the function analogRead() always finished with disabling of the analog channel. This is completely unnecessary, if the analog inputs are used only for streaming read from the ADC.
It was enough to comment out the line 164 where the disabling operation of the analog channel executes:

163: // Disable the corresponding channel
164: // adc_disable_channel (ADC, ulChannel);

Only one this comment dramatically increased the speed of the ADC. Now the function analogRead () holds less than 2 uS. Reading 12 channels in FOR loop now takes exactly 24 uS. This is with analogReadResolution(12). I think that for many applications this is sufficient.
However, for such a relatively powerful processor, as SAM3X8E, the low-level code inserting into Arduino will always be necessary. We need more new libraries to use all features of this processor.
Thanks all for advises.

Y esto si fue una sorpresa mayor porque el tiempo de conversión cayó a 1.79uSeg
Ya estoy en 558 Khz de sampleo.
Excelente!!! ni yo me lo imaginaba.

Pero no se que buscaba y leo que en las especificaciones del DUE dicen 12Bits 1MS/seg entonces realmente alcanza 1MSample/seg?

Decidí buscar a ver si alguien ya lo había resuelto y claro que si...
Me encuentro conque para lograrlo no usan analogRead, ya que el limite con analogRead son esos 550KS/seg

Este es el supercódigo que encontré MARAVILLOSO
El hilo se llama Speed of analogRead

Y este código garantiza 1useg de tiempo de conversión o sea 1MS/seg

unsigned long start_time;
unsigned long stop_time;
unsigned long values[1000];

void setup() {        
  Serial.begin(9600);  
  ADC->ADC_MR |= 0x80;  //set free running mode on ADC
  ADC->ADC_CHER = 0x80; //enable ADC on pin A0
}

void loop() {
  unsigned int i;
    
  start_time = micros();
  for(i=0;i<1000;i++){
    while((ADC->ADC_ISR & 0x80)==0); // wait for conversion
    values[i]=ADC->ADC_CDR[7]; //get values
  }
  stop_time = micros();

  Serial.print("Total time: ");
  Serial.println(stop_time-start_time); 
  Serial.print("Average time per conversion: ");
  Serial.println((float)(stop_time-start_time)/1000);

  Serial.println("Values: ");
  for(i=0;i<1000;i++) {
    Serial.println(values[i]);
  }  
  delay(2000);
}

Espero este ultimo código si te sirva y te motivo a que lo pruebes porque ha sido una grata sorpresa para mi esta búsqueda.

Leyendo el manual otra cosa que descubrí es que el ADC del DUE tiene un Programmable GAIN Amplifier o sea podes controlar la Ganancia de la señal con algunas limitaciones
1 2 4 para Canales individuales
0.5 1 2 2 para Canales diferenciales
No lo probé pero lo haré.

1 Like

Genial Surbyte vaya busqueda la que me has ahorrado, ya estoy con ganas de probarlo en mi Due, a no mas pueda lo hago que ando algo ocupado con unos asuntos, me llama la atención luego crear un codigo en LabView para que sirva de pantalla en la adquisición de señales, sin embargo creo que aun con estas opciones no es suficiente para RAYOZUD, ya que el anhela ver cambios con resoluciones de al menos 100nS y ni con el DUE se llega a eso, por lo tanto ¿que opciones recomendarian ustedes? ya que, yo no conozco de ADCs externos que tengan esa frecuencia.

Muy interesante Surbyte.
Voy a probarlo ya que parece muy interesante. Puede que no consiga toda la velocidad que quería pero si alcanzo 1MS/s creo que podre arreglarmelas.
Gracias a todos una vez mas, a ver si esta semana puedo hacer esto, ya que tambien tengo que hacerle un proceso a la señal que quiero leer, y os cuento en cuanto lo tenga.
Muchas gracias de nuevo a todos!