Inleiding

Als je je eerste robot wilt maken zijn er duizenden (miljoenen) beschrijvingen te vinden hoe je dit kunt aanpakken. En velen daarvan zullen je helpen dat geweldige eureka-gevoel van een robot die rijdt. Dat gevoel heb je weer als je verder knutselt en je robot obstakels vermijdt of een lijn volgt.

Je zult ook merken dat het soms lastig is om je robot te laten doen wat je wilt. En in de 15 jaar dat ik met robotjes bezig ben, heb ik een aantal 'geheimen' ontdekt die je helpen om je robot beter onder controle te brengen. Al deze 'geheimen' hebben met elkaar gemeen dat er bij een beginners-robot meestal niet worden toegepast. En omdat je niet weet waar je naar moet zoeken, vind je zo ook niet zomaar. Maar echt geheim zijn ze natuurlijk niet: ervaren roboteers gebruiken zeker de helft van deze geheimen in iedere robot die ze maken en dat geeft ze een voorsprong.

Zorg voor feedback.

Mijn robots doen nooit in 1 keer wat ze moeten doen. Soms zie je aan het gedrag van de robot wel wat er ongeveer mis gaat en valt je oog direct op de fout in het programma. Maar meestal niet. Waarom stopt de robot bijvoorbeeld niet aan het einde van de lijn? Ziet de sensor niet dat de lijn eindigt? Wordt de sensor goed uitgelezen? EN wordt het commando goed verwerkt?

Vandaar als eerste tip: zorg voor feedback. Een ledje is een goede eerste stap, maar als je robotprogramma complexer wordt, is de seriële poort hiervoor de beste oplossing. Kies daarbij voor een hoge baudrate (bijvoorbeeld 115.200) en print niet teveel, zodat het de werking van je robot niet beïnvloedt. Met een lange USB kabel en Serial.print (op Arduino) krijg je inzicht wat er gebeurt met je robot

Je kunt het natuurlijk ook mooier maken:

  • Gebruik een draadloze verbinding (als Wifi of Bluetooth ).
  • Maak de (debug) print commando's dynamisch in het uit te schakelen. Als je robot niet stopt, kun je bijvoorbeeld de sensor informatie laten printen zonder dat je het programma opnieuw hoeft te laden.

Harde aansturing van motoren.

Overal lees je dat je motoren aanstuurt met een PWM signaal. En vervolgens zie je voorbeelden met het PWM signaal op de enable ingang. Zelfs de meeste Arduino shields zijn hiervoor ontworpen. Dan zal het wel goed zijn. Toch?

Nou.... meestal niet. Deze manier van aansturen is efficient, maar de controle over de motor (en dus de robot) is slecht. Dit komt omdat het verband van de PWM waarde en het toerental verre van linear is. Het toerental loopt veel sneller op dan je zou verwachten, maar zakt direct terug als de belasting toeneemt. En als je PWM op 0 zet, bolt de robot uit en komt ergens tot stilstand, afhankelijk van de snelheid, het gewicht en de condities van de vloer.

De tweede tip is daarom: stuur je motor 'hard' aan. Dat betekent geen vrijloop (enable) tijdens de inactive periode van je PWM, maar kortsluiten van de motor. Dat klinkt wellicht eng, maar bij redelijke kwaliteit motoren die niet al te zwaar zijn (<2A kortsluitstroom) is dit geen probleem.

Met harde aansturing wordt het verband tussen PWM en motor toerental linear, ook bij verschillende belasting. Als je bijvoorbeeld vanaf een vlakke vloer een helling oprijdt, zal de snelheid veel minder terugvallen. En ook belangrijk: als je PWM op nul zet stopt je robot direct.

Meer details over harde aansturing in ons archief.

Opmerking: Moderne FET H-bruggen zoals de TB6612FNG zijn ontworpen voor harde aansturing en ontwerpen die hierop gebaseerd zijn geven standaard betere resultaten.

Goede bediening

Mijn eerste robot ging meteen aan de slag zodra deze werd ingeschakeld. Dit werd al snel uitgebreid met een paar drukknopjes. Voldoende voor de taak.

Bij de ontwikkeling van je robot heb je vaak behoefte aan extra knopjes. Om een stukje code uit te testen, om de taak te starten met een andere snelheid, om debug informatie te schakelen als dat nodig blijkt.

Zorg dus voor ruim voldoende 'knopjes'. Bijvoorbeeld:

  • Meerdere knopjes op eigen ingangen (kost relatief veel IO pinnen)
  • Meerdere knopjes met weerstanden op een analoge ingang (slechts IO pin, complexere code)
  • Matrix toetsenbordje (minder IO pinnen, of zelf met 1 analoge ingang)
  • Infra-rood afstandsbediening (neemt weinig plaats in op je robot, gebruikt slechts 1 IO pin, speciale library nodig)
  • Commando's via serial port (enkel-letter commando's zijn eenvoudig. commando-regels zijn krachtig maar ingewikkelder).

Gebruik geen blokkerende wachttijden.

Blokkeren wachttijden, zoals delay() in Arduino laten je even wachten. Het wordt veel gebruikt in voorbeelden en je bereikt er snel resultaat mee. Maar... het gebruik van blokkerende wachttijden is een serieuze belemmering als je meer wilt doen dan dat ene ding van het voorbeeld. Zoals hier wordt beschreven:

Soms moet je twee dingen tegelijk te doen. Zo je misschien een LED knipperen tijdens het lezen van een druk op de knop. In dat geval kun je geen gebruik maken van delay(), omdat dit Arduino je programma pauzeert. Als de toets wordt ingedrukt terwijl Arduino is gepauzeerd door delay(), zal je programma zal de druk op de knop te missen.

[..]

Een analogie is het opwarmen van een pizza in de magnetron terwijl je wacht op een belangrijke e-mail. Je zet de pizza in de magnetron en zet de tijd op 10 minuten. In analogie met delay() ga je voor de magnetron zitten en kijkt hoe de timer aftelt van 10 minuten tot nul. Als de belangrijke e-mail in deze tijd aankomt, zul je dit missen.

In het echte leven zou je de magnetron inschakelen voor de pizza en daarna iets anders gaan doen, zoals het controleren van je e-mail. En daarna nog wat anders (als het maar niet te lang duurt!). En om de zoveel tijd ga je even terug naar de magnetron te zien of de timer op nul staat, wat aangeeft dat je pizza klaar is.

De code bij het aangegeven artikel laat zien dat het vermijden van iets meer code oplevert. En het vraagt ook een andere manier benaderen van je code (zie ook 'state machines' hieronder). Is het waard om hier moeite in te steken? Dat moet je zelf bepalen, maar zoals ik het zie is gebruik van delay() vergelijkbaar met een breed fietspad dat recht een berg opgaat. Het eerste stukje gaat vlot, maar snel wordt de weg te steil en kom je niet verder. Het vermijden van delay() is als een fietspad met haarspeldbochten: ietsje langer, maar je komt wel aan op de top!

Vaste kadans van de hoofdlus

Ergens in je programma heb je een lus, waarin je (bijvoorbeeld) de lijnsensor uitleest en deze waarde gebruikt om de motoren aan te sturen. Dat gaat goed als je een eenvoudige (proportionele) regeling gebruikt, maar zodra je complexere regeling gaat gebruiken zoals PID regelingen zul je merken dat het gedrag van je robot verandert als je (bijvoorbeeld) meer informatie gaat uitprinten.

In de praktijk is een stabiele regeling belangrijker dan een snelle regeling.

De meest eenvoudige manier om dit te bereiken is een korte, vaste wachttijd toevoegen (bijvoorbeeld delay(20) in Arduino). Aansluitend op de analogie bij blokkerende wachttijden: je rent niet steeds heen en weer tussen je pc (om je mail te controleren) en de magnetron (om te kijken of de timer al op 0 staat), maar neemt steeds een halve minuut rust.

Maar beter is de systeemklok (millis() in Arduino) te gebruiken om te bepalen wanneer het tijd is om de regellus weer aan te roepen. Je neemt dan geen halve minuut rusttijd, maar kijkt op de klok of het tijd is om weer 'een rondje' te doen. Zo heeft de duur van het rondje geen invloed heeft op de starttijd van het volgende rondje (tenminste, als het rondje niet te lang duurt!).

State machines voor multi-tasken

In een 'normaal' programma gebruik je lussen om iets te doen. Bij een robot is dit een lusje om een lijn te volgen tot een kruising, gevolgd door lus voor een draai naar links of een andere lus voor een draai naar rechts. Dat zijn dus drie lussen waar je sensoren leest, motor-snelheid berekent en de motoren aanstuurt. Kortom, een aantal taken die op meerdere plaatsen in je programma worden aangeroepen. Naar mate je programma groeit, wordt het steeds ingewikkelder om te overzien wat er gebeurt of om iets te veranderen.

Hiervoor zijn in essentie drie oplossingen:

  • Gebruik een mutli-taksing operating system als Linux.
  • Gebruik een Real Time Operating System (RTOS). Dit is vaak een library die je meelinkt.
  • Gebruik state machines (ook wel Finit State Machines, FSM).

Als je geen software-adept bent en nu met een embedded controller als Arduino werkt, zijn State Machines het eenvoudigst om mee te beginnen. Begin met een hoofdlus die in een vaste cadans wordt doorlopen en voeg een knipperend ledje toe dat (op basis van een teller) eens per seconde knippert. Dit is je bewaking: zolang dit ledje blijft knipperen in het gewenste tempo gaat alles goed.

Vervolgens bouw je iedere 'taak' op zodat deze niet blokkerend is. Bijvoorbeeld een routine die de lijnsensor uitleest en het resultaat in een variabele zet, zodat alle taken deze waarde kunnen gebruiken als dat nodig is. En een routine die de snelheid van de beide motoren uitleest en - mede op basis van de encoder informatie - de motoren aanstuurt met een PID controller. In je 'missie' hoef je nu alleen maar de motorsnelheid klaar te zetten. En... als je later de motor-besturing uitbreid zodat de robot altijd rustig start en stopt, werkt dit meteen voor alle bewegingen.

Zoals gezegd is het gebruik van state machines maar 1 van de mogelijke oplossingen en hier lees je meer over de afwegingen.

Tot slot

Zoals gezegd heb ik bovenstaande 'geheimen' ontdekt. Ze zijn dus niet van mezelf*, maar het lijkt me de moeite waard om ze te delen. Niet als ijzeren regels, niet als volledige gedocumenteerde oplossing maar als aanwijzingen waarmee je op het internet verder kunt. 

 

 * Zelf de titel is niet, dit  is een variant op 'Improving the Beginner's PID', de titel van de uitstekende blog van Brett Beauregard, waarnaar in het artikel wordt verwezen.