Tutorial · Mittelstufe · 14 Min. Lesezeit

Linienlabyrinth-Kreuzungen mit IR-Array erkennen

Wie ein Linienfolger ein T von einem Kreuz unterscheidet, Sackgasse vom Ziel – und warum eine einzelne Sensormessung nie zum Entscheiden reicht.

Ein Linienfolger hat einen Job: die Linie mittig halten. Ein Linienlabyrinth-Roboter hat zwei – und beim zweiten bleiben Anfänger stecken. Er muss weiter der Linie folgen, aber auch die Momente bemerken, in denen die Linie eine Wahl anbietet – und entscheiden, welche Art Wahl es ist.

Diese Entscheidungsschicht sitzt auf der PID-Schleife, nicht darin. Der Regler darf nie wissen, was ein Labyrinth ist. Er lenkt auf eine Linie zu; die Kreuzungsschicht beobachtet dieselben Sensormesswerte auf die Muster, die „etwas hat sich geändert“ heißen, und übernimmt, wenn eines auftaucht.

Sechs Dinge, die eine Kreuzung sein kann

Jede Kreuzung in einem geklebten Linienlabyrinth reduziert sich auf einen von sechs Fällen. Mit einem 8-Kanal-Reflexionsarray, das 1 für schwarzes Klebeband und 0 für die weiße Platte liest, erzeugt jeder eine Signatur:

Sechs Felder mit Linienlabyrinth-Kreuzungstypen als schwarzes Klebeband auf heller Platte, jeweils überlagert mit einer Achtkanal-Sensorleiste: Nur-links-Abbiegung liest 11111000, nur-rechts-Abbiegung liest 00011111, T-Kreuzung liest nur Einsen, Kreuz liest nur Einsen, Sackgasse liest nur Nullen und die Zielmarkierung ist ein massiver Block, der nur Einsen liest.
Die sechs Kreuzungstypen und das Muster, das jeder auf ein 8-Kanal-Array legt. Drei davon sind im Ankunftsmoment ununterscheidbar. SVG herunterladen
  • Nur-links-Abbiegung – die äußeren linken Sensoren werden schwarz, während die rechte Seite weiß bleibt.
  • Nur-rechts-Abbiegung – das Spiegelbild.
  • T-Kreuzung – Klebeband spannt die volle Breite, mit nichts voraus.
  • Kreuz – Klebeband spannt die volle Breite, und die Linie läuft voraus weiter.
  • Sackgasse – jeder Sensor liest weiß; die Linie hörte schlicht auf.
  • Ziel – ein massiver gefüllter Block, breit und lang, der beim Hineinfahren schwarz bleibt.

Die Messung allein ist mehrdeutig

Die T-Kreuzung, das Kreuz und die Zielmarkierung ansehen. Alle drei melden 11111111. Eine Linksabzweigung, wo die Linie auch geradeaus weiterläuft, meldet dasselbe wie eine schlichte Linksabbiegung.

Das lohnt Verinnerlichen: Das momentane Sensormuster kann eine Kreuzung nicht klassifizieren. Ein Roboter, der entscheidet, sobald seine äußeren Sensoren schwarz werden, biegt selbstbewusst links ab, wo er geradeaus hätte durchfahren sollen.

Die Lösung ist Bewegung, nicht mehr Sensoren. Wenn das Array erstmals ein breites schwarzes Muster meldet:

  1. Notieren, welche Seite (oder Seiten) schwarz wurde – das ist die Menge verfügbarer Abzweigungen.
  2. Etwa eine Roboterlänge geradeaus weiterfahren.
  3. Erneut lesen. Sehen die Mittelsensoren weiter Klebeband, war geradeaus auch eine Option. Ist alles weiß, war es ein T. Ist alles weiter schwarz, ist man in den Zielblock hineingefahren.

Erst jetzt weiß man, welche Art Kreuzung man hinter sich ließ – und erst jetzt kann die Labyrinthschicht wählen.

Junction classify(Sensors& s) {
  Branches at = s.readWideBranches();      // left / right seen on arrival
  driveForward(ONE_ROBOT_LENGTH);          // commit past the intersection
  Reading after = s.read();

  if (after.allBlack())  return FINISH;    // still on tape = solid block
  at.straight = after.centreOnLine();      // line continued through
  return at.toJunction();
}

Beachte: Der Klassierer fährt. Das ist ungewöhnlich für eine Wahrnehmungsfunktion und der Punkt: Kreuzungstyp ist eine Eigenschaft einer kurzen Trajektorie, nicht einer einzelnen Messung.

Einen Abzweig wählen

Sobald eine Kreuzung klassifiziert ist, wählt die Labyrinthschicht per fester Regel einen Abzweig. Die Linke-Hand-Regel – immer den am weitesten links verfügbaren Abzweig nehmen und an einer Sackgasse umkehren – ist die Standardwahl: zustandslos, erschöpfend auf einem schleifenfreien Labyrinth und trivial von Hand prüfbar.

Konsistenz zählt hier mehr als Klugheit. Ein Roboter, der immer links bevorzugt, besucht irgendwann jeden erreichbaren Abzweig – und die Aufzeichnung seiner Drehungen ist das, was der Routenvereinfachungsschritt später zum optimalen Pfad kollabiert. Dasselbe Erkunden-dann-Optimieren im Labyrinthlöser-Simulator laufen sehen – der spürt Wände statt Klebeband, aber die Entscheidungsschicht über der Wahrnehmung ist identisch.

Das Array muss breiter sein als die Linie

Kreuzungserkennung stellt eine bestimmte Frage an den Sensor: Sind die äußersten Kanäle schwarz geworden, während die Mitte noch auf der Linie ist? Ein Array, kaum breiter als das Klebeband, kann sie physisch nicht beantworten.

    left branch                    8-channel array, ~60 mm wide
    ────────────────┐          [ 1 1 1 0 0 0 0 0 ]   outer left black -> a branch exists
                    │
    ════════════════╪═══════   [ 0 0 0 1 1 0 0 0 ]   centre still on the line
                    │
Klebebandbreite Minimale sinnvolle Arraybreite Warum
19 mm (Standard) ~60 mm Äußere Sensoren müssen das Klebeband um vollen Sensorabstand freigeben
25 mm ~75 mm
12 mm ~40 mm

Die Faustregel lautet dreifache Klebebandbreite. Schmaler, und der Roboter kann „die Linie bog scharf“ nicht von „die Linie verzweigte“ unterscheiden – die zentrale Entscheidung, auf der das ganze Projekt ruht. Ein 8-Kanal-Array bei 8 mm Abstand ergibt etwa 60 mm – weshalb das die Standardwahl für 19-mm-Klebeband ist.

Eine Kreuzung entprellen

Das Array überquert eine Kreuzung über mehrere Schleifendurchläufe, und jeder ist eine frische Messung. Ein Klassierer, der beim ersten breiten Muster feuert, feuert oft zwei-, dreimal für eine Kreuzung – und füllt den aufgezeichneten Pfad mit Zeichen, die nie echte Drehungen waren.

const uint8_t CONFIRM_SAMPLES = 3;      // consecutive readings that must agree
const unsigned long JUNCTION_DEADTIME_MS = 300;

uint8_t agreeCount = 0;
uint8_t lastPattern = 0;
unsigned long lastJunctionAt = 0;

bool junctionDetected(uint8_t pattern) {
  // Ignore everything for a short while after committing to one.
  if (millis() - lastJunctionAt < JUNCTION_DEADTIME_MS) return false;

  if (pattern == lastPattern) agreeCount++;
  else { lastPattern = pattern; agreeCount = 1; }

  if (agreeCount >= CONFIRM_SAMPLES && isWide(pattern)) {
    lastJunctionAt = millis();
    agreeCount = 0;
    return true;
  }
  return false;
}

Die Totzeit zu dimensionieren ist ein echter Zielkonflikt – und lohnt mit Zahlen statt nach Gefühl:

deadtime < (minimum spacing between junctions) / speed

Bei 0,2 m/s mit Kreuzungen nicht enger als 15 cm sind das 750 ms Spielraum – 300 ms sind also sicher. Bei 0,5 m/s fahren in einem Labyrinth mit 10-cm-Abstand schrumpft das Budget auf 200 ms – weshalb Erkundungsläufe langsam fahren. Diese Zwangsbedingung, nicht der Algorithmus, setzt das Erkundungstempo.

Die Drehung selbst

Die Kreuzung zu klassifizieren ist die halbe Arbeit; die Drehung auszuführen, ohne die Linie zu verlieren, die andere Hälfte – und eine Festwinkeldrehung ist der falsche Weg.

Methode Wie sie endet Zuverlässigkeit
Für feste Zeit drehen Nach N Millisekunden Schlecht – ändert sich mit Akkuspannung und Untergrund
Festen Winkel auf Encodern drehen Nach N Zählwerten Besser, aber Spiel und Schlupf summieren sich
Drehen, bis die Linie wieder da ist Wenn die Mittelsensoren Klebeband sehen Am besten – selbstkorrigierend, endet wo die Linie ist
void turnUntilLine(int direction) {
  pivot(direction);
  delay(120);                        // clear the junction's own tape first
  while (!centreOnLine()) {          // then rotate until the new line appears
    if (millis() - startedAt > TURN_TIMEOUT_MS) { stopMotors(); fault(); return; }
  }
  stopMotors();
}

Das delay(120) zählt. Direkt nach Drehbeginn liegen die Mittelsensoren noch über dem Klebeband der eigenen Kreuzung – ein naives „drehen, bis du die Linie siehst“ stoppt also instant und der Roboter dreht nie überhaupt. Erst die Kreuzung freigeben, dann schauen.

Das Timeout zählt auch: Ohne dreht ein Roboter, der am Abzweig vorbeidreht – weil er zu schnell fuhr oder der Abzweig fehlerkannt wurde –, ewig.

Die volle Klassierung, mit aufgelöstem Geradeaus

Entprellen, Vorbeifahren und Drehen zusammengefügt:

Junction classifyAndTurn() {
  const Branches at = readWideBranches();     // left and/or right, on arrival

  driveForward(ONE_ROBOT_LENGTH);             // commit past the intersection
  const Reading after = read();

  if (after.allBlack()) return FINISH;        // still on tape = the solid block

  Junction j;
  j.left     = at.left;
  j.right    = at.right;
  j.straight = after.centreOnLine();          // resolved only by moving

  if (!j.left && !j.right && !j.straight) return DEAD_END;
  return j;
}

Der Klassierer fährt – ungewöhnlich für eine Wahrnehmungsfunktion und der ganze Punkt: Kreuzungstyp ist eine Eigenschaft einer kurzen Trajektorie, nicht einer einzelnen Messung. Drei der sechs Fälle – T, Kreuz und Ziel – sind im Ankunftsaugenblick ununterscheidbar.

Testen ohne Labyrinth

Man braucht kein Labyrinth, um diese Schicht zu verifizieren – und eines zu bauen, bevor die Erkennung arbeitet, ist eine gängige Abendverschwendung. Je eine Kreuzung jedes Typs auf einer Platte auslegen und den Roboter der Reihe nach darüberfahren lassen, mit ausgegebener Klassierung:

Teststück Erwartet Gängiger Fehler
Schlichte Gerade Gar keine Kreuzung Feuert auf leichter Kurve – Außenschwelle zu empfindlich
Sanfte Kurve, 15 cm Radius Keine Kreuzung Ebenso
Nur-Links-Abzweig LEFT Als T erkannt – Totzeit zu kurz, doppelt gefeuert
Nur-Rechts-Abzweig RIGHT Spiegelbild des obigen
T-Kreuzung T Als LEFT erkannt – nicht vorbeigefahren, um geradeaus aufzulösen
Kreuz CROSS Als T erkannt – Vorbeifahrstrecke zu kurz
Sackgasse DEAD_END Als Linienverlust erkannt und der Roboter fuhr weiter
Massiver Zielblock FINISH Als CROSS erkannt – der Block ist länger als eine Kreuzung

Der Sanfte-Kurve-Test fängt die meisten Fehler – und an ihn denkt niemand. Eine enge Kurve legt momentan mehrere äußere Sensoren aufs Klebeband, was exakt wie ein Abzweig aussieht. Erfindet dein Roboter Kreuzungen auf Kurven, als Lösung verlangen, dass die äußeren Sensoren über eine Mindeststrecke schwarz bleiben, nicht nur eine Mindestzahl Messwerte: Eine Kurve gibt sie schnell wieder frei, ein echter Abzweig nicht.

Fehlersuche

Symptom Wahrscheinliche Ursache Lösung
Kreuzungen auf Kurven erkannt Außenschwelle zu empfindlich Verlangen, dass das Muster über eine Mindeststrecke besteht
Eine Kreuzung doppelt aufgezeichnet Keine Totzeit 200–300 ms nach Festlegen auf eine Kreuzung
T als Linksabbiegung gemeldet Nicht vorbeigefahren, um geradeaus aufzulösen Der Klassierer muss sich bewegen, bevor er entscheidet
Kreuz als T gemeldet Vorbeifahrstrecke zu kurz Eine volle Roboterlänge an der Schnittstelle vorbei
Ziel als Kreuz gemeldet Der Block ist länger als eine Kreuzung Weiterfahren; er bleibt schwarz, ein Kreuz nicht
Kreuzungen glatt verfehlt Zu schnell oder Array zu schmal Erkundungslauf verlangsamen; Array ≥ 3× Klebebandbreite
Drehungen schießen über die neue Linie hinaus Festzeit- oder Festwinkeldrehung Drehen, bis die Mittelsensoren wieder fassen
Roboter dreht nach Kreuzung ewig Kein Dreh-Timeout Eines hinzufügen und sauber fehlen
Dreht nie überhaupt Das Klebeband der eigenen Kreuzung als neue Linie gelesen Die Kreuzung freigeben, bevor man schaut
Auf einer Platte in Ordnung, auf anderer nicht Kalibrierung oder Umgebungslicht Bei jedem Einschalten neu kalibrieren; Emitter-aus-Subtraktion

Auf echtem Klebeband verlässlich bekommen

Drei Dinge brechen Kreuzungserkennung auf Hardware weit öfter als der Algorithmus:

  • Kalibrierdrift. Den Kalibriersweep des Arrays über schwarzes Klebeband wie weiße Platte bei jedem Einschalten fahren – und erneut, wenn man den Raum wechselt. Umgebungsinfrarot aus Sonnenlicht und Leuchtstofflampen ändert die Messwerte.
  • Sensorhöhe. 5–8 mm über dem Boden. Zu hoch und der Kontrast kollabiert; zu niedrig und eine leicht verzogene Platte schabt am Array.
  • Klebebandqualität. 18–20-mm-Klebeband verwenden und Kreuzungen physisch sauber legen. Eine ausgefranste Ecke liest sich als intermittierender Abzweig – und kein Code repariert Klebeband, das lügt.

Erst schlichtes Linienfolgen auf einem einfachen Oval stabil bekommen – im Linienfolger-Simulator abstimmen, dann auf echtem Klebeband. Ein Labyrinth versteckt Mechanik- und Sensorprobleme hinter dem, was wie ein Logikfehler aussieht.

Den Graphen erkunden

Teil dieser Bauprojekte

Projekte und Lernpfade, die dieses Tutorial enthalten.

Weiterführende Literatur

Quellen