Oversampling del segnale
Prima di parlare del sovracampionamento del segnale è forse meglio ribadire alcuni punti visti in precedenza. Se ricordate dicevamo che a causa di un’oscillazione di soli 2V del segnale fornito dal sensore sui 5 disponibili di Arduino, ogni singola unità misurata rappresentava, dopo le debite conversioni, un consumo di circa 32W. Per rendere più chiaro il concetto vediamo il grafico qui sotto che rappresenta l’andamento del segnale di un carico resistivo di 100W così come lo vede Arduino.
La domanda che ci possiamo fare è quindi: c’è un modo per ridurre l’ampiezza di quei gradini per ottenere una maggior precisione? La risposta è si anche se è limitata da alcuni fattori. Per prima cosa la tecnica dell’oversampling ha bisogno di un po’ di rumore del segnale da campionare, potrebbe sembrare una contraddizione ma nel caso di un segnale eccessivamente pulito sarebbe necessario aggiungere un rumore di fondo voluto. Nel nostro caso il segnale è sufficientemente rumoroso da poter applicare la tecnica dell’oversampling. Il secondo requisito è dato dal fatto che durante i campionamenti non devono esserci grosse modificazioni del segnale. L’oversampling infatti prevede di effettuare un certo numero di campionamenti consecutivi, numero che però non è casuale e che dipende da fattori ben precisi. Prima di tutto c’è un primo limite da conoscere e che riguarda il campionamento di qualunque segnale analogico, ossia la frequenza di Nyquist che stabilisce che la frequenza di campionamento di un segnale analogico deve essere almeno doppia rispetto al segnale da campionare. Nel nostro caso abbiamo un segnale a 50Hz per cui dobbiamo campionare ad almeno 100Hz, ossia 100 campionamenti al secondo, cosa che Arduino è in grado di fare visto che, se ricordate, è in grado di campionare a 9600Hz. Si noti quell'”almeno” scritto in neretto che indica una condizione minima e non certo ideale: resta sottointeso che più è alta la frequenza di campionamento e migliore è il risultato finale.
L’oversampling del segnale impone però un secondo limite, ossia la frequenza di oversampling deve essere 4n*fn, ossia 4 elevato alla n la frequenza di Nyquist. Manca un dettaglio: cos’è n? n è il numero di bit che vogliamo aggiungere al campionamento. Ad esempio il nostro Arduino UNO campiona i segnali a 10 bit, se vogliamo fare un campionamento a 11 bit, ossia un bit in più, dobbiamo campionare con una frequenza minima di 4fn, ossia 400Hz. Se vogliamo passare a 12bit dobbiamo campionare ad almeno 16fn, ossia 1600Hz.
bit | Campionamenti | Frequenza |
10 | 1 | 100 |
11 | 4 | 400 |
12 | 16 | 1600 |
13 | 64 | 6400 |
14 | 256 | 25600 |
15 | 1024 | 102400 |
16 | 4096 | 409600 |
Ora vediamo il medesimo campionamento di prima effettuato a 11bit, come potete vedere il miglioramento è significativo, ma balza subito all’occhio come la necessità di effettuare maggiori calcoli e campionamenti riduce molto rapidamente la “risoluzione temporale”, per cui a fronte di una maggior precisione sull’asse y abbiamo una riduzione su quello x. Vediamo ora cosa succede a 12bit, si nota chiaramente che le onde sono talmente ravvicinate da essere al limite della riconoscibilità, nel particolare si avverte come i picchi siano ancora tutto sommato allineati, ma sicuramente il calcolo dell’area della curva diventerebbe inaffidabile anche se da quanto detto sopra siamo ancora ampiamente entro i limiti teorici del sovracampionamento.
Concludiamo con i 13 bit dove benchè la teoria ci dica che siamo ancora entro i limiti di applicabilità dell’oversampling, si vede chiaramente come c’è una totale perdita dell’originaria forma d’onda, con alterazione sia dei valori di picco che dell’area sottesa dalle curve, oramai non più delineabile.
Come si procede per fare l’oversampling del segnale? Non dobbiamo far altro che sommare 4n campionamenti consecutivi e poi dividere il risultato per 2n ricordandoci che n rappresenta il numero di bit aggiuntivi rispetto quelli base. Vi faccio notare che parlo sempre di “bit rispetto a quelli base” che su Arduino UNO corrispondono a 10, ma ci sono boards che campionano i segnali in modo diverso, ad esempio Arduino DUE effettua campionamenti a 12bit. Ora verrebbe quasi spontaneo pensare che abbiamo aumentato la risoluzione sull’asse y ma ci sarebbe comodo riuscire a farlo anche sull’asse x, aumentanto la velocità di campionamento.
Il prescaler
Per capire cosè il fattore di prescaler vediamo qualè la situazione base di Arduino UNO. Sappiamo che funziona a 16Mhz, ossia esegue 16 milioni di operazioni elementari al secondo. Il convertitore analogico-digitale è settato con un fattore di prescaler base di 128. Cosa significa? Significa che la sua velocità è settata ad un 128° del clock principale. Quindi se dividiamo 16MHz per 128 otteniamo 125kHz. Siccome la conversione analogico-digitale richiede 13 cicli di clock, 125/13=9615Hz, ossia i famosi 9,6kHz che avevamo detto essere la velocità del convertitore analogico-digitale. Come avrete già campito però, il prescaler è modificabile, in particolare lo possiamo ridurre per aumentare la velocità dell’ADC (convertitore analogico-digitale). Cosa succede ad esempio se lo impostiamo a 32? Otteniamo 16MHz/32=500kHz che diviso per 13 ci ritorna 38,461kHz. Funziona veramente?
Guardate voi stessi, quello qui sopra è sempre il campionamento degli esempi precedenti, fatto con oversampling a 11bit e prescaler settato a 32. Di fatto abbiamo una risoluzione temporale simile a quella del primissimo esempio, ma con un’aumentata risoluzione sull’asse y grazie all’oversampling. C’è da aggiungere però che la riduzione del prescaler porta ad una riduzione della precisione del campionamento. In rete ho trovato un grafico che riporta la qualità della conversione, non ho peò trovato una sicura ufficialità di questi dati, comunque vi riporto qui vicino il grafico.
Ho fatto tutta una serie di prove per cui mi sento di poter affermare che al di sotto del 32 si comincia a perdere precisione a livello dei picchi, in particolare a 11bit si tende ad avere una linea piatta più che una curva appuntita, in quel caso risulta necessario passare a 12bit per riprendere risoluzione, almeno parzialmente, sui picchi. Nella realtà la piena risoluzione a 10bit dell’ADC è garantita fino ad un massimo di 200kHz per cui solo il prescaler a 128 garantisce la massima precisione. Per chi si chiedesse il senso del prescaler se poi la massima risoluzione è solo con l’impostazione a 128, si faccia presente che anche se Arduino esce di fabbrica con un clock a 16MHz non è per nulla scontato che non lo si possa modificare, portandolo ad esempio a 12MHz.
A titolo di esempio vi riporto qui sotto il solito campionamento fatto a 12bit con prescaler a 16, che comunque rispetto a quanto ottenuto con il prescaler a 128 è un gran passo avanti.
Dopo tutta una serie di prove ho deciso, per il mio software, di utilizzare 11bit con prescaler a 32. Ritengo però che la scelta dipenda molto dal segnale che si vuole campionare per cui non si prenda questa mia scelta come una verità assoluta e valida per ogni tipo di segnale.
Ora devo spiegarvi come modificare il prescaler il che è semplice da fare, non dobbiamo far altro che settare i bits ADPS nel registro ADCSRA. Tranquilli, non importa se non avete capito, nella pratica è più semplice di quel che pensate. Per capirci meglio vediamo la prossima tabella:
ADPS2 | ADPS1 | ADPS0 | Prescaler |
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 |
Come vedete non è nulla di particolarmente complesso, semplice notazione binaria. Per fare il tutto usiamo dei #define che poi possiamo sfruttare più facilmente. Se fate ad esempio riferimento al prescaler=128, vedete che i tre bit ADPS sono tutti settati a 1 formando il numero binario 111 che corrisponde a 7. Fate però attenzione che NON potete settare il registro ADCSRA direttamente a 7 perchè quel registro contiene anche altri bit con un loro specifico significato e rischiereste di alterarli. Vi riporto la tabella con i restanti bit, non li approfondiamo, è solo per comprendere che non dovete andare ad alterare i restanti.
Registro ADCSRA | ||||||||
---|---|---|---|---|---|---|---|---|
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
nome | ADEN | ADSC | ADATE | ADIF | ADIE | ADPS2 | ADPS1 | ADPS0 |
#define PS_8 ((1 << ADPS1) | (1 << ADPS0))
#define PS_16 (1 << ADPS2)
#define PS_32 ((1 << ADPS2) | (1 << ADPS0))
#define PS_64 ((1 << ADPS2) | (1 << ADPS1))
#define PS_128 ((1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0))
ADCSRA &= ~PS_128;
ADCSRA |= PS_32;
Il punto zero
La media quadratica
Calibrazione
Onde e media

Sketch
// Define various ADC prescaler
#define PS_8 ((1 << ADPS1) | (1 << ADPS0))
#define PS_16 (1 << ADPS2)
#define PS_32 ((1 << ADPS2) | (1 << ADPS0))
#define PS_64 ((1 << ADPS2) | (1 << ADPS1))
#define PS_128 ((1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0))
const int sensorPin=A1;
//voltage range of sensor
#define sensorvoltagerange 1
//how many ampere the sensor reads (RMS)
#define sensorrange 30
//arduino adc range (5V, 3.3V or href value)
#define arduinorange 5
//numero ripetizione misure
#define misure 5
// i bit del convertitore ADC di arduino, su UNO sono 10
#define basic_bits 10
//i bit che vogliamo ottenere col sovravcampionamento
#define extended_bits 11
#define taratura 1
//parametri calcolati automaticamente, NON modificare!
// bit di sovracampionamento
#define overbit extended_bits-basic_bits
//divisore oversampling
#define decimatoreoversample (pow(2,overbit))
//calcola risoluzione sensore
double sensorunit;
double sensorstep;
double puntozero;
void setup()
{
Serial.begin(9600);
//while (!Serial) {} // wait for serial port to connect. Needed for Leonardo only
sensorunit=(((int)(1<<extended_bits))*(float)sensorvoltagerange/(float)arduinorange);
sensorstep=((float)sensorrange/(float)sensorunit);
puntozero=(1<<(extended_bits-1)); // da tenere se non fai la lettura analogica
ADCSRA &= ~PS_128; //Set up the ADC: remove bits set by Arduino library
ADCSRA |= PS_32; // set our own prescaler to XX
double tstart,tstop,test;
analogRead(A1);
for(int i=0;i<5;i++)
{
tstart=micros();
analogRead(A1);
tstop=micros();
Serial.print("Basic read time: ");
Serial.print(tstop-tstart);
Serial.print(" us");
tstart=micros();
test=OversampledAnalogRead(A1);
tstop=micros();
Serial.print(" Oversampled time: ");
Serial.print(tstop-tstart);
Serial.println(" us ");
}
// cancellare se usi punto fisso
Serial.print("Misured voltage divider (AnalogAvg) : ");
puntozero=SampleAnalogAvg(A5,1500);
Serial.println(puntozero,4);
}
float SampleAnalogAvg(int localpin,unsigned char campioni)
{
float valore=OversampledAnalogRead(localpin);
for(int i=0;i<campioni;i++) valore=(valore+OversampledAnalogRead(localpin))/2;
return(valore);
}
inline double OversampledAnalogRead(int pin)
{
unsigned int val=0;
unsigned int misureoversample=pow(4,overbit);
for(unsigned char z=0;z<misureoversample;z++) val+=analogRead(pin);
return(val>>overbit); //2^n
}
double GetWaveValue(int pin,char wavenumber)
{
double sensore;
double somma=0;
int contatore=0;
double tstart;
double timelen=(double)wavenumber*1/50*1000*1000; //0.02sec
//puntozero=OversampledAnalogRead(A5);
puntozero=SampleAnalogAvg(A5,1500);
tstart=micros();
while(micros()-tstart<timelen)
{
sensore=OversampledAnalogRead(pin);
sensore-=puntozero;
somma+=(sensore*sensore);
contatore++;
}
return(sqrt(somma/contatore));
}
void loop()
{
double Vmax=0;
double localval=0;
for(int z=0;z<misure;z++)
{
localval=GetWaveValue(sensorPin,3);
Vmax+=localval;
}
Vmax=((Vmax/misure));
//if(Vmax<20) Vmax=0;
Serial.print("Sensor Max: ");
Serial.print(Vmax,2);
Serial.print(" / Ampere: ");
Serial.print(Vmax*sensorstep,2);
Serial.print(" / Consumo: ");
Serial.print(Vmax*sensorstep*220*taratura,2);
Serial.println(" W");
}
Bene, anche questo lungo articolo è terminato. Sono convinto che si possano fare ancora dei miglioramenti, in particolare per ridurre il rumore sui bassi. La via più semplice è impostare una soglia di cut-off, vi ho già impostato la riga di codice, basta eliminare il commento per tagliare i valori al di sotto di 20W. Ho altre idee per migliorare il sistema, ma come detto non volevo esagerare con questo articolo per cui questa puntata si conclude qui. Magari quando deciderò di aggiungere un sistema per misurare anche la tensione e l’angolo di sfasamento scriverò un nuovo articolo.