Acquisizione dati ADC ATMega328 in AVR e Media

Salve a tutti! Sto realizzando un piccolo mixer audio digitale con ARDUINO UNO R3. In pratica invio 3 segnali in A0,A1,A2, il micro fa la media e mi butta fuori in parallelo il numero in binario su di un DAC0800. Il mio problema è che scrivendo in linguaggio C di Arduino non riesco a campionare velocemente il segnale. Avrei bisogno di una mano con il linguaggio AVR. Da solo, guardando qua e la, sono riuscito ad ottenere il campionamento di un canale con buoni risultati, ma non riesco a campionare gli altri due ed a operare la loro media! Qualcuno che mi aiuti?

Non insultatemi è solo uno schizzo!

#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>

volatile uint8_t counter;
volatile float result;
volatile float result1;
volatile float result2;
volatile float result3;

void int_adc1(void)
{
  ADMUX = _BV(REFS0) |_BV(ADLAR);
  ADCSRA = _BV(ADEN) |_BV(ADATE) |_BV(ADSC) |_BV(ADPS2);
}
void int_adc2(void)
{
  ADMUX = _BV(REFS0) |_BV(ADLAR) |_BV(MUX0);
  ADCSRA = _BV(ADEN) |_BV(ADATE) |_BV(ADSC) |_BV(ADPS2);
}
void int_adc3(void)
{
  ADMUX = _BV(REFS0) |_BV(ADLAR) |_BV(MUX1) |_BV(MUX0); 
  ADCSRA = _BV(ADEN) |_BV(ADATE) |_BV(ADSC) |_BV(ADPS2);
}
void setup()
{
  DDRD = B11111111;
}
void loop()
{
  int_adc1();
  result1 = (float)(ADCH);
  int_adc2();
  result2 = (float)(ADCH);
  int_adc3();
  result3 = (float)(ADCH);
  result = (result1+result2+result3)/3;
  PORTD = result;
}

che velocità raggiungi e che velocità vuoi ottenere?

Arduino e il ATmega328 non é adatto a questo uso perché troppo poco potente.
Ciao Uwe

In realtà sono già riuscito ad ascoltare un canale convertito in digitale e riconvertito in analogico! certo la qualità non è da alta definizione ma per l'uso che ne devo fare è più che sufficiente! ho solo bisogno di una mano con il programma AVR!

dunque, esiste un modo per abbassare il prescaler all'ADC, rendendo le letture molto più veloci. Ho letto che col prescaler fino a 16 non si ha perdita di precisione (da datasheet se non erro), e oltre non ci sono test ufficiali, cmq a quanto pare non si scende al di sotto degli 8 bit

Si infatti la mia intenzione è quella di farlo lavorare il più velocemente possibile cambiando i valori da inserire nei registri dell'ADC in modo da cambiare il prescaler ma fino ad ora, sicuramente per la mia ignoranza in AVR sono riuscito a fare poco!

dopo ci lavoro sopra, visto che questo codice mi serve anche a me per un tizio a cui avevo promesso una cosa simile.

però mi pare che stai leggendo solo 8 bit della lettura ADC, quando invece sono 10! spero che stai perdendo solo i 2 bit meno significativi, ma dubito

ci sto lavorando, comunque questo è sbagliato: ADMUX = _BV(REFS0) |_BV(ADLAR);

stai settando varie cose (inutili) e non cambi valore del pin che vuoi leggere... in pratica stai leggendo sempre lo stesso ingresso!

ADCSRA = _BV(ADEN) |_BV(ADATE) |_BV(ADSC) |_BV(ADPS2);

stessa cosa va quì, son l'aggiunta che stai settando l'ADC per misure consecutive, che quindi richiedono di essere "analizzate" in modo particolare.

Sto usando i più significativi!...o almeno penso!
Teoricamente essendo settato l'ADLAR dovrei, andando a legger ADCH, selezionare i bit più significativi!

Mettendo l'ADC nella modalità free running non velocizzo la conversione?

per ADCH hai ragione, se ti bastano 8 bit

per il free running, funziona in modo completamente diverso.

Stai settando i registri sbagliati: ADMUX lo modifichi solo una volta per settare la configurazione, poi da li in poi cambi solo gli ultimi 4 bit per selezionare il pin da leggere e il bit ADSC per avviare una lettura.

Ti posto il codice che ho scritto, anche se per ora non funziona, ma son troppo stanco per capire cosa ho sbagliato, spero in un'anima pia, altrimenti domani provo a sbatterci ancora la testa:

/*
  Fast Analog Input

  it read 3 analog pin with a 10bit precision. (until 200kHz according to datasheet, but it will work at 8MHz)
  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(9600);
  Serial.flush();
  //just a bit of delay
  delay(2000);
  #if USE10BIT
    Serial.println("10BIT");
  #else
    Serial.println("8BIT");
  #endif
  
  delay(2000);
  
  //ADCSRA: with this register we set the prescaler to 2, on 16MHz clock this mean 8MHz reading
  //ADEN to 1: ADC enable
  //ADSC to 0: do not start ADC read now
  //ADATE to 0: single conversion mode
  //ADIF to 0: we don't have reading done yet
  //ADIE to 0: we don't want ADC interrupt
  //ADPS[2:0] to 000: prescaler = 2
  //ADCSRA |= B00000111;
  
  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
  Serial.flush();
  delay(100);
}

per leggere i dati, usa processing:

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


import processing.serial.*;

Serial myPort;  // Create object from Serial class
int val;      // Data received from the serial port

void setup() 
{
  size(200, 200);
  // I know that the first port in the serial list on my mac
  // is always my  FTDI adaptor, so I open Serial.list()[0].
  // On Windows machines, this generally opens COM1.
  // Open whatever port is the one you're using.
  String portName = Serial.list()[0];
  println(portName);
  myPort = new Serial(this, portName, 9600);
}

void draw()
{

  byte[] inBuffer = new byte[30];

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

Non capisco una cosa: perché metti nel mezzo i numeri in virgola mobile?
Perdi di precisione nonché rallenti i calcoli dato che chiedi continuamente di convertire da un tipo all'altro.
Ad esempio, perché converti la lettura del pin analogico in float? E poi perché assegni ad un registro ad 8 bit (PORTD) il valore di un float?
Non ho capito, ma c'è un motivo per cui fai così?

leo, pls riusciresti a capire perchè il mio codice non va?

eppure mi sembra di fare gli stessi passi che fa anche analogRead (cores/wiringAnalog.c)

tra l'altro mai avuto problemi a switchare velocità nel serial monitor con l'ide 1.0.1?

Stavo andando in doccia... :sweat_smile:
Cmq da una prima occhiata non mi torna come piloti i bit dei registri.

ADCSRA |= ADSC

Questo dovrebbe essere:

ADCSRA |= (1<<ADSC)

Poi a mente non mi ricordo come opera analogRead, dovrei darci un'occhiata per vedere le operazioni da fare per far partire una campionatura analogica. Back soon... :stuck_out_tongue:

La analogRead nell'ordine fa:

  1. imposta il canale ADC da leggere, registro ADMUX e bit MUX3..0
  2. imposta il reference, registro ADMUX e bit REFS1..0
  3. imposta il bit ADLAR a 0 (i bit verranno restituiti da destra)
  4. avvia la lettura, bit ADSC del registro ADCSRA
  5. attende la fine della conversione (azzeramento del suddetto bit)
  6. lettura di ADCL e ADCH

Diciamo che in codice sarebbe:

ADMUX = (analogReference << 6 ) | ((pinDaLeggere-14) & 0b00000111); //uguale a analogRead
ADMUX &= ~(1<<ADLAR);
ADCSRA |= (1<<ADSC);
while (ADCSRA & ADSC) {}
int lettura = (ADCH<<8) | ADCL;

leo72:
Stavo andando in doccia... :sweat_smile:
Cmq da una prima occhiata non mi torna come piloti i bit dei registri.

ADCSRA |= ADSC

Questo dovrebbe essere:

ADCSRA |= (1<<ADSC)

Poi a mente non mi ricordo come opera analogRead, dovrei darci un'occhiata per vedere le operazioni da fare per far partire una campionatura analogica. Back soon... :stuck_out_tongue:

direi che il problema è tutto quà! ora provo

edit: ho aggiornato il codice con una versione funzionante al 100% ma usando analogRead

notare che il buffer seriale sembra tenere in memoria circa 10/15 secondi di trasmissione, quindi senza un delay (com'è il codice ora) non sorprendetevi di vedere letture vecchie di molti secondi (per esempio scambiando VCC con GND all'entrata analogica). devo dire che questi tempi mi fanno pensare, sono troppo elevati per essere un buffer di 64byte...

comunque, ora son stanco, ma CREDO che l'errore sia nel NON settaggio corretto di ADMUX.
Il tuo while mi risulta errato, ci vorrebbe una negazione, oppure son io che son stanco e la lettura ADC non mi parte nemmeno.

Scusa se rispondo solo ora ma avevo perso di vista questa discussione.
Il while può essere errato, ma la logica deve essere: per far partire la campionatura, impostare il bit ADSC a 1, poi attendere finché tale bit non torna a 0. Quando ciò accade, la campionatura è stata completata ed il risultato è disponibile in ADCL/H.
while (ADCSRA & (1<<ADSC))
dovrebbe andare. Confronta ADCSRA con ADSC impostato a 1. Quando è 0, deve uscire.

azz mannaggia a me che ho voluto sostistuire il codice del post con uno funzionante. Comq per il while ti basi su ADSC (che è meglio), mentre io mi basavo su un altro bit che funziona al contrario, ma viene usato dall'HW solo se sono attivi gli interrupt ADC.. quindi meglio usare ADSC

Sta di fatto che ancora non riesco a usare i registri per fare una lettura ADC, se sostituisco il codice con analogRead impostando a mano il mux e facendo partire la conversione, leggo sempre valori random....

ieri per caso (errore di impostazione bit) avevo settato lettura continua e funzionava da dio.. uff!

Domani leggerò bene con calma! ora sono troppo stanco! comunque grazie mille per l'interesse! vi farò anche vedere quello che sto preparando per fare questo pseudomixer... XD

lesto:
azz mannaggia a me che ho voluto sostistuire il codice del post con uno funzionante. Comq per il while ti basi su ADSC (che è meglio), mentre io mi basavo su un altro bit che funziona al contrario, ma viene usato dall'HW solo se sono attivi gli interrupt ADC.. quindi meglio usare ADSC

Io mi sono basato su quanto riporta il datasheet e cioè che per attivare la conversione bisogna settare il bit ADSC a 1 e poi attendere che torni a 0. Solo allora il risultato è disponibile nel registro ADC. Punto 24.9.2:

Bit 6 – ADSC: ADC Start Conversion
In Single Conversion mode, write this bit to one to start each conversion. In Free Running mode,
write this bit to one to start the first conversion. The first conversion after ADSC has been written
after the ADC has been enabled, or if ADSC is written at the same time as the ADC is enabled,
will take 25 ADC clock cycles instead of the normal 13. This first conversion performs initializa-
tion of the ADC.
ADSC will read as one as long as a conversion is in progress. When the conversion is complete,
it returns to zero. Writing zero to this bit has no effect.

Dai miei test non è sufficiente, e non capisco perché