Basi · 14 min di lettura

Filtrare i dati dei sensori Arduino: media, mediana, EMA, Kalman

Filtrare i dati di un sensore Arduino: media mobile, filtro mediano, EMA, isteresi, campionamento, confronto nel Plotter seriale e introduzione al Kalman.

Filtrare i dati di un sensore Arduino significa trasformare i valori tremolanti e saltellanti che arrivano da analogRead() o da un sensore di distanza in un numero calmo e affidabile che il tuo progetto possa usare. Vedere 511, 513, 510, 514 nel Monitor seriale senza toccare il potenziometro, un HC-SR04 che ogni tanto misura 3 cm o 400 cm senza motivo, una luce notturna che lampeggia al confine tra buio e luce: sono facce dello stesso problema. Quasi sempre il sensore non è guasto; nella misura si sono infilati rumore elettrico, errori di gradino dell'ADC o qualche eco sbagliato. La buona notizia: un filtro di poche righe risolve la maggior parte di questi problemi.

In questa guida vedrai prima i tipi di rumore e le prime contromisure lato circuito. Poi programmeremo cinque metodi, partendo dal più semplice: la media di più campioni, la media mobile (moving average), il filtro mediano che elimina i picchi, il filtro esponenziale (EMA) che usa pochissima memoria e l'isteresi, che calma le soglie instabili. Li disegneremo tutti insieme nel Plotter seriale dell'Arduino IDE, parleremo di come scegliere la frequenza di campionamento e faremo una breve introduzione al filtro di Kalman. Le ultime sezioni trattano l'ADC rumoroso dell'ESP32 e il codice MicroPython per il Raspberry Pi Pico.

Sensore di distanza a ultrasuoni HC-SR04
Sensori come l'HC-SR04 danno ogni tanto una lettura sbagliata; il filtro mediano elimina questi picchi.

Perché i dati dei sensori sono rumorosi?

Pensare all'errore di misura come a una cosa sola è fuorviante. Tipi di rumore diversi richiedono filtri diversi, quindi prima devi capire qual è il tuo problema. Per farlo basta stampare per un po' i valori grezzi nel Monitor seriale e guardare i numeri. Se i valori tremano di continuo intorno a un numero, c'è rumore casuale; se quasi tutte le misure sono giuste ma ogni tanto arriva un valore assurdo, ci sono picchi (spike); se il valore scivola lentamente, il problema non è il rumore ma la deriva.

Tipo di rumoreCome si presentaCausa tipicaSoluzione adatta
Rumore casualeIl valore trema di ±2–10 puntiAlimentazione instabile, cavi lunghi, rumore proprio dell'ADCMedia, media mobile, EMA
Picco (spike)Ogni tanto un valore molto diversoEco perso sull'HC-SR04, contatto instabile, disturbi dei motoriFiltro mediano
Errore di gradino (quantizzazione)Il valore salta tra due numeri viciniRisoluzione dell'ADC (5 V / 1024 = 4,9 mV sull'Uno)Media, isteresi
Rumore periodicoOndulazione regolareSfarfallio a 100 Hz delle lampade, rete a 50 HzMedia su multipli di 20 ms
Deriva (drift)Il valore cambia lentamente nel giro di minutiTemperatura, tensione della batteria che calaCalibrazione e riferimento stabile, non un filtro

Prima il circuito: ridurre il rumore alla fonte

Un filtro software non può recuperare informazioni che il circuito non ha mai catturato; il primo passo è quindi rendere difficile l'ingresso al rumore. Tieni corti i cavi dei sensori analogici e falli passare lontano dai cavi dei componenti che assorbono molta corrente, come motori e relè. Un condensatore ceramico da 100 nF tra l'ingresso analogico e GND, vicino al sensore, assorbe gran parte dei disturbi rapidi. La frequenza di taglio di un filtro passa-basso RC fatto con una resistenza e un condensatore si calcola con f = 1 / (2πRC): 10 kΩ e 1 µF danno circa 16 Hz, quindi le variazioni più rapide vengono attenuate. Puoi calcolare i valori con lo strumento costante di tempo RC. Nei progetti con motori aiuta anche portare il GND dei sensori alla scheda lungo un percorso separato da quello del driver motore e alimentare la scheda con una fonte stabile. Spieghiamo come funziona l'ADC e quanto conta la tensione di riferimento in Che cos'è un ADC?.

Montaggio di prova

Proveremo il codice di questo articolo con tre sensori semplici. Il potenziometro è ideale per vedere il rumore casuale, la LDR per il rumore periodico e le decisioni a soglia, l'HC-SR04 per i picchi. Collega la LDR tra 5 V e il pin analogico e la resistenza da 10 kΩ tra il pin analogico e GND; così la lettura aumenta con la luce. Trovi i dettagli negli articoli sul sensore di luce LDR e sul potenziometro.

CollegamentoArduino UnoESP32Raspberry Pi Pico
Cursore del potenziometroA0GPIO 34GP26 (ADC0)
Punto centrale LDR + partitore da 10 kΩA1GPIO 35GP27 (ADC1)
HC-SR04 TRIGD9GPIO 5GP2
HC-SR04 ECHOD10GPIO 18 (con partitore)GP3 (con partitore)
LED o relè (esempio di isteresi)D8--
AlimentazionePot e LDR 5V, HC-SR04 5VPot e LDR 3V3, HC-SR04 5VPot e LDR 3V3, HC-SR04 VBUS
Su ESP32 e Pico collega il potenziometro e il partitore della LDR a 3V3; i pin analogici non devono mai ricevere più di 3,3 V. L'HC-SR04 funziona a 5 V e la sua uscita ECHO dà 5 V, quindi su ESP32 e Pico abbassa il pin ECHO a 3,3 V con un partitore di tensione da 1 kΩ e 2 kΩ. Puoi controllare i valori del partitore con lo strumento Partitore di tensione.

1. Media di più campioni

Il filtro più semplice consiste nel fare più letture di fila e usarne la media. Il rumore casuale sposta ogni lettura in una direzione diversa, quindi nella somma si compensa. La statistica ha una regola: nella media di N letture indipendenti il rumore cala di circa √N volte. 4 campioni dimezzano il rumore, 16 lo riducono a un quarto; 64 lo portano a un ottavo, ma richiedono quattro volte il tempo. Su Arduino Uno un analogRead() dura circa 112 µs, quindi una media di 16 campioni si conclude in meno di 2 ms. Gli unici difetti: occupa brevemente il processore a ogni lettura e resiste male ai picchi, perché se uno dei 16 campioni è assurdo rovina anche la media.

C++ (Arduino IDE)
1const int POT_PIN = A0;
2
3// Art arda "adet" ölçüm al, ortalamasını döndür / take "adet" readings in a row, return the average
4int ortalamaOku(int pin, int adet) {
5  long toplam = 0;                  // taşmasın diye long / long to avoid overflow
6  for (int i = 0; i < adet; i++) {
7    toplam += analogRead(pin);
8    delayMicroseconds(100);         // ölçümler arasında kısa ara / short gap between readings
9  }
10  return toplam / adet;
11}
12
13void setup() {
14  Serial.begin(115200);
15}
16
17void loop() {
18  int ham = analogRead(POT_PIN);
19  int ort4 = ortalamaOku(POT_PIN, 4);
20  int ort16 = ortalamaOku(POT_PIN, 16);
21  // Serial Plotter için etiket:değer biçimi / label:value format for the Serial Plotter
22  Serial.print("Ham:");
23  Serial.print(ham);
24  Serial.print(",Ort4:");
25  Serial.print(ort4);
26  Serial.print(",Ort16:");
27  Serial.println(ort16);
28  delay(20);
29}

2. Media mobile (moving average)

La media mobile tiene la media delle ultime N letture e, a ogni nuova lettura, scarta la più vecchia e aggiunge la nuova. Così fai una sola lettura per ciclo ma sfrutti comunque una media su N letture. Invece di sommare N numeri ogni volta, il codice tiene una variabile somma: sottrae il valore che esce e aggiunge quello che entra. Per questo il calcolo è veloce uguale qualunque sia la dimensione della finestra. Il prezzo della media mobile è il ritardo: l'uscita segue il valore reale con circa (N−1)/2 letture di ritardo. Una finestra da 10 con una lettura ogni 10 ms ritarda di circa 45 ms; per un potenziometro o una temperatura non importa, per un robot veloce si sente.

C++ (Arduino IDE)
1const int POT_PIN = A0;
2const int PENCERE = 10;            // son 10 ölçüm / last 10 readings
3
4int tampon[PENCERE];               // dairesel tampon / circular buffer
5int sira = 0;
6long toplam = 0;
7bool dolu = false;
8
9int hareketliOrtalama(int yeni) {
10  toplam -= tampon[sira];          // en eski değeri çıkar / remove the oldest value
11  tampon[sira] = yeni;
12  toplam += yeni;                  // yeni değeri ekle / add the new value
13  sira = (sira + 1) % PENCERE;
14  if (sira == 0) dolu = true;
15  return dolu ? toplam / PENCERE : toplam / sira;   // ilk turda bölen küçük / smaller divisor at start
16}
17
18unsigned long sonOlcum = 0;
19
20void setup() {
21  Serial.begin(115200);
22}
23
24void loop() {
25  if (millis() - sonOlcum >= 10) { // 100 Hz örnekleme / 100 Hz sampling
26    sonOlcum = millis();
27    int ham = analogRead(POT_PIN);
28    int filtreli = hareketliOrtalama(ham);
29    Serial.print("Ham:");
30    Serial.print(ham);
31    Serial.print(",Hareketli:");
32    Serial.println(filtreli);
33  }
34}
Finestra (N)Riduzione del rumoreRitardo (una lettura ogni 10 ms)Uso adatto
4Circa 2 volte15 msControlli che devono reagire in fretta
10Circa 3 volte45 msPotenziometro, joystick, LDR
32Circa 5,7 volte155 msTemperatura, umidità, tensione della batteria
10010 volte495 msValori che cambiano molto lentamente, visualizzazione tranquilla

3. Filtro mediano: eliminare i picchi

La media resiste male ai picchi. Se l'HC-SR04 misura quattro volte 50 cm e una volta perde l'eco e misura 0, la media dei cinque è 40 cm; un valore né giusto né rivelatore dell'errore. Il filtro mediano invece ordina le ultime N letture dalla più piccola alla più grande e prende quella centrale. Nello stesso esempio la lista ordinata è 0, 50, 50, 50, 50 e il valore centrale è 50; la lettura sbagliata non raggiunge mai il risultato. Un filtro mediano a 5 elementi elimina completamente fino a 2 letture errate consecutive. Il secondo pregio della mediana è che non spalma un cambiamento brusco e reale come fa la media: quando un ostacolo si avvicina davvero, il valore passa al nuovo livello con un gradino netto in poche letture invece che con una curva morbida. Il codice qui sotto misura con l'HC-SR04 ogni 60 ms e stampa la mediana delle ultime 5 letture. Per i dettagli guarda l'articolo sul sensore di distanza HC-SR04.

C++ (Arduino IDE)
1const int TRIG_PIN = 9;
2const int ECHO_PIN = 10;
3const int N = 5;                   // medyan penceresi, tek sayı olmalı / median window, must be odd
4
5int olcumler[N];
6int sira = 0;
7
8int mesafeOlc() {
9  digitalWrite(TRIG_PIN, LOW);
10  delayMicroseconds(2);
11  digitalWrite(TRIG_PIN, HIGH);
12  delayMicroseconds(10);
13  digitalWrite(TRIG_PIN, LOW);
14  unsigned long sure = pulseIn(ECHO_PIN, HIGH, 25000);   // ~4 m zaman aşımı / ~4 m timeout
15  return sure / 58;                                       // yankı yoksa 0 / 0 if no echo
16}
17
18int medyan() {
19  int kopya[N];
20  for (int i = 0; i < N; i++) kopya[i] = olcumler[i];
21  for (int i = 1; i < N; i++) {    // eklemeli sıralama / insertion sort
22    int deger = kopya[i];
23    int j = i - 1;
24    while (j >= 0 && kopya[j] > deger) {
25      kopya[j + 1] = kopya[j];
26      j--;
27    }
28    kopya[j + 1] = deger;
29  }
30  return kopya[N / 2];             // ortadaki eleman / middle element
31}
32
33void setup() {
34  Serial.begin(115200);
35  pinMode(TRIG_PIN, OUTPUT);
36  pinMode(ECHO_PIN, INPUT);
37  for (int i = 0; i < N; i++) {    // tamponu ilk ölçümlerle doldur / fill the buffer first
38    olcumler[i] = mesafeOlc();
39    delay(60);
40  }
41}
42
43void loop() {
44  int ham = mesafeOlc();
45  olcumler[sira] = ham;
46  sira = (sira + 1) % N;
47  Serial.print("Ham:");
48  Serial.print(ham);
49  Serial.print(",Medyan:");
50  Serial.println(medyan());
51  delay(60);                       // HC-SR04 için en az 60 ms / at least 60 ms for HC-SR04
52}
Puoi anche combinare mediana e media: prima elimina i picchi con una mediana a 5, poi passa il risultato a una media mobile o a una EMA per smussare il tremolio rimasto. Questa coppia è la soluzione più comune per misure con picchi e tremolio insieme, come quelle dei sensori di distanza e di flusso. Non fare grande la finestra della mediana; 3 o 5 bastano quasi sempre, 9 e oltre aggiungono ritardo inutile.

4. Filtro esponenziale (EMA): smussare con poca memoria

La media mobile esponenziale (Exponential Moving Average, EMA) non usa alcun buffer; ricorda solo il risultato precedente. A ogni nuova lettura il risultato si avvicina alla lettura di una piccola parte della differenza tra i due: y = y + α × (x − y). Qui x è la nuova lettura, y il valore filtrato e α (alfa) un coefficiente tra 0 e 1. Più α è piccolo, più il filtro è calmo ma lento. Si chiama esponenziale perché l'influenza di ogni lettura si spegne in modo esponenziale nel tempo. È la controparte software del filtro passa-basso RC dell'elettronica. La costante di tempo dell'EMA vale circa τ ≈ T / α (T è l'intervallo di campionamento): con una lettura ogni 10 ms e α = 0,1, il filtro raggiunge il 63 per cento di un cambiamento brusco in circa 100 ms e il 95 per cento in circa 300 ms. Il codice contiene una versione in virgola mobile e una versione intera più veloce su schede piccole come l'Uno; la versione intera usa α = 1/16 e uno scorrimento (>>) al posto di una divisione.

C++ (Arduino IDE)
1const int LDR_PIN = A1;
2const float ALFA = 0.1;            // 0-1 arası; küçük = daha sakin / 0-1; smaller = smoother
3
4float ema = -1;                    // henüz başlamadı / not started yet
5long emaX16 = -1;                  // tamsayılı EMA x16 / integer EMA times 16
6
7unsigned long sonOlcum = 0;
8
9void setup() {
10  Serial.begin(115200);
11}
12
13void loop() {
14  if (millis() - sonOlcum >= 10) {
15    sonOlcum = millis();
16    int ham = analogRead(LDR_PIN);
17
18    if (ema < 0) ema = ham;        // ilk değerle başlat / start from the first reading
19    ema += ALFA * (ham - ema);     // y = y + a(x - y)
20
21    if (emaX16 < 0) emaX16 = (long)ham << 4;
22    emaX16 += ham - (emaX16 >> 4); // alfa = 1/16, float yok / alpha = 1/16, no float
23
24    Serial.print("Ham:");
25    Serial.print(ham);
26    Serial.print(",EMA:");
27    Serial.print(ema, 1);
28    Serial.print(",EMA_int:");
29    Serial.println(emaX16 >> 4);
30  }
31}
α (alfa)Finestra equivalente approssimativaTempo di risposta al 63 % (campionamento 10 ms)Carattere
0,5310–20 msVelocissimo, poco smussamento
0,2950 msVeloce, smussamento leggero
0,119100 msEquilibrato, buon punto di partenza per quasi tutti i sensori
0,0539200 msCalmo, lento
0,011991 sMolto calmo, solo per valori lenti

La "finestra equivalente" della tabella è la dimensione approssimativa di una media mobile che dà lo stesso smussamento e si calcola con N ≈ 2/α − 1. Il grande vantaggio dell'EMA è la memoria: una media mobile da 100 elementi richiede 200 byte di RAM, l'EMA una sola variabile. Se devi filtrare più sensori nei 2 KB di RAM di Arduino Uno, la differenza conta. Il suo difetto è che resiste ai picchi male quanto la media; un picco grande trascina il risultato per diverse letture.

Confrontare i filtri nel Plotter seriale

La differenza tra i filtri si vede male nei numeri, ma in un grafico salta all'occhio. Il codice qui sotto legge il potenziometro a 100 Hz, fa passare la stessa lettura per quattro strade diverse e stampa tutto nel formato etichetta:valore. Per rendere la differenza più evidente, il codice aggiunge un picco artificiale a circa il 3 per cento delle letture; in un progetto reale cancella quella riga. Nel grafico vedrai che la mediana elimina del tutto i picchi, mentre la media mobile e l'EMA li trasformano in piccole gobbe.

  1. Carica il codice e chiudi il Monitor seriale se è aperto; Monitor seriale e Plotter seriale non possono essere aperti insieme.
  2. Nell'Arduino IDE apri Strumenti > Plotter seriale o clicca sull'icona del grafico in alto a destra.
  3. Imposta la velocità in basso a destra su 115200 baud.
  4. Gira il potenziometro prima lentamente e poi in fretta, e osserva come risponde ogni linea.
  5. Accendi e spegni le linee con le caselle accanto alle etichette per confrontare direttamente due filtri.
C++ (Arduino IDE)
1const int POT_PIN = A0;
2
3// Hareketli ortalama / moving average
4const int PENCERE = 10;
5int maTampon[PENCERE];
6int maSira = 0;
7long maToplam = 0;
8
9int hareketliOrtalama(int yeni) {
10  maToplam += yeni - maTampon[maSira];
11  maTampon[maSira] = yeni;
12  maSira = (maSira + 1) % PENCERE;
13  return maToplam / PENCERE;
14}
15
16// 5'lik medyan / median of 5
17int mdTampon[5];
18int mdSira = 0;
19
20int medyan5(int yeni) {
21  mdTampon[mdSira] = yeni;
22  mdSira = (mdSira + 1) % 5;
23  int a[5];
24  for (int i = 0; i < 5; i++) a[i] = mdTampon[i];
25  for (int i = 1; i < 5; i++) {    // eklemeli sıralama / insertion sort
26    int d = a[i], j = i - 1;
27    while (j >= 0 && a[j] > d) { a[j + 1] = a[j]; j--; }
28    a[j + 1] = d;
29  }
30  return a[2];
31}
32
33// Üstel filtre / exponential filter
34float ema = 0;
35const float ALFA = 0.1;
36
37unsigned long sonOlcum = 0;
38
39void setup() {
40  Serial.begin(115200);
41  int ilk = analogRead(POT_PIN);
42  for (int i = 0; i < PENCERE; i++) maTampon[i] = ilk;   // tamponları doldur / prefill buffers
43  maToplam = (long)ilk * PENCERE;
44  for (int i = 0; i < 5; i++) mdTampon[i] = ilk;
45  ema = ilk;
46  randomSeed(analogRead(A5));
47}
48
49void loop() {
50  if (millis() - sonOlcum < 10) return;   // 100 Hz
51  sonOlcum = millis();
52
53  int ham = analogRead(POT_PIN);
54  if (random(100) < 3) {                  // TEST: %3 yapay sıçrama, projede sil / TEST: 3% fake spikes, remove in a project
55    ham = constrain(ham + random(-400, 400), 0, 1023);
56  }
57  ema += ALFA * (ham - ema);
58
59  Serial.print("Ham:");
60  Serial.print(ham);
61  Serial.print(",Hareketli:");
62  Serial.print(hareketliOrtalama(ham));
63  Serial.print(",Medyan:");
64  Serial.print(medyan5(ham));
65  Serial.print(",EMA:");
66  Serial.println(ema, 1);
67}

Nel grafico le etichette compaiono colorate in alto a destra. Senza una virgola tra le etichette e i due punti tra etichetta e valore, il Plotter seriale non riesce a separare bene le linee. Il vecchio plotter dell'Arduino IDE 1.8 gestisce le etichette in modo limitato; in caso di problemi invia solo numeri separati da virgole. Per i problemi con la porta seriale e la velocità in baud, dai un'occhiata all'articolo sul Monitor seriale di Arduino.

5. Isteresi: fermare le decisioni che lampeggiano sulla soglia

Il filtro calma il valore, ma da solo non risolve il problema della decisione. Una luce notturna che dice "accendi la lampada quando la luce scende sotto 350" al tramonto si accende e si spegne più volte al secondo, mentre il valore oscilla tra 349 e 351. Così i contatti di un relè si consumano presto. La soluzione è l'isteresi: si usano due soglie distinte per accendere e per spegnere. Per esempio la lampada si accende quando il valore scende sotto 300, ma per spegnersi deve risalire sopra 400. La "zona morta" di 100 punti in mezzo impedisce che piccole oscillazioni cambino la decisione. Il codice qui sotto unisce l'isteresi a una EMA e a un breve tempo di conferma: la condizione deve restare vera senza interruzioni per 1 secondo, così nemmeno un'ombra di passaggio o un lampo di fari cambia la decisione.

C++ (Arduino IDE)
1const int LDR_PIN = A1;
2const int LAMBA_PIN = 8;           // LED ya da röle modülü / LED or relay module
3const int YAK_ESIK = 300;          // bunun altı karanlık / below this is dark
4const int SONDUR_ESIK = 400;       // bunun üstü aydınlık / above this is bright
5const unsigned long ONAY_MS = 1000;   // koşul 1 sn sürmeli / condition must last 1 s
6
7float ema = -1;
8bool lambaAcik = false;
9unsigned long kosulBasladi = 0;
10bool kosulVar = false;
11
12void setup() {
13  Serial.begin(115200);
14  pinMode(LAMBA_PIN, OUTPUT);
15}
16
17void loop() {
18  int ham = analogRead(LDR_PIN);
19  if (ema < 0) ema = ham;
20  ema += 0.05 * (ham - ema);       // yavaş EMA / slow EMA
21
22  // Durum değiştirmek için hangi koşul gerekiyor? / which condition flips the state?
23  bool degistir = lambaAcik ? (ema > SONDUR_ESIK) : (ema < YAK_ESIK);
24
25  if (degistir) {
26    if (!kosulVar) {
27      kosulVar = true;
28      kosulBasladi = millis();     // koşul şimdi başladı / condition just started
29    } else if (millis() - kosulBasladi >= ONAY_MS) {
30      lambaAcik = !lambaAcik;      // 1 sn sürdü, kararı değiştir / lasted 1 s, flip the decision
31      digitalWrite(LAMBA_PIN, lambaAcik ? HIGH : LOW);
32      kosulVar = false;
33    }
34  } else {
35    kosulVar = false;              // koşul bozuldu, sayacı sıfırla / condition broke, reset the timer
36  }
37
38  Serial.print("EMA:");
39  Serial.print(ema, 0);
40  Serial.print(",Yak:");
41  Serial.print(YAK_ESIK);
42  Serial.print(",Sondur:");
43  Serial.print(SONDUR_ESIK);
44  Serial.print(",Lamba:");
45  Serial.println(lambaAcik ? 600 : 100);   // grafikte görünsün / visible on the plot
46  delay(10);
47}

Prima di fissare le soglie, osserva nel Plotter seriale i valori della stanza durante la giornata. La zona morta deve essere larga almeno due o tre volte il tremolio del valore filtrato. Lo stesso metodo si usa nei termostati (per esempio spegnere il riscaldamento a 22 °C e riaccenderlo a 20 °C), nelle pompe dei serbatoi d'acqua e nei circuiti di protezione delle batterie. Anche l'antirimbalzo dei pulsanti è in fondo un tempo di conferma; i dettagli sono in Come collegare un pulsante.

Come si sceglie la frequenza di campionamento?

Perché un filtro funzioni bene bisogna leggere il sensore alla velocità giusta. Se leggi troppo piano perdi i cambiamenti rapidi; se leggi troppo in fretta il sensore restituisce sempre lo stesso valore o non è nemmeno pronto. La regola base è il criterio di Nyquist: campiona almeno al doppio della frequenza del cambiamento più rapido che vuoi seguire; in pratica 5–10 volte è più sicuro. Alcuni sensori hanno poi un proprio limite, e leggerli più velocemente non serve. Attenzione alla luce delle lampade: con la rete a 50 Hz le lampade sfarfallano 100 volte al secondo. Il modo più semplice per annullare questo sfarfallio su una LDR o un altro sensore analogico è prendere letture equidistanti per un tempo multiplo di 20 ms e farne la media; poiché la finestra copre periodi interi della rete, lo sfarfallio si annulla da solo.

SensoreIntervallo di lettura consigliatoNota
DHT11 / DHT221 s / 2 sLeggendo più spesso ottieni lo stesso valore o un errore
DS18B20 (12 bit)750 msÈ il tempo di conversione
HC-SR04Almeno 60 msBisogna aspettare che l'eco precedente si spenga
Potenziometro, joystick10–20 ms50–100 Hz bastano e avanzano per i movimenti umani
LDR (contro lo sfarfallio delle lampade)Ogni 1 ms per 20 ms, poi la mediaLo sfarfallio della rete a 50 Hz si annulla
MPU60505–10 msIntervallo comune per misurare movimenti a 100–200 Hz

Breve introduzione al filtro di Kalman

Il filtro di Kalman tiene conto di quanto è rumorosa la misura e di quanto in fretta può cambiare il valore reale, e regola da solo il proprio guadagno. Nella forma monodimensionale più semplice ha due impostazioni: R è la varianza del rumore di misura (quanto trema il sensore); Q è il rumore di processo (quanto in fretta ti aspetti che cambi il valore reale). A ogni passo il filtro prima aumenta la propria incertezza di Q, poi calcola il guadagno K = P / (P + R) e sposta la stima verso la misura nella proporzione K. Se la misura è rumorosa (R grande), K è piccolo e il filtro si fida poco della misura; se ci si aspetta un cambiamento rapido (Q grande), K cresce e il filtro reagisce in fretta. Un risultato curioso: se Q e R restano fissi, dopo un po' K si stabilizza su un valore costante e il Kalman monodimensionale diventa in pratica una EMA. La vera forza di Kalman emerge quando stima più grandezze insieme, per esempio unendo accelerometro e giroscopio per trovare un angolo di inclinazione. Il suo parente più semplice, il filtro complementare, lo trovi nell'articolo sull'MPU6050.

C++ (Arduino IDE)
1const int POT_PIN = A0;
2
3struct Kalman1D {
4  float q;      // süreç gürültüsü / process noise
5  float r;      // ölçüm gürültüsü (varyans) / measurement noise (variance)
6  float p;      // tahminin belirsizliği / estimate uncertainty
7  float x;      // tahmin / estimate
8  bool ilk;
9};
10
11Kalman1D kf = {0.05, 9.0, 1.0, 0.0, true};   // r = 9: yaklaşık ±3 basamak titreşim / about ±3 counts of jitter
12
13float kalmanGuncelle(Kalman1D &k, float olcum) {
14  if (k.ilk) { k.x = olcum; k.ilk = false; return k.x; }
15  k.p += k.q;                          // 1. tahmin: belirsizlik artar / predict: uncertainty grows
16  float kazanc = k.p / (k.p + k.r);    // 2. Kalman kazancı / Kalman gain
17  k.x += kazanc * (olcum - k.x);       // 3. ölçüme doğru düzelt / correct toward the measurement
18  k.p *= (1.0 - kazanc);               // 4. belirsizlik azalır / uncertainty shrinks
19  return k.x;
20}
21
22void setup() {
23  Serial.begin(115200);
24}
25
26void loop() {
27  int ham = analogRead(POT_PIN);
28  float tahmin = kalmanGuncelle(kf, ham);
29  Serial.print("Ham:");
30  Serial.print(ham);
31  Serial.print(",Kalman:");
32  Serial.println(tahmin, 1);
33  delay(10);
34}

R è facile da stimare: prendi 100 letture grezze senza toccare il potenziometro e guarda quanto sono sparse. Se oscillano di circa ±3 punti intorno alla media, la deviazione standard è circa 3 e la varianza circa 9. Q si trova per tentativi: un Q piccolo (0,001) dà un'uscita calmissima ma lenta, un Q grande (1) un'uscita veloce ma più tremolante. Per iniziare scegli Q tra l'1 e il 10 per cento di R e rifiniscilo nel Plotter seriale girando il potenziometro.

Rumore dell'ADC e filtraggio sull'ESP32

L'ADC a 12 bit dell'ESP32 misura in modo più fine di quello di Arduino Uno, ma è più rumoroso e non lineare, soprattutto agli estremi 0–0,1 V e 3,1–3,3 V. Con il Wi-Fi attivo il rumore aumenta ancora. Due cose semplificano la vita: usare analogReadMilliVolts(), che applica la calibrazione di fabbrica del chip invece di restituire un valore grezzo, e fare la media di 16–64 campioni per lettura prima di applicare una EMA. Il codice qui sotto legge il potenziometro con questi due passaggi; nei progetti con Wi-Fi collega sempre il sensore ai pin ADC1, GPIO 32–39.

C++ (Arduino IDE)
1const int POT_PIN = 34;            // ADC1 pini, Wi-Fi ile çalışır / ADC1 pin, works with Wi-Fi
2const int ORNEK = 32;              // her ölçümde 32 örnek / 32 samples per reading
3const float ALFA = 0.15;
4
5float ema = -1;
6
7int millivoltOku() {
8  uint32_t toplam = 0;
9  for (int i = 0; i < ORNEK; i++) {
10    toplam += analogReadMilliVolts(POT_PIN);   // kalibre mV / calibrated mV
11  }
12  return toplam / ORNEK;
13}
14
15void setup() {
16  Serial.begin(115200);
17  analogReadResolution(12);        // 0-4095
18}
19
20void loop() {
21  int ham = analogRead(POT_PIN);   // karşılaştırma için tek ham ölçüm / single raw reading for comparison
22  int mv = millivoltOku();
23  if (ema < 0) ema = mv;
24  ema += ALFA * (mv - ema);
25  Serial.printf("Ham:%d,mV:%d,EMA_mV:%.1f\n", ham, mv, ema);
26  delay(20);
27}

Filtraggio con Raspberry Pi Pico e MicroPython

Sul Raspberry Pi Pico ADC.read_u16() restituisce un valore tra 0 e 65535, ma la vera risoluzione dell'ADC è di 12 bit e i bit bassi sono rumorosi. In MicroPython la funzione sorted() rende la mediana facilissima, e per la media mobile bastano una lista e una somma progressiva. Il codice qui sotto legge il potenziometro a 100 Hz e stampa l'uscita di tre filtri in un formato che Thonny può disegnare nella finestra Visualizza > Plotter.

MicroPython
1from machine import ADC
2import time
3
4pot = ADC(26)                     # GP26 = ADC0
5
6PENCERE = 10
7tampon = [pot.read_u16()] * PENCERE   # hareketli ortalama tamponu / moving average buffer
8toplam = sum(tampon)
9sira = 0
10son5 = tampon[:5]                 # medyan için son 5 değer / last 5 values for the median
11ema = float(tampon[0])
12ALFA = 0.1
13
14while True:
15    ham = pot.read_u16()
16
17    toplam += ham - tampon[sira]  # hareketli ortalama / moving average
18    tampon[sira] = ham
19    sira = (sira + 1) % PENCERE
20    hareketli = toplam // PENCERE
21
22    son5.pop(0)                   # medyan / median
23    son5.append(ham)
24    medyan = sorted(son5)[2]
25
26    ema += ALFA * (ham - ema)     # üstel filtre / exponential filter
27
28    print("Ham:", ham, "Hareketli:", hareketli, "Medyan:", medyan, "EMA:", int(ema))
29    time.sleep_ms(10)

Il plotter di Thonny mostra i numeri di ogni riga come linee separate e usa il testo davanti ai numeri come etichetta. MicroPython è più lento di Arduino, quindi non spingere i filtri a frequenze troppo alte; circa 100 Hz con finestre piccole girano tranquillamente sul Pico. Se sei alle prime armi con MicroPython, inizia dall'articolo basi di MicroPython.

Quale filtro scegliere?

SituazioneFiltro consigliatoPerché?
Un potenziometro, un joystick o un sensore analogico tremaMedia mobile o EMA (α = 0,1–0,2)Semplice, veloce, smussamento sufficiente
Ogni tanto arriva un valore assurdo (HC-SR04, flussimetro)Mediana a 3 o 5Elimina del tutto gli errori isolati
Picchi e tremolio insiemePrima mediana, poi EMARisolve i due problemi uno dopo l'altro
Pochissima RAM, molti sensoriEMA interaUna variabile per sensore
Una decisione acceso/spento lampeggia sulla sogliaIsteresi e tempo di confermaRisolve il problema della decisione; un filtro da solo non basta
Ondulazione da lampade o dalla reteMedia equidistante su 20 msCopre un intero periodo a 50 Hz
Combinare più sensori (inclinazione, posizione)Filtro di Kalman o complementareUnisce i punti di forza dei singoli sensori

Errori comuni e soluzioni

SintomoCausa probabileSoluzione
Il valore filtrato reagisce troppo tardiFinestra troppo grande o α troppo piccoloRiduci la finestra o aumenta α; calcola il ritardo con le tabelle
La media mobile sale lentamente da zero all'avvioIl buffer parte pieno di zeriRiempi il buffer con la prima lettura o dividi per il numero di valori finché non è pieno
La media impazzisce a 64 campioniLa somma int è andata in overflow (64 × 1023 = 65.472 > 32.767)Dichiara la variabile somma come long
Il filtro mediano non cambia nullaFinestra da 2 o pari, oppure picchi consecutiviUsa una finestra dispari (3, 5, 7) più grande del numero di picchi di fila
Le linee del Plotter seriale sono unite o di un solo coloreFormato delle etichette sbagliatoUsa etichetta:valore con virgole in mezzo e chiudi la riga con println
Il valore dell'EMA non cambia maiDivisione intera: scritto 1/10 invece di 0.1Scrivi il coefficiente come float (0.1) o usa la formula EMA intera
I valori oscillano nonostante il filtroIl problema non è il rumore ma la deriva o un'alimentazione deboleControlla l'alimentazione, aggiungi un condensatore da 100 nF e calibra
Guarda sempre i dati grezzi prima di scegliere un filtro. Osservare il valore grezzo per qualche minuto nel Plotter seriale mostra subito se il problema è tremolio, picchi, ondulazione periodica o deriva. Poi parti dal filtro più semplice e passa al successivo solo se serve. Nella maggior parte dei progetti una mediana a 5 più una EMA con α = 0,1 funzionano meglio di un Kalman complicato e si capiscono molto più facilmente.

Filtrare i dati dei sensori è il modo più economico per ottenere misure affidabili: non servono componenti in più e poche righe di codice rendono i tuoi progetti molto più stabili. Ora che sai combinare media, media mobile, mediana, EMA e isteresi, display tremolanti, falsi allarmi e relè che scattano di continuo sono acqua passata. Nell'app BlueGrays puoi esplorare progetti come la misura della luce con una LDR, la luce notturna, la misura di distanza con l'HC-SR04, il sensore di parcheggio e il misuratore di altezza, con schemi passo passo e codice pronto all'uso, e vedere tu stesso il risultato aggiungendo questi filtri.

Domande frequenti

Perché i valori dei sensori su Arduino cambiano di continuo?

Nelle letture analogiche, le variazioni dell'alimentazione, i cavi lunghi, i motori vicini e il rumore proprio dell'ADC spostano il valore di qualche punto. È normale e non significa che il sensore sia guasto. Un condensatore da 100 nF sull'ingresso analogico e un filtro a media o EMA nel software calmano i valori.

Che cos'è una media mobile?

Una media mobile è la media delle ultime N letture; a ogni nuova lettura si toglie il valore più vecchio e si aggiunge quello nuovo. Riduce il rumore di circa √N volte ma ritarda l'uscita di (N−1)/2 letture. Su Arduino si scrive in poche righe con un buffer circolare e un'unica variabile di somma.

Che differenza c'è tra filtro mediano e media?

La media considera tutti i valori, quindi una sola lettura assurda rovina il risultato. Il filtro mediano ordina i valori e prende quello centrale, perciò ignora del tutto le letture errate isolate. Per questo la mediana è migliore per sensori come l'HC-SR04 che ogni tanto sbagliano, la media per i valori analogici tremolanti.

Come si sceglie il coefficiente di un filtro esponenziale (EMA)?

Il coefficiente α sta tra 0 e 1; un α piccolo dà un'uscita più calma ma più lenta, un α grande un'uscita più veloce ma più tremolante. Con una lettura ogni 10 ms, α = 0,1 dà una costante di tempo di circa 100 ms ed è un buon punto di partenza per la maggior parte dei sensori. Puoi calcolare il tempo di risposta con τ ≈ T / α.

Come si disegnano più valori nel Plotter seriale di Arduino?

Stampa su ogni riga i valori nel formato etichetta:valore separati da virgole e chiudi la riga con println, per esempio Ham:512,EMA:509. Il Plotter seriale mostra ogni etichetta come una linea di colore diverso. Se il Monitor seriale è aperto chiudilo prima e imposta la stessa velocità in baud del codice.

Si può usare un filtro di Kalman su Arduino?

Sì, un semplice filtro di Kalman monodimensionale gira in poche righe persino su Arduino Uno. Si regola con il rumore di misura R e il rumore di processo Q; con un solo sensore i risultati sono spesso molto simili a quelli di una EMA ben regolata. La sua vera utilità emerge quando si combinano più sensori, come accelerometro e giroscopio.

Vedi tutti i progetti
Misurare l'intensità luminosa con LDR
#11Principiante

Misurare l'intensità luminosa con LDR

Vuoi vedere quanto è luminosa la tua stanza sotto forma di numero? L'LDR, un piccolo componente sensibile alla luce, misura la luce ambiente e l'Arduino scrive il valore sullo schermo del computer ogn

4.6(81)
140
HC-SR04 Misura della distanza (display seriale)
#27Principiante

HC-SR04 Misura della distanza (display seriale)

Il sensore HC-SR04 funziona come un pipistrello che trova la strada al buio: manda un suono che non puoi sentire, ne ascolta l'eco e misura quanto è lontano l'oggetto davanti a sé. L'Arduino scrive qu

4.6(47)
74
Luce notturna con LDR
#12Principiante

Luce notturna con LDR

Una piccola luce notturna che si accende da sola quando fa buio e si spegne quando arriva il mattino. Un sensore di luce misura quanto è illuminata la stanza, e l'Arduino accende il LED quando è abbas

4.8(103)
210
Sensore di parcheggio con HC-SR04
#28Intermedio
Premium

Sensore di parcheggio con HC-SR04

Una versione in miniatura del sensore di parcheggio delle auto: più qualcosa si avvicina, più i bip accelerano. Il sensore di distanza misura l'oggetto davanti a sé; se è a più di 20 centimetri resta

4.8(64)
85
Progetto di misurazione dell'altezza
#33Intermedio
Premium

Progetto di misurazione dell'altezza

Un misuratore d'altezza da appendere allo stipite della porta che ti dice in centimetri quanto è alto chi ci sta sotto. Prima premi il primo pulsante senza nessuno sotto e il sensore di distanza misur

4.5(25)
34
Funzionamento del servomotore con potenziometro
#36Principiante
Premium

Funzionamento del servomotore con potenziometro

Mentre giri la manopola del potenziometro, il braccio del servomotore ruota insieme a lei: un'estremità della manopola corrisponde a zero gradi, l'altra a mezzo giro. L'Arduino legge di continuo la po

4.6(37)
53
Lettura dell'intensità luminosa con il modulo LDR ESP32
#113Principiante

Lettura dell'intensità luminosa con il modulo LDR ESP32

Monitoraggio in tempo reale sul Serial Monitor del valore grezzo dell'ADC del modulo LDR e della percentuale relativa di luce calcolata dai valori di riferimento al buio e alla luce. Questa percentuale non è una misurazione in lux. Spiega il principio di calibrazione del modulo. È consigliabile eseguirlo prima del Progetto 10, poiché serve a determinare il valore di soglia.

4.6(36)
55
ESP32 HC-SR04 Misuratore di distanza a ultrasuoni
#116Intermedio

ESP32 HC-SR04 Misuratore di distanza a ultrasuoni

Il sensore a ultrasuoni HC-SR04 calcola la distanza misurando il tempo di volo (Time of Flight) dell'onda sonora che emette dopo aver colpito un oggetto e essere rimbalzata indietro. In questo progetto invieremo un impulso di 10 microsecondi dall'ESP32 al pin TRIG del sensore e leggeremo il tempo di eco proveniente dal pin ECHO con pulseIn(). Visualizziamo la distanza calcolata in cm su un display LCD I2C 16x2. Poiché l'HC-SR04 funziona a 5V, è necessario un divisore di tensione sul pin EC

4.9(28)
46