Handboek · hoofdstuk 33Een mobiele robot
Twee wielen, drie lichtsensoren en een oor: een inspectierobotje voor de gang van de klimaatmodule
Een robot die zelf door een ruimte rijdt, moet drie dingen kunnen: bewegen en sturen, weten waar hij is, en stoppen voor wat in de weg staat. In dit hoofdstuk bouw je de natuurkunde en de code van een klein robotje met twee aangedreven wielen: hoe het draait, hoe snel het rijdt door de tandwielkast, hoe het een lijn volgt, hoe het afstand meet met geluid, en hoe een toestandsmachine alles samenhoudt. Het robotje rijdt in de gang van de klimaatmodule en meet langs het kweekbed temperatuur, vocht en lekken.

- Je kan met v = (vL + vR)/2 en ω = (vR − vL)/b de snelheid, de hoeksnelheid en de draaistraal van een robot met twee wielen berekenen.
- Je kan uit de motordata, de overbrengingsverhouding en de wieldiameter de snelheid van een robot berekenen, en de PWM-waarde kiezen.
- Je kan uitleggen hoe een reflectiesensor werkt, hem ijken, en een lijnvolger maken met aan/uit en met een P-regelaar.
- Je kan met s = v · t / 2 een afstand berekenen uit een echo, en de invloed van de temperatuur op de geluidssnelheid inschatten.
- Je kan het gedrag van een robot ontwerpen als toestandsmachine, met obstakels, meetpunten, time-outs en een veilige toestand.
- Je kan een simulatie van een robotpad lezen en gebruiken om een versterking of een snelheid te kiezen.
Een trekker die een draad volgt
In 1953 bouwde het Amerikaanse bedrijf Barrett Electronics een trekker zonder bestuurder. Hij trok karren door een magazijn en vond zijn weg door een draad te volgen. Het was de eerste AGV (automated guided vehicle), een 'automatisch geleid voertuig'. Vandaag rijden er duizenden door fabrieken, magazijnen en ziekenhuizen: ze volgen een lijn of magneetband op de vloer, of rekenen hun plaats uit met lasers en camera's.
De meeste kleine robots, ook stofzuigrobots, hebben geen stuur. Ze rijden op twee wielen met elk een eigen motor, en een zwenkwieltje dat meeloopt. Draaien ze beide wielen even snel, dan rijden ze rechtdoor; draait het ene sneller, dan maken ze een bocht; draaien ze tegengesteld, dan draaien ze op de plaats. Dat maakt ze wendbaar in smalle ruimtes, precies wat nodig is in de gang van de klimaatmodule.
Het inspectierobotje van Habitat is zo'n kleine AGV. De vaste sensorkast meet op één plaats, maar sla groeit langs het hele kweekbed, en een lek onder een goot zie je niet vanuit de sensorkast. Het robotje rijdt een ronde van bijna 3 m, stopt op drie meetpunten, meet er temperatuur, luchtvochtigheid en of de vloer nat is, en rijdt terug naar zijn laadstation.
Overbrenging en tandwielen (Fundamental, hoofdstuk 12), snelheid en beweging (hoofdstukken 7 tot 9), de spanningsdeler en sensoren (22), PWM (25), motoren en de H-brug (26), millis() en taken (29), regelen (31) en toestandsmachines (32). Dit hoofdstuk zet ze samen in één machine.

Het inspectierobotje
Het robotje is 20 cm lang en 16 cm breed: klein genoeg voor de gang van 80 cm tussen de buitenwand en het kweekbed, en groot genoeg voor een batterij die een dag meegaat. Onder staan de wielen, de motoren en de sensoren; op een tweede verdieping de Arduino Uno.
- 1Wielen
Twee wielen van 65 mm, 140 mm uit elkaar (de spoorbreedte b). Elk heeft een eigen motor: zo stuurt het robotje.
- 2Motor met tandwielkast
Een motortje van 6 V met een tandwielkast 1:48: de wielen draaien 48 keer trager, met bijna 48 keer meer koppel.
- 3Zwenkwiel
Een balletje of wieltje onder de achterkant: het draagt, maar stuurt niet.
- 4Batterij
Twee LiFePO4-cellen in serie: 6,4 V, ongeveer 1,5 Ah (±10 Wh). LiFePO4, zoals de batterij van de module (hoofdstuk 16).
- 5Motordriver
Een TB6612FNG: twee H-bruggen (hoofdstuk 26) op één printje, tot 1,2 A per motor. Per motor een PWM-pin en twee richtingspinnen.
- 6Reflectiesensoren
Drie sensoren onder de neus, 60 mm voor de as en 15 mm uit elkaar, 5 mm boven de vloer: ze zien de zwarte tape.
- 7Ultrasone sensor
De HC-SR04 met zender en ontvanger: meet de afstand tot wat voor het robotje staat, van 2 cm tot 4 m.
- 8Arduino Uno
Op de bovenste verdieping. Het robotje heeft zijn eigen microcontroller; de Mega van de module blijft voor de module.
- 9Mast met SHT31
Een dun mastje met een SHT31 op 0,50 m, de hoogte van de onderste goot: daar meet de vaste sensorkast niet.
- 10Lekdetectie
Twee koperstrips 3 mm boven de vloer, als spanningsdeler op A3: water tussen de strips laat de spanning zakken.
Twee wielen, één draaipunt
Rijden beide wielen even snel, dan gaat het robotje rechtdoor. Draait het rechterwiel sneller dan het linker, dan legt het rechterwiel in dezelfde tijd een langere weg af: het robotje draait naar links. Beide wielen draaien dan rond hetzelfde punt op de verlengde as, het draaipunt. Het rechterwiel ligt op afstand ervan, het linker op , met de straal van het midden van de as en de spoorbreedte. Omdat het hele robotje met dezelfde hoeksnelheid (in rad/s) draait, geldt en . Tel op en trek af:
Vier gevallen vallen op. Rechtdoor: , dus en oneindig. Bocht: en verschillend maar met hetzelfde teken. Om één wiel: geeft . Op de plaats: geeft en . Dat laatste kan een auto niet: die heeft een stuur en een kleinste draaicirkel.
Bochten en draaien in de gang
Het robotje () krijgt drie opdrachten. (a) en : bereken , en . (b) De lus in de gang heeft bochten met . Welke wielsnelheden horen bij in zo'n bocht naar rechts? (c) Hoe lang duurt een halve draai op de plaats als elk wiel rijdt?
- (a) ,
- (b) ,
- (c) ,
- , ,
- ,
- tijd voor 180°
- 1(a) ; ; .
- 2(b) , naar rechts dus negatief. Het buitenste (linker) wiel: ; het binnenste: .
- 3(c) Op de plaats: en . Een halve draai is rad: .
- 4Controle voor (c): elk wiel rijdt een halve cirkel met straal : , en dat duurt aan ook 2,20 s.
Van motor tot wiel: de overbrenging
Een klein gelijkstroommotortje draait snel maar zwak: onbelast ongeveer 9000 omwentelingen per minuut bij 6 V, met een piepklein koppel. Een wiel van 65 mm aan die as zou willen rijden, maar kreeg het robotje niet eens in beweging. Een tandwielkast ruilt toerental voor koppel, zoals de versnellingen van een fiets (Fundamental, hoofdstuk 12).
De kast van het robotje heeft drie trappen: 10 tanden op 40, nog eens 10 op 40, en 10 op 30. Samen . Het wiel draait dan onbelast omw/min, en het robotje rijdt . Het koppel aan het wiel is bijna 48 keer groter dan dat van de motor; 'bijna', omdat elk tandwielpaar wat wrijving verliest (hoofdstuk 9).
Onder belasting en met wat wrijving rekenen we met hoogstens 0,60 m/s bij PWM 255. En er is een dode zone: bij een kleine PWM-waarde draait het wiel niet, omdat de motor de wrijving in de kast niet overwint. Bij dit motortje ligt die grens rond 18 % (PWM 46). Daarboven loopt de snelheid ongeveer recht evenredig op. De code overbrugt de dode zone: wie 0,20 m/s wil, stuurt PWM 116, niet PWM 85.
Van motordata tot rijsnelheid
Het robotje moet 0,20 m/s rijden. Hoe snel draaien het wiel en de motoras dan? Welke PWM-waarde stuurt de code? Hoeveel kracht en vermogen vraagt het rijden, als de rolweerstand 2,5 % van het gewicht is (hoofdstuk 8) en het robotje 0,80 kg weegt?
- ,
- model: , met = PWM/255
- , rolweerstand 2,5 %
- ,
- PWM-waarde
- kracht en vermogen
- 1.
- 2.
- 3PWM: , dus PWM .
- 4Rolweerstand: voor het hele robotje, 0,098 N per wiel. Koppel per wiel: (hoofdstuk 10).
- 5Vermogen om te rijden: (hoofdstuk 9).
De dode zone van een wielmotor
Vanaf welke PWM-waarde draait een wiel, en hoe hangt de snelheid van de PWM-waarde af?
- het robotje (of één motor met wiel en driver)
- Arduino met een programma dat een vaste PWM-waarde stuurt
- meetlint en stopwatch (of een smartphone die filmt)
- tape: een rechte lijn van 1,0 m
- 1Laat beide motoren draaien met dezelfde PWM-waarde, te beginnen bij 0, en verhoog telkens een beetje.
- 2Noteer de kleinste waarde waarbij het robotje zelf vertrekt.
- 3Meet voor een reeks PWM-waarden hoe lang het robotje over 1,0 m doet (na een aanloop van 20 cm). Herhaal elke meting drie keer.
- 4Zet de snelheid uit tegen de PWM-waarde en trek een rechte door de punten boven de dode zone.
| PWM | 0 | 40 | 50 | 60 | 90 | 120 | 150 | 200 | 255 |
|---|---|---|---|---|---|---|---|---|---|
| v (m/s) | 0,000 | 0,000 | 0,012 | 0,043 | 0,123 | 0,218 | 0,293 | 0,448 | 0,592 |
Voorbeeldmeting. Tot PWM 40 blijft het robotje staan; bij 50 kruipt het nauwelijks vooruit; daarboven stijgt de snelheid netjes recht evenredig.
Een motor levert een koppel dat evenredig is met de stroom (hoofdstuk 26). Bij een kleine PWM-waarde is de gemiddelde spanning te laag om de wrijving in de tandwielkast en de rolweerstand te overwinnen: het wiel staat stil, de motor wordt alleen wat warm. Boven die drempel is de snelheid ongeveer evenredig met wat er boven de drempel uitkomt. Het snijpunt van de rechte met de horizontale as is de dode zone, hier rond PWM 46. Een regelaar die niets van de dode zone weet, stuurt bij kleine correcties niets: het binnenste wiel in een bocht zou stilvallen.
Een lijn zien: de reflectiesensor
Een reflectiesensor (bijvoorbeeld de TCRT5000) heeft een infrarood-led en een fototransistor naast elkaar, met een filter tegen daglicht. De led schijnt op de vloer; hoe meer licht er terugkaatst, hoe beter de fototransistor geleidt. Met een weerstand van 10 kΩ naar 5 V vormt hij een spanningsdeler (hoofdstuk 22): op een witte vloer geleidt hij goed en zakt de spanning op de pin; boven zwarte tape komt er weinig licht terug en blijft de spanning hoog. De sensor werkt het best vlak boven de vloer (de datasheet noemt 2,5 mm als beste afstand); het robotje heeft hem op ongeveer 5 mm, veilig boven drempels en voegen.
IJken is nodig, want elke sensor en elke vloer is anders. Houd elke sensor boven de vloer en boven de tape, en noteer de ADC-waarden. Hier: ongeveer 100 op wit en 850 op zwart. Het getal (ruw − wit) / (zwart − wit) geeft dan 0 voor wit en 1 voor zwart; de drempel in het midden is 475.
Het robotje heeft drie sensoren, 15 mm uit elkaar. De tape is 19 mm breed: ligt ze in het midden, dan ziet alleen de middelste sensor zwart. Schuift de lijn opzij, dan wordt een buitenste sensor geleidelijk zwarter. Die overgang is niet scherp, omdat elke sensor een vlekje van enkele millimeter ziet.
Lijnvolgen: aan/uit of evenredig
De eenvoudigste lijnvolger werkt aan/uit (hoofdstuk 31): elke sensor is zwart of wit, en een tabel zegt wat het robotje doet.
| links | midden | rechts | betekenis | aan/uit doet |
|---|---|---|---|---|
| wit | zwart | wit | lijn in het midden | rechtdoor |
| zwart | zwart | wit | lijn iets links | bijsturen naar links |
| zwart | wit | wit | lijn ver links | bijsturen naar links |
| wit | zwart | zwart | lijn iets rechts | bijsturen naar rechts |
| wit | wit | zwart | lijn ver rechts | bijsturen naar rechts |
| zwart | zwart | zwart | dwarsstreep | meetpunt |
| wit | wit | wit | lijn kwijt | zoeken aan de laatste kant |
Dat werkt, maar het robotje zigzagt: het stuurt altijd even hard bij, of het nu 2 of 12 mm naast de lijn rijdt. Beter is een evenredige of P-regelaar: hoe verder de lijn opzij ligt, hoe harder het robotje draait. De plaats van de lijn volgt uit de drie sensoren als gewogen gemiddelde:
In de simulatie (hierna) start het robotje 1 cm naast de lijn en rijdt het de bocht van de lus. Met aan/uit wijkt het midden van de as tot 48 mm af van de lijn: zijn vaste correctie (het ene wiel , het andere ) geeft een draaistraal van 0,17 m, te ruim voor de bocht van 0,15 m, en het zwaait naar buiten. Met de P-regelaar () is dat maar 7 mm. Kies je te klein, dan draait het robotje in de bocht niet genoeg en verliest het bijna de lijn (33 mm). Te groot, en het slingert na elke correctie (12 mm): de wielen hebben tijd nodig om een nieuwe snelheid te halen, en de regelaar overcorrigeert. Hetzelfde verschijnsel ken je van de trage sensor in hoofdstuk 31.
De lijnvolger simuleren
De simulatie rekent elke 2 ms uit waar het robotje staat: uit de wielsnelheden volgen en , en daaruit de nieuwe plaats en richting (hoofdstuk 7: ). Elke 20 ms kijkt de regelaar naar de sensoren, zoals de Arduino. Het model kent ook de dode zone en de traagheid van de wielen.
# lijnvolger.py - een robot met twee wielen volgt de lus in de gang (P-regelaar of aan/uit)import math B = 0.14 # m tussen de wielenA = 0.06 # m: de reflectiesensoren zitten voor de asV_MAX, D0 = 0.60, 0.18 # m/s bij PWM 255; dode zone van de motorenTAU = 0.08 # s: zo snel volgt een wiel een nieuwe snelheidDT, T_REGEL = 0.002, 0.02XA, XB, YA, YB, YC, R = 0.45, 1.40, 0.55, 0.25, 0.40, 0.15 # de lus (m) def tot_lijn(x, y): d = 9.0 if XA <= x <= XB: d = min(abs(y - YA), abs(y - YB)) if x >= XB: d = min(d, abs(math.hypot(x - XB, y - YC) - R)) if x <= XA: d = min(d, abs(math.hypot(x - XA, y - YC) - R)) return d def zwart(dy, x, y, th): """Hoe zwart ziet de sensor op dy (m, + = links) het? 0 = wit, 1 = zwart (tape 19 mm, vlekje 8 mm).""" sx = x + A * math.cos(th) - dy * math.sin(th) sy = y + A * math.sin(th) + dy * math.cos(th) return min(1.0, max(0.0, (0.0135 - tot_lijn(sx, sy)) / 0.008)) def wiel(v): """Wat het wiel echt doet: de code overbrugt de dode zone, PWM in hele stappen.""" if v == 0: return 0.0 pwm = round(255 * (D0 + (1 - D0) * min(abs(v) / V_MAX, 1.0))) return math.copysign(V_MAX * max(0.0, pwm / 255 - D0) / (1 - D0), v) def rij(regelaar, K=0.25, v0=0.20, x=0.55, y=0.56, th=0.0, T=10.0): vl = vr = doel_l = doel_r = 0.0 kant, pad = 1, [] for k in range(int(T / DT)): if k % int(T_REGEL / DT) == 0: # 50 keer per seconde beslissen w = [zwart(dy, x, y, th) for dy in (0.015, 0.0, -0.015)] if regelaar == "P": if sum(w) > 0.2: pos = (w[0] * 15 - w[2] * 15) / sum(w) # mm, + = lijn links kant = 1 if pos > 0 else -1 else: pos = 15 * kant # kwijt: zoek aan de laatste kant om = K * pos doel_l, doel_r = v0 - om * B / 2, v0 + om * B / 2 else: # aan/uit met drempel 0,5 links, midden, rechts = (s > 0.5 for s in w) if links and not rechts: kant = 1 elif rechts and not links: kant = -1 if midden and not (links or rechts): doel_l = doel_r = v0 else: # bijsturen naar de kant van de lijn doel_l, doel_r = (0.5 * v0, 1.2 * v0) if kant > 0 else (1.2 * v0, 0.5 * v0) vl += (wiel(doel_l) - vl) * DT / TAU vr += (wiel(doel_r) - vr) * DT / TAU v, om = (vl + vr) / 2, (vr - vl) / B x += v * math.cos(th) * DT y += v * math.sin(th) * DT th += om * DT pad.append((k * DT, x, y, tot_lijn(x, y))) return pad if __name__ == "__main__": for naam, K in (("aan/uit", 0), ("P, K = 0,08", 0.08), ("P, K = 0,25", 0.25), ("P, K = 1,5", 1.5)): pad = rij("aanuit" if K == 0 else "P", K) afw = [p[3] * 1000 for p in pad if p[0] > 0.5] weg = sum(math.hypot(b[1] - a[1], b[2] - a[2]) for a, b in zip(pad, pad[1:])) print(f"{naam:12} grootste afwijking {max(afw):5.1f} mm, gemiddeld {sum(afw) / len(afw):4.1f} mm, afgelegd {weg:.2f} m")Uitvoer:
aan/uit grootste afwijking 47.8 mm, gemiddeld 14.1 mm, afgelegd 1.85 m P, K = 0,08 grootste afwijking 33.4 mm, gemiddeld 7.6 mm, afgelegd 1.99 m P, K = 0,25 grootste afwijking 7.0 mm, gemiddeld 2.4 mm, afgelegd 1.99 m P, K = 1,5 grootste afwijking 12.2 mm, gemiddeld 3.2 mm, afgelegd 1.96 m
'Afgelegd' is de weg van het midden van de as in 10 s. Aan/uit komt minder ver: het zigzagt en rijdt bij elke correctie met één wiel trager. Alle paden in de figuren hiervoor komen uit deze functie.
Afstand meten met een echo
De HC-SR04 meet afstand zoals een vleermuis: met geluid dat we niet horen. Op een puls van 10 µs op TRIG zendt hij 8 trillingen van 40 kHz uit. Daarna maakt hij ECHO hoog, tot de echo terugkomt. De breedte van de ECHO-puls is de tijd die het geluid nodig had om heen en terug te gaan. Omdat het geluid de afstand twee keer aflegt:
Op 40 cm duurt de echo . De datasheet geeft de vuistregel 'microseconden gedeeld door 58 = centimeter': dat hoort bij ongeveer 22 °C. Geluid gaat sneller in warme lucht, dus meet het robotje de temperatuur met zijn SHT31 en rekent het met de echte geluidssnelheid.
In C++ doet pulseIn() het meetwerk: de functie wacht tot ECHO hoog wordt en meet hoe lang het hoog blijft, in microseconden. Met een time-out van 25 ms (4 m heen en terug) blijft het programma nooit hangen als er geen echo komt:
unsigned long us = pulseIn(ECHO, HIGH, 25000UL); // 0 als er geen echo kwam float v = 331.3 + 0.606 * luchtT; // m/s float cm = us * 1e-6 * v / 2.0 * 100.0;
Een echo omrekenen
De HC-SR04 meet een echo van 1,75 ms. (a) Hoe ver staat het voorwerp bij 20 °C? (b) Het is 35 °C in de module, maar de code rekent met 343,4 m/s. Hoe groot is de fout? (c) Het robotje rijdt 0,20 m/s en meet om de 60 ms. Hoeveel rijdt het tussen twee metingen?
- meten om de 60 ms, 0,20 m/s
- afstand
- fout bij 35 °C
- weg tussen twee metingen
- 1(a) .
- 2(b) Bij 35 °C: ; de echte afstand is . De code geeft 0,300 m: 8 mm te weinig, of 2,6 %.
- 3(c) : 12 mm per meting.
Wat een ultrasone sensor niet ziet
De HC-SR04 meet van 2 cm tot ongeveer 4 m, maar niet alles:
- Een bundel, geen straal. Het geluid waaiert uit (de datasheet noemt een meethoek van 15°; in de simulatie rekenen we met 15° naar elke kant). Alles in die kegel kan een echo geven, en de sensor meldt de dichtste. Een smalle tafelpoot net naast de kegel ziet hij niet.
- Schuine en zachte oppervlakken. Een schuine wand kaatst het geluid weg, weg van de sensor; zachte bladeren en stof slorpen het op. Het robotje ziet een krop sla slecht, een emmer goed.
- Te snel na elkaar meten. Een late echo van de vorige meting kan als nieuwe echo tellen. Daarom meet het robotje hoogstens om de 60 ms.
- Andere sensoren. Twee robots met een HC-SR04 in dezelfde ruimte kunnen elkaars puls horen.
Het robotje stopt daarom niet op 'net niet', maar met marge: bij minder dan 25 cm staat het stil, en het rijdt pas verder boven 30 cm. Dat verschil is hysterese (hoofdstuk 31): zonder dat zou het robotje rond 25 cm schokkend stoppen en vertrekken, want de meting schommelt een paar millimeter.
Een robotje van 0,8 kg aan 0,20 m/s doet niemand pijn, maar een voet of een knie die het niet ziet, kan het wel laten vallen of vastlopen. Traag rijden, stoppen met marge, en nooit rijden in de modus ONDERHOUD van de module (hoofdstuk 32) zijn eenvoudige regels.
Het gedrag als toestandsmachine
Een lijn volgen en een afstand meten is niet genoeg: het robotje moet ook weten wat het wil. Dat is een toestandsmachine (hoofdstuk 32) met zeven toestanden. RIJDEN is een samengestelde toestand: uit elk van de vijf binnenste toestanden leidt een fout (lijn 1,5 s kwijt, vast, een time-out) naar FOUT.
- LADEN: op het laadstation. Een druk op de knop (of later een opdracht van de module) start een ronde.
- VOLGEN: lijn volgen aan 0,20 m/s met de P-regelaar. Een dwarsstreep (alle drie de sensoren zwart) is een meetpunt.
- METEN: 10 s stilstaan, meten en melden.
- WACHTEN: er staat iets op minder dan 25 cm. Wordt het vrij, dan verder. Blijft het 20 s, dan is de gang geblokkeerd.
- KEREN: op de plaats draaien tot de lijn weer onder de middelste sensor ligt, maar niet voor 1,8 s voorbij is: anders ziet het robotje meteen de lijn waar het al op stond.
- TERUG: trager naar het laadstation; dwarsstrepen tellen niet meer.
- FOUT: stil, de led knippert, tot iemand het robotje op het laadstation zet.
Merk op hoe dezelfde meting in verschillende toestanden iets anders betekent. Een dwarsstreep in VOLGEN is een meetpunt, in TERUG niets. Een afstand van 20 cm in VOLGEN betekent stoppen; in KEREN wordt ze niet eens bekeken.
robot.ino: het robotje in C++
Het programma volgt het patroon van hoofdstuk 32: een enum, één toestandsvariabele, naar() met acties bij binnenkomen, en een switch die 50 keer per seconde de gebeurtenissen bekijkt. De afstand wordt om de 60 ms gemeten, los van het regelen. De rechtermotor is gespiegeld gemonteerd; in de bedrading zijn daarom zijn twee motordraden verwisseld, zodat 'vooruit' voor beide motoren hetzelfde betekent.
// robot.ino - het inspectierobotje van de klimaatmodule (hoofdstuk 33)// Arduino Uno, motordriver TB6612FNG, drie reflectiesensoren (A0-A2), lekdetectie (A3), HC-SR04, SHT31 op een mastje.#include <Wire.h>#include <Adafruit_SHT31.h> // ---------------------------------------------------------------- pinnenconst int TRIG = 2, ECHO = 3, STBY = 4;const int PWM_L = 5, IN1_L = 7, IN2_L = 8; // linkermotor (kanaal A)const int PWM_R = 6, IN1_R = 9, IN2_R = 10; // rechtermotor (kanaal B)const int KNOP = 11, LAAD = 12, LED = 13; // startknop, laadcontacten, statusledconst int REFL_L = A0, REFL_M = A1, REFL_R = A2, LEK = A3; // ---------------------------------------------------------------- instellingenconst float B_SPOOR = 0.14; // m tussen de twee wielenconst float V_MAX = 0.60; // m/s bij PWM 255const float D0 = 0.18; // dode zone: onder 18 % PWM draait het wiel nietconst float V_RIJ = 0.20, V_TERUG = 0.12, V_DRAAI = 0.10; // m/sconst float K_P = 0.25; // rad/s per mm dat de lijn opzij ligtconst int WIT = 100, ZWART = 850; // ADC-waarden na het ijkenconst float STOP_CM = 25.0, VRIJ_CM = 30.0; // hysterese op de afstandconst unsigned long REGEL_MS = 20, SONAR_MS = 60;const unsigned long METEN_MS = 10000, WACHT_MAX_MS = 20000, KWIJT_MS = 1500;const unsigned long DRAAI_MIN_MS = 1800, DRAAI_MAX_MS = 5000; enum Toestand { LADEN, VOLGEN, METEN, WACHTEN, KEREN, TERUG, FOUT };const char* NAAM[] = {"LADEN", "VOLGEN", "METEN", "WACHTEN", "KEREN", "TERUG", "FOUT"}; Adafruit_SHT31 sht;Toestand toestand = LADEN, naWachten = VOLGEN;unsigned long sinds = 0, vorigeRegel = 0, vorigeSonar = 0, kwijtSinds = 0;int punten = 0, kant = 1; // meetpunten deze ronde; waar de lijn laatst lagbool opStreep = false, kwijt = false;float afstand = 999.0, luchtT = 20.0; // ---------------------------------------------------------------- motoren (TB6612FNG)void zetMotor(int pwm, int in1, int in2, float v) { // v in m/s, negatief = achteruit float f = min(fabs(v) / V_MAX, 1.0); int d = (v == 0.0) ? 0 : (int)(255 * (D0 + (1.0 - D0) * f) + 0.5); // de dode zone overbruggen digitalWrite(in1, v > 0 ? HIGH : LOW); digitalWrite(in2, v < 0 ? HIGH : LOW); analogWrite(pwm, d);} void rijden(float vL, float vR) { zetMotor(PWM_L, IN1_L, IN2_L, vL); zetMotor(PWM_R, IN1_R, IN2_R, vR);} // ---------------------------------------------------------------- sensorenfloat wit01(int pin) { // 0 = wit, 1 = zwart return constrain((analogRead(pin) - WIT) / (float)(ZWART - WIT), 0.0, 1.0);} float lijnPositie(bool& streep) { // mm, + = de lijn ligt links; NAN = kwijt float wl = wit01(REFL_L), wm = wit01(REFL_M), wr = wit01(REFL_R); streep = wl > 0.6 && wm > 0.6 && wr > 0.6; // dwarsstreep: alle drie zwart float g = wl + wm + wr; if (g < 0.2) return NAN; return (wl * 15.0 - wr * 15.0) / g; // gewogen gemiddelde van +15, 0 en -15 mm} float meetAfstand() { // HC-SR04: s = v * t / 2 digitalWrite(TRIG, LOW); delayMicroseconds(2); digitalWrite(TRIG, HIGH); delayMicroseconds(10); digitalWrite(TRIG, LOW); unsigned long us = pulseIn(ECHO, HIGH, 25000UL); // na 25 ms (4 m heen en terug) opgeven if (us == 0) return 999.0; // geen echo: niets binnen bereik float v = 331.3 + 0.606 * luchtT; // geluidssnelheid in m/s return us * 1e-6 * v / 2.0 * 100.0; // cm} // ---------------------------------------------------------------- toestandenvoid melding(const char* tekst) { Serial.print(millis() / 1000.0, 1); Serial.print(" s "); Serial.println(tekst);} void meetpunt() { // temperatuur, vocht en lekdetectie float t, rv; sht.readBoth(&t, &rv); if (!isnan(t)) luchtT = t; Serial.print(millis() / 1000.0, 1); Serial.print(" s meetpunt "); Serial.print(punten); Serial.print(": "); Serial.print(t, 1); Serial.print(" C, "); Serial.print(rv, 0); Serial.println(analogRead(LEK) < 500 ? " %, NAT: lek onder de goot!" : " %, droog");} void naar(Toestand nieuw, unsigned long nu) { toestand = nieuw; sinds = nu; melding(NAAM[nieuw]); switch (nieuw) { // acties bij binnenkomen case KEREN: rijden(-V_DRAAI, V_DRAAI); break; // op de plaats draaien (tegen de klok in) case METEN: rijden(0, 0); meetpunt(); break; case VOLGEN: case TERUG: kwijt = false; break; default: rijden(0, 0); break; // LADEN, WACHTEN, FOUT: stilstaan }} void volg(float pos, float v0) { // P-regelaar: draai naar de lijn toe if (isnan(pos)) pos = 15.0 * kant; // kwijt: zoek aan de kant waar ze laatst lag else kant = pos > 0 ? 1 : -1; float omega = K_P * pos; // rad/s rijden(v0 - omega * B_SPOOR / 2, v0 + omega * B_SPOOR / 2);} bool lijnKwijt(float pos, unsigned long nu) { if (!isnan(pos)) { kwijt = false; return false; } if (!kwijt) { kwijt = true; kwijtSinds = nu; } return nu - kwijtSinds > KWIJT_MS;} void setup() { int uit[] = {TRIG, STBY, PWM_L, IN1_L, IN2_L, PWM_R, IN1_R, IN2_R, LED}; for (int p : uit) pinMode(p, OUTPUT); pinMode(ECHO, INPUT); pinMode(KNOP, INPUT_PULLUP); pinMode(LAAD, INPUT); digitalWrite(STBY, HIGH); // motordriver aan Serial.begin(115200); Wire.begin(); sht.begin(0x44); naar(digitalRead(LAAD) == HIGH ? LADEN : FOUT, millis()); // niet op het laadstation: eerst terugzetten} void loop() { unsigned long nu = millis(); if (nu - vorigeSonar >= SONAR_MS) { // hoogstens om de 60 ms meten (datasheet) vorigeSonar += SONAR_MS; afstand = meetAfstand(); } if (nu - vorigeRegel < REGEL_MS) return; // 50 keer per seconde beslissen vorigeRegel += REGEL_MS; bool streep; float pos = lijnPositie(streep); bool nieuweStreep = streep && !opStreep; opStreep = streep; unsigned long t = nu - sinds; digitalWrite(LED, toestand == FOUT ? (nu / 200) % 2 : toestand != LADEN); switch (toestand) { case LADEN: if (digitalRead(KNOP) == LOW) { punten = 0; naar(VOLGEN, nu); } break; case VOLGEN: if (afstand < STOP_CM) { naWachten = VOLGEN; naar(WACHTEN, nu); } else if (nieuweStreep) { punten++; naar(METEN, nu); } else if (lijnKwijt(pos, nu)) naar(FOUT, nu); else volg(pos, V_RIJ); break; case METEN: if (t >= METEN_MS) naar(punten >= 3 ? TERUG : VOLGEN, nu); break; case WACHTEN: if (afstand > VRIJ_CM && t >= 1000) naar(naWachten, nu); else if (t >= WACHT_MAX_MS) { if (naWachten == VOLGEN) { melding("gang geblokkeerd: omkeren"); naar(KEREN, nu); } else { melding("vast: de weg terug is ook geblokkeerd"); naar(FOUT, nu); } } break; case KEREN: if (t >= DRAAI_MIN_MS && wit01(REFL_M) > 0.6) naar(TERUG, nu); // lijn weer onder het midden else if (t >= DRAAI_MAX_MS) naar(FOUT, nu); break; case TERUG: // trager, dwarsstrepen tellen niet meer if (digitalRead(LAAD) == HIGH) naar(LADEN, nu); else if (afstand < STOP_CM) { naWachten = TERUG; naar(WACHTEN, nu); } else if (lijnKwijt(pos, nu)) naar(FOUT, nu); else volg(pos, V_TERUG); break; case FOUT: // stil en knipperen tot iemand hem op het laadstation zet if (digitalRead(LAAD) == HIGH) naar(LADEN, nu); break; }}Getest in een gesimuleerde gang: de sensoren krijgen hun waarden uit de plaats van het robotje, de echo uit de afstand tot de wanden, het vat en wat er in de weg staat. In ronde 1 staat er van 26 tot 38 s een emmer op de lijn, en ligt er een plas bij het tweede meetpunt. In ronde 2 blijft er een krat staan. De seriële monitor:
0.0 s LADEN 2.0 s VOLGEN 6.5 s METEN 6.5 s meetpunt 1: 20.0 C, 68 %, droog 16.5 s VOLGEN 18.0 s METEN 18.0 s meetpunt 2: 20.0 C, 68 %, NAT: lek onder de goot! 28.0 s VOLGEN 28.6 s WACHTEN 38.0 s VOLGEN 39.0 s METEN 39.1 s meetpunt 3: 20.0 C, 68 %, droog 49.0 s TERUG 59.8 s LADEN 110.0 s VOLGEN 114.5 s METEN 114.5 s meetpunt 1: 20.0 C, 68 %, droog 124.5 s VOLGEN 125.8 s WACHTEN 145.8 s gang geblokkeerd: omkeren 145.8 s KEREN 148.0 s TERUG 157.6 s LADEN
Ronde 1 duurt 57,8 s, inclusief 30 s meten en 9 s wachten. In ronde 2 wacht het robotje 20 s voor de krat, keert om op de plaats (2,2 s) en rijdt tegen de draairichting in terug naar het laadstation, via de andere bocht. Het midden van de as bleef tijdens het volgen overal binnen 1 cm van de lijn.
Habitat: de route in de module
In de klimaatmodule ligt tussen de buitenwand en het kweekbed een gang van 0,80 m breed. Daarin loopt een lus van zwarte tape (19 mm) met twee rechte stroken (strook A langs het kweekbed, strook B langs de wand) en twee bochten met een straal van 0,15 m. Op strook A liggen drie dwarsstrepen: de meetpunten. Op strook B staat het laadstation, met contacten in de vloer. Het vat van 200 L staat in de hoek: de lus blijft er bijna 30 cm van weg, zodat de ultrasone sensor het in de bocht niet als obstakel ziet.
De lus is lang. Een ronde (heen aan 0,20 m/s, terug aan 0,12 m/s, drie keer 10 s meten) duurt ongeveer 50 s en kost ongeveer 0,024 Wh. Overdag elk uur een ronde (16 per dag) is 0,4 Wh. Tussen de rondes staat het robotje op zijn laadstation; daar gebruikt vooral de Uno stroom (±0,3 W), en die levert het laadstation. Wat het robotje meet, stuurt het later draadloos naar de module (Expert, hoofdstuk 37); voorlopig schrijft het elke meting naar zijn seriële poort.
Waarom een lijn en geen vrije route? Een robotje dat zijn plaats alleen uit de omwentelingen van zijn wielen berekent (odometrie), dwaalt af: een wiel dat 1 % groter is dan het andere, laat het na 10 m rijden 41° scheef staan. De lijn verbetert die fout bij elke meting. Zonder lijn heb je andere sensoren nodig en een slimme combinatie ervan (Expert, hoofdstuk 40).
Formules en gegevens van het robotje
| symbool | betekenis | eenheid |
|---|---|---|
| snelheid van linker- en rechterwiel | m/s | |
| spoorbreedte | m | |
| hoeksnelheid | rad/s |
| symbool | betekenis | eenheid |
|---|---|---|
| toerental | omw/min | |
| wieldiameter | m |
| symbool | betekenis | eenheid |
|---|---|---|
| zwartheid van een sensor (0 tot 1) | – | |
| plaats van de lijn | mm | |
| versterking | rad/(s·mm) |
| symbool | betekenis | eenheid |
|---|---|---|
| tijd heen en terug | s | |
| temperatuur | °C |
| symbool | betekenis | eenheid |
|---|---|---|
| rijrichting | rad |
| onderdeel | waarde |
|---|---|
| maten | 200 × 160 mm, b = 140 mm, wielen Ø 65 mm, ±0,8 kg |
| motoren | 6 V, tandwielkast 1:48; wiel 187,5 omw/min onbelast; dode zone PWM 46 |
| snelheden | volgen 0,20 m/s (PWM 116), terug 0,12 m/s, draaien 0,10 m/s per wiel |
| pinnen (Uno) | TRIG D2, ECHO D3, STBY D4, motor links D5/D7/D8, rechts D6/D9/D10, knop D11, laadcontact D12, led D13, sensoren A0-A2, lek A3, SHT31 op A4/A5 (I²C) |
| regelaar | K = 0,25 rad/s per mm, 50 keer per seconde |
| afstand | stop onder 25 cm, verder boven 30 cm, meten om de 60 ms |
| batterij | 2 × LiFePO4, 6,4 V, ±1,5 Ah |
Waar robotica vandaan komt en waar ze heen gaat
Een mobiele robot is een klein systeem waarin bijna alle leerlijnen samenkomen: krachten en beweging (de wielen, de overbrenging), elektronica (sensoren, H-brug, PWM), programmeren (taken, toestandsmachines), regelen (de lijnvolger) en ontwerpen (maten, veiligheid). In Expert leert het robotje zijn eigen weg zoeken en zijn plaats schatten met meerdere sensoren.
Tandwielen en de overbrengingsverhouding: de tandwielkast van de motoren.
Snelheid, plaats en tijd: de plaats van de robot stap voor stap bijhouden.
Vermogen = kracht × snelheid; rendement van een kleine motor.
De reflectiesensor is een spanningsdeler met een fototransistor.
PWM stuurt de snelheid van de motoren.
Gelijkstroommotor, koppel en de H-brug.
Aan/uit tegenover evenredig, en hysterese op de afstand.
Het gedrag van het robotje als toestandsmachine met veilige toestand.
De kortste route door het station voor de inspectierobot.
De plaats schatten uit wielen, lijn en afstandssensor samen.
Een PID-lijnvolger: sneller en zonder blijvende afwijking in de bocht.
In het kort
- Met twee aangedreven wielen: , en ; tegengesteld draaien geeft .
- De tandwielkast maakt het toerental keer kleiner en het koppel bijna keer groter; .
- Een motor heeft een dode zone; de code moet ze overbruggen om kleine snelheden te kunnen rijden.
- Een reflectiesensor is een spanningsdeler met een fototransistor; ijk op wit en zwart en werk met 0 tot 1.
- Een P-lijnvolger draait evenredig met hoe ver de lijn opzij ligt; te kleine K mist bochten, te grote K slingert.
- Een ultrasone sensor meet met ; hij ziet een kegel, geen punt.
- Het gedrag van een robot is een toestandsmachine; dezelfde meting betekent iets anders in een andere toestand.
- Het inspectierobotje rijdt een lus van ±3 m in de gang, meet op drie punten en keert terug naar zijn laadstation.
Wat je nu kent
- differentiële aandrijving
- Twee wielen met elk een eigen motor; de robot stuurt door de wielen verschillend snel te laten draaien.
- hoeksnelheid
- Hoe snel een voorwerp draait, in radialen per seconde (ω).
- draaistraal
- De straal R van de cirkel die het midden van de as beschrijft; R = v/ω.
- dode zone
- Het gebied van kleine stuursignalen waarin een motor nog niet draait, omdat de wrijving te groot is.
- reflectiesensor
- Een IR-led en een fototransistor naast elkaar: hoe meer licht de vloer terugkaatst, hoe meer stroom de transistor geleidt.
- lijnvolger
- Een robot die met sensoren onder zijn neus een lijn op de vloer volgt.
- versterking
- Bij een P-regelaar: hoeveel harder de robot draait per millimeter dat de lijn opzij ligt.
- echo
- Geluid dat terugkaatst op een voorwerp; de tijd heen en terug geeft de afstand.
- odometrie
- De plaats van een robot bijhouden uit hoeveel elk wiel gedraaid heeft.