Tutorial · Gevorderd · 14 min lezen

Lijnmaze-kruisingen detecteren met een IR-array

Hoe een lijnvolger een T-kruising van een kruis onderscheidt, een doodlopende weg van de finish — en waarom één sensorwaarde nooit genoeg is om te beslissen.

Een lijnvolger heeft één taak: de lijn gecentreerd houden. Een lijnmaze-robot heeft er twee, en bij de tweede lopen beginners vast. Hij moet de lijn nog steeds volgen, maar hij moet ook de momenten opmerken waarop de lijn een keuze aanbiedt — en bepalen wat voor soort keuze dat is.

Die beslislaag ligt bovenop de PID-regellus, niet erin. De regelaar mag nooit weten wat een doolhof is. Hij stuurt naar een lijn; de kruisingslaag let dezelfde sensorwaarden op patronen die betekenen dat “er iets is veranderd” en neemt het over wanneer er zo’n patroon opduikt.

Zes dingen die een kruising kan zijn

Elke kruising in een lijnmaze met plakband valt terug op een van de zes gevallen. Met een 8-kanaals reflectie-array die 1 leest voor zwarte tape en 0 voor het witte bord levert elk geval een signatuur op:

Zes panelen met de typen kruisingen in een lijnmaze, getekend als zwarte tape op een licht bord, elk overlegd met een sensorbalk van acht kanalen: alleen links leest 11111000, alleen rechts leest 00011111, een T-kruising leest allemaal enen, een kruis leest allemaal enen, een doodlopende weg leest allemaal nullen, en de finishmarker is een vol blok dat allemaal enen leest.
De zes kruisingstypen en het patroon dat elk ervan op een 8-kanaals array achterlaat. Drie daarvan zijn bij aankomst niet van elkaar te onderscheiden. SVG downloaden
  • Alleen links — de buitenste sensoren links gaan zwart terwijl de rechterkant wit blijft.
  • Alleen rechts — het spiegelbeeld daarvan.
  • T-kruising — de tape loopt de volle breedte door, met niets ervoor.
  • Kruis — de tape loopt de volle breedte door, en de lijn gaat rechtdoor.
  • Doodlopende weg — elke sensor leest wit; de lijn houdt gewoon op.
  • Finish — een volledig gevuld blok, breed en lang, dat zwart blijft terwijl je erin rijdt.

De waarde alleen is dubbelzinnig

Kijk naar de T-kruising, het kruis en de finishmarker. Alle drie melden 11111111. Een zijtak waar de lijn ook rechtdoor gaat meldt hetzelfde als een gewone linkerbocht.

Dit is het stuk dat je moet onthouden: het momentane sensorpatroon kan een kruising niet classificeren. Een robot die beslist op het moment dat zijn buitenste sensoren zwart worden, draait vol overtuiging links op een vierwegsplitsing waar hij rechtdoor had moeten rijden.

De oplossing is beweging, niet meer sensoren. Als de array voor het eerst een breed zwart patroon meldt:

  1. Leg vast welke kant (of kanten) zwart is geworden — dat is de verzameling beschikbare takken.
  2. Rij ongeveer één robotlengte door.
  3. Lees opnieuw. Zien de middelste sensoren nog tape, dan was ook rechtdoor een optie. Is alles wit, dan was het een T. Is alles nog steeds zwart, dan ben je in het finishblok gereden.

Pas nu weet je welke soort kruising je achterliet, en pas nu kan de maze-laag kiezen.

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();
}

Merk op dat de classifier rijdt. Dat is ongebruikelijk voor een meetfunctie, en dat is precies het punt — het type kruising is een eigenschap van een korte baan, niet van één meting.

Een tak kiezen

Zodra een kruising is geclassificeerd, kiest de maze-laag een tak volgens een vaste regel. De linkerhandregel — neem altijd de meest linkse beschikbare tak en keer om bij een doodlopende weg — is de standaardkeuze, omdat hij geen geheugen gebruikt, volledig is in een doolhof zonder lussen, en met de hand te controleren valt.

Hier is consistentie belangrijker dan slimheid. Een robot die altijd links prefereert, bezoekt uiteindelijk elke bereikbare tak, en de registratie van zijn bochten is wat de routevereenvoudiging later ineenvouwt tot het optimale pad. Bekijk hetzelfde verkennen-en-optimaliseren in de Maze Solver Simulator — die neemt muren waar waar de lijn ligt, maar de beslislaag boven de waarneming is identiek.

De array moet breder zijn dan de lijn

Kruisingsdetectie stelt de sensor één specifieke vraag: zijn de buitenste kanalen zwart geworden terwijl het midden nog op de lijn ligt? Een array die nauwelijks breder is dan de tape kan dat fysiek niet beantwoorden.

   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
                   │
Tapebreedte Minimale bruikbare arraybreedte Waarom
19 mm (standaard) ~60 mm Buitenste sensoren moeten met een volledige sensorafstand voorbij de tape komen
25 mm ~75 mm
12 mm ~40 mm

De vuistregel is drie keer de tapebreedte. Smaller en de robot kan niet onderscheiden of “de lijn maakte een scherpe bocht” of “de lijn vertakte” — en dat is precies de beslissing waar het hele project op rust. Een 8-kanaals array met 8 mm sensorafstand is ongeveer 60 mm breed, en daarom is dat de standaardkeuze voor 19 mm tape.

Een kruising ontdenderen

De array rijdt in meerdere lusiteraties over een kruising, en elke iteratie is een verse meting. Een classifier die bij het eerste brede patroon aanslaat, slaat voor één kruising vaak twee of drie keer aan en vult het vastgelegde pad met tekens die nooit echte bochten 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;
}

De dode tijd dimensioneren is een echte afweging, en dat doe je beter met getallen dan op gevoel:

deadtime < (minimum spacing between junctions) / speed

Bij 0,2 m/s met kruisingen die niet dichter bij elkaar liggen dan 15 cm heb je 750 ms speelruimte — dus 300 ms is veilig. Rij je 0,5 m/s in een doolhof met 10 cm tussenruimte, dan zakt het budget naar 200 ms, en daarom verkent langzaam. Deze randvoorwaarde, niet het algoritme, bepaalt de verkenningsnelheid.

De bocht zelf

Een kruising classificeren is de helft van het werk; de bocht uitvoeren zonder de lijn kwijt te raken is de andere helft, en een bocht met een vaste hoek is de verkeerde manier om dat te doen.

Methode Hoe hij eindigt Betrouwbaarheid
Draai een vaste tijd Na N milliseconden Slecht — verandert met accuspanning en ondergrond
Draai een vaste hoek op encoders Na N tellingen Beter, maar speling en slip stapelen zich op
Draai tot de lijn opnieuw gevonden is Zodra de middelste sensoren tape zien Beste — zelfcorrigerend, eindigt waar de lijn ligt
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();
}

De delay(120) is belangrijk. Direct na het starten van de bocht liggen de middelste sensoren nog over de eigen tape van de kruising, dus een naïef “draai tot je de lijn ziet” stopt meteen en de robot draait helemaal niet. Verlaat eerst de kruising, kijk dan pas.

De timeout is net zo belangrijk: zonder timeout draait een robot die voorbij de tak stuurt — omdat hij te snel reed, of omdat de tak verkeerd werd gedetecteerd — eindeloos in het rond.

De volledige classificatie, met rechtdoor opgelost

Debounce, doorrijden en bochten samen:

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;
}

De classifier rijdt, wat ongebruikelijk is voor een meetfunctie en precies het punt is: het type kruising is een eigenschap van een korte baan, niet van één meting. Drie van de zes gevallen — T, kruis en finish — zijn bij aankomst niet van elkaar te onderscheiden.

Testen zonder doolhof

Je hebt geen doolhof nodig om deze laag te verifiëren, en er een bouwen voordat de detectie werkt is een klassieke verspilling van een avond. Leg één kruising van elk type op een bord en laat de robot er achtereenvolgens overheen rijden, met de classificatie geprint:

Teststuk Verwacht Gangbare fout
Rechte lijn Helemaal geen kruising Slaat aan op een lichte bocht — buitendrempel te gevoelig
Lichte bocht, straal 15 cm Geen kruising Idem
Alleen linkertak LEFT Gedetecteerd als T — dodetijd te kort, twee keer aangeslagen
Alleen rechtertak RIGHT Spiegelbeeld van het vorige
T-kruising T Gedetecteerd als LEFT — niet doorgereden om rechtdoor op te lossen
Kruis CROSS Gedetecteerd als T — de doorrijafstand was te kort
Doodlopende weg DEAD_END Gedetecteerd als lijnverlies, en de robot reed door
Volledig finishblok FINISH Gedetecteerd als CROSS — het blok is langer dan een kruising

De test met de lichte bocht vangt de meeste bugs, en het is de test waar niemand aan denkt. Een scherpe bocht zet meerdere buitenste sensoren tijdelijk op de tape, wat er precies uitziet als een tak. Als je robot kruisingen verzint op bochten, is de oplossing dat de buitenste sensoren minstens een bepaalde afstand zwart moeten blijven, en niet minstens een bepaald aantal metingen — een bocht laat ze snel weer los, een echte tak niet.

Storingzoeken

Symptoom Waarschijnlijke oorzaak Oplossing
Kruisingen gedetecteerd op bochten Buitendrempel te gevoelig Verlang dat het patroon over een minimale afstand blijft bestaan
Eén kruising tweemaal vastgelegd Geen dode tijd 200–300 ms nadat je voor een kruising hebt gekozen
T gemeld als linkerbocht Niet doorgereden om rechtdoor op te lossen De classifier moet bewegen voordat hij beslist
Kruis gemeld als T Doorrijafstand te kort Eén volle robotlengte voorbij de splitsing
Finish gemeld als kruis Het blok is langer dan een kruising Blijf rijden; het blijft zwart, een kruis niet
Kruisingen volledig gemist Te snel, of de array is te smal Verken langzamer; array ≥ 3× de tapebreedte
Bochten schieten voorbij de nieuwe lijn Bocht met vaste tijd of vaste hoek Draai tot de middelste sensoren de lijn opnieuw vinden
Robot draait eindeloos door na een kruising Geen timeout voor de bocht Voeg er een toe en stop netjes met een foutmelding
Draait helemaal nooit De eigen tape van de kruising gelezen als de nieuwe lijn Verlaat de kruising voordat je kijkt
Werkt op het ene bord, niet op het andere Kalibratie, of omgevingslicht Kalibreer bij elke spanningsaanleg; aftrekken met uitgeschakelde zender

Betrouwbaar maken op echte tape

Drie dingen breken kruisingsdetectie op hardware veel vaker dan het algoritme:

  • Kalibratiedrift. Voer de kalibratiesweep van de array bij elke spanningsaanleg uit over zowel zwarte tape als wit bord, en herhaal hem als je naar een andere ruimte gaat. Omgevingsinfrarood van zonlicht en tl-lampen verandert de meetwaarden.
  • Sensorhoogte. 5–8 mm boven de vloer. Te hoog en het contrast verdwijnt; te laag en een licht gebogen bord schuurt over de array.
  • Tapekwaliteit. Gebruik 18–20 mm tape en maak de kruisingen fysiek schoon. Een gerafelde hoek leest als een wisselende tak, en geen hoeveelheid code repareert tape die tegen je liegt.

Maak eerst gewone lijnvolging stabiel op een simpele ovaal — stel hem af in de Line Follower Simulator en daarna op echte tape. Een doolhof verbergt mechanische en sensorproblemen achter wat op een logische bug lijkt.

Verken de graaf

Onderdeel van deze bouwprojecten

Projecten en leerroutes waarin deze tutorial voorkomt.

Verder lezen

Bronnen