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.

Fünf Liniensensoren in einer Reihe, jeder mit Gewichtung von minus zwei links über null in der Mitte bis plus zwei rechts beschriftet, jeder mit einer Eins- oder Null-Ablesung darunter. Die beiden Sensoren bei Gewichtung null und plus eins lesen eins und sind hervorgehoben; der Rest liest null. Die Formel des gewichteten Mittels berechnet Summe aus Gewichtung mal Messwert geteilt durch Summe der Messwerte, ergibt null plus eins durch zwei gleich plus null Komma fünf. Ein Warnhinweis besagt, dass lauter Nullen Linie verloren bedeutet, ein anderer Fall und kein Fehler null.
Fünf An-Aus-Messwerte kollabieren zu einer vorzeichenbehafteten Zahl, auf die die Lenkschleife reagieren kann. Ihr Vorzeichen ist die Richtung, ihre Größe, wie weit du danebenliegst. SVG herunterladen

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

Quellen