esempio di AnalogRead con prescaler a 16 e logging su PC

ecco il codice per fare le analogRead con prescaler a 16 (meno di 20 microsecondi a lettura)
ciò vuol dire ~8600 letture al secondo
notare che per loggare questa quantità di dati serve la seriale 760800 baud, una velocità quasi-standar. se non vi funziona provate a usare 921600.
Se non vi funziona nemmeno così, allora siete costretti a usare 115200 baud e accontentarsi di loggare a 2972Hz. (In questo caso aggiungete una Serial.flush() prima delle Serial.write(), in modo da non subire una lag sulle letture analizzate, che arriva tranquillamente a 30 secondi)

/*
  Fast Analog Input

  it read 3 analog pin with a 10bit precision. (until 200kHz of ADC's clock according to datasheet, but it will work at 1MHz)
  Using a prescaler of 2 give 8MHz reading, but i've found value to be inconsistent. Haven't tryed other prescaler. Arduino use 200Hz ADC clock, so prescaler = 128
  it will store the 3 read of 30bit into one unsigned long (32bit) and then print it to serial.
  That way it use 4 byte instead of 6 to store the data, and because it will write 5 byte instead of 7 into serial, it is 1,4 times faster to write
  
  If in 8bit mode, it will store the 3 read of 24bit + 8bit of '\n' into one unsigned long (32bit) and then print it to serial.
  That way it use 3 byte instead of 6 to store the data, and because it will write 4 byte instead of 7 into serial, it is 1,75 times faster to write
  
  End of String value is \n
  
  it will use analog pin
  A0 = 14
  A1 = 15
  A2 = 16
  
  This code should work on all arduino IDE version, but faster on version > 1.0 because of asincronous serial communication.
  
  That mean that serial will write data while you are reading the next one, but this also mean we cannot use the ADC noise canceller mode.
 
  This code is tested on Atmega328P, but should work for:
  Atmega48A
  Atmega48PA
  Atmega88A
  Atmega88PA
  Atmega168A
  Atmega168PA
  Atmega328
  Atmega328P
  
  Created by lesto for http://www.arduino.cc
  12 June 2012
 
  This example code is LPGL licence Version 3, 29 June 2007
  you can find it on http://www.gnu.org/licenses/lgpl.html
 
 */
 
#define USE10BIT 1

void setup() {
  // start serial (it is slow, change this to higher value)
  Serial.begin(460800);//460800 is best value for prescaler @ 16
  Serial.flush();
  //just a bit of delay
  delay(2000);
  #if USE10BIT
    Serial.println("10BIT");
  #else
    Serial.println("8BIT");
  #endif
  
  delay(2000);
  
  ADCSRA &= B11111100; //prescaler to 16, about ~77 kHz sample rate
  
  Serial.println(ADCSRA, DEC);
}

int i;
unsigned long ris;
void loop() {
  //reset ris
  ris=0;
  
  for (int pin = A0; pin < A3; pin++){
    #if USE10BIT
       ris = ris << 10;
       ris += analogRead(pin);
    #else
       ris = ris << 8;
       ris += analogRead(pin)>>2; //remove 2 lower bit
    #endif
  }
  
  #if USE10BIT
    ris = ris<<2;
    Serial.write(ris>>24);
    Serial.write(ris>>16);
    Serial.write(ris>>8);
    Serial.write(ris);
    Serial.print('\n');
  #else
    ris = ris<<8;
    ris |='\n';
    Serial.write(ris>>24);
    Serial.write(ris>>16);
    Serial.write(ris>>8);
    Serial.write(ris);
  #endif
//  delay(100); //just for debug
}

e la controparte processing che salva i dati su file:

/**
 * Simple Read Fast
 * 
 * from lesto http://www.arduino.cc
 */


import processing.serial.*;


//START - you can saftely change this data
String portName = Serial.list()[0];
int baudRate = 460800; //best value for prescaler @ 16
//END - you can saftely change this data

long time;//to calculate read/s

String fileName;

void setup() 
{
  int i = 0;
  do{
    fileName = "savefile_"+year()+":"+month()+":"+day()+"_"+hour()+":"+minute()+":"+second()+"_"+i+".csv";
    i++;
  }while (loadStrings(fileName) != null); //until we don't find a file
  try{
    FileWriter pw = new FileWriter(fileName, false);
    pw.close();
  }catch(Exception e){
    e.printStackTrace();
  }
  size(600, 200);
  
  println(portName);
  Serial myPort;  // Create object from Serial class
  myPort = new Serial(this, portName, baudRate);
  
  //now attach an event (a software interrupt) to serial when a \n is readed
  myPort.bufferUntil('\n'); // if newline launch event
  
  time = millis();
}
boolean areYouSure = false;
static Object syncronizer = new Object(); //just for syncronization
float readXseconds=0, byteXseconds=0;
int x, y, z;
void draw()
{
  synchronized(syncronizer){
    background(0);
    File a = new File(fileName);
    text("Save file: "+a.toURI(), 15, 10);
    text("Serial Port: "+portName, 15, 30);
    text("Baud Rate: "+baudRate, 15, 50);
    text("Read per Seconds: "+readXseconds, 15, 70);
    text("Byte per Seconds: "+byteXseconds, 15, 90);
    text("Bandwidth used: "+((byteXseconds*10)/baudRate)*100+"%", 15, 110);
    text("Last Read X: "+x, 15, 140);
    text("Last Read Y: "+y, 15, 160);
    text("Last Read Z: "+z, 15, 180);
  }
}

int read = 0;
long byteRead = 0;
int a=0, b=0, c=0;
final ArrayDeque<String> output = new ArrayDeque<String>();
String aCapo = System.getProperty("line.separator");
synchronized void serialEvent(Serial p){ //synchronized because we want process only one evet at time

  long time2 = millis();
  if ( (time2 - time) >= 1000 ){
    synchronized(syncronizer){
      float partialTime= ((time2 - time)/1000);
      readXseconds = read / partialTime;
      byteXseconds = byteRead / partialTime;
      byteRead=read=0;
      time = time2;
      x = a;
      y = b;
      z = c;
      saveData(); 

//      println("read at seconds:"+readXseconds);
    }
  }
  
  byte[] inBuffer = new byte[30];

  int l = p.readBytesUntil('\n', inBuffer);
  byteRead+=l;
  /* FOR DEBUG
  if (l != 0)
    println("readed "+l);
  */
  if (inBuffer != null && l == 5) {
    read++;
//    for (int i = 0; i < 4; i++){
//      println(inBuffer[i]& 0xFF);
//    }
    a = ( inBuffer[0] & 0xFF)<<2; //all 0
    a += ( inBuffer[1]& 0xFF) >>6; //and 2 first 2 bit of 1
    
    b = ( (inBuffer[1]& 0xFF) & 63)<<4; //last 6bit of 1
    b += ( (inBuffer[2]& 0xFF) >>4);  //and first 4 bit of 2
    
    c = ( (inBuffer[2])& 0xFF & 15)<<6;  //last 4bit of if 2
    c += ( inBuffer[3] & 0xFF) >> 2; //fisrt 6 bit of 3
    
//    println("a:"+a+" b:"+b+" c:"+c);
      output.add(a+","+b+","+c+aCapo);
  }
  
  if (inBuffer != null && l == 4) {
    read++;
    a = inBuffer[0] & 0xFF;
    
    b = inBuffer[1] & 0xFF;
    
    c = inBuffer[2] & 0xFF;
    output.add(a+","+b+","+c+aCapo);
//    println("a:"+a+" b:"+b+" c:"+c);
  }
}

void saveData() { // Append data
  
  FileWriter pw = null;
  try{ 
    pw = new FileWriter(fileName, true);
    String tmp;
    while( (tmp = output.poll()) != null ){
      pw.write(tmp, 0, tmp.length());
    }
  }catch (IOException e) {
    e.printStackTrace(); // Dump and primitive exception handling...
  }finally{
    if (pw != null){
      try{ 
        pw.close();
      }catch (IOException e) { 
        e.printStackTrace(); // Dump and primitive exception handling...
      }
    }
  } 
}

notare che il sample rate è di 8.2kHz invece che ~25kHz(77 kHz / 3 pin)... credo ciò sia dovuto alla seriale asincrona, visto che usare il baud-rate 921600 ABBASSA il sample rate a 7.6kHz...

edit: piuttosto che fare (77 kHz / 3 pin) è più corretto fare (8.2kHz*3 = 24.6kHz, perchè faccio 3 sample ogni scrittura). sta di fatto che il resto del codice sembra abbattere della metà le letture possibili.

da provare il free-running mode, ma ho paura che cozzi con gli interrupt della seriale, ma magari è una mia paranoia.

Ciao Lesto,questo tuo topic mi interessa molto,il prescaler se non ho capito male, in base alla sua impostazione stabilisce il fattore di divisione della frequenza , non riesco a capire quale, che tu hai modificato con questo codice:

ADCSRA &= B11111100; //prescaler to 16, about ~77 kHz sample rate

dove in ordine dal bit più significativo hai abilitato l'ADC,ADC start conversion,autotrigger,interrup flag,adc interrup,ed infine con 100 hai fatto eseguire la divisione al prescaler con fattore 16 tramite ADPS2-1-0 secondo questa tabella del datasheet
23.9.2 ADCSRA – ADC Control and Status Register A.
Se quello che ho capito è giusto mi diresti qual'è la frequenza che viene divisa perchè non mi è chiaro ?
Grazie.

La frequenza di riferimento è sempre il clock principale. Lo dice anche il datasheet, pag. 266, punto 24.9.2:

Bits 2:0 – ADPS[2:0]: ADC Prescaler Select Bits
These bits determine the division factor between the system clock frequency and the input clock
to the ADC.

 for (int pin = A0; pin < A3; pin++){
    #if USE10BIT
       ris = ris << 10;
       ris += analogRead(pin);
    #else
       ris = ris << 8;
       ris += analogRead(pin)>>2; //remove 2 lower bit
    #endif

Poi da qui ho capito che hai usato un ciclo for per leggere sui 3 ingressi analogici A0-2 ed hai fatto degli shift verso sx di 10 e 8 per vedere anche a condizione di tutti zeri le rispettive letture (penso sia per agevolarne le letture).
Mentre qui

#if USE10BIT
    ris = ris<<2;
    Serial.write(ris>>24);
    Serial.write(ris>>16);
    Serial.write(ris>>8);
    Serial.write(ris);
    Serial.print('\n');
  #else
    ris = ris<<8;
    ris |='\n';
    Serial.write(ris>>24);
    Serial.write(ris>>16);
    Serial.write(ris>>8);
    Serial.write(ris);
  #endif
//  delay(100); //just for debug

ho capito che fai degli shift verso destra di 24,16,8 sempre della stessa variabile (ris) ma non riesco a capire il perchè .
So che sei sempre disponibile e non vorrei approfittare di ciò ma sarei contento (tanto,tanto) se mi aiutassi a capire.
Grazie di nuovo.

Grazie Leo72 quindi il resto del discorso ovvero il valore dei bit usati da Lesto per settare il prescaler l'ho capito bene?

Sì, son giusti.

@lesto:

ADCSRA &= B11111100;
non è corretto.
Devi fare:

ADCSRA = B11111100;
questo perché se ADCSRA ha già un valore, fai l'AND con i suoi bit per cui potresti avere dei registri impostati a zero.
Io comunque tendo a non usare questa forma ma a modificare i singoli registri.
Quindi per attivare l'ADEN metto ADCSRA |= (1<<ADEN) ecc...
So che viene una riga più lunga ma è anche più leggibile e più esplicativa

ADCSRA = B11111100;
questo perché se ADCSRA ha già un valore, fai l'AND con i suoi bit per cui potresti avere dei registri impostati a zero. 
Io comunque tendo a non usare questa forma ma a modificare i singoli registri.
Quindi per attivare l'ADEN metto ADCSRA |= (1<<ADEN) ecc...
So che viene una riga più lunga ma è anche più leggibile e più esplicativa

Effettivamente assegnando (=) il valore di bit non si ricorre a nessun rischio visto che quando si carica il codice in arduino lui è acceso quindi dovrebbe avere dei valori impostati di default (penso).
Quando dici

Quindi per attivare l'ADEN metto ADCSRA |= (1<<ADEN) ecc...

quello shift sx ADEN vuol dire vai tutto a sx fino sd ADEN e dai il valore 1 ?
Quindi diventa una cosa del genere
ADCSRA|=(1<<ADEN)|=(1<<ADSC)...|=(0<<ADPS0)
è giusto ?

Sarebbe: shifta 1 a sinistra per il valore di ADEN. I registri sono costanti preassegnate dal compilatore.
Quindi 1<<ADEN equivale alla fine a "metti ad 1 il bit ADEN"

Puoi usare anche _BV(ADEN) se preferisci, che setta il bit passato come parametro

Grazie Leo72,ora comincio a capire il significato di quelle che mi sembravano parolacce,quindi in teoria se durante un programma ho la necessità di impostare/modificare un solo parametro all'interno di un registro posso farlo chiamando nome registro,valore bit,shift e quindi quello che devo modificare senza necessariamente impostare il valore all'intero registro ogni volta.
Giusto ?
Quel _BV sta per bit value,è giusto anche questo?

tonid:
Grazie Leo72,ora comincio a capire il significato di quelle che mi sembravano parolacce,quindi in teoria se durante un programma ho la necessità di impostare/modificare un solo parametro all'interno di un registro posso farlo chiamando nome registro,valore bit,shift e quindi quello che devo modificare senza necessariamente impostare il valore all'intero registro ogni volta.
Giusto ?
Quel _BV sta per bit value,è giusto anche questo?

Non "puoi", ma "devi" farlo :wink:
E' buona norma non modificare mai un intero registro perché si potrebbero alterare anche bit già preimpostati.
Tornando all'esempio del bit ADEN, la buona norma per impostarlo è questa:
ADCSRA |= (1<<ADEN)
Ossia, imposta ad 1 il bit ADEN e poi fai l'OR logico con il registro ADCSRA. In questo modo, qualunque valore avesse in precedenza ADEN, dopo l'operazione conterrà il valore 1.

Per deselezionarlo, si fa invece:
ADCSRA &= ~(1<<ADEN)

Spiego. 1<<ADEN abbiamo già visto che imposta ad 1 il bit ADEN. ~ è l'operatore NOT, per cui ~(1<<ADEN) equivale a mettere a 0 il bit ADEN. Fatto questo, esegui l'AND con il registro ADCSRA per cui la logica booleana ti dice che qualunque valore abbia in quel momento il bit ADEN nel registro ADCSRA, un AND con 0 restituirà sempre 0. Quindi alla fine avrai impostato a 0 il bit in questione.

Inoltre, come già detto, in questo modo tu non tocchi nessun altro bit del registro, e ciò potrebbe essere importante in registri dove ci sono diversi altri bit, vedi ad esempio i timer, che regolano molte cose importanti.

Grazie Leo, dopo questa spiegazione ti posso dire che ho finalmente capito la logica da utilizzare per accedere a registri e per modificare i dati/valori contenuti. Ora non mi resta altro che provare a mettere in pratica in modo da prendere confidenza,grazie di nuovo per la tua disponibilità.Non credo di avere cose da insegnarti ma se potrò esserti di aiuto lo farò sicuramente.

tonid:
Ciao Lesto,questo tuo topic mi interessa molto,il prescaler se non ho capito male, in base alla sua impostazione stabilisce il fattore di divisione della frequenza , non riesco a capire quale, che tu hai modificato con questo codice:

ADCSRA &= B11111100; //prescaler to 16, about ~77 kHz sample rate

dove in ordine dal bit più significativo hai abilitato l'ADC,ADC start conversion,autotrigger,interrup flag,adc interrup,ed infine con 100 hai fatto eseguire la divisione al prescaler con fattore 16 tramite ADPS2-1-0 secondo questa tabella del datasheet
23.9.2 ADCSRA – ADC Control and Status Register A.
Se quello che ho capito è giusto mi diresti qual'è la frequenza che viene divisa perchè non mi è chiaro ?
Grazie.

attento, ti è sfuggito il & davanti al =
quindi io con quel codice ho SPENTO gli ultimi 2 bit a destra lasciando gli altri immodificabili. (ADPS di default arduino è 111)

per l'altra domanda ora non posso rispondere per esteso, sappi che la soluzione è da ricercare in

 If in 8bit mode, it will store the 3 read of 24bit + 8bit of '\n' into one unsigned long (32bit) and then print it to serial.
  That way it use 3 byte instead of 6 to store the data, and because it will write 4 byte instead of 7 into serial, it is 1,75 times faster to write

e poi per la Serial.write va ricercata nel fatto che essa scrive un solo byte alla volta, quindi devo estrarre i vari byte a partire da sinistra fino a destra, in modo da scrivere tutti e 4 i byte che compongono la variabile ris

lesto:
attento, ti è sfuggito il & davanti al =
quindi io con quel codice ho SPENTO gli ultimi 2 bit a destra lasciando gli altri immodificabili. (ADPS di default arduino è 111)

Non mi è sfuggito :stuck_out_tongue:
Però è errato perché tu dai per scontato che il tuo codice sia usato così com'è, quindi su un registro ADCSRA non manipolato.
Ma se un utente sprovveduto, che non conosce la gestione dei registri, lo unisse ad un altro codice non scritto da lui che, per pura coincidenza, manipolasse quel registro PRIMA di eseguire le letture col tuo metodo? XD
Succederebbe che non tornerebbe più nulla perché se uno di quei bit fosse a 0, con l'AND resterebbe a 0.

Ciao lesto

attento, ti è sfuggito il & davanti al =
quindi io con quel codice ho SPENTO gli ultimi 2 bit a destra lasciando gli altri immodificabili. (ADPS di default arduino è 111)

&= vuol dire fare una and tra due valori,esempio con ADCSRA &= b11111100 intendi : entra nel registro(ADCSRA) e fai una and tra il bit 7 (del reg.) e il bit 7 che gli hai assegnato(b11111100), di default il bit 7 è a 1 quindi una and tra due 1 e uguale a 1 e via dicendo fino agli ultimi due bit che sono quelli che hai cambiato perchè una and di 1(default) con 0 è uguale a zero.
Penso di aver capito bene,giusto?

Non mi è sfuggito
Però è errato perché tu dai per scontato che il tuo codice sia usato così com'è, quindi su un registro ADCSRA non manipolato.
Ma se un utente sprovveduto, che non conosce la gestione dei registri, lo unisse ad un altro codice non scritto da lui che, per pura coincidenza, manipolasse quel registro PRIMA di eseguire le letture col tuo metodo?
Succederebbe che non tornerebbe più nulla perché se uno di quei bit fosse a 0, con l'AND resterebbe a 0.

Effettivamente stavo andando un po in confusione ma devo dire che è tornato utile confrontare i due esempi che avete fatto per rendersi conto di come si può arrivare alla soluzione in modi diversi valutandone le conseguenze.

leo, non parlavo con te per il fatto della &, anzi la tua osservazione è corretta, e sistemerò il codice per essere "assoluto". se non mi sbaglio dovrebbe essere B10000100, ovvero attiva l'ADC e prescaler a 16, tuttoil resto disattivo (anche l'avvio lettura)

@tonid:

ADCSRA è B10000111 settato da arduino, ma io lo voglio B10000100, quindi per spegnere i 2 pin a destra faccio
B10000111 & B11111100 = B10000100

Ok Lesto non ho guardato come era settato di default comunque ho capito il concetto che hai usato.
Stavo cercando di capire anche il senso di quella variabile int i che è stata dichiarata e non utilizzata.

dimenticanza mia, la usavo per smontare ris per la write usando un for, ma preferisco così è più leggibile.

ok grazie siete stati "miei" insegnanti,che dire...
grazie