← Back to CoursesStemExpert
StemExpert
Intermediate · Handboek · 33. Een mobiele robot
StemExpert · Brecht Corbeel · schoolium.me
StemExpert
I33. Een mobiele robot
IIntermediate · deel I8 · Regelen en robotica

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.

8× uitleg3× uitgewerkt voorbeeld1× proef1× ontleed1× naslag1× verhaal1× het geheel2× code16 opdrachten in het werkboek± 9 lestijden
Het inspectierobotje meet waar de vaste sensorkast niet komt: laag, langs het hele kweekbed.
Het inspectierobotje meet waar de vaste sensorkast niet komt: laag, langs het hele kweekbed.
Na dit hoofdstuk
Verhaal · 33.1

Een trekker die een draad volgt

1/18

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.

Wat je al weet

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.

Een AGV in een magazijn volgt een lijn op de vloer, net als het robotje in de module.
Een AGV in een magazijn volgt een lijn op de vloer, net als het robotje in de module.
Ontleed · 33.2

Het inspectierobotje

2/18

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.

1:481:486,4VUno (boven)200 mmb = 140 mmØ 65 mm
Bovenaanzicht; de neus wijst naar rechts. Stippellijnen: onder de bodemplaat of op de bovenste verdieping.
Wijs een nummer aan: de figuur zoomt in. Klik om vast te zetten.
  1. 1
    Wielen

    Twee wielen van 65 mm, 140 mm uit elkaar (de spoorbreedte b). Elk heeft een eigen motor: zo stuurt het robotje.

  2. 2
    Motor met tandwielkast

    Een motortje van 6 V met een tandwielkast 1:48: de wielen draaien 48 keer trager, met bijna 48 keer meer koppel.

  3. 3
    Zwenkwiel

    Een balletje of wieltje onder de achterkant: het draagt, maar stuurt niet.

  4. 4
    Batterij

    Twee LiFePO4-cellen in serie: 6,4 V, ongeveer 1,5 Ah (±10 Wh). LiFePO4, zoals de batterij van de module (hoofdstuk 16).

  5. 5
    Motordriver

    Een TB6612FNG: twee H-bruggen (hoofdstuk 26) op één printje, tot 1,2 A per motor. Per motor een PWM-pin en twee richtingspinnen.

  6. 6
    Reflectiesensoren

    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.

  7. 7
    Ultrasone sensor

    De HC-SR04 met zender en ontvanger: meet de afstand tot wat voor het robotje staat, van 2 cm tot 4 m.

  8. 8
    Arduino Uno

    Op de bovenste verdieping. Het robotje heeft zijn eigen microcontroller; de Mega van de module blijft voor de module.

  9. 9
    Mast met SHT31

    Een dun mastje met een SHT31 op 0,50 m, de hoogte van de onderste goot: daar meet de vaste sensorkast niet.

  10. 10
    Lekdetectie

    Twee koperstrips 3 mm boven de vloer, als spanningsdeler op A3: water tussen de strips laat de spanning zakken.

Uitleg · 33.3

Twee wielen, één draaipunt

3/18

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:

differentiële aandrijving
: snelheid van het midden van de as (m/s); : hoeksnelheid (rad/s), positief = naar links (tegen de klok in); : spoorbreedte (m); : draaistraal (m).

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.

draaipuntvLvRvbRv = (vL + vR) / 2ω = (vR − vL) / bR = v / ωvoorbeeld:vL = 0,15 m/svR = 0,25 m/sb = 0,14 mR = 0,28 m
De snelheid van elk wiel is evenredig met zijn afstand tot het draaipunt.
rechtdoorvL = vRR oneindigbochtvL < vRR = 0,14 mom één wielvL = 0R = b/2 = 0,07 mop de plaatsvL = −vRR = 0
Uitgerekend met de formules: vier keer ongeveer een kwartslag, telkens met een andere draaistraal.
Uitgewerkt voorbeeld · 33.4

Bochten en draaien in de gang

4/18

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?

Gegeven
  • (a) ,
  • (b) ,
  • (c) ,
Gevraagd
  • , ,
  • ,
  • tijd voor 180°
Oplossing
  1. 1
    (a) ; ; .
  2. 2
    (b) , naar rechts dus negatief. Het buitenste (linker) wiel: ; het binnenste: .
  3. 3
    (c) Op de plaats: en . Een halve draai is rad: .
  4. 4
    Controle voor (c): elk wiel rijdt een halve cirkel met straal : , en dat duurt aan ook 2,20 s.
Antwoord
(a) 0,20 m/s, 0,71 rad/s, R = 0,28 m. (b) Buitenwiel 0,29 m/s, binnenwiel 0,11 m/s. (c) 2,2 s.
Klopt dit? Een bocht met een straal kleiner dan b/2 = 0,07 m laat het binnenste wiel achteruit draaien: probeer R = 0,05 m. In de gang van 0,80 m kan het robotje overal omkeren: bij een halve draai op de plaats beschrijft zijn verste hoek een cirkel van 0,14 m straal.
Uitleg · 33.5

Van motor tot wiel: de overbrenging

5/18

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).

overbrenging en wielsnelheid
Bij meerdere trappen vermenigvuldig je de verhoudingen. in omw/min, in m, in m/s.

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.

trap 1: 40 : 10 = 4zelfde astrap 2: 40 : 10 = 4zelfde astrap 3: 30 : 10 = 3motor9000 omw/minwiel187,5 omw/minsamen: i = 4 · 4 · 3 = 48
Drie keer een klein tandwiel op een groot: 4 · 4 · 3 = 48 keer trager en sterker.
0204060801001201401601802002202402600,000,050,100,150,200,250,300,350,400,450,500,550,600,65PWM-waardewielsnelheid (m/s)PWM 116 geeft 0,20 m/smodel: dode zone tot PWM 46voorbeeldmeting
Onder PWM 46 staat het wiel stil; daarboven stijgt de snelheid recht evenredig.
Uitgewerkt voorbeeld · 33.6

Van motordata tot rijsnelheid

6/18

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?

Gegeven
  • ,
  • model: , met = PWM/255
  • , rolweerstand 2,5 %
Gevraagd
  • ,
  • PWM-waarde
  • kracht en vermogen
Oplossing
  1. 1
    .
  2. 2
    .
  3. 3
    PWM: , dus PWM .
  4. 4
    Rolweerstand: voor het hele robotje, 0,098 N per wiel. Koppel per wiel: (hoofdstuk 10).
  5. 5
    Vermogen om te rijden: (hoofdstuk 9).
Antwoord
Wiel 58,8 omw/min, motoras 2821 omw/min, PWM 116. Rijden kost maar 0,04 W mechanisch.
Klopt dit? De motoren nemen samen toch ongeveer 2 W op: bijna alles gaat verloren in de weerstand van de wikkelingen, de wrijving in de kast en de driver. Bij zo'n klein vermogen is het rendement laag (hoofdstuk 26). Een ronde van ongeveer 50 s kost zo, met de elektronica erbij, ongeveer 0,024 Wh: op één lading haalt het robotje honderden rondes.
Proef · 33.7

De dode zone van een wielmotor

7/18
Onderzoeksvraag

Vanaf welke PWM-waarde draait een wiel, en hoe hangt de snelheid van de PWM-waarde af?

Materiaal
  • 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
VeiligheidLaat het robotje niet van een tafel rijden: meet op de vloer, of zet het op een blokje met de wielen vrij.
0204060801001201401601802002202402600,000,050,100,150,200,250,300,350,400,450,500,550,600,65PWM-waardewielsnelheid (m/s)PWM 116 geeft 0,20 m/smodel: dode zone tot PWM 46voorbeeldmeting
Voorbeeldmeting en model: de rechte snijdt de as bij de grens van de dode zone.
Werkwijze
  1. 1Laat beide motoren draaien met dezelfde PWM-waarde, te beginnen bij 0, en verhoog telkens een beetje.
  2. 2Noteer de kleinste waarde waarbij het robotje zelf vertrekt.
  3. 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.
  4. 4Zet de snelheid uit tegen de PWM-waarde en trek een rechte door de punten boven de dode zone.
Waarneming
PWM040506090120150200255
v (m/s)0,0000,0000,0120,0430,1230,2180,2930,4480,592

Voorbeeldmeting. Tot PWM 40 blijft het robotje staan; bij 50 kruipt het nauwelijks vooruit; daarboven stijgt de snelheid netjes recht evenredig.

Verklaring

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.

Uitleg · 33.8

Een lijn zien: de reflectiesensor

8/18

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.

5 V220 ΩIR-led10 kΩnaar A1fototransistorvloer: wit kaatst veel IR terug, zwarte tape weinig
Veel teruggekaatst IR-licht: de transistor geleidt, de spanning op A1 zakt.
−40−30−20−100102030400100200300400500600700800900plaats van de lijn tegenover het midden (mm)ADC-waardedrempel 475links (+15 mm)middenrechts (−15 mm)
Model van de drie sensoren: elke sensor ziet de tape over ongeveer 25 mm.
Uitleg · 33.9

Lijnvolgen: aan/uit of evenredig

9/18

De eenvoudigste lijnvolger werkt aan/uit (hoofdstuk 31): elke sensor is zwart of wit, en een tabel zegt wat het robotje doet.

linksmiddenrechtsbetekenisaan/uit doet
witzwartwitlijn in het middenrechtdoor
zwartzwartwitlijn iets linksbijsturen naar links
zwartwitwitlijn ver linksbijsturen naar links
witzwartzwartlijn iets rechtsbijsturen naar rechts
witwitzwartlijn ver rechtsbijsturen naar rechts
zwartzwartzwartdwarsstreepmeetpunt
witwitwitlijn kwijtzoeken 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:

P-regelaar voor een lijnvolger
: hoe zwart elke sensor ziet (0 tot 1); in mm, positief = de lijn ligt links; in rad/s per mm. De wielen krijgen en .

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.

aan/uitP (K = 0,25)zwarte tape, 19 mmrijrichtingR = 0,15 m
Exact getekend uit de simulatie: aan/uit zwaait in de bocht naar buiten, P blijft bij de tape.
bocht01234567891005101520253035t (s)afwijking van de lijn (mm)K = 0,08 (te zwak)K = 1,5 (te sterk)K = 0,25
Afwijking van de as: te zwak mist de bocht bijna, te sterk slingert, K = 0,25 is een goede keuze.
Code · 33.10

De lijnvolger simuleren

10/18

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.

Pythonlijnvolger.py77 regelsDownload
# 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.

Uitleg · 33.11

Afstand meten met een echo

11/18

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:

afstand uit een echo
: geluidssnelheid in m/s, : luchttemperatuur in °C. Bij 20 °C is .

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;
TRIG (pin 2)40 kHz-pulsECHO (pin 3)−1004009001400190024002900t (µs)
De 40 kHz-puls duurt 200 µs; de breedte van ECHO (2,33 ms) is de tijd heen en terug.
−10010203040325330335340345350355360luchttemperatuur T (°C)geluidssnelheid (m/s)20 °C: 343,4 m/sv = 331,3 + 0,606 · T
Tussen 5 en 35 °C verandert de geluidssnelheid met ongeveer 5 %.
Uitgewerkt voorbeeld · 33.12

Een echo omrekenen

12/18

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?

Gegeven
  • meten om de 60 ms, 0,20 m/s
Gevraagd
  • afstand
  • fout bij 35 °C
  • weg tussen twee metingen
Oplossing
  1. 1
    (a) .
  2. 2
    (b) Bij 35 °C: ; de echte afstand is . De code geeft 0,300 m: 8 mm te weinig, of 2,6 %.
  3. 3
    (c) : 12 mm per meting.
Antwoord
(a) 0,300 m. (b) ongeveer 8 mm (2,6 %) te kort. (c) 12 mm.
Klopt dit? Een fout van 2,6 % op 30 cm is 8 mm: klein tegenover de marge die het robotje neemt (het stopt op 25 cm). Voor het stoppen is de temperatuurcorrectie dus niet nodig, voor een meting van de gang wel nuttig.
Uitleg · 33.13

Wat een ultrasone sensor niet ziet

13/18

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 robot tussen mensen

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.

15°emmertafelpoot (buiten de bundel)buitenwandafstand gemeten tot het dichtste punt in de bundel
Alles in de kegel geeft een echo; de sensor meldt de kortste afstand.
Uitleg · 33.14

Het gedrag als toestandsmachine

14/18

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.

RIJDENLADENop het stationVOLGEN0,20 m/sMETENstil, 10 sWACHTENstilKERENop de plaatsTERUG0,12 m/sFOUTstil, led knippertknop /punten = 0streep / punten + 1[10 s en punten < 3][10 s enpunten = 3][< 25 cm][> 30 cm][20 s] /'geblokkeerd'[1,8 s en lijnonder het midden][< 25 cm][> 30 cm]laadcontact[lijn kwijt, vast,time-out]laadcontact
Het robotje heeft zeven toestanden; alle fouten tijdens het rijden leiden naar FOUT.
Code · 33.15

robot.ino: het robotje in C++

15/18

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.

C++ (Arduino)robot.ino183 regelsDownload
// 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;  }}
rand van het kweekbedvatemmer (26-38 s)krat (vanaf 100 s)plas123kerenlaadstation
Het gelogde pad uit de test. Blauw: VOLGEN, paars: TERUG, rood: KEREN. De cijfers zijn de meetpunten.

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.

Uitleg · 33.16

Habitat: de route in de module

16/18

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).

deur(station)kweekbed 2,40 × 1,20 m (goten)vat 200 Lmeetpunten 1, 2, 3laadstationrijrichting1 mgang 0,80 m
De lus in de gang, de drie meetpunten, het laadstation en het vat.
Naslag · 33.17

Formules en gegevens van het robotje

17/18
differentiële aandrijving
symboolbetekeniseenheid
snelheid van linker- en rechterwielm/s
spoorbreedtem
hoeksnelheidrad/s
overbrenging en wielsnelheid
symboolbetekeniseenheid
toerentalomw/min
wieldiameterm
P-lijnvolger
symboolbetekeniseenheid
zwartheid van een sensor (0 tot 1)–
plaats van de lijnmm
versterkingrad/(s·mm)
afstand uit een echo
symboolbetekeniseenheid
tijd heen en terugs
temperatuur°C
plaats bijhouden (simulatie, odometrie)
symboolbetekeniseenheid
rijrichtingrad
onderdeelwaarde
maten200 × 160 mm, b = 140 mm, wielen Ø 65 mm, ±0,8 kg
motoren6 V, tandwielkast 1:48; wiel 187,5 omw/min onbelast; dode zone PWM 46
snelhedenvolgen 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)
regelaarK = 0,25 rad/s per mm, 50 keer per seconde
afstandstop onder 25 cm, verder boven 30 cm, meten om de 60 ms
batterij2 × LiFePO4, 6,4 V, ±1,5 Ah
Het geheel · 33.18

Waar robotica vandaan komt en waar ze heen gaat

18/18

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.

HabitatDe klimaatmodule krijgt een inspectierobotje: een lus in de gang met drie meetpunten langs het kweekbed, een laadstation, en een gedrag dat stopt voor obstakels en omkeert als de gang geblokkeerd is. In het integratieproject (hoofdstuk 40) koppel je zijn metingen aan het logboek van de module; in Expert zoekt het zijn weg door het hele station (hoofdstuk 34).
Samenvatting

In het kort

Begrippen

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.
Aan de slag in het werkboek16 opdrachten, van oefenen tot uitdagen