Tutorial · Einsteiger · 20 Min. Lesezeit
IR-Reflexionsarray lesen für Linienfolger
Wie ein Linienfolger aus einer Reihe IR-Reflexionssensoren einen Lenkfehler macht: analog vs. digital, Kalibrierung und die Formel per gewichtetem Mittel.
Ein Linienfolgerroboter ist nur so gut wie die Zahl, nach der er lenkt. Diese Zahl kommt von einer Reihe infraroter Reflexionssensoren Richtung Boden: Die Linie hat eine Farbe, der Untergrund eine andere, und die Sensoren melden, wie viel IR-Licht zurückprallt. Diese Anleitung macht aus der rohen Lesungsreihe eine einzige, glatte Linienposition — den Fehler, auf den dein Regler wirkt.
Wie ein Reflexionssensor die Linie sieht
Jeder Sensor paart eine IR-LED mit einem Fototransistor. Die LED strahlt nach unten; der Fototransistor misst die Reflexion. Eine matte schwarze Linie reflektiert wenig IR (niedrige Lesung); der hellere Untergrund reflektiert mehr (hohe Lesung). Fährst du das Array über eine schwarze Linie auf Weiß, schwingt jeder Sensor zwischen einem „auf der Linie“-Wert und einem „neben der Linie“-Wert.
Zwei Dinge ändern diese Rohwerte von Lauf zu Lauf: Sensorhöhe über der Oberfläche und Umgebungslicht. Darum programmierst du nie feste Schwellen — du kalibrierst. Dieselben Sensoren, nach unten auf eine Ringgrenze statt Linie gerichtet, werden zum Kantendetektor, wo eine verpasste Lesung den Roboter das Match kostet.
Analoge vs. digitale Sensoren
- Analoge Sensoren geben einen kontinuierlichen Wert (z. B. 0–1023 am Arduino-ADC). Du erkennst, wie weit die Linie von jedem Sensor weg ist — was eine glatte Position und proportionale Lenkung erlaubt.
- Digitale Sensoren melden nur an/aus nach interner Schwelle. Einfacher zu verdrahten, aber die Zwischeninformation geht verloren, also ist die Lenkung gröber.
Für proportionales Linienfolgen — die Art, die du mit einem PID-Regler abstimmst — nutze analoge Lesungen. Diesen Ansatz modelliert der Linienfolger-Simulator.
Erst kalibrieren
Fahre vor einem Lauf jeden Sensor über Linie und Untergrund und zeichne je Sensors Minimum und Maximum auf. Normiere dann jede Live-Lesung gegen ihren eigenen Bereich auf 0–1000:
// Per-sensor calibration captured during a sweep.
int minv[N], maxv[N];
int normalized(int raw, int i) {
long span = maxv[i] - minv[i];
if (span <= 0) return 0; // sensor never saw contrast
long v = (long)(raw - minv[i]) * 1000 / span; // 0 (background) … 1000 (line)
return constrain(v, 0, 1000);
}
Kalibrieren pro Sensor löscht ungleiche LED-Helligkeit und leichte Höhenunterschiede über die Leiste aus, sodass kein Sensor still die ganze Schätzung verzerrt.
Von Lesungen zu einer Linienposition
Verdichte nun das Array per reflexionsgewichtetem Mittel zu einer einzigen Position. Gib jedem Sensor eine feste Position (…−2, −1, 0, +1, +2… über die Leiste) und gewichte sie mit ihrer normierten Lesung. Das Ergebnis ist die Lage der Linie relativ zur Leistenmitte — positiv auf einer Seite, negativ auf der anderen, null, wenn die Linie exakt mittig sitzt.
// Sensor positions across the bar, e.g. { -2000,-1000,0,1000,2000 }.
long readLinePosition() {
long weighted = 0, total = 0;
for (int i = 0; i < N; i++) {
int v = normalized(analogRead(pins[i]), i);
weighted += (long)v * position[i];
total += v;
}
if (total == 0) return lastPosition; // line lost — reuse last direction
lastPosition = weighted / total;
return lastPosition; // 0 = centered, sign = which side
}
Zwei Details machen das robust:
- Teile durch die Lesungssumme, nicht durch die Sensorzahl, sodass eine blasse und eine fette Linie auf dieselbe Positionsskala abbilden.
- Wenn jeder Sensor die Linie verliert (
total == 0), nutze die letzte bekannte Position weiter statt null zurückzugeben. Damit dreht der Roboter weiter dorthin, wo die Linie war, statt in einer scharfen Kurve geradeaus von der Strecke zu fahren.
Wie viele Sensoren?
Fünf sind ein guter Start — genug, um Richtung und Betrag zu sehen, bei gut inspizierbarer Mathe. Drei gehen bei niedrigem Tempo; Wettbewerbsroboter nutzen acht oder mehr für feinere Auflösung bei Tempo. Mehr Sensoren kaufen dir vor allem eine glattere Positionsschätzung in engen Kurven.
Die Kalibrierroutine, vollständig
„Erst kalibrieren“ sagt sich leicht und macht sich leicht schlecht. Hier die ganze Routine, inklusive der Wache, die einen toten Kanal davon abhält, die Schätzung zu ruinieren:
const int N = 8;
const int PIN[N] = {A0, A1, A2, A3, A4, A5, A6, A7};
int minv[N], maxv[N];
void calibrate(unsigned long durationMs) {
for (int i = 0; i < N; i++) { minv[i] = 1023; maxv[i] = 0; }
const unsigned long start = millis();
while (millis() - start < durationMs) {
for (int i = 0; i < N; i++) {
const int v = analogRead(PIN[i]);
if (v < minv[i]) minv[i] = v;
if (v > maxv[i]) maxv[i] = v;
}
}
// A channel that never saw contrast would turn noise into huge normalised
// values and drag the whole estimate toward itself.
for (int i = 0; i < N; i++) {
if (maxv[i] - minv[i] < 50) {
Serial.print(F("channel dead or never crossed the line: "));
Serial.println(i);
}
}
}
Schwenke das Array während des ganzen Kalibrierfensters über die Linie. Ein stillstehender Roboter zeichnet dieselbe Lesung als Minimum und Maximum auf, erzeugt null Spanne und einen Sensor, der nichts beiträgt. Den Roboter drei Sekunden auf der Stelle über der Linie zu drehen ist die Standardbewegung, und sie funktioniert, weil jeder Kanal das Klebeband kreuzt.
Kalibriere neu zu Beginn jedes Laufs, nicht einmal beim Bau. Umgebungslicht ändert sich, die Fahrhöhe ändert sich, wenn der Akkupack verrutscht, und das Band staubt ein. Das ist der häufigste Grund, warum ein Linienfolger auf einem Tisch läuft und auf einem anderen versagt.
Umgebungslicht killen
Sonnenlicht enthält viel 940-nm-Infrarot, und der Fototransistor kann es nicht von der Reflexion des eigenen Senders unterscheiden. Ein Roboter, der drinnen läuft und am Fenster versagt, sieht genau das.
Wenn sich die Sender deines Arrays schalten lassen — die meisten haben einen LEDON- oder CTRL-Pin — ist der Fix vollständig und kostet eine zusätzliche Lesung pro Kanal:
int ambientSubtracted(int i) {
digitalWrite(LEDON, LOW);
delayMicroseconds(200); // let the phototransistor settle
const int dark = analogRead(PIN[i]); // ambient only
digitalWrite(LEDON, HIGH);
delayMicroseconds(200);
const int lit = analogRead(PIN[i]); // ambient + our emitter
return lit - dark; // just our emitter
}
Alles Konstante in der Umgebung löscht sich exakt aus. Der Preis ist doppelte Abtastzeit, was an einem schnellen Linienfolger echt ist — aber es verwandelt einen unzuverlässigen Sensor in einen zuverlässigen, und es ist die Technik, die kommerzielle Arrays nutzen.
Es hat einen zweiten nützlichen Nebeneffekt: Bei ausgeschalteten Sendern zwischen Lesungen sinkt ihr Mittelstrom stark. Die Sender eines 8-Kanal-Arrays können dauerhaft 100–200 mA ziehen, was an einem kleinen Pack ein spürbarer Budgetanteil ist.
Die Linie verlieren und wiederfinden
Der obige total == 0-Fall verdient mehr Aufmerksamkeit als üblich, denn dort versagt ein Linienfolger meist sichtbar.
Null zurückzugeben ist die naheliegende Implementierung und die falsche: Null heißt „perfekt mittig“, also fährt der Roboter mit Volldampf geradeaus — weg von der gerade verlorenen Linie, die fast sicher eine Kurve machte.
Die letzte Position zurückzugeben lässt den Roboter weiterdrehen, wie er schon drehte — dorthin, wohin die Linie ging. Diese eine Änderung ist der Unterschied zwischen einem Roboter, der sich aus einer scharfen Kurve rettet, und einem, der die Strecke verlässt.
Eine vollständigere Variante fügt ein Timeout hinzu, sodass ein wirklich verlorener Roboter anhält statt zu kreiseln:
long lastPosition = 0;
unsigned long lostSince = 0;
long readLinePosition() {
long weighted = 0, total = 0;
for (int i = 0; i < N; i++) {
const int v = normalized(analogRead(PIN[i]), i);
if (v > 100) { // ignore channels clearly on the floor
weighted += (long)v * POSITION[i];
total += v;
}
}
if (total == 0) {
if (lostSince == 0) lostSince = millis();
if (millis() - lostSince > 1500) stopMotors(); // genuinely lost
return lastPosition; // keep turning the way we were
}
lostSince = 0;
lastPosition = weighted / total;
return lastPosition;
}
Die v > 100-Schwelle zählt auch. Ohne sie ziehen acht Kanäle mit je kleinem Bodenrestwert ungleich null das gewichtete Mittel zur Arraymitte, egal wo die Linie wirklich ist — was sich als Roboter zeigt, der über fettes Band gut folgt und über verblasstes schlecht.
Die Fahrhöhe entscheidet den Kontrast
Reflexionssensoren haben einen engen Sweet Spot, und es ist die mechanische Variable, die sich am häufigsten als Softwareproblem tarnt.
| Fahrhöhe | Was du in der Kalibrierspanne siehst |
|---|---|
| 1–3 mm | Größte Spanne, aber das Array schabt an jeder Bodenwelle |
| 3–8 mm | Das nutzbare Band. Die meisten Arrays spezifizieren um 3 mm |
| 8–15 mm | Spanne schrumpft merklich; Normierung verstärkt Rauschen |
| > 15 mm | Auf den meisten Flächen effektiv blind |
Daraus folgen zwei Konsequenzen. Montiere es starr — ein Array an flexibler Halterung ändert die Höhe, wenn der Roboter beschleunigt, was die Lesungen ändert, was der Regelkreis als wandernde Linie interpretiert. Und montiere es waagerecht, denn ein gekipptes Array gibt jedem Kanal eine andere effektive Höhe und damit andere Kalibrierung — was sich als Roboter zeigt, der in eine Richtung gut folgt und in die andere schlecht.
Wie viele Sensoren, quantifiziert
| Kanäle | Positionsauflösung über 19 mm Linie | Gut für |
|---|---|---|
| 3 | Im Grunde links / Mitte / rechts | Die Idee verstehen |
| 5 | ~4 diskrete Regionen, dazwischen glatt | Einen ersten Linienfolger auf sanfter Strecke |
| 8 | ~7 Regionen | Schnelle Strecken, enge Kurven, Kreuzungserkennung |
| 12–16 | Fein | Wettbewerbsroboter bei Tempo |
Die Zahl, die mehr zählt als die Anzahl, ist die Arraybreite relativ zur Linie. Für schlichtes Linienfolgen muss das Array breit genug sein, dass mindestens ein Sensor an jeder Linienkante auf dem Boden ist — also grob doppelte Klebebandbreite.
Für Linienlabyrinth-Kreuzungserkennung ist die Anforderung strenger: Der Roboter erkennt einen Abzweig daran, dass die äußersten Sensoren dunkel sehen, während die Mitte noch auf der Linie ist — also muss das Array mindestens dreifache Bandbreite haben. Ein 8-Kanal-Array mit 8 mm Raster ist etwa 60 mm, was für 19-mm-Band passt.
Fehlersuche
| Symptom | Wahrscheinliche Ursache | Fix |
|---|---|---|
| Läuft auf einer Fläche, versagt auf anderer | Alte oder fehlende Kalibrierung | Zu Beginn jedes Laufs neu kalibrieren |
| Ein Kanal zieht die Schätzung weg | Toter Sender — seine Spanne ist winzig | Jedes Kanals maxv − minv prüfen; darauf wachen |
| Roboter fährt geradeaus weiter, wenn er die Linie verliert | 0 bei Linienverlust zurückgeben | Stattdessen die letzte Position zurückgeben |
| Folgt über fettem Band, irrt über blassem | Bodenrauschen im Mittel | Die v > 100-Schwelle hinzufügen |
| Versagt am Fenster | Umgebungs-Infrarot | Sender-aus-Subtraktion oder Array abschirmen |
| Position springt, wenn die Linie Sensoren kreuzt | Nicht pro Kanal normiert | Jeden Kanal gegen sein eigenes Min und Max kalibrieren |
| Messwerte driften über langen Lauf | Batterie sackt ab, also dimmen die Sender | Schiene unter Last prüfen |
| Alle Kanäle lesen gleich | Array zu hoch | 3–8 mm, dann neu kalibrieren |
| Folgt in eine Richtung gut, in die andere schlecht | Array nicht waagerecht | Jeder Kanal hat andere effektive Höhe |
Wohin das als Nächstes geht
Dieser eine Positionswert ist der Fehler-Term. Füttere ihn in eine Regelschleife, die ein Rad beschleunigt und das andere bremst, und du hast einen Linienfolger. Lerne diese Schleife in PID-Regler abstimmen und probiere das Ganze Ende zu Ende im Linienfolger-Simulator.
Den Graphen erkunden
Teil dieser Bauprojekte
Projekte und Lernpfade, die dieses Tutorial enthalten.
Weiterführende Literatur