Tutorial · Beginner · 20 min lezen
Bouw een lijnvolger: van sensoren tot PID-besturing
Hoe een lijnvolger werkt: reflectiesensoren, een gewogen positiefout en een PID-lus die een differentiaalaandrijving vloeiend bestuurt.
Een lijnvolger is de klassieke eerste autonome robot: hij leest een lijn onder zijn sensoren en stuurt bij om erop te blijven. Op de bodem is het een klein regelprobleem — sensormetingen omzetten in een stuurcorrectie — waardoor het een perfecte manier is om teruggekoppelde regeling te leren. Volg het in de Line Follower Simulator.
De lijn waarnemen
Een reflectiesensor schiet infrarood licht naar beneden en meet hoeveel er terugkaatst. Een donkere lijn kaatst minder terug dan het heldere oppervlak eromheen, dus elke sensor meldt of hij boven de lijn staat. Een rijtje van meerdere sensoren over de voorkant van de robot vertelt je niet alleen of je op de lijn zit, maar waar de lijn zit ten opzichte van het midden.
Vijf sensoren zijn een goed startpunt. Drie werken bij lage snelheid; wedstrijdrobots gebruiken er vaak acht voor een fijnere resolutie.
Metingen omzetten in één foutgetal
In plaats van op losse sensoren te reageren, combineer je ze tot één gewogen positie. Geef elke sensor een positiewaarde (negatief links, nul in het midden, positief rechts) en neem het gemiddelde gewogen naar hoe sterk elke sensor de lijn ziet:
// positions: {-2, -1, 0, 1, 2}; readings: reflectance per sensor
float weightedPosition = sum(position[i] * reading[i]) / sum(reading[i]);
Het resultaat is je fout: nul betekent gecentreerd, negatief betekent dat de lijn links ligt, positief dat ze rechts ligt. Een gladde, continue fout is precies wat een regelaar nodig heeft.
Sturen met PID
Voer die fout in een PID-regelaar. De correctie versnelt het ene wiel en vertraagt het andere, waardoor de robot terugdraait naar de lijn:
float correction = kp * error + ki * integral + kd * derivative;
setMotorSpeeds(baseSpeed - correction, baseSpeed + correction);
De proportionele versterking verzorgt het meeste van het stuurwerk; de differentiële versterking dempt de wiebeling in bochten; een kleine integrerende term haalt eventuele blijvende drift weg. Ben je nog niet gewend om versterkingen te kiezen, werk dan eerst hoe je een PID-regelaar afstelt door.
Verhelp de veelvoorkomende fouten
- Oscilleert rond de lijn: de proportionele versterking is te hoog of de differentiële te laag. Verlaag eerst de snelheid en stel daarna opnieuw af. Op echte hardware voegt een lange
delay()in de lus dode tijd toe die de differentiële term ondermijnt. - Verliest de lijn in scherpe bochten: de robot is sneller dan de sensors bij kunnen werken. Vertraag, verbreed de sensorrij, of hergebruik de laatst bekende lijnrichting zodat de robot terugdraait in plaats van rechtdoor van de lijn af te rijden.
- Drijft naar één kant: kalibreer de sensoren. Beweeg ze over de lijn en de achtergrond, leg per sensor het minimum en het maximum vast, en normaliseer live uitlezingen tegen dat bereik.
De hele lus op één plek
De fragmenten hierboven worden pas een werkende robot als de timing, de kalibratie en het geval van lijnverlies allemaal samen worden afgehandeld:
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);
}
Vijf dingen daarin scheiden een robot die een lijn volgt van een die het bijna doet.
Echte dt. Zowel I als D delen door de verstreken tijd. Een lus waarvan de duur verandert naarmate je functies toevoegt — en dat doet ze — rekent allebei met een leugen als je een constante periode aanneemt.
De drempel v > 100. Zonder hem trekken vijf kanalen die elk een kleine vloerwaarde uitlezen het gewogen gemiddelde, ongeacht waar de lijn ligt, naar het midden. Het symptoom is een robot die een dikke lijn goed volgt en een vage slecht.
De laatste fout vasthouden bij lijnverlies. Nul betekent “gecentreerd”, dus een robot die op nul zet rijdt recht vooruit op volle snelheid — van de lijn af, die vrijwel zeker aan het buigen was. De laatste fout vasthouden laat hem blijven draaien zoals hij al draaide.
Differentiatie op de meting. De fout differentiëren betekent dat een setpointverandering een enorme piek veroorzaakt. Hier is de setpoint altijd nul dus het maakt weinig verschil, maar de gewoonte is het waard om aan te leren.
Voorwaardelijke integratie. Zodra de correctie satureert, verder integreren alleen maar een overschot op te slaan dat je later moet terugbetalen.
Een afstelvolgorde die werkt
Stel niet alle drie tegelijk af. Zet KI = 0 en KD = 0 en dan:
| Stap | Wijziging | Let op | Stop wanneer |
|---|---|---|---|
| 1 | Verhoog KP vanaf een kleine waarde |
de robot gaat reageren op de lijn | hij een flauwe bocht volgt en een beetje slingert |
| 2 | Blijf KP verhogen |
het slingeren groeit uit tot oscilleren | hij gestaag oscilleert — noteer deze waarde als Ku |
| 3 | Zet KP op ongeveer de helft van Ku |
respons is snel, overschot aanwezig | — |
| 4 | Verhoog KD |
het slingeren dempt uit | rechte stukken zijn schoon; meer KD maakt hem schutterig |
| 5 | Alleen als hij consequent naast het midden volgt, voeg wat KI toe |
de verschuiving sluit | ze sluit — KI is zelden nodig bij een lijnvolger |
| 6 | Verhoog de BASE-snelheid |
alles degradeert tegelijk | stel KP en KD opnieuw af bij de nieuwe snelheid |
Stap 6 is de stap die mensen vergeten: versterkingen zijn snelheidsafhankelijk. Een afstelling die perfect is bij duty 140 zal oscilleren bij 220, omdat dezelfde stuurcorrectie nu per tijdseenheid een grotere verandering in richting oplevert. Verwacht telkens opnieuw af te stellen als je de snelheid wezenlijk verandert.
Integreren is hier werkelijk zelden nuttig. De setpoint van een lijnvolger is nul en de verstoringen zijn tijdelijk, dus meestal is er geen blijvende afwijking die KI kan dichten. Zie je een aanhoudende verschuiving, verdenk dan ongelijke motortrim of een ongekalibreerde sensor voordat je naar integrerende versterking grijpt.
De lusfrequentie begrenst stilletjes alles
| Lussnelheid | Interval | Afgelegde afstand bij 0,4 m/s |
|---|---|---|
| 20 Hz | 50 ms | 20 mm tussen beslissingen |
| 50 Hz | 20 ms | 8 mm |
| 200 Hz | 5 ms | 2 mm |
| 500 Hz | 2 ms | 0,8 mm |
Een robot die elke 20 mm een beslissing neemt, kan een bocht niet strak volgen hoe de versterkingen ook zijn ingesteld, en geen differentiële term herwint informatie die nooit is bemonsterd.
De gebruikelijke boosdoeners zijn makkelijk te verwijderen: een delay() ergens in de lus, Serial.print van meer dan een paar waarden op 9600 baud, en sensoren uitlezen die je niet gebruikt. Ze eruit halen doet een eerste bouw doorgaans van 50 Hz naar enkele honderden stijgen, en het volgen verbetert zonder één versterking aan te raken. Meet de frequentie vóór het afstellen, niet erna.
Problemen oplossen
| Symptoom | Waarschijnlijke oorzaak | Oplossing |
|---|---|---|
| Slingert op rechte stukken | KP te hoog of KD te laag |
halveer KP en verhoog daarna KD |
| Oscilleert steeds sneller | KP boven de uiterste versterking |
verlaag tot hij stabiel blijft |
| Snijdt hoeken | te snel voor de voorsprong van de sensor | vertraag, of verplaats de sensorrij naar voren |
| Rijdt weg als hij de lijn verliest | fout op nul gezet bij lijnverlies | houd de laatste fout vast |
| Volgt de ene kant goed, de andere slecht | sensorrij niet waterpas, of motortrim | zet de sensorrij waterpas; trim elke motor |
| Drijft naar één kant | ongekalibreerde sensoren, of motorverschil | kalibreer per kanaal en trim daarna |
| Prima op de werkbank, dwaalt op de vloer | rijhoogte veranderd | 3–8 mm, stevige montage, opnieuw kalibreren |
| Werkte gisteren, traag vandaag | batterijspanning | versterkingen zijn eigenschappen van het proces — stel opnieuw af of sluit een snelheidsregeling aan |
| Trilt alleen bij lage snelheid | onder de dode zone van de motoren | meet de startduty per motor en compenseer |
Niets reageert op KD |
een delay() in de lus |
dode tijd ondermijnt de differentiële werking volledig |
Probeer het zelf
In de Line Follower Simulator kun je een baan tekenen, het aantal sensoren en de snelheid wijzigen, en de versterkingen afstellen terwijl je de fout en de rondetijd observeert. Alles wat je leert over de gewogen fout en de stuurlus gaat direct mee naar een Arduino-robot.
Verken de graaf
Onderdeel van deze bouwprojecten
Projecten en leerroutes waarin deze tutorial voorkomt.
Verder lezen