Tutorial · Beginner · 20 min

Een IR-reflectiearray lezen voor lijnvolgen

Hoe een lijnvolger een rij IR-reflectiesensoren in één stuurfout verandert: analoog tegenover digitaal, kalibratie en de formule voor de gewogen positie.

Een lijnvolgerrobot is zo goed als het getal waarop hij stuurt. Dat getal komt van een rij infraroodreflectiesensoren die op de vloer gericht staan: de lijn heeft één kleur, de ondergrond een andere, en de sensoren melden hoeveel IR-licht terugkaatst. Deze gids maakt van die ruwe rij metingen één vloeiende lijnpositie—de fout waarop je regelaar handelt.

Hoe een reflectiesensor de lijn ziet

Elke sensor combineert een IR-led met een fototransistor. De led schijnt omlaag; de fototransistor meet de reflectie. Een matzwarte lijn kaatst weinig IR terug (lage waarde); de lichtere ondergrond kaatst meer terug (hoge waarde). Beweeg je de array over een zwart-op-wit parcours, dan zwaait elke sensor heen en weer tussen een “op de lijn”-waarde en een “van de lijn”-waarde.

Twee dingen veranderen die ruwe waarden van rit tot rit: de sensorhoogte boven het oppervlak en het omgevingslicht. Daarom hardcode je nooit drempelwaarden—je kalibreert. Dezelfde sensoren, omlaag gericht op een ringrand in plaats van op een lijn, worden een randdetector, waarbij een gemiste meting de robot de wedstrijd kost.

Analoge tegenover digitale sensoren

  • Analoge sensoren geven een doorlopende waarde (bijv. 0–1023 op een ADC van een Arduino). Daarmee kun je zien hoe ver de lijn van elke sensor ligt, zodat je een vloeiende positie berekent en proportioneel kunt sturen.
  • Digitale sensoren melden alleen aan/uit na een interne drempel. Simpeler te bedraden, maar je verliest de informatie ertussen, dus het sturen wordt grover.

Gebruik analoge metingen voor proportioneel lijnvolgen—het soort dat je afstelt met een PID-regelaar. Dat is de aanpak die de lijnvolgsimulator modelleert.

Eerst kalibreren

Beweeg vóór een rit elke sensor over zowel de lijn als de ondergrond en leg per sensor het minimum en het maximum vast. Normaliseer daarna elke live-meting naar 0–1000 op basis van het eigen bereik:

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

Per sensor kalibreren heft ongelijke LED-helderheid en kleine hoogteverschillen langs de balk op, zodat één sensor niet stilletjes de hele schatting bevooroordeelt.

Van metingen naar één lijnpositie

Vat de array nu samen tot één positie met een reflectiegewogen gemiddelde. Geef elke sensor een vaste positie (…−2, −1, 0, +1, +2… dwars over de balk) en weeg die met zijn genormaliseerde meting. Het resultaat is de plek van de lijn ten opzichte van het midden van de balk—positief aan de ene kant, negatief aan de andere, nul als de lijn precies in het midden ligt.

Staafdiagram van vijf reflectiesensoren over de robotbalk. De lijn ligt rechts van het midden, dus de sensoren rechts lezen het sterkst. Een stippellijn markeert het midden van de balk (fout nul) en een doorgetrokken lijn markeert de reflectiegewogen positie op plus 0,69, de berekende stuurfout.
Het gewogen gemiddelde van de sensormetingen geeft een vloeiende lijnpositie: hier ligt de lijn rechts van het midden, dus is de fout positief en stuurt de regelaar terug naar nul. SVG downloaden
// 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
}

Twee details maken dit robuust:

  • Deel door de totale meting, niet door het aantal sensoren, zodat een vage lijn en een dikke lijn op dezelfde positieschaal vallen.
  • Als alle sensoren de lijn kwijtraken (total == 0), hergebruik dan de laatst bekende positie in plaats van nul terug te geven. Zo blijft de robot draaien naar waar de lijn was, in plaats van over een scherpe hoek heen te rijden.

Hoeveel sensoren?

Vijf is een goed startpunt—genoeg om richting en grootte te zien terwijl de wiskunde eenvoudig te controleren blijft. Drie werkt bij lage snelheid; wedstrijdrobots gebruiken er acht of meer voor een fijnere resolutie bij hoge snelheid. Meer sensoren levert vooral een gladdere positieschatting in scherpe bochten op.

De volledige kalibratieroutine

“Eerst kalibreren” is makkelijk te zeggen en makkelijk slecht te doen. Dit is de hele routine, inclusief de controle die voorkomt dat een dood kanaal de schatting verpest:

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

Beweeg de array over de lijn gedurende het hele kalibratievenster. Een robot die stilstaat registreert dezelfde meting zowel als zijn minimum als zijn maximum, wat een bereik van nul oplevert en een sensor die niets bijdraagt. De robot ter plaatse over de lijn draaien gedurende drie seconden is de standaardbeweging, en het werkt omdat elk kanaal over de tape gaat.

Kalibreer aan het begin van elke rit, niet één keer bij het bouwen. Het omgevingslicht verandert, de rijhoogte verandert terwijl de accupack verschuift, en de tape wordt stoffig. Dit is de meest voorkomende reden dat een lijnvolger op de ene tafel werkt en op de andere niet.

Omgevingslicht uitschakelen

Zonlicht bevat veel infrarood rond 940 nm, en de fototransistor kan dat niet onderscheiden van de reflectie van je eigen zender. Een robot die binnen werkt en bij een raam faalt, ziet precies dit.

Kunnen de zenders van je array aan en uit (de meeste hebben een LEDON- of CTRL-pin), dan is de oplossing compleet en kost ze één extra leesbeurt per kanaal:

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 wat constant is in de omgeving valt precies weg. De prijs is dubbele meettijd, en dat is op een snelle lijnvolger merkbaar— maar het maakt van een onbetrouwbare sensor een betrouwbare, en het is de techniek die commerciële arrays gebruiken.

Het heeft nog een tweede voordeel dat de moeite waard is: met de zenders uit tussen de metingen daalt hun gemiddelde stroom sterk. De zenders van een array met 8 kanalen kunnen doorlopend 100–200 mA trekken, wat op een kleine accu een behoorlijk deel van het budget is.

De lijn kwijtraken en terugvinden

Het geval total == 0 hierboven verdient meer aandacht dan het meestal krijgt, want daar faalt een lijnvolger het vaakst zichtbaar.

Nul teruggeven is de voor de hand liggende implementatie en de verkeerde: nul betekent “precies gecentreerd”, dus rijdt de robot op volle snelheid rechtdoor—weg van de lijn die hij zojuist verloor, en die vrijwel zeker een bocht maakte.

De laatste positie teruggeven laat de robot doorgaan met draaien zoals hij al draaide, en dat is waar de lijn heen ging. Die ene wijziging is het verschil tussen een robot die van een scherpe hoek herstelt en een robot die het parcours af rijdt.

Een volledigere versie voegt een time-out toe, zodat een echt verdwenen robot stopt in plaats van te blijven wervelen:

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

De drempel v > 100 telt ook mee. Zonder haar trekken acht kanalen die elk een kleine niet-nul vloerwaarde lezen het gewogen gemiddelde naar het midden van de array, ongeacht waar de lijn werkelijk ligt—en dat zie je als een robot die een dikke lijn goed volgt en een vervaagde slecht volgt.

Rijhoogte bepaalt het contrast

Reflectiesensoren hebben een smal optimaal bereik, en het is de mechanische variabele die zich het vaakst als een softwareprobleem voordoet.

Rijhoogte Wat je in het kalibratiebereik ziet
1–3 mm Grootste bereik, maar de array schuurt over elke oneffenheid in de vloer
3–8 mm Het bruikbare bereik. De meeste arrays schrijven ongeveer 3 mm voor
8–15 mm Bereik merkbaar kleiner; normalisatie gaat ruis versterken
> 15 mm Vrijwel blind op de meeste oppervlakken

Twee gevolgen volgen daaruit. Monteer hem stijf—een array op een flexibele steun verandert van hoogte terwijl de robot versnelt, wat de metingen verandert, wat de regellus interpreteert als bewegende lijn. En monteer hem waterpas, want een gekantelde array geeft elk kanaal een andere effectieve hoogte en dus een andere kalibratie, en dat zie je als een robot die in de ene richting goed volgt en in de andere slecht.

Hoeveel sensoren, gekwantificeerd

Kanalen Positieresolutie over een lijn van 19 mm Geschikt voor
3 Vrijwel links / midden / rechts Het idee begrijpen
5 ~4 discrete gebieden, daartussen glad Een eerste lijnvolger op een mild parcours
8 ~7 gebieden Snelle parcours, scherpe bochten, kruispuntdetectie
12–16 Fijn Wedstrijdrobots op snelheid

Het getal dat belangrijker is dan het aantal is de breedte van de array ten opzichte van de lijn. Voor gewoon lijnvolgen moet de array breed genoeg zijn dat bij elke rand van de lijn minstens één sensor op de vloer ligt—dus ongeveer tweemaal de tapebreedte.

Voor detectie van kruispunten in een lijnmaze is de eis strenger: de robot herkent een tak doordat de buitenste sensoren donker worden terwijl het midden nog op de lijn zit, dus de array moet minstens driemaal de tapebreedte zijn. Een array met 8 kanalen op 8 mm afstand is ongeveer 60 mm, en dat past bij tape van 19 mm.

Problemen oplossen

Symptoom Waarschijnlijke oorzaak Oplossing
Werkt op het ene oppervlak, faalt op het andere Verouderde of ontbrekende kalibratie Kalibreer aan het begin van elke rit
Eén kanaal trekt de schatting scheef Dode zender — zijn bereik is minimaal Controleer maxv − minv van elk kanaal; zet er een controle op
Robot rijdt rechtdoor als hij de lijn kwijtraakt Bij lijnverlies 0 teruggeven Geef de laatste positie terug
Volgt dikke tape, dwaalt over vervaagde tape Vloerruis die het gemiddelde binnenkomt Voeg de drempel v > 100 toe
Faalt bij een raam Infrarood uit de omgeving Aftrekken met uitgeschakelde zender, of de array afschermen
Positie springt als de lijn sensoren kruist Niet per kanaal genormaliseerd Kalibreer elk kanaal op zijn eigen min en max
Metingen drijven tijdens een lange rit Accu zakt weg, dus zenders worden zwakker Controleer de rail onder belasting
Alle kanalen lezen hetzelfde Array te hoog 3–8 mm, en kalibreer opnieuw
Volgt de ene kant goed, de andere slecht Array niet waterpas Elk kanaal heeft een andere effectieve hoogte

Waar dit hiernaartoe gaat

Die ene positiewaarde is de foutterm. Voer hem in een regellus in die het ene wiel versnelt en het andere vertraagt, en je hebt een lijnvolger. Leer die lus in Een PID-regelaar afstellen, en probeer het dan end-to-end in de lijnvolgsimulator.

Verken de graaf

Onderdeel van deze bouwprojecten

Projecten en leerroutes waarin deze tutorial voorkomt.

Verder lezen

Bronnen