Tutorial · Einsteiger · 20 Min. Lesezeit
Linienfolger-Roboter bauen: Sensoren bis PID-Lenkung
Wie ein Linienfolger-Roboter funktioniert: Reflexionssensoren, ein gewichteter Positionsfehler und eine PID-Schleife, die ein Differenzialfahrwerk sanft lenkt.
Ein Linienfolger ist der klassische erste autonome Roboter: Er liest eine Linie unter seinen Sensoren und lenkt, um darauf zu bleiben. Darunter steckt ein kleines Regelungsproblem – Sensormesswerte in eine Lenkkorrektur verwandeln –, was ihn zum perfekten Weg macht, Rückkopplungsregelung zu lernen. Folge im Linienfolger-Simulator.
Die Linie wahrnehmen
Ein Reflexionssensor strahlt Infrarotlicht nach unten und misst, wie viel zurückkommt. Eine dunkle Linie reflektiert weniger als die helle Fläche darum, jeder Sensor meldet also, ob er über der Linie steht. Eine Reihe mehrerer Sensoren quer vor dem Roboter sagt dir nicht nur ob du auf der Linie bist, sondern wo die Linie relativ zur Mitte sitzt.
Fünf Sensoren sind ein guter Start. Drei funktionieren bei niedriger Drehzahl; Wettbewerbsroboter nutzen oft acht für feinere Auflösung.
Aus Messwerten eine Fehlerzahl machen
Statt auf einzelne Sensoren zu reagieren, kombiniere sie zu einer einzigen gewichteten Position. Gib jedem Sensor einen Positionswert (negativ links, null in der Mitte, positiv rechts) und bilde den Mittelwert, gewichtet damit, wie stark jeder die Linie sieht:
// positions: {-2, -1, 0, 1, 2}; readings: reflectance per sensor
float weightedPosition = sum(position[i] * reading[i]) / sum(reading[i]);
Das Ergebnis ist dein Fehler: Null heißt mittig, negativ heißt Linie links, positiv rechts. Ein glatter, kontinuierlicher Fehler ist genau das, was ein Regler braucht.
Mit PID lenken
Speise diesen Fehler in einen PID-Regler. Die Korrektur beschleunigt ein Rad und bremst das andere, dreht den Roboter also zurück zur Linie:
float correction = kp * error + ki * integral + kd * derivative;
setMotorSpeeds(baseSpeed - correction, baseSpeed + correction);
Die Proportionalverstärkung treibt den Großteil der Lenkung; die Differenzialverstärkung dämpft das Pendeln in Kurven; ein kleiner Integralanteil beseitigt jede stationäre Drift. Bist du neu beim Wählen von Verstärkungen, arbeite erst das Abstimmen eines PID-Reglers durch.
Die gängigen Ausfälle beheben
- Pendeln um die Linie: Proportionalverstärkung zu hoch oder Differenzialverstärkung zu niedrig. Erst Drehzahl senken, dann neu abstimmen. Auf echter Hardware fügt ein langes
delay()in der Schleife Totzeit hinzu, die den D-Anteil besiegt. - Linienverlust in scharfen Kurven: Der Roboter überholt seine Sensoraktualisierungsrate. Verlangsamen, Reihe verbreitern oder die letzte bekannte Linienrichtung wiederverwenden, damit der Roboter zurückdreht statt geradeaus davonzufahren.
- Drift zu einer Seite: Kalibriere die Sensoren. Fahre sie über Linie und Hintergrund, nimm je Min und Max auf und normiere Live-Messwerte gegen diesen Bereich.
Die ganze Schleife an einem Ort
Die Fragmente oben werden erst zum arbeitenden Roboter, wenn Timing, Kalibrierung und Linienverlustfall gemeinsam behandelt werden:
const int N = 5;
const int PIN[N] = {A0, A1, A2, A3, A4};
const int POS[N] = {-2000, -1000, 0, 1000, 2000}; // weights, in the error's units
int minv[N], maxv[N];
long lastError = 0;
float integral = 0;
unsigned long lastMicros = 0;
const float KP = 0.10f, KI = 0.0f, KD = 1.6f;
const int BASE = 140; // out of 255
void loop() {
// 1. Real elapsed time — never assume the loop period.
const unsigned long now = micros();
const float dt = (now - lastMicros) * 1e-6f;
if (dt < 0.002f) return; // cap the rate at 500 Hz
lastMicros = now;
// 2. Position from the weighted average.
long weighted = 0, total = 0;
for (int i = 0; i < N; i++) {
const int v = normalized(analogRead(PIN[i]), i);
if (v > 100) { weighted += (long)v * POS[i]; total += v; }
}
// 3. Line lost: hold the last error, do NOT zero it.
const long error = (total == 0) ? lastError : (weighted / total);
// 4. PID, with derivative on measurement and conditional integration.
const float derivative = -(float)(error - lastError) / dt;
const float candidate = integral + error * dt;
const float raw = KP * error + KI * candidate + KD * derivative;
const float correction = constrain(raw, -BASE, BASE);
if (raw == correction) integral = candidate;
lastError = error;
// 5. Differential steering.
setMotors(BASE - correction, BASE + correction);
}
Fünf Dinge darin trennen einen Roboter, der einer Linie folgt, von einem, der es fast tut.
Echtes dt. Sowohl I als auch D teilen durch verstrichene Zeit. Eine Schleife, deren Dauer sich ändert, sobald du Funktionen hinzufügst – und das wird sie –, rechnet beides gegen eine Lüge, wenn du eine konstante Periode annimmst.
Die v > 100-Schwelle. Ohne sie ziehen fünf Kanäle mit je kleinem Bodenwert den gewichteten Mittelwert zur Mitte, egal wo die Linie ist. Symptom ist ein Roboter, der eine fette Linie gut und eine blasse schlecht verfolgt.
Letzten Fehler bei Linienverlust halten. Null heißt „mittig“, ein Roboter mit Nullung fährt also mit Vollgas geradeaus – weg von der Linie, die mit ziemlicher Sicherheit kurvte. Den letzten Fehler zu halten lässt ihn weiterdrehen, wie er es schon tat.
Differenzial auf Messwert. Den Fehler zu differenzieren heißt, eine Sollwertänderung erzeugt eine riesige Spitze. Hier ist der Sollwert immer null, macht also wenig Unterschied, aber die Gewohnheit lohnt sich.
Bedingte Integration. Sobald die Korrektur sättigt, speichert weiteres Aufsummieren des Integrals nur ein Überschwingen, das später zurückzuzahlen ist.
Eine Abstimmreihenfolge, die funktioniert
Stimme nicht alle drei zugleich ab. Setze KI = 0 und KD = 0, dann:
| Schritt | Änderung | Beobachte | Stopp, wenn |
|---|---|---|---|
| 1 | KP von kleinem Wert erhöhen |
Der Roboter beginnt, auf die Linie zu reagieren | Er folgt einer sanften Kurve, pendelt leicht |
| 2 | KP weiter erhöhen |
Pendeln wächst zu Schwingung | Er schwingt stetig – notiere diesen Wert als Ku |
| 3 | KP auf etwa halbes Ku setzen |
Reaktion ist schnell, Überschwingen vorhanden | — |
| 4 | KD erhöhen |
Das Pendeln dämpft heraus | Geraden sind sauber; weiteres KD macht ihn zappelig |
| 5 | Nur wenn er beharrlich außermittig folgt, wenig KI ergänzen |
Die Abweichung schließt sich | Sie schließt sich – KI braucht ein Linienfolger selten |
| 6 | BASE-Drehzahl erhöhen |
Alles wird gemeinsam schlechter | KP und KD bei neuer Drehzahl neu abstimmen |
Schritt 6 ist der vergessene: Verstärkungen sind drehzahlabhängig. Eine bei 140 Tastverhältnis perfekte Abstimmung schwingt bei 220, weil dieselbe Lenkkorrektur nun eine größere Kursänderung pro Zeiteinheit erzeugt. Erwarte Neuabstimmung, wann immer du die Drehzahl spürbar änderst.
Integral ist hier wirklich selten nützlich. Sollwert eines Linienfolgers ist null und Störungen sind transient, es gibt also meist keine bleibende Regelabweichung für KI zu schließen. Siehst du eine beharrliche Abweichung, verdächtige ungleichen Motortrimm oder einen unkalibrierten Sensor, bevor du nach Integralverstärkung greifst.
Die Schleifenrate begrenzt leise alles
| Schleifenrate | Intervall | Weg bei 0,4 m/s |
|---|---|---|
| 20 Hz | 50 ms | 20 mm zwischen Entscheidungen |
| 50 Hz | 20 ms | 8 mm |
| 200 Hz | 5 ms | 2 mm |
| 500 Hz | 2 ms | 0,8 mm |
Ein Roboter, der alle 20 mm entscheidet, kann eine Kurve nicht eng verfolgen, egal wie die Verstärkungen stehen, und kein D-Anteil holt Information zurück, die nie abgetastet wurde.
Die üblichen Verdächtigen sind leicht zu entfernen: ein delay() irgendwo in der Schleife, Serial.print von mehr als wenigen Werten mit 9600 Baud, und Sensoren lesen, die du nicht nutzt. Sie zu entfernen hebt einen ersten Aufbau typisch von 50 Hz auf mehrere Hundert, und die Verfolgung bessert sich, ohne eine Verstärkung anzufassen. Miss die Rate vor dem Abstimmen, nicht danach.
Fehlersuche
| Symptom | Wahrscheinliche Ursache | Lösung |
|---|---|---|
| Pendelt auf Geraden | KP zu hoch oder KD zu niedrig |
KP halbieren, dann KD erhöhen |
| Schwingt immer schneller | KP über der ultimativen Verstärkung |
Senken, bis er sich setzt |
| Schneidet Kurven | Zu schnell für den Sensorvorhalt | Verlangsamen oder Array nach vorn schieben |
| Fährt bei Linienverlust davon | Fehler bei Linienverlust genullt | Letzten Fehler halten |
| Folgt gut in eine Richtung, schlecht in die andere | Array nicht eben oder Motortrimm | Array ebnen; jeden Motor trimmen |
| Driftet zu einer Seite | Unkalibrierte Sensoren oder Motorfehlanpassung | Pro Kanal kalibrieren, dann trimmen |
| Gut auf der Werkbank, irrt auf dem Boden | Fahrhöhe geändert | 3–8 mm, starr montieren, neu kalibrieren |
| Lief gestern, träge heute | Akkuspannung | Verstärkungen sind Streckeneigenschaften – neu abstimmen oder Drehzahlschleife schließen |
| Zittert nur bei niedriger Drehzahl | Unter dem Totband der Motoren | Anlauf-Tastverhältnis je Motor messen und kompensieren |
Nichts reagiert auf KD |
Ein delay() in der Schleife |
Totzeit besiegt D-Wirkung vollständig |
Probiere es selbst
Im Linienfolger-Simulator kannst du eine Strecke zeichnen, Sensorzahl und Drehzahl ändern und die Verstärkungen abstimmen, während du Fehler und Rundenzeit beobachtest. Alles, was du über gewichtete Fehler und Lenkschleife lernst, überträgt sich direkt auf einen Arduino-Roboter.
Den Graphen erkunden
Teil dieser Bauprojekte
Projekte und Lernpfade, die dieses Tutorial enthalten.
Weiterführende Literatur