Ciao a tutti.
Volevo proporre un metodo diverso per calcolare la media di una serie continua di valori, che io uso spesso e che spero faccia piacere anche a voi conoscere.
Nel forum qualcuno ne parla ma nessuno ne spiega il funzionamento e l'implementazione in Arduino.
Parto col dire che la media a finestra mobile viene utilizzata per calcolare la media di un sotto gruppo di una serie di valori ma, essendo questi valori molti di più, la media viene ricalcolata ogni volta togliendo dal sotto gruppo il valore acquisito più vecchio ed inserendo al suo posto un nuovo valore.
Ovviamente, per definizione di media, anche questa restituirà un valore solo.
Si definisce "finestra" il numero di valori di cui si vuole calcolare la media, e "passo" il numero di valori di cui spostiamo la finestra in avanti.
Il vantaggio è nel tempo utilizzato per l'acquisizione e il calcolo della media che è notevolmente più basso a quello impiegato per il calcolo della media col mettodo "classico".
Lo svantaggio, è l'utilizzo di un linguaggio più "complesso" per lo sketch.
Ma andiamo al lato pratico.
Quando vogliamo calcolare la media, ad esempio, delle temperature rilevate da un sensore, dobbiamo acquisire (sempre per esempio) 10 valori, sommarli tra loro e dividerli per 10.
Per la media a finestra mobile il discorso è più lungo: acquisiamo 10 valori (finestra) in un array (più facile da utilizzare piuttosto di avere 10 variabili diverse) e calcolare la media dei 10 valori, fare uno shift a destra o a sinistra di un numero tot di valori (passo della finestra), acquisire tanti valori quanto è il passo della finestra, ricalcolare la nuova media, e riprendere il ciclo dallo shift.
L'accortezza da prendere è quella che l'array in cui memorizziamo i valori letti, all'inizio è vuoto e va quindi popolato all'avvio del programma.
Per semplicità, utilizzo una finestra di 10 valori ed un passo di 1.
Ho calcolato il tempo impiegato da Arduino ethernet a fare le varie operazioni ed ho ottenuto questi valori:
-Tempo di acquisizione di 10 valori= 1220uS
-Tempo di acquisizione analogico di un valore (mediamente)=112uS
-Tempo di shift di 10 valori=12uS
-Tempo per sommare 10 valori (da un array)=12uS
La media "classica", impiega in tutto 1220uS su 10 valori, mentre la media a finestra mobile impiega la prima volta 1220uS (perchè l'array va popolato), poi solo 136uS!
Implementazione:
Acquisizione di 10 valori per popolare l'array (1220uS):
for(i=0; i<10; i++){
temp_array[i]=analogRead(A0);
}
Calcolo della media (12uS):
for(i=0; i<10; i++)
{
somma += temp_array[i];
}
somma=somma/10;
Shift a destra (12uS):
for(i=0; i<9; i++) \\Lasciamo uno spazio vuoto per un nuovo valore
{
temp_array[i]=temp_array[i+1];
}
Acquisizione di un nuovo valore (112uS):
temp_array[9]=analogRead(A0);
Poi il loop riprende dal calcolo della media.
Come esempio allego il grafico dello schema a blocchi e della temperatura dell'acqua della mia caldaia, rilevata da un sensore posto sul tubo che esce dalla caldaia stessa.
La serie in blu dà il valore così come il sensore l'ha rilevata, in viola applicando la media a finestra mobile con finestra di 10 e passo 1, in giallo la media con finestra 5 e passo 1.
ATTENZIONE: L'allineamento del grafico della media è a sinistra, nel senso che il valore della media è allineato al primo valore a sinistra del grafico non mediato!
EDIT:
Inserisco qui la versione con buffer circolare come suggerito da RobertoBochet e astrobeed.
Ho creato da zero la mia versione (molto più facile da capire) perchè quella suggerita da Roberto era troppo complicata per i miei gusti.
Il buffer, dopo averlo popolato all'inizio viene gestito dalla funzione buffer:
float buffer(int value){
if(i>=10)i=0;
somma=somma-array[i]+value;
array[i]=value;
i++;
return somma/10.0;
}
Questa funzione riceve in ingresso il valore attuale (value), lo aggiunge alla somma e toglie il valore precedentemente memorizzato nel buffer in posizione i.
Poi memorizza al suo posto il valore attuale, calcola la media e la restituisce al chiamante tramite il return.
Il tempo impiegato per tutti questi passaggi è di soli 8uS.
Il nuovo schema a blocchi sarebbe composto dalla sola acquisizione di un valore e dalla funzione buffer.
In base ai tempi precedentemente descitti, il tutto si compie in 120uS!!
Esempio senza popolazione dell'array all'inizio del programma:
int array[10];
int i=0;
int somma=0;
float buffer(int value){
if(i>=10)i=0;
somma=somma-array[i]+value;
array[i]=value;
i++;
return somma/10.0;
}
void setup() {
Serial.begin(9600);
}
void loop(){
float media;
int a=analogRead(A0);
media=buffer(a);
Serial.print("Valore letto: ");
Serial.println(a);
Serial.print("Media: ");
Serial.println(media);
delay (1000);
}