OptekGradeDestroyer

Af Sebastian Andresen, Christian Skjerning & Jakob Tøttrup

 

Testkørsel:

Bedste tid: 17.89 sekunder


Indholdsfortegnelse

1. Opbygning af hardware

2. Opbygning af LEGO

3. Programopbygning

3.1 Tilsluttet hardware

3.2 Loop()

3.3 NextState()

3.4 HandleBtn()

3.5 SetLeds()

3.6 Drive()

3.7 GetTrackSensor()

3.8 CheckTrigger()

4. Sammenspil mellem komponenter

4.1 Spændinger og strøm

4.2 Pulse-width Modulation

4.3 “Feeler”

4.4 “Linjesensor”

5. Brug af kunstig intelligens

6. Konkurrence

6.1 Resultat

7. Konklusion

8. Perspektivering

9. Appendix


 

 

1. Opbygning af hardware

Figur 1. Fritzing kredsløbstegning

 

 

Komponentliste

1x 10kΩ Resistor

3x 220Ω Resistor

1x Rød LED

1x Gul LED

1x Grøn LED

1x Arduino UNO

1x Breadboard

2x LEGO 9V DC-motorer

2x BUZ11 MOSFETs

1x Pushbutton

1x LEGO button

1x 5V Spændingsregulator

1x 9V Strømforsyning

1x KY-033 Tracing Black White Line Hunting Sensor

1x Ambient Light Sensor (TEMT6000 Breakout Board)

 

 

 

Kredsløbet ovenfor tegnet i programmet Fritzing viser hvordan sensorer er koblet sammen med arduinoen. En af de væsentligste ting ved tegningen, er anvendelsen af to transistorer og en spændingsregulator. De to transistorer er nødvendige for at kunne styre to 9V DC-motorer uden brug af et MotorShield. Årsagen til denne løsning er, at der på robottens konstruktionstidspunkt ikke var et MotorShield tilgængeligt.

En 5V spændingsregulator er nødvendig da kredsløbet sluttes til en strømforsyning på 9V, som er for meget for en Arduino der kan tåle 5V. Som illustreret i kredsløbstegningen ovenfor sidder input-voltage pinden i samme bane som Vcc fra stømforsyningen, GND sidder i breadboardets nederste bane for jordforbindelse mens den sidste pin – output-voltage forbindes til breadboardets øverste Vcc bane. Med denne opbygning sikres det at der altid vil være 9V tilgængelig til de to DC-motorer (der også er de eneste enheder der ellers har en forbindelse til den nederste Vcc bane i breadboardet). Den øverste bane i breadboardet tilfører 5V (vha. spændingsregulatoren) til arduinoen som vist ved den røde ledning næstlængst til højre på tegningen. Til denne Vcc bane er der også forbindelse til de to lysrefleksionssensoreres Vin-pin.

 

 

2. Opbygning af LEGO

I dette afsnit redegøres der for robottens konstruktion af legoklodser, hvorfor den er opbygget som den er, herunder årsagen til kun to hjul anvendes, sensorplacering og funktionen af den ekstra arm.

Figur 2. Videostilbillede af tidlig udgave af robotten

På figur 2 ovenfor ses et stilbillede af en videooptagelse fra 27. april 2018, og en tidlig udgave af robottens opbygning. På denne første iteration blev der anvendt to store hjul, og den totale længde var væsentlig større. Ved en senere udgave blev det fulde breadboard skiftet ud med et på halv størrelse, og der blev tilføjet en ultralydssensor til at gennemføre anden del af banen. Sidenhen efter adskillige timers trial-and-error er det ændret til en forkortet udgave med mindre hjul og anderledes gearing, og fravalget af ultralydssensoren, som vist på figur 3 herunder.

Figur 3. Endelig udgave af robotten

Figuren der viser den endelige udgave af robotten viser også skiftet fra store hjul til små. De store hjul blev oprindeligt valgt med den tanke at de ville gennemføre banen på kortest tid, men det viste sig at ved at skifte til mindre hjul opnåede robotten markant bedre stabilitet og præcision uden at hastigheden blev forringet alt for meget. Der er tilføjet yderligere gearing for at kunne få de små hjul til at nå ned til jorden, da det (grundet sensornes placering under robotten) ikke var muligt at ændre motorernes placering.

 

Til denne robot er der kun benyttet to hjul placeret i midten af konstruktionen. Idéen med denne tilgang har været at placere et kugle element i hhv. for- og bagenden, der byder på meget lidt friktion med banens underlag, og muligheden for at dreje robotten ved at slukke for det ene hjul eller skrue ned for strømtilførslen (alt efter hvor skarpt svinget skal være). Derudover har tilgangen til robottens konstruktion generelt været præget af et ønske om optimering. Både ift. at placere sensorere så tæt på jorden som muligt, at skærme linjesensoreren af for ikke at blive forstyrret af udefrakommende lys og at bygge en konstruktion der er så solid og robust som mulig.

 

Linjesensorens (KY-033) placering har ændret sig gennem de forskellige iterationer. Det stod hurtigt klart, at jo længere foran hjulene den blev placeret, jo oftere svingede den ud og skulle korrigere. En sådan placering relativt langt foran hjulene gav robotten en meget vibrerende eller zitrende opførsel hvor den meget hurtigt ændrede på de to hjuls hastighed. Dermed blev tiden for at gennemføre banen overordnet langsommere end når den var placeret tættere på hjulene. Det bemærkes også, at en placering for tæt på hjulene har den modsatte effekt – at der skal en meget højere grad af afvigelse fra linjen til, for at sensoren opfanger at den ikke er på rette kurs. Den resulterende opførsel for robotten har været meget større sving, men med langt færre hastighedsændringer for hjulene. Overordnet gav en placering tæt på hjulene en væsentlig hurtigere gennemførselstid, men også meget mere upræcis og ustabil. Derfor er den endelige placering for sensoren et kompromis mellem fart og præcision.

Den anden linjesensor sidder på robottens venstre side og bliver kun brugt som en trigger der skal fortælle systemet, at første del af banen er gennemført.

For en detaljeret forklaring på hvordan de to sensorer fungerer, se afsnittet “linjesensor” under “Samspil mellem komponenter”.

Robottens arm som vises nederst på figur 3 bruges til at følge væggen ved anden del af banen. Som illustreret på figur 4 nedenfor, vil legoknappen trykkes ind når armen presses tilpas nok ind mod den. Dette sker, når robotten er i kontakt (men uden at vælte) væggen. Armen fungerer præcis ligesom en løftestang. Jo længere den er, jo nemmere er det at trykke knappen ind. Den endelige konstruktion er et resultat af adskillige tests for at se hvornår armen undlod at vælte væggen og samtidig på pålidelig vis trykke knappen i bund.

For en mere detaljeret forklaring på hvordan dette “følehorn” fungerer, se afsnit “Feeler” under “Samspil mellem komponenter”.

Figur 4. LEGO knap med robottens “følerhorn”

 

 

Programopbygning

Tilsluttet hardware

På pin 5 og 6 sidder gaten til to transistorer der skal åbne og lukke for strømmen til de to motorer (se figur 5). Ved at styre transistorerne med et digitalt pwm-signal, kan motorernes hastighed justeres med software. Hjulene vil aldrig have behov for at kunne køre baglæns, og motorerne kan derfor styres af to transistorer fremfor to H-bridges.

Figur 5.

På pin 9 til 11 sidder 3 LED’er, her i farverne rød, gul og grøn. Den røde LED indikerer at robotten er tændt, men holder stille og venter på at måtte begynde. Den gule LED viser at robotten er i gang med den sektion af banen der handler om at følge en linje, og den grønne LED indikerer at robotten nu følger en væg. Udover at kunne skifte imellem disse stadier løbende i koden, sidder en knap på pin 8 der skifter stadie manuelt.

Under robotten sidder en lysrefleksionssensor der er tilsluttet arduinoen på pin 4. Sensoren er af en sådan slags, der returnerer en digital høj eller lav spænding. Ved siden af dén sidder endnu en sensor af samme slags, forbundet til pin A0. Denne sensor giver sit output som en analog-værdi, og bruges som en trigger. da den på grund af sin placering, kun vil kunne se linjer der ligger på tværs af kørselsretningen; det vil sige linjen mellem linjesegmentet og vægsegmentet, og linjen ved målstregen.

På siden af robotten sidder en knap med en lang arm. Knappen er tilsluttet til pin 2, og skal mærke om robotten rør en væg eller ikke. For at denne knap’s pin ikke hænger i void når knappen ikke trykkes nede, benyttes arduinoens indbyggede pullup-funktion.
(se figur 6). Knappen til skift af stadie har en fysisk pull-down modstand i breadboardet.

Figur 6.

 

Alle pin-konfigurationer vil have værdier mindre end den maksimale størrelse for en byte (8bit). Derfor bruges uint8_t, og da pin-konfigurationer aldrig bør ændre sig løbende, sættes det hele til konstante værdier.

 

Loop()

Ved hver gennemgang af loopet tjekkes stadie-knappen for ændringer, og led’er sættes til det pågældende stadie (se figur X). Hvis robotten ikke er i stadie 0, vil den forsøge at køre robotten fremad (se afsnit Drive), og tjekke om den er stødt på en linje der ligger på tværs af kørebanen (se afsnit CheckTrigger). Hvis robottens stadie er 0, slukkes begge motorer.

Figur 7.

NextState()

Robotten har 3 stadier. I stadie 0 står robotten stille og venter på at måtte køre. I stadie 1 følger robotten en linje på jorden, og i stadie 2 følges en væg. Til at skifte imellem disse stadier, benyttes funktionen nextState (se figur 8).

Figur 8.

 

Ved kald af funktionen øges robottens stadie med 1. Det sikres bagefter at stadiet ikke er højere end 2, og hvis det er, sættes stadiet til 0. Denne funktion benyttes både af knapper (se afsnit HandleBtn) og indbyggede funktioner.

Ved at give robotten et stadie hvor den ikke kører, vil robotten kunne sættes i dette stadie når der arbejdes på den eller den programmeres. Selv hvis strømkilden tages af robotten, så den kun er forbundet med et usb-kabel til computeren, vil arduinoen forsøge at trække en spænding gennem transistorerne for at dreje motorerne. Dette giver en ubehagelig hyletone, da der ikke vil være i nærheden af nok spænding til at dreje motorerne med den spænding der kan trækkes baglæns gennem en spændingsregulator. Med dette stadie kan robotten stå i stilhed og vente på ny kode, endda samtidigt med at spare strøm.

Stadie 0 giver også den fordel, at robotten ikke vil prøve at køre før den får ordre om at skulle køre, så den er lettere at kontrollere ved tidtagning. Knappen har også været brugt til automatisk tidtagning imellem de forskellige segmenter af banen (Se afsnit Kunstig Intelligens), men dén kode bruges ikke mere.

 

HandleBtn()

I begyndelsen af hver gennemgang af softwarens loop, tjekkes stadiet af knappen på breadboarded. Ved tryk på denne knap, ændres robottens stadie sig til det næste (se afsnit NextState). Til at tjekke knappen benyttes fire variabler (se figur 9).

Figur 9.

 

En boolean kaldet canChangeState fortæller koden, om den har tilladelse til at forsøge at ændre stadiet. Når stadiet ændres, sættes denne tilladelse til værdien false, indtil knappen slippes igen, for at undgå at stadiet vil skifte ukontrollerbart, men i stedet fungere som Flip-Flop-logik. En anden boolean kaldet btnDown viser knappens stadie.

Det viste sig at knappen nogle gange kunne finde på at aktivere sig selv, trods altid at være forbundet til GND med en pulldown-modstand. Dette blev fikset ved at tilføje en funktionalitet der gør, at knappen skal holdes nede før den aktiveres. Her bruges variablen btnCounter til at holde styr på hvor mange loop-gennemgange knappen har været holdt nede i træk, og variablen btnCountLimit til at sætte et mål for hvor meget btnCounter skal tælle til. Disse er begge initialiseret som 16bit-værdier, så de kan tælle til værdier højere end 255, der er grænsen for 8bit-værdier.

Ved funktionens opstart, læses knappens digitale input, der vil være højt hvis knappen trykkes på, og lavt hvis den ikke trykkes ned (se figur 10, linje 108).

Figur 10.

 

Hvis knappen holdes nede, og programmet har tilladelse til at måtte ændre stadie, tælles counteren én op, som det eneste for dét funktionskald. Ved næste kald tælles counteren endnu en op, og dette fortsætter til counteren når den satte grænse, der her er sat til 50. Dette tal er valgt, da det er højt nok til ikke at kunne ske af sig selv, samtidigt med at være så lavt at tiden ikke er betydelig når knappen benyttes: at knappen ikke skal holdes nede i lang tid før den aktiverer (se figur 10, linje 109 til 110).

Når counteren når det ønskede antal funktionskald hvor knappen har været holdt nede hver gang, ændres stadiet til det næste (se afsnit NextState). Tilladelsen til at måtte ændre stadie sættes til boolean-værdien false, og counteren sættes tilbage til 0, og vil ikke tælle op igen før tilladelsen er true. Derfor ændres stadiet kun én gang pr tryk, ligegyldigt hvor længe trykket varer (se figur 10, linje 111 til 115).

Så snart knappen ikke er trykket på, og tilladelsen til at måtte ændre stadie er false, sættes tilladelsen til true, og knappen kan nu aktiveres igen med et nyt tryk (se figur 10, linje 117 til 119).

På ethvert givent tidspunkt vil counteren blive sat til 0, lige meget hvad den har talt til. Dette medfører, at knappen ikke kan aktiveres af mange små tryk i træk, da counteren er tvunget til at måtte begynde forfra for hvert funktionskald knappen ikke er nede (se figur 10, linje 120 til 122).

 

SetLeds()

Efter der er foretaget et tjek om ændringer af knappen, kaldes funktionen setLeds, der opdaterer de tre LED’er tilsluttet kredsløbet. Hver LED repræsenterer ét stadie. Da pin-konfigurationerne ligger i et array, kan LED 0 repræsentere stadie 0, LED 1: stadie og LED2: stadie 2. Samtidigt, da der benyttes et array, kan LED’erne sættes i et for-loop (se figur 11).

Figur 11.

 

Med digitalWrite sættes arduinoens pins til en høj eller lav værdi, der for LED’erne i setup er angivet som OUTPUTs. Funktionen er i stand til at oversætte en boolean-værdi til HIGH / LOW i funktionen. Dette udnyttes ved at opsætte den betingelse, at variablen i er det samme som det nuværende stadie. Hvis robotten er i sit andet stadie, vil den i de to første gennemgange af for-loopet være falsk, og derfor sætte LED 0 og 1 til en LOW spænding, der slukker LED’erne. Ved det tredje gennemgang vil i nu være 2, samtidigt med at stadiet er 2, og betingelsen vil være sand, hvortil LED 2 får en HIGH spænding

 

Drive()

Hvis robotten er i stadie 1 eller 2, vil den kalde funktionen Drive. Motorerne styres af hver deres transistor der åbner og lukker for en høj spænding der ikke løber gennem arduinoen. Transistorerne styres af PWM-signaler, der gør det muligt at justere hjulenes hastighed individuelt fra at stå stille (PWM = 0) til at køre med fuld hastighed (PWM = 255). Da et PWM-signal aldrig vil kunne overstige 255, benyttes 8bit-variabler til at gemme motorernes hastighed (se figur 12).

Figur 12.

 

Forskellige funktioner og segmenter af banen vil have forskellige hastigheder, for at sikre at den højest mulige hastighed benyttes i alle situationer. Disse gemmes i arrays, hvor position0 i arrayet vil have den hurtigste hastighed for det hurtige hjul i den situation, og position1 vil have den hurtigste hastighed for det langsomme hjul. Ratioen mellem værdierne vil sikre at robotten kan nå at dreje i de forskellige typer sving. Jo højere den store værdi er, jo hurtigere vil robotten generelt køre, og jo højere den langsomme værdi er, jo større vil radius af robottens sving blive, da begge hjul kører fremad. På figur 13 ses at den hurtige hastighed for linjesegmentet ikke er den absolut maksimale hastighed for robotten. Dette skyldes, at robotten har bevæget sig så hurtigt, at den er nået forbi den linje den skal følge, inden den igen tjekker hvor linjen er.

Figur 13.

 

Når funktionen kaldes, findes først en boolean der fortæller systemet om den kører den rigtige vej (se figur 14, linje 87). Hvis den følger linjesegmentet, vil variablen onTrack være sand når robotten er på linjen, og falsk når den ikke kan se linjen. I vægsegmentet vil variablen være sand når robotten rør væggen, og falsk hvis den ikke gør. Dette vil altid fortælle robotten, om den skal forsøge at rette ind mod forhindringen, eller forsøge at holde lidt mere afstand til forhindringen.

Figur 14.

 

Hvis robottens stadie er 1, vil variablen ‘fast’ blive sat til linjesegmentets høje hastighed. Hvis stadiet ikke er 1, ved robotten at stadiet kun kan være 2, og ‘fast’ sættes til vægsegmentets høje hastighed. Variablen ‘slow’ sættes på samme måde til linjesegmentets eller vægsegmentets lave hastighed alt efter robottens stadie (se figur 14, linje 89 – 90).

Robotten vil i linjesegmentet altid forsøge at holde sig på venstre side af linjen. Hvis ‘onTrack’ er true, ved robotten, at den bør dreje lidt til venstre for at sikre sig ikke at komme ud over linjens højre side. Den drejer til venstre ved at sætte det højre hjul til den høje hastighed, og det venstre hjul til den lave hastighed. Hvis ‘onTrack’ er false, ved robotten at den ikke kan se linjen, men regner med at være et sted på venstre side af linjen, og vil derfor dreje til højre for at finde linjen igen. I et højresving har det højre hjul den lave hastighed, imens det venstre hjul har den høje hastighed (se figur 14, linje 92 – 92).

Når variablerne er sat til de ønskede hastigheder, sendes de ud til transistorerne med PWM-signaler (se figur 14, linje 95 – 96).

 

GetTrackSensor()

I begyndelsen af funktionen Drive, tjekkes om robotten kan føle eller ikke kan føle sin forhindring i den givne situation (se figur 15).

Figur 15.

 

Hvis robotten er i stadie 1 (linjesegmentet), vil funktionen returnere resultatet af den lysrefleksionssensor der skal følge linjen. Hvis robotten er i stadie 2, returneres om der trykkes på robottens arm eller ikke. Da armen bruger en knap der bruger arduinoens indbyggede PULLUP-funktion, vil knappens pin have en logisk-høj spænding indtil knappen trykkes på. Dette inverteres inden resultatet returneres (se figur 15, linje 102). Da en switch-case benyttes til at afgøre resultatet, bør der altid være en default løsning. Dette vil dog aldrig ske, da funktionen kun kan blive kaldt i stadier højere end 0, og stadiet aldrig vil kunne være højere end 2. Dog, i god praksis, returneres værdien false.

CheckTrigger()

Hver gang robotten har bevæget sig, undersøges om den er nået til en af de to tværliggende linjer. Ved siden af den lysrefkelsionssensor der en linje under robotten, sidder en sensor af samme slags og holder øje med tværgående linjer på banen. Hvis der ikke er en linje, vil sensoren give en analog værdi under 500. Denne ligger helst omkring 40, hvis sensoren sidder korrekt tæt på jorden. Så snart en sort linje er under sensoren, gives en værdi over 500. Denne er omkring 880. 500 bruges derfor som værdi til sammenligning med sensoren (se figur 16).

Figur 16.

 

Hvis sensoren giver en værdi over 500, ved robotten at den er nået til en tværgående linje. Hvis denne linje nås i stadie 1, vil det være dén tværgående linje der viser slutningen af linjesegmentet og begyndelsen af vægsegmentet. Her vil robotten lave et sving til venstre for at ligge parallelt med væggen (se figur 17, linje 59 – 64).

Figur 17.

 

Hvis robotten støder på en tværgående linje i stadie 2, ved robotten at det er dén linje der fungere som banens målstreg. Her sættes begge motorer i max-hastighed i et kort stykke tid, for at sikre at robotten når lige over linjen, selvom robottens arm slipper væggen imens.

Lige meget hvilket stadie robotten er i, vil den skifte til næste stadie efter den har udført en særlig funktion baseret på dens situation. Efter stadie 1, hvor robottens særlige funktion er at lave et blindt venstresving i håbet om at ramme væggen igen, vil den skifte til stadie 2, og hele koden vil nu reagere på væggen frem for en linje under robotten. Efter stadie 2, hvor robottens særlige funktion er at ræse over mållinjen, sættes stadiet til 0, hvor robotten står stille og venter på at kunne køre banen igen.

Hvis robotten løftes fra gulvet, giver trigger-sensoren en værdi over 500. Den vil derfor aktivere sit venstresving og derefter sit boost, hvor efter at gå til stadie 0. I tilfælde af at robotten skulle køre af sporet eller skal sættes længere tilbage på banen, vil robotten genstarte sig selv så snart den løftes.

 

 

Sammenspil mellem komponenter

For at de individuelle komponenter af hele systemet virker optimalt, er det vigtigt at optimere samspillet mellem disse. Nogle af de ting, der er essentielle, for at komponenterne virker optimalt, er spændinger, strømmængder og kommunikationstyper, så som PWM og ADC.

 

Spændinger og strøm

Hele robotten forsynes med 9V, hvilket er for højt for den Arduino UNO, der styrer hele robotten, så derfor er der mellem strømforsyningen og Arduinoen opkoblet en spændingsregulator (Se afsnittet om hardwareopbygning) , der sænker spændingen til 5V på Arduino siden.

Strømmen ville sagtens kunne være ledt igennem Arduinoen’s egen indbygget spændingsregulator, der kan håndtere alt fra 6.5VDC til 15VDC, men da strømmængden herigennem er begrænset af den lille 3.5W spændingsregulator der er indbygget, ville strømmen som motorerne har brug for være for stor og afsætte alt for meget effekt, der ville blive til varme, samt at udgangene på ATMEGA328p-chippen også maksimalt kan holde til et par 100mA, hvilket slet ikke er nok til at drive et par motorer med vedvarende belastning.

Den høje strøm og spænding bliver derfor ledt udenom Arduinoen og igennem et par MOSFETs (transistors), der til forskel fra en traditionel H-bro, til regulere strømretningen på motorerne, blot åbner og lukker for strømmen, i én retning. Derved kan robotten kun kører fremad og ikke baglæns, hvilket der kun har været brug for.

 

Pulse-width Modulation

For at justere motorhastighederne på robotten benyttes Arduinoens PWM funktion, der via impulser kan tænde og slukke for strømmen i korte intervaller. Dette er benyttet da PWM ofte bruges til at konvertere et digitalt signal til en analog spænding. Længden af den høje puls betegnes som duty cycle beskriver hvor længe signalet har en spænding ift. hvor længe det ikke har. Dvs. 50% duty cycle har en høj spænding i 50% af tiden, hvilket med Arduinoens PWM frekvens på 980 Hz giver et højt signal på 1 / 1960 del af ét sekund, hvorefter den er lav i samme periode, for så derefter at blive sat høj og lav i gentagne intervaller.

50% duty cycle vil fremstå som den halve spænding i kombination med en kondensator.

Regulering af duty cycle kommer her til udtryk i bevægelsen af motorene, da de kun bliver påtrykt en spænding og strøm i korte eller længere intervaller og kan dermed dreje i forskellige hastigheder.

For at kunne regulere denne duty cycle, benyttes den indbyggede funktion analogWrite(), der som funktionen er navngivet, simulere en analog spænding ved at regulere duty cycle.

Da Arduinoens indbyggede DAC (digital-to-analog converter) er 8bit (2^8 = 256 steps) kan man derfor gradvist justere bredden af pulsen ved at kalde analogWrite() funktionen med en integer mellem 0 og 255, hvilket så justere duty cycle fra 0% til 100%.
Arduinoen har specifikke udgange, der er forbundet til et krystal, der kan hjælpe med at holde den nødvendige frekvens, således intervallerne er ensartet i signalet.

Robotten regulere gradvist hastigheden på hvert hjul, uafhængigt af hinanden, for at kunne køre fremad og dreje henholdsvist til højre og venstre, med forskellig drejeradius.

Der er benyttet MOSFETs da disse til fordel fra relæer, kan skifte stadie i løbet af en milliardendedel af ét sekundt, hvilket tillader systemet at overføre PWM signalet til motorerne. Dette ville ikke have haft samme effekt med relæer, da de har fysiske kontakter der flyttes indeni og derfor tager en del længere tid om dette.

 

“Feeler”

Robotten er udstyret med et følehorn, meget lig et insekts antenne. Denne benyttes til at kunne registrere, hvor væggen er.

Ultralydssensor

Der blev ved første udgangspunkt benyttet en ultralydssensor, der via lydbølger måler tiden for ekkoet til afstandsdetektering. Ved flere tests og fejlsøgninger, stod det dog klart at lydbølgerne fra sensoren rigtigt nok reflekterede af på muren og tilbage til sensoren, men kun når denne stod næsten vinkelret på muren. Ved en vinkel på 10-15 grader, hvilket hurtigt kunne opstå, da robotten zig-zaggede sig fremad, i takt med at den kom for tæt og for langt væk fra væggen, kunne lydbølgerne hurtigt blive reflekteret i en anden retning, så den ikke hørte ekkoet eller hørte støj og derved målte en meget størrer afstand en hvad der reelt var.
I stedet blev sensoren skiftet ud med en følearm, (Se afsnittet om opbygning af LEGO) der aktiverede en knap, når armen blev presset langt nok ind. Armen tillader robotten at kunne “mærke” muren og dermed følge langs muren. Knappen der sidder på armen er forbundet til Arduinoens digitale indgang 2, og benytter en indbygget pull-up funktion, der er fastholder signalet på indgangen til 5VDC, når knappen ikke er aktiveret/forbundet til noget. Pull-up/down benyttes generelt hvis kredsløbet ikke er fuldendt på en indgang, så ledningerne agerer som antenner og dermed opfanger signalstøj, hvilket kan varier spændingen på indgangen og derved lade Arduinoen tro at der bliver trykket på knappen, uden at der reelt bliver. Dette kunne være gjort med en ekstern modstand til enten 0VDC (pull-down) eller til 5VDC (pull-up), men her er den indbygget benyttet, da det var nemmest og har samme funktion.
Når knappen aktiveres forbindes indgangen til 0VDC, hvilket Arduinoen registrere som et LOW, FALSE eller 0 – alle af samme stadie. Spændingen trækkes ned, da der er mindre modstand gennem ledningerne og knappen end der er gennem den indbyggede 10k ohm modstand og derfor vil strømmen ledes denne vej og der opstår et spændingsfald.

 

“Linjesensor”

Der benyttes to sensorer til registrering af linjen. Disse sensorer generer en infrarød belysning og registrerer så mængden af refleksioner. Sensorerne består begge af en LDR (fotomodstand), der i opsat i en spændingsdeler, for at kunne variere den spænding, Arduinoen måler på. Sensorerne er hver forsynet med 5VDC og hele komponenten er placeret på det der kaldes et breakout board, der er med til at simplificere brugen af denne.

De har begge en udgang der enten sættes til en analog eller digital indgang på Arduinoen. Den ene sensor er konstrueret, så den automatisk giver 5VDC eller 0VDC ud, alt afhængig af lysmængden den måler, hvor den anden giver en spænding ud ift. lysniveauet.

Det kan til tider være brugbart at kunne aflæse den varierende spænding disse giver ud, men der er her kun benyttet “enten eller” tilstande (Se afsnittet om programmets opbygning), da sensorerne kun er enten over linjen eller ej.

Spændingsdeleren der er en del af disse sensorer, består af to modstande i serie med et fælles spændingsfald på 5VDC. Hvis disse modstande var af samme størrelse, ville spændingsfaldet over den enkelte være det halve af det totale spændingsfald. Da LDR varierer modstand i takt med lysniveauet ændres størrelsesforholdet mellem disse modstande også, hvilket medfører forskelligt spændingsfald over disse, som så kan måles og benyttes i robottens logik.

Feks. hvis begge modstande var på 10k ohm og med et totalt spændingsfald på 5VDC, vil spændingsfaldet over den ene modstand være (10k / (10k + 10k) ) * 5V = 2.5V.

Denne spænding kan detekteres af Arduinoens inbygget ADC (analog-to-digital converter), der er på hver analog indgang (A0 til A5). Denne ADC er på 10bit, hvilket kan omdannes til en integer på mellem 0 og 1023, hvor hvert step er svarende til 4.88mV ændring (2^10 / 5 * 1000).

 

 

Brug af kunstig intelligens

I robottens tidlige fase var den meget svær at kontrollere, da hjulene var store, så den derfor bevægede sig meget ad gangen, og gearingen ikke var særlig god i forhold til god kontrol over robotten. Det var dog muligt for den at udføre opgaven, hvilket var det vigtigste på daværende tidspunkt. Værdierne skulle bare lige finjusteres så den klarede sig helt optimalt.

Opsætning af algoritme

En metode til at finde et større antal værdier der skal optimeres perfekt men stadigt virke med hinanden, er at benytte sig at kunstig intelligens. I stedet for at have et menneske til at gætte sig frem til, hvilke værdier der er bedst til de forskellige scenarier, sættes maskinen til at udvikle sig selv over noget tid.

På dette tidspunkt havde robotten fire ukendte variabler: Det hurtige hjuls hastighed, det langsomme hjuls hastighed, og hvor mange loop-cycles det skulle tage henholdsvist at hæve og sænke farten, da det havde vist sig at være lettere at styre robotten hvis hjulene ikke skifter imellem min- og max-værdier øjeblikkeligt. Hver enhed i algoritmen skulle derfor have disse fire variabler til rådighed. Det blev valgt, at der skulle være 12 enheder i hver generation, da tallet er let at arbejde med næsten ligemeget hvordan det senere skal bruges til at dele generationen op og fylde en ny generation.

Nedenfor ses et udklip af den data algoritmen har genereret (se figur 18).

Figur 18.

 

I den første generation blev der valgt tilfældige værdier til de første 8 enheder i generationen. De sidste 4 enheder fik forudbestemte værdier der før havde været brugt til at styre robotten. Dette blev gjort for at få algoritmen hurtigt i gang, dog uden at udelukke chancen for at en tilfældig enhed vil ‘vinde’, da netop denne type algoritme er god til at finde oversete muligheder.

Dataen for en enhed tastes ind i koden til robotten, og robotten kører banen så langt den kan før den diskvalificeres. I starten gives point efter, hvor godt robotten reagerer på linjen. Robotter der næsten ikke reagerer på linjen vil få mange point, imens robotter der viser tegn på at kunne forstå hvordan linjen skal bruges får få point. Point i denne algoritme er netop for at straffe enheden, da dette vil være en lettere måde at give point på i fremtiden når point kommer til at handle om tid.

Koden blev senere omskevet til, at dataen kunne indtastes

Opstart af algoritme

Det kan ses ovenover på figur 18 at enhed 3, 9 og 11 fik den laveste score i dén generation. Dette overraskede, da en tilfældig enhed klarede sig bedre end to forudsatte enheder. Et interessant træk ved den tilfældige enhed er, at dens efterkommere vil følge den modsatte side af linjen end først tiltænkt, da enheden har byttet om på sit hurtige og langsomme hjul.

Den bedste enhed i generation 1 får 5 efterkommere i generation 2. Den andenbedste enhed i generation 1 får 4 efterkommere, og den tredjebedste får 3. Alle værdier i disse efterkommere har en mutationsrate på op til +/-20%. En værdi på 10 vil i dette tilfælde kunne blive ethvert tilfældigt tal mellem 8 og 12, imens en værdi på 100 vil få en tilfældig værdi mellem 80 og 120. Dette vil skabe ændringer samtidigt med at holde værdierne i nærheden af deres forfædres, da pointen med algoritmen er at enhederne skal udvikle sig. Senere i algoritmen vil mutationsraten falde, så ændringerne bliver mindre og mindre markante og tilfældige.

I generation 2 på figur 18 kan det ses, at enhed 3 og 5 fra deres parent, enhed 9 i generation 1, vinder sammen med enhed 10 der stammer fra enhed 11 i første generation. Den tilfældigt skabte enhed der tidligere var en af de bedste er nu uddød. De tre vindere fylder en ny generation op på samme måde som før (5 + 4 + 3), og også disse enheder muteres – igen med op til +/-20%.

 

Algoritmens forløb

Den tredje generation er den første til at få genereret deres score direkte fra præstation. Her bedømmes de efter hvor meget af banen de mangler at gennemføre (5 point pr. centimeter). Data for de næste generationer kan ses i Appendix. Enhed 1 og 2 af den fjerde generation er de første til at nå i mål. Her gives 10 point pr sekund det har taget at komme i mål + 1000 for ikke at nå i mål.

Muteringsraten til den 5. generation er +/-16%. Til resten af generationerne er den +/-12%. Fra generation 7 gives scoren ud fra antallet af centisekunder robotten har været om at tage ruten. Robotten tager selv tid, fra den aktiveres til den når den tværgående linje. Midt i den 10. generation løb robottens batteri tør for strøm.

 

 

Evaluering af algoritme

Det viste sig til sidst, at det var lettere at gætte sig til en god værdi til de forskellige hastigheder. Dette virkede efter robotten blev ombygget til at have en større gearing og mindre hjul, der gjorde den lettere at styre. Med den nye konstruktion blev værdierne fundet gennem mange testforsøg, der endeligt fik robotten til at klare linjestykket på 8.94 sekunder.

Efter tilbygning med armen på siden, og med justering i variablerne for at øge sandsynligheden for at klare banen i ét forsøg, tager linjestykket nu omkring 12 sekunder. Fra dette punkt ville det kunne øge robottens hastighed hvis dens nuværende værdier blev justeret gennem en kunstig intelligens, men det vil ikke kunne betale sig i forhold til opgavens omfang, da hver generation tager omkring en time at score. Ved næste forsøg på kunstig intelligens bør strømkilden være konstant, i stedet for et batteri der langsomt forvringer enhedernes score når det mister energi.

På figur 19 ses udviklingen for motorernes hastighed for de tre bedste enheder for hver generation.

Figur 19.

 

Det kan ses på figuren, at motorernes hastighed øges under udviklingen, og at der er en tæt sammenhæng i ratioen mellem den hurtige og den langsomme hastighed. Figuren viser også, hvilke enheder der nedstammer fra hinanden.

Konkurrence

Ved morgenen op til konkurrencen blev robotten testkørt for at sikre at den vil kunne gennemføre banen. Robotten gennemførte et par gange i træk, men stoppede da med at klare banen fejlfrit. Trigger-sensoren læste uregelmæssige værdier under kørsel. Det viste sig, at sensoren skal sidde meget tæt på gulvet i en speciel position før den læser den ønskede lave værdi. Der blev tilføjet en LED på arduinoens pin 3, der er tændt når sensoren læser en værdi under 100 (se figur 20). Denne kunne bruges til tydeligt kunne se om sensoren sad forkert.

Figur 20.

 

Da robotten havde det med at køre uden for linjen når den fulgte ydersiden af et sving, blev der byttet om på robottens hurtige og langsomme hastighed for linjesegmentet (se figur 21, linje 22). Dette gør, at robotten følger ydersiden af det første sving fremfor ydersiden af det andet sving på banen, og den vil derfor fejle hurtigere, der mindsker den totale tid selv ved fejlkørsel.

Figur 21.

 

Det blinde sving mellem linjesegmentet og vægsegmentet var blevet for blødt, der gjorde, at robotten ramte væggen inden svinget var fuldendt. Her blev svingets langsomme hjul mindsket i hastighed fra 60 til 30, for at gøre svinget skarpere (se figur 21, linje 23).

Efter ændringerne fuldendte robotten banen tre gange fejlfrit i træk, og var derfor klar til konkurrencen.

 

Resultat

Robotten gennemførte banen på 19,35 sekunder på første forsøg. I et forsøg på at forbedre tiden, blev det hurtige hjuls linjehastighed sat til 190, og det langsommes hjul til 26. Med den nye hastighed var robotten ude af stand til at følge linjen, da ratioen imellem hastighederne ikke passede sammen. Andet forsøg fik derfor en tid på 180 sekunder, da banen ikke blev gennemført. Til tredje forsøg blev det hurtige hjuls hastighed sat til 180, og det langsomme hjuls hastighed til 20. På tredje forsøg blev banen gennemført på 17,89 sekunder, der gav en andenplads i konkurrencen.

 

 


Konklusion

Som det fremgår af videoen i afsnittet om algoritmen under kunstig intelligens, har systemet på et tidligere tidspunkt været i stand til at klare første del af banen på ca. 8 sekunder. Den endelige version klarer hele banen på ca. 20 sekunder, uden at kvalificere til straftid. Den overholder reglerne som beskrevet i opgavebeskrivelsen ved at undlade kontakt med de røde streger i første banehalvdel og undlader at vælte eller flytte væggen ved anden halvdel. Den følger linjen fint, og kører også elegant uden om væggen. Dermed kan der konkluderes at robotten løser opgaven.

 

Mekanik, elektronik og software bærer præg af en klassisk ingeniørtilgang – iterativt arbejde hvor der testes og forbedres hele tiden. Det har været en utrolig god og udfordrende opgave, hvor konkurrenceelementet uden tvivl har været med til at øge motivationen hos projektgruppen, såvel som hos de fleste andre fra holdet. Der har været god sparring og hjælp at hente fra andre  Det har været tydeligt at se læringsprocessen for hvordan en sådan opgave kan løses hos projektgruppen. Det har været fantastisk træning i det iterative arbejde i form af testkørsel, observation, ændringer, nye observationer, brainstorm og fejlsøgning. Opgavens omfang har et niveau der passer godt til at være udfordrende og samtidig mulig at løse. Til fremtidige hold, bør deadlines fastholdes for at man kan lære at arbejde under pres.

 

Perspektivering

Robottens stabilitet ville kunne forbedres ved at specialfremstille et chassis vha. 3D-print. Dette ville betyde at den overordnede rigiditet stiger, samt en forsikring om at sensorer aldrig ændrer position. Ved flere testkørsler kunne småjusteringer ift. sensorudslag også jævnes. Yderligere testkørsler ville også give mulighed for at justere sensorenes og armens position og sensitivitet.

Et mekanisk punkt der kunne forbedres er de runde kugleelementer foran og bagved. I den endelige version af konstruktionen, er det en plastikkugle der ikke kan trille. Ved at benytte et element der netop kan rulle i alle retninger, ville friktionen med underlaget også mindskes.

Robotten har ingen feedback fra motorernes bevægelser, her kunne feedback fra fx. en encoder, have været brugbart til styring. Evt, så man kunne scanne dele rundt om robotten og derefter reagere ud fra det scannede.

Som det er nu sættes motorerne i gang med en relativ høj hastighed, her ville en glidende acceleration af motorerne kunne jævne nogle af de store zig-zag sving ud, og så muligvis kunne undgå noget overshooting af sensorens placering.

Et aspekt af opgavens udformning, der vil give det hele et lidt mere “tænk ud af boksen” koncept ville være at banens udformning på konkurrencedagen ville være en lille smule anderledes i proportioner, således at man var tvunget til at få en robot, der reelt fulgt linjen og muren. Evt. gøre således at opgaven tillader andre konstruktioner end en kørende bil, men så blot lade folk tænke ud af boksen til løsning af banen.

 

Appendix

Datasæt for 10 generationer

 

Funktionsdiagram

 

Leave a Reply