ADC lenta sull'arduino ZERO

Salve a tutti,

nella necessità di effettuare del signal processing per il mio progetto ho optato per la board Zero data la sua velocità di clock, 32bit ecc..

Con forte sorpresa (e disappunto) ho misurato con micros() la velocità di campionamento ad alto livello di AnalogRead() a ~2.3 kHz (~440 us per campione).
Una prestazione insoddisfacente per le mie necessità.

Vista la alta velocità del clock (4 ordini di grandezza maggiore!) mi aspettavo prestazioni migliori. Sarebbe sufficiente una velocità di ~10kHz, ma non so come conseguirla.

Mi sono informato un po', e ho recuperato il codice di qualcuno che dimostra come attraverso l'uso di DMA si può arrivare a velocità di campionamento di addirittura 2us (~500kHz !), ma nel suo codice il contenuto effettivo dei campioni è instabile e sballa dopo un po', saturando a 1024 o facendo altre cose spiacevoli. Inoltre 500kHz è troppo! (link ZERO's ADC with DMA - Arduino Zero - Arduino Forum )

Addentrandomi nella giungla dell'argomento sono riuscito a trovare un link di riferimento al sito della atmel dove suggeriscono come implementare il DMA, ma non vi è alcun frammento di codice!!
(link Smart | Connected | Secure | Microchip Technology )

C'è qualcuno che ha qualche idea di come io possa risolvere il problema?
Grazie in anticipo.

L'ADC del SAMD21, usato sulla ZERO, ha queste caratteristiche:

One 12-bit, 350ksps Analog-to-Digital Converter (ADC) with up to 20 channels
• Differential and single-ended input
• 1/2x to 16x programmable gain stage
• Automatic offset and gain error compensation
• Oversampling and decimation in hardware to support 13-, 14-, 15- or 16-bit resolution

Ovvero nativamente arriva fino a 350 kps, non è possibile andare oltre, trovo molto difficile credere che di default, in ambiente Arduino, lavora a solo 2.3 ksps, sugli AVR a 8 bit l'ADC lavora a 10 ksps in ambiente Arduino.
Sicuramente ci sono problemi nel tuo codice e/o in come usi l'ADC, il DMA serve solo se devi prelevare i dati dal ADC senza impegnare la CPU, comunque per lavorare a 10 ksps è praticamente inutile su un micro di quella categoria, puoi benissimo prendere i dati con un normale polling.

>mrektor: non riesco a trovare il tuo post di presentazione, pertanto, nel rispetto del regolamento, ti chiedo cortesemente di presentarti QUI (spiegando bene quali conoscenze hai di elettronica e di programmazione ... possibilmente evitando di scrivere solo una riga di saluto) e di leggere con attenzione il su citato REGOLAMENTO ... Grazie.

Guglielmo

astrobeed:
L’ADC del SAMD21, usato sulla ZERO, ha queste caratteristiche:

One 12-bit, 350ksps Analog-to-Digital Converter (ADC) with up to 20 channels

• Differential and single-ended input
• 1/2x to 16x programmable gain stage
• Automatic offset and gain error compensation
• Oversampling and decimation in hardware to support 13-, 14-, 15- or 16-bit resolution




Ovvero nativamente arriva fino a 350 kps, non è possibile andare oltre, trovo molto difficile credere che di default, in ambiente Arduino, lavora a solo 2.3 ksps, sugli AVR a 8 bit l'ADC lavora a 10 ksps in ambiente Arduino.
Sicuramente ci sono problemi nel tuo codice e/o in come usi l'ADC, il DMA serve solo se devi prelevare i dati dal ADC senza impegnare la CPU, comunque per lavorare a 10 ksps è praticamente inutile su un micro di quella categoria, puoi benissimo prendere i dati con un normale polling.

Guarda, ti mostro il codice con cui ho effettuato la misura

void setup() {
  // open a serial connection
  Serial.begin(9600); 
  pinMode(A1,INPUT);
  analogReadResolution(10);


}

void loop() {

int tempo=0;
 for(int i=0; i<lunghezzaArray; i++)
  {
      int prima = micros();
      campioni[i]= analogRead(A1); //campiona a 2.3kHz
      tempo = micros() - prima;
      Serial.println(tempo);      
  }
 
}

e in allegato ho caricato l’output sul monitor seriale, ovvero 424/425 (microsecondi), a cui corrisponde una frequenza di campionamento di ~ 2360Hz.

Ho provato anche a togliere la linea di comando pinMode(A1,INPUT); ma il risultato non cambia.

PS: @guglielmo mi scuso per essermi dimenticato la presentazione, ho provveduto a rispondere :slight_smile:

Screen Shot 2016-09-03 at 5.57.18 PM.png

Quello che hai misurato è la velocità dell'iterazione, inclusa la comunicazione sulla seriale che richiede il suo tempo, non la velocità di campionamento del ADC, che è di gran lunga maggiore.

@Astrobeed mi sento di dissentire, il comando di micros() lo ho invocato all’interno del for e prima della comunicazione seriale, quindi il numero misurato è effettivamente il tempo con cui l’ADC converte il valore e lo inserisce nell’array.

per fare la prova del 9, ho misurato il tempo di campionamento di 5000 elementi (senza nemmeno inserirli in un array) e fatto la media:

void loop() {
int dopo=0;
int a;
int tempo=0;
int prima = micros();
 for(int i=0; i<lunghezzaArray; i++)
  {
      
      a = analogRead(A1); //campiona a 2.3kHz
           
  }
dopo = micros();
tempo = dopo-prima;
float samplingTime = tempo / lunghezzaArray;
Serial.println(samplingTime);
delay(3000);  
}

il risultato è 422us.

Così hai conteggiato i cicli…
Hai messo prima = micros, hai eseguito il for e poi hai messo dopo = micros.
Fra prima e dopo ci sono tutte le operazioni di ciclo, incremento variabile (quel ++), confronto variabile (quel i < lunghezzaArray).

Per toglierti ogni dubbio, fai le stesse cose con il micros ma metti altre operazioni al posto dell’analogread, metti un pinMode, poi vedi con un un digitalWrite, poi con un Serial.qualchecosa e anche… con il solo for senza mettere nulla

Si ok ma comunque io devo salvare dei campioni in un array!

Era solo a dimostrazione che la misura di prima non si discostava tanto da questa seconda.

paulus1969:
Così hai conteggiato i cicli…
Hai messo prima = micros, hai eseguito il for e poi hai messo dopo = micros.
Fra prima e dopo ci sono tutte le operazioni di ciclo, incremento variabile (quel ++), confronto variabile (quel i < lunghezzaArray).

Per toglierti ogni dubbio, fai le stesse cose con il micros ma metti altre operazioni al posto dell’analogread, metti un pinMode, poi vedi con un un digitalWrite, poi con un Serial.qualchecosa e anche… con il solo for senza mettere nulla

Ho seguito il tuo consiglio (magari mi sbagliavo!) e eseguire un for da 5000 iterazioni (senza alcuna istruzione dentro) misura 1 o 2 us.

Invece per eseguire il comando pinMode ci mette 1us.

AnalogRead continua a metterci 420us.