Kalibrierung der analogen Eingänge

Medium · 30 min · Kostenlos

Ziel & Lerninhalt

Lerne, wie du Offset, Spannweite und kleine Nichtlinearitäten deiner DIY-Sensoren ausgleichst – ohne neue Hardware, nur mit Code.

  • Offset-Fehler des Sensors ermitteln und subtrahieren.
  • Min/Max-Werte für map() korrekt erfassen.
  • Mehrpunkt-Kalibrierung mit Look-up-Tabelle bei nichtlinearen Sensoren einsetzen.

Erklärung & Wissen

Basic

Selbstgebaute Sensoren – Graphit-Widerstand, Schaum-Wiegezelle, Bleistift-Tastenfeld – liefern nie genau die 'idealen' Werte aus dem Lehrbuch. Wenn du Wasser anfasst, liest dein Salzwasser-Sensor vielleicht ADC 412 statt 1023; wenn dein selbstgebautes Poti ganz links steht, kommt nicht 0 raus, sondern 47. Diese Abweichungen sind normal und nennen sich Offset und Spannweite. Statt Hardware zu tauschen, korrigierst du sie im Code: 'Was sehe ich, wenn der Sensor im Ruhezustand ist?' (= Offset). 'Welchen Wert sehe ich am Maximum?' (= Skala). Mit diesen zwei Zahlen rechnest du jeden Messwert in eine saubere Skala um.

Medium

Mathematisch ist das eine lineare Korrektur: $y = a \cdot (x - x_0)$ – $x_0$ ist der Ruhewert (Offset), $a$ der Skalenfaktor. In Arduino-Code reicht meist map(): `wert = map(adc, adcMin, adcMax, 0, 100)`. Damit das funktioniert, ermittelst du adcMin und adcMax einmal experimentell – am besten gleich am Anfang von setup() automatisch: 1 s warten, ADC-Wert als Offset speichern. Bei Sensoren mit Drift (Temperatur, Alterung) kannst du den Offset auch regelmäßig nachfahren – z.B. den niedrigsten ADC-Wert der letzten 60 s als neun Offset nehmen (Auto-Tara). Eine constrain()-Klammer um das map() verhindert, dass Werte außerhalb des Bereichs als negative oder >100% auftauchen.

Expert

Bei nichtlinearen Sensoren (Schaum-Wiegezelle, Salzwasser-Leitfähigkeit, LED als Lichtsensor) reicht die lineare Korrektur nicht. Dann arbeitest du mit einer Look-up-Tabelle (LUT) und linearer Interpolation zwischen den Stützpunkten: gemessene Werte für z.B. 0 g/100 g/500 g/1000 g, dazwischen wird interpoliert. Schon mit 4–5 Stützpunkten erreichst du Genauigkeiten von <5 %. Ein zweiter Profi-Trick ist Oversampling: Wenn du 16 Messungen mittelst, bekommst du effektiv 1 zusätzliches Bit Auflösung (aus 10-Bit-ADC werden 11-Bit-Werte), bei 256 Messungen +4 Bits. Voraussetzung: Das Rauschen muss zufällig sein und mindestens 1 LSB groß – bei DIY-Sensoren fast immer gegeben.

Fun Fact

Profis nennen das Kalibrieren in der Industrie 'Trimmen' – früher waren das winzige Stellschrauben an Trimmpotis, heute ist es immer Code, und der Trimmer-Industriezweig ist über die Jahre fast verschwunden.

Offset-Fehler
Sensor zeigt im Ruhezustand nicht 0, sondern einen festen Versatz – der muss herausgerechnet werden. Entsteht durch mechanische Toleranzen, Bauteil-Streuung und Temperaturabhängigkeit. Korrektur: im Ruhezustand messen, Wert als $x_0$ speichern, dann $y = x - x_0$. Bei Drucksensoren heißt das 'Tara', beim Multimeter 'Nullabgleich'. Im ProtoPeaks-Code: beim Start automatisch über 64 Samples mitteln.
map() – Skalierung
Wandelt einen Wertebereich in einen anderen um – z.B. ADC-Werte 0–1023 in Prozent 0–100. `map(adc, adcMin, adcMax, 0, 100)` rechnet linear um: $y = 100 \cdot (x - x_{min}) / (x_{max} - x_{min})$. Wichtig: Immer mit `constrain(adc, adcMin, adcMax)` kombinieren, sonst entstehen Werte unter 0 oder über 100 wenn der Sensor leicht außerhalb der Grenzen misst.
Oversampling
Mehrere Messungen mitteln: beseitigt Rauschen und erhöht die effektive Auflösung. $4^N$ Samples geben $N$ Bit mehr Auflösung — Voraussetzung: Das Rauschen ist zufällig und mindestens 1 LSB groß. Aus 10-Bit-ADC (1024 Schritte) werden mit 16 Samples 11 Bit (2048 Schritte), mit 256 Samples 12 Bit. Im ProtoPeaks-Code: `for (int i = 0; i < 16; i++) sum += analogRead(A0); int adc = sum / 16;`
Look-up-Tabelle (LUT)
Bei nichtlinearen Sensoren: Messwerte an bekannten Punkten + Interpolation dazwischen. Wenn die Kennlinie nicht linear ist (z.B. LED-Lichtsensor, Salzwasser-Leitfähigkeit), reicht map() nicht. Lösung: An 4–5 bekannten Punkten messen (ADC-Wert + wahrer Wert), in Arrays speichern, zwischen den Punkten linear interpolieren. Schon 5 Stützpunkte ergeben Genauigkeiten von unter 5%.

Praktischer Versuch: Auto-Kalibrierung im setup()

Sensor kalibriert sich beim Start selbst – kein lästiges Trimmen mehr.

  • Beliebiger eigener Analog-Sensor (Graphit, Spritzen-Slider, etc.)
  • Referenzwerte zum Test (z.B. bekannte Endpositionen, Gewichte, Temperaturen)
  1. Sensor in 'Ruheposition' bringen

    Bevor du das Mainboard einschaltest, bring den Sensor in den Zustand 'Null'.

    Check: Sensor zeigt seinen niedrigsten erwarteten Wert.

    Tipp: Bei einem Druck-Sensor: nichts auflegen. Bei einem Slider: ganz links. Bei einem Temperatursensor: Raumtemperatur.

  2. Auto-Offset im setup()

    Im setup() 1 s warten, dann 64 Samples nehmen und den Mittelwert als Offset speichern.

    Check: Im seriellen Monitor erscheint 'Offset: X'.

    Tipp: Mittel über mehrere Samples – ein einzelner Wert reicht für genauen Offset nicht.

  3. Max-Wert kalibrieren

    Bring den Sensor an seinen Vollausschlag, notiere ADC, trage als adcMax in den Code ein.

    Check: Beim Drücken/Bewegen auf 'Vollausschlag' siehst du den korrekten Maximalwert.

    Tipp: Bei Sensoren ohne festen Endwert kannst du den Max auch als Konstante schätzen.

  4. map() + constrain() einsetzen

    Im loop() ADC mit constrain(adc, adcMin, adcMax) klemmen, dann map() in 0–100 % umrechnen.

    Check: Werte erscheinen im Monitor als 0–100 %, ohne Überschwinger.

    Tipp: Ohne constrain() können auch negative Prozente oder >100 auftauchen.

  5. Drift-Kompensation (optional)

    Erweitere den Code: Wenn ADC < adcMin → adcMin = ADC. Damit folgt der Nullpunkt langsam dem Drift.

    Check: Auch nach 10 Minuten Betrieb bleibt der Ruhewert bei 0 %.

    Tipp: Auto-Tara funktioniert nur, wenn der Sensor mehr Zeit in Ruhe verbringt als unter Last.

Beliebiger Sensor an A0, der Rest hängt vom Sensor ab.

// ProtoPeaks - Auto-Kalibrierung
const int sensorPin = A0;
int adcMin = 0, adcMax = 1023;

void setup() {
  Serial.begin(9600);
  delay(500); // Sensor stabilisieren lassen

  // Offset (Ruhewert) automatisch ermitteln
  long sum = 0;
  for (int i = 0; i < 64; i++) {
    sum += analogRead(sensorPin);
    delay(5);
  }
  adcMin = sum / 64;
  Serial.print("Offset (adcMin): "); Serial.println(adcMin);

  // adcMax bitte selbst messen und hier eintragen
  adcMax = 980; // Beispielwert
}

void loop() {
  long sum = 0;
  for (int i = 0; i < 16; i++) sum += analogRead(sensorPin);
  int adc = sum / 16;

  // Drift-Kompensation (langsam dem Minimum folgen)
  if (adc < adcMin) adcMin = adc;

  int prozent = map(constrain(adc, adcMin, adcMax), adcMin, adcMax, 0, 100);
  Serial.print(adc); Serial.print("  ->  "); Serial.print(prozent); Serial.println(" %");
  delay(100);
}
  • Offset ist viel zu klein (z.B. 5 statt 200). — Sensor war beim Start nicht in Ruheposition. Mainboard reseten und beim Start NICHT den Sensor berühren.
  • Wert bleibt auch bei Vollausschlag bei 80 %. — adcMax falsch geschätzt. Sensor manuell ans Maximum bringen, ADC notieren, im Code anpassen.
  • Ergebnisse springen über 100 % oder unter 0 %. — constrain() vor map() vergessen. Klammern hinzufügen.

Kurzzusammenfassung

Mit Auto-Offset im setup() und einem korrekt gewählten Max-Wert bekommt jeder DIY-Sensor saubere 0–100-%-Ausgaben. Für nichtlineare Sensoren erweiterst du das Konzept zu einer Look-up-Tabelle mit Interpolation, und Oversampling holt aus 10-Bit-ADCs noch erstaunliche Auflösung heraus.