Filtrer les données d'un capteur Arduino : moyenne, médiane, EMA
Filtrer les données d'un capteur Arduino : moyenne glissante, filtre médian, EMA, hystérésis, échantillonnage, traceur série et introduction au Kalman.
Filtrer les données d'un capteur Arduino, c'est transformer les valeurs tremblantes et sautillantes venant d'analogRead() ou d'un capteur de distance en un nombre calme et fiable que ton projet peut utiliser. Voir 511, 513, 510, 514 dans le moniteur série alors que tu ne touches pas le potentiomètre, un HC-SR04 qui mesure de temps en temps 3 cm ou 400 cm sans raison, une veilleuse qui clignote à la limite entre jour et nuit : ce sont les différentes facettes d'un même problème. La plupart du temps, le capteur n'est pas en panne ; du bruit électrique, des erreurs de pas du CAN ou quelques faux échos se sont glissés dans la mesure. Bonne nouvelle : un filtre de quelques lignes règle la plupart de ces problèmes.
Dans ce guide, tu verras d'abord les types de bruit et les premières mesures à prendre côté circuit. Ensuite, on codera cinq méthodes, en commençant par la plus simple : la moyenne de plusieurs échantillons, la moyenne glissante (moving average), le filtre médian qui supprime les pics, le filtre exponentiel (EMA) très économe en mémoire et l'hystérésis, qui calme les seuils instables. On les tracera côte à côte dans le traceur série (Serial Plotter) de l'Arduino IDE, on verra comment choisir la fréquence d'échantillonnage et on fera une courte introduction au filtre de Kalman. Les dernières parties traitent du CAN bruité de l'ESP32 et du code MicroPython pour le Raspberry Pi Pico.

Pourquoi les données d'un capteur sont-elles bruitées ?
Voir l'erreur de mesure comme une seule chose est trompeur. Chaque type de bruit demande un filtre différent, il faut donc d'abord savoir quel est ton problème. Pour cela, il suffit d'afficher un moment les valeurs brutes dans le moniteur série et de regarder les nombres. Si les valeurs tremblent sans arrêt autour d'un nombre, c'est du bruit aléatoire ; si la plupart des mesures sont justes mais qu'une valeur absurde arrive de temps en temps, ce sont des pics (spikes) ; si la valeur glisse lentement, le problème n'est pas du bruit mais une dérive.
| Type de bruit | À quoi il ressemble | Cause typique | Solution adaptée |
|---|---|---|---|
| Bruit aléatoire | La valeur tremble de ±2 à 10 points | Alimentation instable, fils longs, bruit propre du CAN | Moyenne, moyenne glissante, EMA |
| Pic (spike) | De temps en temps une valeur très différente | Écho manqué sur le HC-SR04, faux contact, parasites de moteur | Filtre médian |
| Erreur de pas (quantification) | La valeur oscille entre deux nombres voisins | Résolution du CAN (5 V / 1024 = 4,9 mV sur l'Uno) | Moyenne, hystérésis |
| Bruit périodique | Ondulation régulière | Scintillement à 100 Hz des lampes, secteur 50 Hz | Moyenne sur des multiples de 20 ms |
| Dérive (drift) | La valeur change lentement en quelques minutes | Température, tension de batterie qui baisse | Étalonnage et référence stable plutôt qu'un filtre |
D'abord le circuit : réduire le bruit à la source
Un filtre logiciel ne peut pas retrouver une information que le circuit n'a jamais captée ; la première étape consiste donc à compliquer la vie du bruit. Garde des fils courts pour les capteurs analogiques et fais-les passer loin des fils des composants gourmands comme les moteurs et les relais. Un condensateur céramique de 100 nF entre l'entrée analogique et le GND, près du capteur, absorbe une grande partie des parasites rapides. La fréquence de coupure d'un filtre passe-bas RC fait d'une résistance et d'un condensateur se calcule avec f = 1 / (2πRC) : 10 kΩ et 1 µF donnent environ 16 Hz, les variations plus rapides sont donc atténuées. Tu peux calculer les valeurs avec l'outil constante de temps RC. Dans les projets avec moteurs, il aide aussi de ramener le GND des capteurs vers la carte par un chemin séparé de celui du pilote moteur et d'alimenter la carte avec une source stable. On explique le fonctionnement du CAN et l'effet de la tension de référence dans Qu'est-ce qu'un CAN ?.
Montage de test
On testera le code de cet article avec trois capteurs simples. Le potentiomètre est idéal pour voir le bruit aléatoire, la LDR pour le bruit périodique et les décisions à seuil, le HC-SR04 pour les pics. Branche la LDR entre le 5 V et la broche analogique, et la résistance de 10 kΩ entre la broche analogique et le GND ; ainsi la valeur lue augmente avec la lumière. Les détails sont dans les articles sur le capteur de lumière LDR et le potentiomètre.
| Liaison | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| Curseur du potentiomètre | A0 | GPIO 34 | GP26 (ADC0) |
| Point milieu LDR + pont de 10 kΩ | A1 | GPIO 35 | GP27 (ADC1) |
| HC-SR04 TRIG | D9 | GPIO 5 | GP2 |
| HC-SR04 ECHO | D10 | GPIO 18 (avec pont) | GP3 (avec pont) |
| LED ou relais (exemple d'hystérésis) | D8 | - | - |
| Alimentation | Pot et LDR 5V, HC-SR04 5V | Pot et LDR 3V3, HC-SR04 5V | Pot et LDR 3V3, HC-SR04 VBUS |
1. Moyenne de plusieurs échantillons
Le filtre le plus simple consiste à prendre plusieurs mesures d'affilée et à utiliser leur moyenne. Le bruit aléatoire pousse chaque mesure dans une direction différente, il s'annule donc dans la somme. La statistique a une règle pour cela : dans la moyenne de N mesures indépendantes, le bruit diminue d'environ √N. 4 échantillons divisent le bruit par deux, 16 par quatre ; 64 le divisent par huit mais prennent quatre fois plus de temps. Sur l'Arduino Uno, un analogRead() prend environ 112 µs, donc une moyenne de 16 échantillons est faite en moins de 2 ms. Ses seuls défauts : elle occupe brièvement le processeur à chaque mesure et résiste mal aux pics, car si l'un des 16 échantillons est absurde, il fausse aussi la moyenne.
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. Moyenne glissante (moving average)
La moyenne glissante garde la moyenne des N dernières mesures et, à chaque nouvelle mesure, retire la plus ancienne et ajoute la nouvelle. Tu ne fais ainsi qu'une mesure par tour, tout en profitant d'une moyenne sur N mesures. Au lieu d'additionner N nombres à chaque fois, le code tient une variable somme : il retranche la valeur qui sort et ajoute celle qui entre. Le calcul est donc aussi rapide quelle que soit la taille de la fenêtre. Le prix à payer est le retard : la sortie a environ (N−1)/2 mesures de retard sur la valeur réelle. Une fenêtre de 10 avec une mesure toutes les 10 ms retarde d'environ 45 ms ; pour un potentiomètre ou une température c'est négligeable, pour un robot rapide ça se sent.
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}| Fenêtre (N) | Réduction du bruit | Retard (une mesure toutes les 10 ms) | Usage adapté |
|---|---|---|---|
| 4 | Environ 2 fois | 15 ms | Régulation qui doit réagir vite |
| 10 | Environ 3 fois | 45 ms | Potentiomètre, joystick, LDR |
| 32 | Environ 5,7 fois | 155 ms | Température, humidité, tension de batterie |
| 100 | 10 fois | 495 ms | Valeurs très lentes, affichage calme |
3. Filtre médian : supprimer les pics
La moyenne résiste mal aux pics. Si le HC-SR04 mesure quatre fois 50 cm puis rate l'écho une fois et mesure 0, la moyenne des cinq vaut 40 cm ; cette valeur n'est ni juste ni révélatrice de l'erreur. Le filtre médian, lui, trie les N dernières mesures de la plus petite à la plus grande et prend celle du milieu. Dans le même exemple, la liste triée donne 0, 50, 50, 50, 50 et la valeur centrale est 50 ; la mesure erronée n'atteint jamais le résultat. Un filtre médian sur 5 éléments supprime complètement jusqu'à 2 mesures erronées consécutives. Second atout de la médiane : elle n'étale pas un changement brusque et réel comme le fait la moyenne. Quand un obstacle s'approche vraiment, la valeur passe au nouveau niveau par une marche nette en quelques mesures au lieu d'une courbe douce. Le code ci-dessous mesure avec le HC-SR04 toutes les 60 ms et affiche la médiane des 5 dernières mesures. Pour les détails, vois l'article sur le capteur de distance HC-SR04.
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}4. Filtre exponentiel (EMA) : lisser avec peu de mémoire
La moyenne mobile exponentielle (Exponential Moving Average, EMA) n'utilise aucun tampon ; elle ne retient que le résultat précédent. À chaque nouvelle mesure, le résultat se rapproche de la mesure d'une petite fraction de l'écart entre les deux : y = y + α × (x − y). Ici x est la nouvelle mesure, y la valeur filtrée et α (alpha) un coefficient entre 0 et 1. Plus α est petit, plus le filtre est calme mais lent. On l'appelle exponentiel parce que l'influence de chaque mesure s'efface de façon exponentielle avec le temps. C'est l'équivalent logiciel du filtre passe-bas RC en électronique. La constante de temps de l'EMA vaut environ τ ≈ T / α (T est l'intervalle d'échantillonnage) : avec une mesure toutes les 10 ms et α = 0,1, le filtre atteint 63 pour cent d'un changement brusque en environ 100 ms et 95 pour cent en environ 300 ms. Le code propose une version en virgule flottante et une version entière plus rapide sur les petites cartes comme l'Uno ; la version entière utilise α = 1/16 et un décalage (>>) au lieu d'une division.
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}| α (alpha) | Fenêtre équivalente approximative | Temps de réponse à 63 % (échantillonnage 10 ms) | Caractère |
|---|---|---|---|
| 0,5 | 3 | 10–20 ms | Très rapide, peu de lissage |
| 0,2 | 9 | 50 ms | Rapide, lissage léger |
| 0,1 | 19 | 100 ms | Équilibré, bon point de départ pour la plupart des capteurs |
| 0,05 | 39 | 200 ms | Calme, lent |
| 0,01 | 199 | 1 s | Très calme, seulement pour des valeurs lentes |
La « fenêtre équivalente » du tableau est la taille approximative d'une moyenne glissante qui donne le même lissage ; elle se calcule par N ≈ 2/α − 1. Le grand avantage de l'EMA est la mémoire : une moyenne glissante de 100 éléments demande 200 octets de RAM, l'EMA une seule variable. Si tu dois filtrer plusieurs capteurs dans les 2 Ko de RAM de l'Arduino Uno, la différence compte. Son défaut : elle résiste aussi mal que la moyenne aux pics ; un gros pic tire le résultat pendant plusieurs mesures.
Comparer les filtres dans le traceur série
La différence entre les filtres se voit mal dans les nombres, mais saute aux yeux sur un graphique. Le code ci-dessous lit le potentiomètre à 100 Hz, fait passer la même mesure par quatre chemins différents et affiche le tout au format étiquette:valeur. Pour rendre la différence plus nette, le code ajoute un pic artificiel à environ 3 pour cent des mesures ; dans un vrai projet, supprime cette ligne. Sur le graphique, tu verras que la médiane efface complètement les pics, tandis que la moyenne glissante et l'EMA les transforment en petites bosses.
- Téléverse le code et ferme le moniteur série s'il est ouvert ; le moniteur série et le traceur série ne peuvent pas être ouverts en même temps.
- Dans l'Arduino IDE, ouvre Outils > Traceur série ou clique sur l'icône de graphique en haut à droite.
- Règle la vitesse en bas à droite sur 115200 bauds.
- Tourne le potentiomètre lentement puis vite, et observe la réaction de chaque courbe.
- Active et désactive les courbes avec les cases à côté des étiquettes pour comparer directement deux filtres.
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}Sur le graphique, les étiquettes apparaissent en couleur en haut à droite. Sans virgule entre les étiquettes et sans deux-points entre étiquette et valeur, le traceur série ne peut pas séparer correctement les courbes. L'ancien traceur de l'Arduino IDE 1.8 gère mal les étiquettes ; en cas de souci, envoie seulement des nombres séparés par des virgules. Pour les problèmes de port série et de vitesse en bauds, jette un œil à l'article sur le moniteur série Arduino.
5. Hystérésis : stopper les décisions qui clignotent au seuil
Le filtre calme la valeur, mais il ne règle pas à lui seul le problème de décision. Une veilleuse qui dit « allume la lampe quand la lumière passe sous 350 » s'allume et s'éteint plusieurs fois par seconde au crépuscule, tandis que la valeur oscille entre 349 et 351. Cela use vite les contacts d'un relais. La solution est l'hystérésis : on utilise deux seuils distincts pour allumer et pour éteindre. Par exemple, la lampe s'allume quand la valeur passe sous 300, mais pour s'éteindre elle doit remonter au-dessus de 400. La « zone morte » de 100 points entre les deux empêche les petites fluctuations de changer la décision. Le code ci-dessous associe l'hystérésis à une EMA et à un court délai de confirmation : la condition doit être remplie sans interruption pendant 1 seconde, de sorte qu'une ombre qui passe ou un éclat de phares ne change pas non plus la décision.
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}Avant de fixer les seuils, observe les valeurs de la pièce au fil de la journée dans le traceur série. La zone morte doit être au moins deux à trois fois plus large que le tremblement de la valeur filtrée. La même méthode sert dans les thermostats (par exemple couper le chauffage à 22 °C et le rallumer à 20 °C), les pompes de réservoir d'eau et les circuits de protection de batterie. L'anti-rebond des boutons est d'ailleurs une forme de délai de confirmation ; les détails sont dans Comment brancher un bouton.
Comment choisir la fréquence d'échantillonnage ?
Pour qu'un filtre fonctionne bien, il faut lire le capteur au bon rythme. Trop lent, tu rates les changements rapides ; trop rapide, le capteur renvoie sans cesse la même valeur ou n'est même pas prêt. La règle de base est le critère de Nyquist : échantillonne au moins deux fois plus vite que le changement le plus rapide que tu veux suivre ; en pratique, 5 à 10 fois est plus sûr. Certains capteurs ont en plus leur propre limite, et les lire plus vite ne sert à rien. Attention à l'éclairage : sur un réseau 50 Hz, les lampes scintillent 100 fois par seconde. Le moyen le plus simple d'annuler ce scintillement sur une LDR ou un autre capteur analogique est de prendre des mesures régulièrement espacées pendant une durée multiple de 20 ms et d'en faire la moyenne ; comme la fenêtre couvre des périodes entières du secteur, le scintillement s'annule de lui-même.
| Capteur | Intervalle de lecture conseillé | Remarque |
|---|---|---|
| DHT11 / DHT22 | 1 s / 2 s | Plus souvent, tu obtiens la même valeur ou une erreur |
| DS18B20 (12 bits) | 750 ms | C'est le temps de conversion |
| HC-SR04 | Au moins 60 ms | Il faut attendre que l'écho précédent s'éteigne |
| Potentiomètre, joystick | 10–20 ms | 50–100 Hz suffisent largement pour des mouvements humains |
| LDR (contre le scintillement des lampes) | Toutes les 1 ms pendant 20 ms, puis moyenne | Le scintillement du secteur 50 Hz s'annule |
| MPU6050 | 5–10 ms | Plage courante pour mesurer un mouvement à 100–200 Hz |
Courte introduction au filtre de Kalman
Le filtre de Kalman tient compte du bruit de la mesure et de la vitesse à laquelle la vraie valeur peut changer, et règle lui-même son gain. Dans sa forme unidimensionnelle la plus simple, il a deux réglages : R est la variance du bruit de mesure (à quel point le capteur tremble) ; Q est le bruit de processus (à quelle vitesse tu t'attends à voir changer la vraie valeur). À chaque étape, le filtre augmente d'abord son incertitude de Q, calcule ensuite le gain K = P / (P + R) et déplace son estimation vers la mesure dans la proportion K. Si la mesure est bruitée (R grand), K est petit et le filtre se fie peu à la mesure ; si l'on s'attend à un changement rapide (Q grand), K augmente et le filtre réagit vite. Résultat frappant : quand Q et R sont fixes, K se stabilise au bout d'un moment sur une valeur constante et le Kalman unidimensionnel devient en pratique une EMA. La vraie force de Kalman apparaît quand il estime plusieurs grandeurs à la fois, par exemple en combinant accéléromètre et gyroscope pour trouver un angle d'inclinaison. Tu trouveras son cousin plus simple, le filtre complémentaire, dans l'article sur le MPU6050.
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 est facile à estimer : prends 100 mesures brutes sans toucher le potentiomètre et regarde leur dispersion. Si elles varient d'environ ±3 points autour de la moyenne, l'écart type vaut environ 3 et la variance environ 9. Q se trouve par essais : un petit Q (0,001) donne une sortie très calme mais lente, un grand Q (1) une sortie rapide mais plus tremblante. Pour commencer, choisis Q entre 1 et 10 pour cent de R et affine-le dans le traceur série en tournant le potentiomètre.
Bruit du CAN et filtrage sur l'ESP32
Le CAN 12 bits de l'ESP32 mesure plus finement que celui de l'Arduino Uno, mais il est plus bruité et non linéaire, surtout aux extrémités 0–0,1 V et 3,1–3,3 V. Avec le Wi-Fi actif, le bruit augmente encore. Deux choses te simplifient la vie : utiliser analogReadMilliVolts(), qui applique l'étalonnage d'usine de la puce au lieu de renvoyer une valeur brute, et moyenner 16 à 64 échantillons par mesure avant d'appliquer une EMA. Le code ci-dessous lit le potentiomètre selon ces deux étapes ; dans les projets avec Wi-Fi, branche toujours le capteur sur les broches ADC1, GPIO 32–39.
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}Filtrage avec le Raspberry Pi Pico et MicroPython
Sur le Raspberry Pi Pico, ADC.read_u16() renvoie une valeur entre 0 et 65535, mais la vraie résolution du CAN est de 12 bits et les bits de poids faible sont bruités. En MicroPython, la fonction sorted() rend la médiane très facile, et pour la moyenne glissante une liste et une somme courante suffisent. Le code ci-dessous lit le potentiomètre à 100 Hz et affiche la sortie de trois filtres dans un format que Thonny peut tracer dans sa fenêtre Affichage > Traceur (Plotter).
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)Le traceur de Thonny affiche les nombres de chaque ligne comme des courbes séparées et utilise le texte placé devant les nombres comme étiquette. MicroPython est plus lent qu'Arduino, ne pousse donc pas les filtres à des fréquences trop élevées ; environ 100 Hz avec de petites fenêtres tournent sans peine sur le Pico. Si tu débutes en MicroPython, commence par l'article bases de MicroPython.
Quel filtre choisir ?
| Situation | Filtre conseillé | Pourquoi ? |
|---|---|---|
| Un potentiomètre, un joystick ou un capteur analogique tremble | Moyenne glissante ou EMA (α = 0,1–0,2) | Simple, rapide, lissage suffisant |
| Une valeur absurde arrive de temps en temps (HC-SR04, débitmètre) | Médiane sur 3 ou 5 | Supprime complètement les erreurs isolées |
| Pics et tremblement à la fois | Médiane puis EMA | Règle les deux problèmes l'un après l'autre |
| Très peu de RAM, beaucoup de capteurs | EMA entière | Une variable par capteur |
| Une décision marche-arrêt clignote au seuil | Hystérésis et délai de confirmation | Règle le problème de décision ; un filtre seul ne suffit pas |
| Ondulation due aux lampes ou au secteur | Moyenne régulièrement espacée sur 20 ms | Couvre une période complète de 50 Hz |
| Combiner plusieurs capteurs (inclinaison, position) | Filtre de Kalman ou complémentaire | Réunit les points forts de chaque capteur |
Erreurs fréquentes et solutions
| Symptôme | Cause probable | Solution |
|---|---|---|
| La valeur filtrée réagit beaucoup trop tard | Fenêtre trop grande ou α trop petit | Réduis la fenêtre ou augmente α ; calcule le retard avec les tableaux |
| La moyenne glissante monte lentement depuis zéro au démarrage | Le tampon démarre rempli de zéros | Préremplis le tampon avec la première mesure ou divise par le nombre de valeurs jusqu'à ce qu'il soit plein |
| La moyenne devient folle à 64 échantillons | La somme int a débordé (64 × 1023 = 65 472 > 32 767) | Déclare la variable somme en long |
| Le filtre médian ne change rien | Fenêtre de 2 ou paire, ou pics consécutifs | Utilise une fenêtre impaire (3, 5, 7) plus grande que le nombre de pics consécutifs |
| Les courbes du traceur série sont fusionnées ou d'une seule couleur | Mauvais format d'étiquettes | Utilise étiquette:valeur séparés par des virgules et termine la ligne par println |
| La valeur de l'EMA ne bouge jamais | Division entière : 1/10 écrit au lieu de 0.1 | Écris le coefficient en float (0.1) ou utilise la formule d'EMA entière |
| Les valeurs fluctuent malgré le filtre | Le problème n'est pas le bruit mais une dérive ou une alimentation faible | Vérifie l'alimentation, ajoute un condensateur de 100 nF et étalonne |
Filtrer les données des capteurs est le moyen le moins cher d'obtenir des mesures fiables : aucune pièce en plus, et quelques lignes de code rendent tes projets bien plus stables. Maintenant que tu sais combiner moyenne, moyenne glissante, médiane, EMA et hystérésis, les affichages qui tremblent, les fausses alarmes et les relais qui claquent sont derrière toi. Dans l'application BlueGrays, tu peux explorer des projets comme la mesure de lumière avec une LDR, une veilleuse, la mesure de distance avec le HC-SR04, un radar de recul et une toise électronique, avec des schémas pas à pas et du code prêt à l'emploi, puis constater toi-même le résultat en y ajoutant ces filtres.
Questions fréquentes
Pourquoi les valeurs d'un capteur Arduino changent-elles sans arrêt ?
Dans les mesures analogiques, les variations de l'alimentation, les fils longs, les moteurs proches et le bruit propre du CAN font bouger la valeur de quelques points. C'est normal et ne veut pas dire que le capteur est en panne. Un condensateur de 100 nF sur l'entrée analogique et un filtre de moyenne ou EMA dans le logiciel calment les valeurs.
Qu'est-ce qu'une moyenne glissante ?
Une moyenne glissante est la moyenne des N dernières mesures ; à chaque nouvelle mesure, la plus ancienne est retirée et la nouvelle ajoutée. Elle réduit le bruit d'environ √N mais retarde la sortie de (N−1)/2 mesures. Sur Arduino, elle s'écrit en quelques lignes avec un tampon circulaire et une seule variable de somme.
Quelle différence entre un filtre médian et une moyenne ?
La moyenne tient compte de toutes les valeurs, donc une seule mesure absurde fausse le résultat. Le filtre médian trie les valeurs et prend celle du milieu, il ignore donc complètement les mesures erronées isolées. C'est pourquoi la médiane convient mieux aux capteurs comme le HC-SR04, qui se trompent parfois, et la moyenne aux valeurs analogiques qui tremblent.
Comment choisir le coefficient d'un filtre exponentiel (EMA) ?
Le coefficient α est compris entre 0 et 1 ; un petit α donne une sortie plus calme mais plus lente, un grand α une sortie plus rapide mais plus tremblante. Avec une mesure toutes les 10 ms, α = 0,1 donne une constante de temps d'environ 100 ms et constitue un bon point de départ pour la plupart des capteurs. Tu peux calculer le temps de réponse avec τ ≈ T / α.
Comment tracer plusieurs valeurs dans le traceur série Arduino ?
Affiche sur chaque ligne les valeurs au format étiquette:valeur, séparées par des virgules, et termine la ligne par println, par exemple Ham:512,EMA:509. Le traceur série affiche chaque étiquette comme une courbe de couleur différente. Ferme d'abord le moniteur série s'il est ouvert et règle la même vitesse en bauds que dans le code.
Peut-on utiliser un filtre de Kalman sur Arduino ?
Oui, un filtre de Kalman unidimensionnel simple tient en quelques lignes, même sur un Arduino Uno. Il se règle avec le bruit de mesure R et le bruit de processus Q ; pour un seul capteur, ses résultats sont souvent très proches d'une EMA bien réglée. Son vrai intérêt apparaît quand on combine plusieurs capteurs, comme un accéléromètre et un gyroscope.
Projets à réaliser après cet article
Voir tous les projets
Mesurer l'intensité lumineuse avec une LDR
Envie de voir en chiffres à quel point ta chambre est lumineuse ? Le LDR, une petite pièce sensible à la lumière, mesure la lumière ambiante, et l'Arduino écrit la valeur sur l'écran de ton ordinateur

HC-SR04 Mesure de la distance (affichage en série)
Le capteur HC-SR04 fonctionne comme une chauve-souris qui trouve son chemin dans le noir : il envoie un son que tu ne peux pas entendre, écoute l'écho et mesure à quelle distance se trouve l'objet dev

Veilleuse avec LDR
Une petite veilleuse qui s'allume toute seule quand il fait nuit et s'éteint au matin. Un capteur de lumière mesure la clarté de la pièce, et l'Arduino allume la LED quand il fait assez sombre puis l'

Capteur de stationnement avec HC-SR04
Une version miniature du radar de recul des voitures : plus un objet s'approche, plus les bips s'accélèrent. Le capteur de distance mesure l'obstacle devant lui ; au-delà de 20 centimètres, la LED ver

Projet de mesure de la taille
Une toise qui se fixe au cadre de la porte et te dit en centimètres la taille de la personne qui se tient dessous. D'abord, tu appuies sur le premier bouton sans personne en dessous et le capteur de d

Utilisation d'un servomoteur avec un potentiomètre
Quand tu tournes le bouton du potentiomètre, le bras du servomoteur tourne avec lui : un bout du bouton correspond à zéro degré, l'autre à un demi-tour. L'Arduino lit sans arrêt la position du bouton,

Mesure de l'intensité lumineuse avec le module LDR ESP32
Suivi en temps réel sur le moniteur série de la valeur brute de l'ADC du module LDR et du pourcentage relatif de luminosité calculé à partir de références dans l'obscurité et à la lumière. Ce pourcentage n'est pas une mesure en lux. Permet de comprendre le principe de calibrage du module. Il est préférable de réaliser cet exercice avant le projet 10, car il sert à déterminer la valeur seuil.

ESP32 HC-SR04 Télémètre à ultrasons
Le capteur à ultrasons HC-SR04 calcule la distance en mesurant le temps de vol (Time of Flight) de l'onde sonore qu'il émet lorsqu'elle rebondit sur un objet. Dans ce projet, nous enverrons une impulsion de 10 microsecondes depuis l'ESP32 vers la broche TRIG du capteur et nous lirons la durée de l'écho provenant de la broche ECHO à l'aide de la fonction pulseIn(). Nous afficherons la distance calculée en cm sur un écran LCD I2C 16x2. Le capteur HC-SR04 fonctionnant en 5 V, un diviseur de ten
Autres articles
- Convertisseur analogique-numérique (ADC) avec Arduino et ESP32
- Capteur de distance HC-SR04 : mesurer une distance par ultrasons avec Arduino
- Qu'est-ce qu'un capteur de lumière LDR ? Mesurer la lumière avec Arduino et veilleuse automatique
- MPU6050 Arduino : accéléromètre, gyroscope et angle d'inclinaison
Application BlueGrays
Réalise ce montage pas à pas sur ton téléphone
Schéma, code et liste des composants sur un seul écran. Plus de 200 projets Arduino, ESP32 et Pico à réaliser.
Scanne avec ton téléphone
Pointe l’appareil photo vers le code : la boutique d’applications de ton téléphone s’ouvre.
ou ouvre la boutique

