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)
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.
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.
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.
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.
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.