Grundlagen · 13 Min. Lesezeit

Arduino Sensorwerte filtern: Mittelwert, Median, EMA und Kalman

Arduino Sensorwerte filtern: gleitender Mittelwert, Medianfilter, EMA, Hysterese, Abtastrate, Vergleich im Seriellen Plotter und Einstieg in den Kalman-Filter.

Arduino-Sensorwerte filtern heißt, die zappelnden, springenden Werte von analogRead() oder einem Abstandssensor in eine ruhige, verlässliche Zahl zu verwandeln, mit der dein Projekt arbeiten kann. Wenn der Serielle Monitor 511, 513, 510, 514 zeigt, obwohl du das Potentiometer gar nicht berührst, der HC-SR04 ab und zu grundlos 3 cm oder 400 cm misst oder das Nachtlicht an der Grenze zwischen hell und dunkel flackert, sind das verschiedene Gesichter desselben Problems. Meistens ist der Sensor nicht defekt; elektrisches Rauschen, Stufenfehler des ADC oder vereinzelte falsche Echos haben sich in die Messung gemischt. Die gute Nachricht: Ein Filter aus wenigen Zeilen löst die meisten dieser Probleme.

In dieser Anleitung lernst du zuerst die Arten von Rauschen und die ersten Maßnahmen auf der Schaltungsseite kennen. Danach programmieren wir fünf Methoden, angefangen bei der einfachsten: Mittelwert aus mehreren Messungen, gleitender Mittelwert (Moving Average), Medianfilter gegen Ausreißer, Exponentialfilter (EMA) mit minimalem Speicherbedarf und Hysterese, die wackelige Schwellen beruhigt. Wir zeichnen alle nebeneinander im Seriellen Plotter der Arduino IDE, sprechen über die Wahl der Abtastrate und steigen kurz in den Kalman-Filter ein. Die letzten Abschnitte behandeln den verrauschten ADC des ESP32 und MicroPython-Code für den Raspberry Pi Pico.

HC-SR04-Ultraschall-Abstandssensor
Sensoren wie der HC-SR04 liefern gelegentlich falsche Werte; solche Ausreißer entfernt ein Medianfilter.

Warum sind Sensordaten verrauscht?

Messfehler als eine einzige Sache zu betrachten führt in die Irre. Verschiedene Arten von Rauschen brauchen verschiedene Filter; zuerst musst du also wissen, welches Problem du hast. Dafür reicht es, eine Weile Rohwerte im Seriellen Monitor auszugeben und die Zahlen anzusehen. Zappeln die Werte ständig um eine bestimmte Zahl, hast du zufälliges Rauschen; stimmen die meisten Messungen, aber ab und zu kommt ein völlig absurder Wert, sind es Ausreißer (Spikes); wandert der Wert langsam, ist das Problem kein Rauschen, sondern Drift.

RauschartWie sie aussiehtTypische UrsachePassende Lösung
Zufälliges RauschenDer Wert zappelt um ±2–10 StufenSchwankende Versorgung, lange Kabel, Eigenrauschen des ADCMittelwert, gleitender Mittelwert, EMA
Ausreißer (Spike)Vereinzelt ein stark abweichender WertVerpasstes Echo beim HC-SR04, Wackelkontakt, MotorstörungMedianfilter
Stufenfehler (Quantisierung)Der Wert springt zwischen zwei NachbarzahlenADC-Auflösung (beim Uno 5 V / 1024 = 4,9 mV)Mittelwert, Hysterese
Periodisches RauschenRegelmäßige Welligkeit100-Hz-Flackern von Lampen, 50-Hz-NetzMittelwert über Vielfache von 20 ms
DriftDer Wert ändert sich langsam über MinutenTemperatur, sinkende AkkuspannungKalibrierung und stabile Referenz statt Filter

Zuerst die Schaltung: Rauschen an der Quelle verringern

Ein Softwarefilter kann keine Information zurückholen, die die Schaltung nie erfasst hat; deshalb musst du es dem Rauschen zuerst schwer machen. Halte die Kabel analoger Sensoren kurz und führe sie fern von den Leitungen stromhungriger Bauteile wie Motoren und Relais. Ein 100-nF-Keramikkondensator zwischen Analogeingang und GND nahe am Sensor schluckt einen Großteil schneller Störungen. Die Grenzfrequenz eines RC-Tiefpasses aus Widerstand und Kondensator berechnet sich mit f = 1 / (2πRC): 10 kΩ und 1 µF ergeben etwa 16 Hz, schnellere Änderungen werden also gedämpft. Die Werte rechnest du mit dem Tool RC-Zeitkonstante aus. In Motorprojekten hilft es außerdem, die GND-Leitung der Sensoren auf einem eigenen Weg zum Board zu führen statt über den Motortreiber, und das Board aus einer stabilen Quelle zu versorgen. Wie der ADC arbeitet und welchen Einfluss die Referenzspannung hat, erklären wir in Was ist ein ADC?.

Testaufbau

Wir testen die Codes in diesem Artikel mit drei einfachen Sensoren. Das Potentiometer eignet sich ideal für zufälliges Rauschen, der LDR für periodisches Rauschen und Schwellenentscheidungen, der HC-SR04 für Ausreißer. Schließe den LDR zwischen 5 V und dem Analogpin an und den 10-kΩ-Widerstand zwischen Analogpin und GND; so steigt der Messwert mit zunehmender Helligkeit. Details findest du in den Artikeln zum LDR-Lichtsensor und zum Potentiometer.

VerbindungArduino UnoESP32Raspberry Pi Pico
Potentiometer MittelabgriffA0GPIO 34GP26 (ADC0)
Mittelpunkt LDR + 10-kΩ-TeilerA1GPIO 35GP27 (ADC1)
HC-SR04 TRIGD9GPIO 5GP2
HC-SR04 ECHOD10GPIO 18 (mit Teiler)GP3 (mit Teiler)
LED oder Relais (Hysterese-Beispiel)D8--
VersorgungPoti und LDR 5V, HC-SR04 5VPoti und LDR 3V3, HC-SR04 5VPoti und LDR 3V3, HC-SR04 VBUS
Schließe beim ESP32 und Pico Potentiometer und LDR-Teiler an 3V3 an; die Analogpins dürfen nie mehr als 3,3 V sehen. Der HC-SR04 läuft mit 5 V und sein ECHO-Ausgang liefert 5 V; senke ECHO beim ESP32 und Pico deshalb mit einem Spannungsteiler aus 1 kΩ und 2 kΩ auf 3,3 V. Die Teilerwerte prüfst du mit dem Tool Spannungsteiler.

1. Mittelwert aus mehreren Messungen

Der einfachste Filter: mehrere Messungen direkt hintereinander nehmen und ihren Mittelwert verwenden. Zufälliges Rauschen weicht bei jeder Messung in eine andere Richtung ab und hebt sich in der Summe auf. Die Statistik hat dafür eine Regel: Im Mittelwert von N unabhängigen Messungen sinkt das Rauschen etwa um den Faktor √N. 4 Messungen halbieren das Rauschen, 16 vierteln es; 64 Messungen drücken es auf ein Achtel, dauern aber viermal so lange. Beim Arduino Uno dauert ein analogRead() etwa 112 µs, ein Mittelwert aus 16 Messungen ist also in unter 2 ms fertig. Die einzigen Schwächen: Der Prozessor ist bei jeder Messung kurz beschäftigt, und gegen Ausreißer hilft die Methode kaum, denn ist eine der 16 Messungen absurd, verdirbt sie auch den Mittelwert.

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. Gleitender Mittelwert (Moving Average)

Der gleitende Mittelwert hält den Durchschnitt der letzten N Messungen und wirft bei jeder neuen Messung die älteste hinaus. So misst du pro Durchlauf nur einmal und profitierst trotzdem von einem Mittelwert über N Messungen. Statt jedes Mal N Zahlen neu zu addieren, führt der Code eine Summe: Der herausfallende Wert wird abgezogen, der neue addiert. Deshalb ist die Rechnung unabhängig von der Fenstergröße gleich schnell. Der Preis ist eine Verzögerung: Die Ausgabe hinkt dem echten Wert um etwa (N−1)/2 Messungen hinterher. Ein 10er-Fenster mit einer Messung alle 10 ms verzögert um etwa 45 ms; bei Potentiometer oder Temperatur egal, bei einem schnellen Roboter spürbar.

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}
Fenster (N)RauschminderungVerzögerung (eine Messung alle 10 ms)Geeignet für
4Etwa 2-fach15 msRegelungen mit schneller Reaktion
10Etwa 3-fach45 msPotentiometer, Joystick, LDR
32Etwa 5,7-fach155 msTemperatur, Luftfeuchte, Akkuspannung
10010-fach495 msSehr langsam veränderliche Werte, ruhige Anzeige

3. Medianfilter: Ausreißer entfernen

Gegen Ausreißer ist der Mittelwert schwach. Misst der HC-SR04 viermal 50 cm und verpasst einmal das Echo, sodass er 0 liefert, ergibt der Mittelwert der fünf 40 cm; dieser Wert ist weder richtig noch zeigt er den Fehler. Ein Medianfilter sortiert dagegen die letzten N Messungen der Größe nach und nimmt den mittleren Wert. Im selben Beispiel lautet die sortierte Reihe 0, 50, 50, 50, 50, der mittlere Wert ist 50; die Fehlmessung erreicht das Ergebnis nie. Ein Medianfilter mit 5 Werten entfernt bis zu 2 aufeinanderfolgende Fehlmessungen vollständig. Die zweite schöne Eigenschaft: Eine plötzliche, echte Änderung wird nicht wie beim Mittelwert verschmiert. Kommt ein Hindernis wirklich näher, springt der Wert innerhalb weniger Messungen in einer sauberen Stufe auf das neue Niveau statt in einer weichen Kurve. Der folgende Code misst mit dem HC-SR04 alle 60 ms und gibt den Median der letzten 5 Messungen aus. Details findest du im Artikel zum HC-SR04-Abstandssensor.

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}
Du kannst Median und Mittelwert auch kombinieren: Entferne zuerst mit einem 5er-Median die Ausreißer und glätte das Ergebnis dann mit einem gleitenden Mittelwert oder EMA. Dieses Duo ist die gängigste Lösung für Messungen mit Ausreißern und Zappeln zugleich, etwa bei Abstands- und Durchflusssensoren. Mach das Medianfenster nicht groß; 3 oder 5 reichen für die meisten Aufgaben, 9 und mehr erhöhen nur unnötig die Verzögerung.

4. Exponentialfilter (EMA): glätten mit wenig Speicher

Der exponentielle gleitende Mittelwert (Exponential Moving Average, EMA) kommt ohne Puffer aus; er merkt sich nur das vorige Ergebnis. Mit jeder neuen Messung rückt das Ergebnis um einen kleinen Teil der Differenz an die Messung heran: y = y + α × (x − y). Dabei ist x die neue Messung, y der gefilterte Wert und α (Alpha) ein Faktor zwischen 0 und 1. Je kleiner α, desto ruhiger, aber auch träger der Filter. Exponentiell heißt er, weil der Einfluss jeder Messung mit der Zeit exponentiell abklingt. Er ist das Software-Gegenstück zum RC-Tiefpass der Elektronik. Die Zeitkonstante des EMA beträgt ungefähr τ ≈ T / α (T ist das Abtastintervall): Bei einer Messung alle 10 ms und α = 0,1 erreicht der Filter 63 Prozent eines Sprungs nach etwa 100 ms und 95 Prozent nach etwa 300 ms. Im Code steckt eine Gleitkomma-Version und eine Ganzzahl-Version, die auf kleinen Boards wie dem Uno schneller läuft; sie nutzt α = 1/16 und eine Verschiebung (>>) statt einer Division.

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}
α (Alpha)Ungefähr gleichwertiges Fenster63-%-Reaktionszeit (10 ms Abtastung)Charakter
0,5310–20 msSehr schnell, wenig Glättung
0,2950 msSchnell, leichte Glättung
0,119100 msAusgewogen, guter Start für die meisten Sensoren
0,0539200 msRuhig, langsam
0,011991 sSehr ruhig, nur für langsam veränderliche Werte

Das „gleichwertige Fenster“ in der Tabelle ist die ungefähre Größe eines gleitenden Mittelwerts mit gleicher Glättung und wird mit N ≈ 2/α − 1 berechnet. Der größte Vorteil des EMA ist der Speicher: Ein gleitender Mittelwert mit 100 Elementen braucht 200 Byte RAM, der EMA nur eine einzige Variable. Musst du im 2-KB-RAM des Arduino Uno mehrere Sensoren filtern, zählt dieser Unterschied. Sein Nachteil: Gegen Ausreißer ist er so schwach wie der Mittelwert; ein großer Spike zieht das Ergebnis über mehrere Messungen mit.

Filter im Seriellen Plotter vergleichen

Den Unterschied zwischen Filtern sieht man in Zahlen schlecht, im Diagramm aber auf einen Blick. Der folgende Code liest das Potentiometer mit 100 Hz, schickt dieselbe Messung durch vier verschiedene Wege und gibt alles im Format Label:Wert aus. Damit der Unterschied deutlicher wird, fügt der Code etwa 3 Prozent der Messungen einen künstlichen Ausreißer hinzu; in einem echten Projekt löschst du diese Zeile. Im Diagramm siehst du, dass der Median die Ausreißer vollständig entfernt, während gleitender Mittelwert und EMA sie in kleine Höcker verwandeln.

  1. Lade den Code hoch und schließe den Seriellen Monitor, falls er offen ist; Serieller Monitor und Serieller Plotter können nicht gleichzeitig geöffnet sein.
  2. Öffne in der Arduino IDE Werkzeuge > Serieller Plotter oder klicke oben rechts auf das Diagrammsymbol.
  3. Stelle unten rechts die Geschwindigkeit auf 115200 Baud.
  4. Drehe das Potentiometer erst langsam, dann schnell, und beobachte die Reaktion jeder Linie.
  5. Schalte die Linien mit den Kästchen neben den Labels einzeln ein und aus, um zwei Filter direkt zu vergleichen.
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}

Im Diagramm erscheinen die Labels farbig oben rechts. Ohne Komma zwischen den Labels und Doppelpunkt zwischen Label und Wert kann der Serielle Plotter die Linien nicht korrekt trennen. Der alte Plotter der Arduino IDE 1.8 unterstützt Labels nur eingeschränkt; gibt es Probleme, sende nur kommagetrennte Zahlen. Bei Problemen mit seriellem Port und Baudrate hilft der Artikel Arduino Serieller Monitor.

5. Hysterese: flackernde Entscheidungen an der Schwelle stoppen

Ein Filter beruhigt den Wert, löst aber allein noch nicht das Entscheidungsproblem. Ein Nachtlicht nach dem Motto „Lampe an, wenn der Lichtwert unter 350 fällt“ schaltet in der Dämmerung mehrmals pro Sekunde, während der Wert zwischen 349 und 351 pendelt. Das nutzt die Kontakte eines Relais schnell ab. Die Lösung heißt Hysterese: Für Ein- und Ausschalten gibt es zwei getrennte Schwellen. Die Lampe geht zum Beispiel an, wenn der Wert unter 300 fällt, aus aber erst, wenn er über 400 steigt. Der „Totbereich“ von 100 Stufen dazwischen verhindert, dass kleine Schwankungen die Entscheidung ändern. Der folgende Code kombiniert Hysterese mit einem EMA und einer kurzen Bestätigungszeit: Die Bedingung muss 1 Sekunde ununterbrochen erfüllt sein, damit auch ein vorbeiziehender Schatten oder kurz aufblitzende Scheinwerfer die Entscheidung nicht ändern.

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}

Beobachte vor dem Festlegen der Schwellen die Werte des Raums über den Tag im Seriellen Plotter. Der Totbereich sollte mindestens zwei- bis dreimal breiter sein als das Zappeln des gefilterten Werts. Dieselbe Methode steckt in Thermostaten (zum Beispiel Heizung bei 22 °C aus, bei 20 °C an), Pumpen für Wassertanks und Akku-Schutzschaltungen. Auch das Entprellen von Tastern ist eigentlich eine Bestätigungszeit; Details im Artikel Taster richtig anschließen.

Wie wählt man die Abtastrate?

Damit ein Filter gut arbeitet, muss der Sensor in der richtigen Rate gelesen werden. Liest du zu langsam, verpasst du schnelle Änderungen; liest du zu schnell, liefert der Sensor immer wieder denselben Wert oder ist noch gar nicht bereit. Die Grundregel ist das Nyquist-Kriterium: Taste mindestens doppelt so schnell ab wie die schnellste Änderung, die du verfolgen willst; in der Praxis sind 5- bis 10-fach sicherer. Manche Sensoren haben zudem ihre eigene Untergrenze, schnelleres Lesen bringt dort nichts. Achtung bei Lampenlicht: Im 50-Hz-Netz flackern Lampen 100-mal pro Sekunde. Am einfachsten beseitigst du dieses Flackern bei einem LDR oder anderen Analogsensor, indem du gleichmäßig verteilte Messungen über eine Dauer nimmst, die ein Vielfaches von 20 ms ist, und sie mittelst; da das Fenster ganze Netzperioden umfasst, hebt sich das Flackern auf.

SensorEmpfohlenes LeseintervallHinweis
DHT11 / DHT221 s / 2 sHäufiger gibt es denselben Wert oder einen Fehler
DS18B20 (12 Bit)750 msSo lange dauert die Wandlung
HC-SR04Mindestens 60 msDas vorige Echo muss abklingen
Potentiometer, Joystick10–20 ms50–100 Hz reichen für menschliche Bewegungen völlig
LDR (gegen Lampenflackern)20 ms lang jede 1 ms, dann mittelnDas 50-Hz-Flackern hebt sich auf
MPU60505–10 msÜbliches Intervall für Bewegungsmessung mit 100–200 Hz

Kurzer Einstieg in den Kalman-Filter

Der Kalman-Filter berücksichtigt, wie verrauscht die Messung ist und wie schnell sich der echte Wert ändern kann, und stellt seine Verstärkung selbst ein. In der einfachsten eindimensionalen Form gibt es zwei Einstellungen: R ist die Varianz des Messrauschens (wie stark der Sensor zappelt), Q das Prozessrauschen (wie schnell sich der echte Wert deiner Erwartung nach ändert). In jedem Schritt erhöht der Filter zuerst seine Unsicherheit um Q, berechnet dann die Verstärkung K = P / (P + R) und schiebt seine Schätzung um den Anteil K zur Messung hin. Ist die Messung verrauscht (großes R), wird K klein und der Filter vertraut der Messung wenig; wird eine schnelle Änderung erwartet (großes Q), wächst K und der Filter reagiert flott. Ein bemerkenswertes Ergebnis: Bleiben Q und R konstant, pendelt sich K nach einer Weile auf einen festen Wert ein, und der eindimensionale Kalman-Filter wird praktisch zum EMA. Seine eigentliche Stärke zeigt er, wenn er mehrere Größen gemeinsam schätzt, etwa Beschleunigungssensor und Gyroskop zu einem Neigungswinkel kombiniert. Seinen einfacheren Verwandten, den Komplementärfilter, findest du im Artikel zum 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 lässt sich leicht schätzen: Nimm 100 Rohwerte, ohne das Potentiometer zu berühren, und sieh dir an, wie stark sie streuen. Wandern sie etwa ±3 Stufen um den Mittelwert, liegt die Standardabweichung bei etwa 3 und die Varianz bei etwa 9. Q findest du durch Ausprobieren: Ein kleines Q (0,001) ergibt eine sehr ruhige, aber träge Ausgabe, ein großes Q (1) eine schnelle, aber unruhigere. Wähle Q für den Anfang zwischen 1 und 10 Prozent von R und stelle es im Seriellen Plotter fein ein, während du am Potentiometer drehst.

ADC-Rauschen und Filtern beim ESP32

Der 12-Bit-ADC des ESP32 löst feiner auf als der des Arduino Uno, ist aber verrauschter und vor allem an den Enden 0–0,1 V und 3,1–3,3 V nicht linear. Bei aktivem WLAN nimmt das Rauschen weiter zu. Zwei Dinge helfen: analogReadMilliVolts() verwenden, das die Werkskalibrierung des Chips nutzt, statt eines Rohwerts, und pro Messung 16–64 Abtastungen mitteln, bevor ein EMA folgt. Der folgende Code liest das Potentiometer in diesen zwei Schritten; in WLAN-Projekten gehört der Sensor immer an die ADC1-Pins 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}

Filtern mit Raspberry Pi Pico und MicroPython

Beim Raspberry Pi Pico liefert ADC.read_u16() einen Wert zwischen 0 und 65535, die echte Auflösung des ADC beträgt aber 12 Bit und die unteren Bits sind verrauscht. In MicroPython macht die Funktion sorted() den Median sehr einfach, und für den gleitenden Mittelwert genügen eine Liste und eine laufende Summe. Der folgende Code liest das Potentiometer mit 100 Hz und gibt die Ausgabe dreier Filter in einem Format aus, das Thonny im Fenster Ansicht > Plotter zeichnen kann.

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)

Der Plotter von Thonny zeigt die Zahlen jeder Zeile als getrennte Linien und nutzt den Text vor den Zahlen als Label. MicroPython läuft langsamer als Arduino, treibe die Filter also nicht zu sehr hohen Raten; etwa 100 Hz mit kleinen Fenstern laufen auf dem Pico bequem. Bist du neu bei MicroPython, beginne mit dem Artikel MicroPython-Grundlagen.

Welchen Filter solltest du wählen?

SituationEmpfohlener FilterWarum?
Potentiometer, Joystick oder Analogsensor zappeltGleitender Mittelwert oder EMA (α = 0,1–0,2)Einfach, schnell, ausreichend glatt
Ab und zu kommt ein absurder Wert (HC-SR04, Durchflusssensor)Median aus 3 oder 5Entfernt vereinzelte Fehler vollständig
Ausreißer und Zappeln zugleichErst Median, dann EMALöst beide Probleme nacheinander
Sehr wenig RAM, viele SensorenGanzzahl-EMAEine Variable pro Sensor
Eine Ein/Aus-Entscheidung flackert an der SchwelleHysterese und BestätigungszeitLöst das Entscheidungsproblem; ein Filter allein reicht nicht
Welligkeit durch Lampen oder NetzGleichmäßig verteilter Mittelwert über 20 msDeckt eine ganze 50-Hz-Periode ab
Mehrere Sensoren kombinieren (Neigung, Position)Kalman- oder KomplementärfilterVereint die Stärken der einzelnen Sensoren

Häufige Fehler und Lösungen

SymptomMögliche UrsacheLösung
Der gefilterte Wert reagiert viel zu spätFenster zu groß oder α zu kleinFenster verkleinern oder α erhöhen; Verzögerung aus den Tabellen ablesen
Der gleitende Mittelwert steigt beim Start langsam von nullDer Puffer startet mit NullenPuffer mit der ersten Messung füllen oder bis zum Füllen durch die Anzahl teilen
Der Mittelwert spinnt bei 64 MessungenDie int-Summe ist übergelaufen (64 × 1023 = 65.472 > 32.767)Die Summenvariable als long anlegen
Der Medianfilter ändert nichtsFenster 2 oder gerade Zahl, oder die Ausreißer kommen direkt hintereinanderUngerades Fenster (3, 5, 7) größer als die Zahl aufeinanderfolgender Ausreißer
Linien im Seriellen Plotter verschmelzen oder sind einfarbigFalsches Label-FormatLabel:Wert mit Kommas dazwischen und Zeilenende mit println
Der EMA-Wert ändert sich nieGanzzahldivision: 1/10 statt 0.1 geschriebenFaktor als float schreiben (0.1) oder die Ganzzahl-EMA-Formel nutzen
Werte schwanken trotz FilterDas Problem ist kein Rauschen, sondern Drift oder schwache VersorgungVersorgung prüfen, 100-nF-Kondensator ergänzen, kalibrieren
Sieh dir vor der Filterwahl immer die Rohdaten an. Ein paar Minuten Rohwerte im Seriellen Plotter zeigen sofort, ob das Problem Zappeln, Ausreißer, periodische Welligkeit oder Drift ist. Beginne dann mit dem einfachsten Filter und geh nur bei Bedarf zum nächsten über. In den meisten Projekten funktionieren ein 5er-Median und ein EMA mit α = 0,1 besser als ein komplizierter Kalman-Filter und sind viel leichter zu verstehen.

Sensorwerte zu filtern ist der günstigste Weg zu verlässlichen Messungen: Es braucht keine zusätzlichen Bauteile, und ein paar Codezeilen machen deine Projekte deutlich stabiler. Jetzt, da du Mittelwert, gleitenden Mittelwert, Median, EMA und Hysterese zu kombinieren weißt, gehören zappelnde Anzeigen, Fehlalarme und klappernde Relais der Vergangenheit an. In der BlueGrays-App findest du Projekte wie Lichtmessung mit dem LDR, Nachtlicht, Abstandsmessung mit dem HC-SR04, Einparkhilfe und Körpergrößenmesser mit Schritt-für-Schritt-Schaltplänen und fertigem Code; ergänze sie um diese Filter und sieh dir das Ergebnis selbst an.

Häufige Fragen

Warum ändern sich Sensorwerte am Arduino ständig?

Bei analogen Messungen verschieben Versorgungsschwankungen, lange Kabel, Motoren in der Nähe und das Eigenrauschen des ADC den Wert um einige Stufen. Das ist normal und heißt nicht, dass der Sensor defekt ist. Ein 100-nF-Kondensator am Analogeingang und ein Mittelwert- oder EMA-Filter in der Software beruhigen die Werte.

Was ist ein gleitender Mittelwert?

Ein gleitender Mittelwert ist der Durchschnitt der letzten N Messungen; bei jeder neuen Messung fällt der älteste Wert heraus und der neue kommt hinzu. Er verringert das Rauschen etwa um den Faktor √N, verzögert die Ausgabe aber um (N−1)/2 Messungen. Am Arduino genügen ein Ringpuffer und eine Summenvariable in wenigen Zeilen.

Was ist der Unterschied zwischen Medianfilter und Mittelwert?

Der Mittelwert berücksichtigt alle Werte, deshalb verdirbt schon eine absurde Messung das Ergebnis. Der Medianfilter sortiert die Werte und nimmt den mittleren, vereinzelte Fehlmessungen ignoriert er daher vollständig. Deshalb ist der Median bei Sensoren wie dem HC-SR04 mit gelegentlichen Fehlwerten besser, der Mittelwert bei zappelnden Analogwerten.

Wie wähle ich den Faktor für einen Exponentialfilter (EMA)?

Der Faktor α liegt zwischen 0 und 1; ein kleines α ergibt eine ruhigere, aber trägere Ausgabe, ein großes α eine schnellere, aber unruhigere. Bei einer Messung alle 10 ms ergibt α = 0,1 eine Zeitkonstante von etwa 100 ms und ist für die meisten Sensoren ein guter Start. Die Reaktionszeit berechnest du mit τ ≈ T / α.

Wie zeichne ich mehrere Werte im Seriellen Plotter der Arduino IDE?

Gib die Werte jeder Zeile im Format Label:Wert mit Kommas dazwischen aus und beende die Zeile mit println, zum Beispiel Ham:512,EMA:509. Der Serielle Plotter zeigt jedes Label als eigene farbige Linie. Schließe vorher einen offenen Seriellen Monitor und stelle dieselbe Baudrate wie im Code ein.

Kann man einen Kalman-Filter auf dem Arduino nutzen?

Ja, ein einfacher eindimensionaler Kalman-Filter läuft mit wenigen Zeilen sogar auf dem Arduino Uno. Eingestellt wird er mit dem Messrauschen R und dem Prozessrauschen Q; bei einem einzelnen Sensor liegen seine Ergebnisse meist sehr nah an einem gut eingestellten EMA. Sein eigentlicher Nutzen zeigt sich beim Kombinieren mehrerer Sensoren wie Beschleunigungssensor und Gyroskop.

Alle Projekte ansehen
Lichtstärke messen mit LDR
#11Anfänger

Lichtstärke messen mit LDR

Willst du sehen, wie hell dein Zimmer ist, und zwar als Zahl? Der LDR, ein kleines lichtempfindliches Bauteil, misst das Umgebungslicht, und der Arduino schreibt den Wert jede halbe Sekunde auf deinen

4.6(81)
140
HC-SR04 Abstandsmessung (serielle Anzeige)
#27Anfänger

HC-SR04 Abstandsmessung (serielle Anzeige)

Der Sensor HC-SR04 funktioniert wie eine Fledermaus, die im Dunkeln ihren Weg findet: Er sendet einen Ton, den du nicht hörst, lauscht auf das Echo und misst, wie weit der Gegenstand vor ihm entfernt

4.6(47)
74
Nachtlicht mit LDR
#12Anfänger

Nachtlicht mit LDR

Ein kleines Nachtlicht, das von selbst angeht, wenn es dunkel wird, und am Morgen wieder ausgeht. Ein Lichtsensor misst, wie hell es im Zimmer ist; ist es dunkel genug, schaltet der Arduino die LED ei

4.8(103)
210
Parksensor mit HC-SR04
#28Mittel
Premium

Parksensor mit HC-SR04

Eine Mini-Ausgabe der Einparkhilfe im Auto: Je näher etwas kommt, desto schneller piept es. Der Abstandssensor misst das Hindernis vor ihm; ist es weiter als 20 Zentimeter entfernt, leuchtet die grüne

4.8(64)
85
Projekt Körpergrößenmessung
#33Mittel
Premium

Projekt Körpergrößenmessung

Ein Größenmesser für den Türrahmen, der dir in Zentimetern sagt, wie groß die Person darunter ist. Zuerst drückst du den ersten Knopf, während niemand darunter steht, und der Abstandssensor misst die

4.5(25)
34
Servomotorbetrieb mit Potentiometer
#36Anfänger
Premium

Servomotorbetrieb mit Potentiometer

Während du am Knopf des Potentiometers drehst, dreht sich der Arm des Servomotors einfach mit: Das eine Ende des Knopfes bedeutet null Grad, das andere eine halbe Umdrehung. Der Arduino liest die Stel

4.6(37)
53
Messung der Lichtintensität mit dem ESP32-LDR-Modul
#113Anfänger

Messung der Lichtintensität mit dem ESP32-LDR-Modul

Echtzeit-Anzeige des ADC-Rohwerts des LDR-Moduls und des anhand von Dunkel- und Hellreferenzwerten berechneten relativen Lichtwerts in Prozent im Serial Monitor. Dieser Prozentwert ist keine Lux-Messung. Vermittelt die Kalibrierungslogik des Moduls. Sollte idealerweise vor Projekt 10 durchgeführt werden, da es zur Festlegung des Schwellenwerts dient.

4.6(36)
55
ESP32 HC-SR04 Ultraschall-Entfernungsmesser
#116Mittel

ESP32 HC-SR04 Ultraschall-Entfernungsmesser

Der Ultraschallsensor HC-SR04 berechnet die Entfernung, indem er die Zeit misst, die die von ihm ausgesendete Schallwelle benötigt, um auf ein Objekt zu treffen und zurückzukehren (Time of Flight). In diesem Projekt senden wir mit dem ESP32 einen 10-Mikrosekunden-Impuls an den TRIG-Pin und lesen die vom ECHO-Pin kommende Echozeit mit pulseIn(). Die berechnete Entfernung geben wir in cm auf einem 16x2-I2C-LCD aus. Da der HC-SR04 mit 5 V betrieben wird, ist ein Spannungsteiler am ECHO-Pin zw

4.9(28)
46