Af: Simon Svansø og Paul Vuong Hung Pham

Video af testkørsel:

Bedste tid: 72 sekunder.

Materialeliste til opbygningen af hardware i TinkerCAD:

Beskrivelse af opbygningen i TinkerCAD:


Figur 1: Illustrationen af opbygningen i TinkerCAD

Hjernen bag systemet er Arduino Uno’en, som er en mikrokontroller der kobles til computeren, hvorigennem der kan interageres på Arduinoens digitale og analoge indgangs- og udgangs værdier. Værdierne man initialiserer igennem Arduinoen, bestemmer handlingerne for resten af kredsløbet.

De to DC-motorer i samarbejde med L293D’en har til formål at styre Wall·E’s Forgotten Brother’s retningsbaserede handlinger. DC-motorernes positive og negative poler er forbundet til L293D’s fire outputs, som er 1Y, 2Y, 3Y og 4Y. (Figur 2)

Figur 2: Datasheet af L293D – Pin Configuration and Functions

De fire inputspin fra L293D er forbundet til Arduinoens digitale pins, som der bliver fremvist på kodeudsnit 1. Det er igennem disse fire pins at Arduinoen bestemmer handlingerne for DC-motorerne.

Kodeudsnit 1: Pins fra Arduinoen til L293D

For at kunne bestemme udfaldet af handlingerne for Wall·E’s Forgotten Brother. Er der implementeret LDR og LED sensorer, som på baggrund af Braitenberg adfærden, skal kunne navigere Wall·E’s Forgotten Brother autonomt igennem en bane hvor han målrettet følger en sort linje. (Illustreres på figur 1 på højre side af breadboardet)

Opsætningen på TinkerCAD (figur 1) skal illustrere hvordan Wall·E’s Forgotten Brother’s kredsløb skulle struktureres, hvis det var muligt at konstruere robotten i fysisk form. I den fysiske konstruktion, ville LDR-sensorerne vende fladen ned mod jorden, hvilket ville forårsage, at LDR-værdiernes interval ikke ville være mellem 0 – 1023, som den i realiteten skulle.

Derfor er er der implementeret i alt fire LED’s ind i kredsløbet, som har det simple formål at belyse jordfladerne, så LDR-sensorerne har nemmere ved at måle refleksionerne fra jordfladerne og derfor nemmere ved at skelne mellem sort og hvid. Det skal dog lige nævnes, at LDR-sensorerne skal have en form for barrierer omkring sig, for at undgå direkte belysning fra LED-sensorerne.

Hvis man kigger på kodeudsnittet på kodeudsnit 2. Så bruger Wall·E’s Forgotten Brother en samspil af Arduino, DC-motorer, L293D, LDR- og LED sensorer til at navigere sig selv autonomt, ved at følge en sort linje.

Kodeudsnit 2: Koden til navigation af en sort linje

Dette gøres igennem digitale værdier, som LDR-sensorerne opfanger fra jordoverfladerne.

Opførelsen i TinkerCAD er blot en demonstration af komponenterne og deres funktioner, er der derfor valgt at gøre brug af digital frem for analog. Igennem brugen af digital, kan der udelukkende kun bruges værdierne i form af ”HIGH” og ”LOW”, så det er enten eller.

Som man kan se på kodeudsnit 2, registrerer LDR-sensorerne digitale værdier igennem digitalRead(6) og digitalRead(7). Som efterfølgende går ind i if-else statementen der bestemmer hvordan Wall·E’s Forgotten Brother skal handle baseret på input fra Right- og leftsensorValue.

F.eks. Hvis begge værdier fra LDR-sensorerne er høje (Hvid gulv), kører begge hjul fremad, som i overført betydning betyder, at Wall·E’s Forgotten Brother kører ligeud.

Indførelse af Sonor og Servo i TinkerCAD

Sonarsensoren formål er at registrere om der befinder sig fysiske objekter i den retning den peger. Måden det fungerer på er, at sonaren sender en lydbølge/lydfrekvens afsted i den påpegede retning, som efterfølgende gengives på det givne objekt den rammer. Hvorefter objektet reflekterer lyden tilbage til sonaren. Den tid det tager for lyden at rejse til objektet og tilbage, er den afstand, som sonaren måler i meter per sekund. Efterfølgende kan man konvertere enhederne til centimeter, som bliver illustreret i kodeudsnit 3.

Kodeudsnit 3: Konvertering til cm

Grunden til man skal dividere med 29, er fordi hastigheden er lyd rejser 340 meter per sekund, som så svarer til 29 mikrosekunder. Hvorefter man selvfølgelig skal dele værdien med 2 da sonaren registrerer lyden frem til objektet, og tilbage til sonaren igen.

I kodeudsnit 4 bliver der demonstreret hvordan sonarsensorens funktioner fungerer. Som der kan ses, bliver sonarPin sat til ”LOW”, ”HIGH” og så ”LOW” igen. Begrundelsen for det er, at den først bliver sat til ”LOW” da sonarsensoren bliver triggered af en ”HIGH” pulse på 2 millisekunder eller mere. Hvorefter den går ned til ”LOW” igen. Effekten ved denne teknik er, at sonarsensoren får et ”clean” ”HIGH” pulse hvis man starter med at sætte sonarsensoren til ”LOW”, inden den skal registrere et objekt.


Kodeudsnit 4: Kodningen af sonarsensoren

Hvorefter den kun registrerer HIGH værdien og konverterer den til centimeter. Processen gentages hvert 100 millisekunder.

Servomotoren bruges i denne opgave i samspil med sonaren. Servomotoren skal rotere sonaren ud fra det antal af grader den bliver sat til. På kodeudsnit 5, demonstrerer vi hvordan det kan lade sig gøre.

Kodeudsnit 5: Antallet af grader servomotoeren skal dreje

I kodeudsnit 5 kan det ses, at myServo.write(90), drejer servomotoren med sonarsencoren 90 grader i 2000 millisekunder, så Wall·E’s Forgotten Brother kan registrere om der befinder sig et objekt på højre side af ham. Hvorefter Wall·E’s Forgotten Brother drejer endnu 90 grader til 180 grader i 1000 millisekunder, hvor han så kan registrere om der befinder sig et objekt bag ham.

Både Servomotoren og Sonarsencoren bliver tilføjet til Wall·E’s Forgotten Brothers kredsløb, som bliver illustreret helt uge til højre på figur 1. Formålet med disse komponenter er at navigere Wall·E’s Forgotten Brother udenom fysiske objekter i form af vægge mm. Så han ikke kommer til skade. De ekstra komponenter i form af servomoter og sonarsencor understøtter også Braitenberg adfærden.

Derfor afhængigt af hvordan sensorer og hjul er forbundet, udviser Wall·E’s Forgotten Brothers forskellige opførsler (som kan være målrettet – følge en sort linje). Dette betyder, at det, afhængigt af sensormotorledningen, ser ud til at stræbe efter at opnå visse situationer og for at undgå andre ved at ændre kurs (undgå væggen), når situationen ændrer sig.

Opbygning af programmet i unity

Til at gennemføre banen som bliver visualiseret fra et fugleperspektiv på figur 3, blev den til at starte med opdelt i to dele, som både bliver afspejlet i koden og på figur 3. Den grønne markering illustrerer den første del af banen, mens den blå markering illustrerer den anden del af banen.           Formålet med dette var at tage udfordringerne i bidder og dermed holde overblikket over koden og funktionerne.

Figur 3: Banen til PF2 set fra oven – Delt i 2 dele

I starten af programmet blev der sat i alt fire globale variabler, som igennem booleans er sat til at aktivere og deaktivere diverse stadier undervejs, mens Wall·E’s Forgotten Brother skulle navigere sig igennem banen. De globale variabler bliver illustreret i kodeudsnit 6.

Kodeudsnit 6: De Globale variabler i Unity

Det kan læses på kodeudsnit 6, at de første to boolean variabler er sat til ”true” mens de efterfølgende to er sat til ”false”. Begrundelsen for dette ligger i, at de første to variabler bliver brugt til den første del af banen, som er markeret med grøn. I den første del af banen, omhandler det primært om, at få Wall·E’s Forgotten Brother til målrettet, at følge den sorte linje illustreret på figur 3.

De to resterende booleans variabler bliver brugt i den blå markering på figur 3, hvor Wall·E’s Forgotten Brother skal undgå at køre ind i væggen og nå til mål.

Under setup( ) er den eneste tilføjelse:

Denne korte kode gør, at servomotoren som er ”parent” til sonarsensoren vender i den retning Wall·E’s Forgotten Brother bevæger sig i. Dette gøres når programmet begynder så han kan ”se” forhindringer forude når han kører fremad. Måden forhindringerne registreres på er, at sonarsensoren kan registrere objekter ved brug af lydbølger som bliver forklaret mere detaljeret længere nede i bloggen.

Efterfølgende blev der implementeret tre simple funktioner udenfor ”setup” og ”loop”, som indeholder simple kommandoer. (Kodeudsnit 7) Fordelen ved at implementere disse funktioner udenfor ”loop” er, at de kun bliver brugt, når de bliver kaldt.

Kodeudsnit 7: Basale funktioner

driveForward-funktionen sørger for at motoren kører fremad ved at sætte både venstre og højre værdi til 90.

leftTurn- og rightTurn-funktionen sørger for at slukke den ene side ved at sætte den til 0, og derved drejer Wall·E’s Forgotten Brother enten til højre eller venstre afhængig af hvad der bliver kaldt.

Til at bestemme hvornår hvilken funktion bliver kaldt fra kodeudsnot 7, bliver der gjort brug af en anden funktion, som bliver illustreret på kodeudsnit 8. Funktionen ”followLine” bestemmer hvilken af funktionerne på kodeudsnit 7 der skal kaldes, baseret på inputs fra analog(5) og analog(4), som er forbundet til gameobjektet ”LinesensorLeft” og ”LinesensorRight” i Unity (figur 4).

Kodeudsnit 8: Bestemmer hvilken funktion der skal kaldes
Figur 4: Linesensorerne i Unity – LDR-Sensorerne

FollowLine-funktionen tjekker om højre og venstre LDR-sensorværdi på figur 4 er over 900 eller under 900, som i overført betydning indikerer om de befinder sig på en hvid (over 900) eller sort (under 900) overflade. Baseret på hvilket udsagn der er sandt på kodeudsnit 8, eksekveres funktionerne på kodeudsnit 7. Derfor bestemmer koden på kodeudsnit 8, om Wall·E’s Forgotten Brother enten kører ligeud, til højre eller til venstre.

Funktionen på kodeudsnit 8 kaldes igennem ”loop’et” hvor der tjekkes om followBlackLine fra kodeudsnit 9 er lig med ”true”. Hvis dette er tilfældet, bliver followLine-funktionen eksekveret, som der bliver illustreret på kodeudsnit 9.

Kodeudsnit 9: Eksekvering af followLine hvis udsagnet er “true”

Undervejs i første del af banen som er markeret med grønt på figur 3, er der implementeret en udfordring i form af en rød pæl på den sorte linje, som Wall·E’s Forgotten Brother skal køre udenom for at komme videre. Til at løse udfordringen blev der i ”loop’et” konstrueret et udsagn, som siger: Hvis avoidFirstPillar er sat til ”true”, som den så er på kodeudsnit 6. Så skal Wall·E’s Forgotten Brother eksekvere udsagnet illustreret på kodeudsnit 10.

Kodeudsnit 10: Udsagn til at undgå pælen

Inde i det første udsagn i kodeudsnit 10, er der implementeret endnu et udsagn, som tjekker om sonarsensorens værdi, som bliver målt igennem ”pulseIn(6)”, er mellem 500 og 1. Hvis dette er tilfældet, betyder det at pælen som Wall·E’s Forgotten Brother skal undgå er mindre end 500 enheder væk fra den, hvorefter funktionerne i udsagnet eksekveres med tidsmæssige mellemrum i form af ”delay”, implementeret i millisekunder. Den første funktion som bliver kaldt, er avoidPiller-funktionen, som sætter en stopper for fremdriften af Wall·E’s Forgotten Brother, ved at sætte analogWrite-index 0 og 2 til hastigheden 0. Efterfulgt af en langsom tilbagekørende handling med en hastighed på 50. (kodeudsnit 11). Hvor funktionen eksekveres i 1000 millisekunder igennem ”yield return delay(1000)”, som bliver illustreret på kodeudsnit 10.

Kodeudsnit 11: AvoidPillar-funktion

De efterfølgende funktioner efter avoidPillar-funktionen sørger for, at Wall·E’s Forgotten Brother bevæger sig statisk udenom pælen og tilbage på den sorte linje igen. Dog med en langsommere hastighed grundet værdierne for analogWrite-index 1 og 3 på kodeudsnit 11, går imod værdierne for analogWrite-index 0 og 2 på kodeudsnit 7, når funktionerne bliver kaldt i kodeudsnit 10. Hvilket betyder at farten efter den har fundet tilbage på den sorte streg efter undgåelsen af pælen er 40 enheder i stedet for 90 enheder da værdierne for analogWrite-index 1 og 3 ikke bliver sat tilbage til 0 på noget tidspunkt.

Valget om at beholde den langsomme fart efter undgåelsen af pælen bunder ud i, at Wall·E’s Forgotten Brother begik markante mindre fejl senere på banen.

Det sidste udsagn på kodeudsnit 8 tjekker om både højre og venstre LDR-værdi er under 900. Dette sker kun når Wall·E’s Forgotten Brother når til vejs ende på den sorte linje på figur 3, og dermed registrerer værdier under 900 i både højre og venstre side af LDR-sensorerne. I dette udsagn skal Wall·E’s Forgotten Brother køre fremad, og skifte til sonarsensoren. Dette gøres ved at avoidFirstPillar og followBlackLine fra kodeudsnit 6 sættes til false.

Anden del af banenSonarsensor og Servomotor

I slutningen af den første del af banen på figur 3, som er markeret med grønt, bliver variablerne followBlackLine og avoidFirstPillar i kodeudsnit 6, sat til ”false” frem for ”true” igennem kodeudsnit 8 som nævnt i tidligere stående afsnit. Dette medfører at sonarsensor-koden påbegynder, som der bliver illustreret på kodeudsnit 12, som siger, hvis begge tidligere nævnte variabler er sat til ”false”, bliver den globale variabel redWallDetection, som starter med at være ”false” i kodeudsnit 6, sat til ”true” i kodeudsnit 12.

Kodeudsnit 12: Påbegyndelse af banens anden del

Når ”redWallDetection” er bliver til ”true”, eksekverer koden på kodeudsnit 13. Koden på kodeudsnit 13. Koden tjekker først om pulseIn(6) værdien, som kommer fra sonarsensoren er mellem 500 og 1. Hvis dette er tilfældet, er det fordi Wall·E’s Forgotten Brother er tæt på en ræd væg, og derfor eksekverer endnu en hardcoded del som illustreres på kodeudsnit 13. Koden kalibrerer i sin simpleste form bare Wall·E’s Forgotten Brother udgangspunkt for den nye udfordring på anden del af banen, som er markeret med blå på figur 3.            

Dette gøres ved at vende Wall·E’s Forgotten Brother i retning af den rigtige vej og begynde at køre fremad. Undervejs bliver servomotoren drejet 150 grader, så den kigger på Wall·E’s Forgotten Brother højre side.

Kodeudsnit 13: Red Wall Detection

Til sidst i kodeudsnit 13 bliver ”followWall” variablen fra kodeudsnit 6 sat til ”true” fra ”false”. Når variablen ”followWall” er sat til ”true” eksekveres funktionen ”followRedWall” som illustreres på kodeudsnit 14.

Kodeudsnit 14: FollowRedWall bliver kaldt

Funktionen followRedWall, som bliver kaldt og som illustreres på kodeudsnit 15, eksekverer nogle udsagn baseret på værdierne fra sonarsensoren.

Kodeudsnit 15: Funktionen: followRedWall

Funktionen ”followRedWall” bruger sonarsensoren til at måle distancen mellem væggen og sensoren ved brug af lydbølger. Som der illustreres på kodeudsnit 15 i første udsagn: Hvis pulseIn(6) (sonarsensoren) måler en værdi under 800 samtidig med at den er over 400, så skal Wall·E’s Forgotten Brother køre fremad. I efterfølgende udsagn tjekker den om værdien på sonarsensoren enten er over 800 eller lig med 0. Hvis værdien er lig med nul, betyder det at Wall·E’s Forgotten Brother ikke kan raycast til et objekt i den retning han kigger, og derfor kommer der ikke et signal tilbage. Men i dette tilfælde, så drejer Wall·E’s Forgotten Brother til venstre. Til slut tjekkes der om værdien er under 400 samtidig med at den er over 1, så skal venstre motor stoppe og højre motor tændes, så robotten drejer til højre.

Til slut bliver funktionen på kodeudsnit 16 kaldt når de sidste udsagn er opfyldte. Som der illustreres på figur 3, er der placeret en sort linje i slutningen af den anden del af banen, som er markeret med blåt. Den linje betegner målstregen.

Kodeudsnit 16: GoalFunktion

Goal-funktionen tjekker om begge LDR-sensorer er under 900 i værdi (sort linje) og at followWall er true, hvis hele udsagnet opfyldes, er robotten i mål, og stopper. Goal kaldes konstant i loop, og derved tjekkes der konstant om Wall·E’s Forgotten Brotherer i mål.

Flowchart

Samspillet mellem mekanik, elektronik og software

Samspillet mellem mekanik, elektronik og software har været essentiel for at konstruere den optimale version af Wall·E’s Forgotten Brother.

Mekanik

Den mekaniske del danner grundlaget for Wall·E’s Forgotten Brother, da dette indeholder de fundamentale tanker om opbygningen af robotten, evt. gearingen, placeringer af komponenter mm. Herunder skal der været generelle overvejelse omkring Wall·E’s Forgotten Brother’s muligheder for handlinger i form af bevægelser og kræfter hvis bilen ikke allerede var opbygget i Unity.

Men selvom opbyggelsen i Unity allerede var forhåndskonstrueret grundet de fysiske arbejdsmæssige udfordringer, har vi stadig haft nogle overvejelser omkring opbyggelsen af Wall·E’s Forgotten Brother, og hvordan komponenterne skulle placeres, som der bliver illustreret på figur 4.

Figur 4: Placering af LDR og LED

I den fysiske konstruktion, ville LDR-sensorerne vende fladen ned mod jorden, hvilket ville forårsage, at LDR-værdiernes interval ikke ville være mellem 0 – 1023, som den i realiteten skulle.

Derfor er er der implementeret i alt fire LED’s ind i kredsløbet, som har det simple formål at belyse jordfladerne, så LDR-sensorerne har nemmere ved at måle refleksionerne fra jordfladerne og derfor nemmere ved at skelne mellem sort og hvid. Det skal dog lige nævnes, at LDR-sensorerne skal have en form for fysiske barrierer omkring sig, for at undgå direkte belysning fra LED-sensorerne.

Desuden er der gjort overvejelser omkring anvendelsen af resistorer og LDR-sensorerne. Igennem anvendelsen en LDR-sensorer, kan man give Arduinoen forskellige værdier ud fra det lys/mørke LDR-sensoren udsættes for. Dette kan man kalde en variabel modstand, der ændrer sig alt efter sine omgivelser.

Der blev anvendt to 10k ohm resistorer til LDR-sensorerne i vores TinkerCAD. Vi antager fra tidligere projekt omkring solcellen at vores LDR-type vil give en maksimummodstand ved lys på op til 10 lux vil ohmen være mellem 20-230 Kohm (Kilo Ohm), og ved mørke vil modstanden være 2 Mohm (Mega Ohm).

Ud fra Ohms lov og reglen om at serieforbundne modstande samlet yder en modstand der er lig med summen af de forbundne modstande, kan man påvise, at spændingerne over modstandene i en spændingsdeler er givet ved:

I dette tilfælde sætter vi modstanden til 20K ohm. Her vil LDR-sensoren være meget lys og derfor vil spændingen være lig med 3.33V:

I det næste tilfælde sætter vi modstanden til 230K ohm. Her bliver mørkere og LDR-sensoren vil spændingen være lig med 4.79V:

Til slut hvis det blev rigtig mørkt, og i dette tilfælde, hvis bilens sensor rammer den sorte streg. Vil LDR værdien, som vi har antaget være 2M ohm.

Som man kan se fra de ovenstående tal, vil der være forskellige spændinger afhængig af hvad LDR-sensorerne registrerer. Disse værdier konverteres efterfølgende til Arduinoen, hvor værdierne anvendes til at eksekvere forskellige funktionalliteter ud fra lysintensiteten.

Derudover kan det ses, at det synlige lys fra LDR-sensoren har en rækkevidde på 1.46 Volt (4,79V – 3,33V).

Efterfølgende skal man bruge LDR-sensorens rækkevidde på 1.46 Volt, til at finde antallet af værdier LDR-sensoren kan registrere ud fra dens optimale antal, som er 1024, da den burde kunne tælle værdierne mellem 0 og 1024.

Igennem denne lille udregning finder vi så ud af, at LDR-sensoren registrerer 1 enhed pr. 0.0048V på en optimal spændingsdeler.

Derefter tager man bare den værdi for LDR-sensorens synlige lys som vi beregnede tidligere til at være 1.46, og findet antallet af værdier den kan aflæse:

Dette betyder at vores LDR-sensor i et spændingsdelerkredsløb med 10Kohm kan registrere 299 forskellige værdier når LDR-sensoren udsættes for en lux værdi på 10.

Til at optimere dette så der kunne registreres flere værdier igennem LDR-sensoren, skulle der nok have været brugt en højere resistor end 10Kohm. Hvis valget havde været på 100kohm havde rækkevidden antallet af værdier den kunne registrere højrere.

Derudover har det ikke været flere store overvejelser omkring mekanikken hos Wall·E’s Forgotten Brother, da det hele blev konstrueret virtuelt, og at der derfor ikke har været brug for begrundelser af opbygning og placering af komponenter for at optimere ham.

Elektronik

Elektronikken består af kredsløbet i samspil med komponenterne som er blevet brugt til konstruktionen af Wall·E’s Forgotten Brother. Igennem elektronikken bliver der konstrueret et kredsløb (figur 5), som behandler information i form af signaler, lydbølger, lys og distribution af elektrisk energi. Derudover ligger der allerede en beskrivelse af komponenterne og deres funktioner i tidligere afsnit af bloggen.

Figur 5. Kredsløbet af Wall·E’s Forgotten Brother

Igennem brugen af komponenterne i form af signaler, lydbølger, lys og distribution af elektrisk energi, kan man igennem softwaredelen bestemme hvordan Wall·E’s Forgotten Brother skal agere baseret på de input der bliver registreret fra kredsløbet.

Software

Softwaredelen bestemmer handlingsmulighederne for Wall·E’s Forgotten Brother og hænger stærkt sammen med elektronikken. Måden handlingerne bestemmes på, er igennem funktioner og udsagn i koden, som gør brug af værdierne som komponenterne registrerer. Beskrivelsen af software i samarbejde med elektronik bliver tydeliggjort under rubrikken: Opbygning af programmet i unity.

Konklusion

Wall·E’s Forgotten Brother kommer igennem banen på knap 72 sekunder, som der bliver illustreret i den vedlagte video. Til at klare udfordringerne på banen bruger Wall·E’s Forgotten Brother en kombination af mekanik, elektronik og software, som giver ham kompetencerne og redskaberne til at udføre Braitenberg adfærden i form af en målrettet handling og undgåelsen af objekter ved brug af diverse komponenter.

Perspektivering

Wall·E’s Forgotten Brother formår at klare banen, dog ikke uden hjælp fra en smule ”hardcode”. Hvilket er tidspunkter i frekvensen vi direkte forsætter Wall·E’s Forgotten Brother hvad den skal gøre i den pågældende situation. Men grundet vores kompetenceniveau indenfor elektronik og software, følte vi ikke vi havde de rigtige redskaber til at kunne skabe en selvtænkende AI robot, som kunne komme igennem banen af sig selv.

Koden der gjorde det muligt at få en hurtigere tid blev anvendt til at vises i videoen. Dog ville robotten køre galt 1 ud af 10 gange, hvilket var grunden til at man skiftede koden tilbage til den nedstående Unity kode. Det der ville kontrollere om, hvorvidt robotten skulle køre hurtigere, var en boolean driveExtraFast.

public bool driveExtraFast = false;

Når robotten ville fornemme den første forhindring, vil den efterfølgende sætte driveExtraFast til ”true”.

void followLine()
{
    if (analogRead(5) > 900 && analogRead(4) > 900) //if LineSensorRight value is greater than 900 && LineSensorLeft is greater than 900 driveForward.
    {


        if(!driveExtraFast)
         driveForward();
         if (driveExtraFast)
         {
             analogWrite(0, 130);
             analogWrite(2, 130);

        }
    }
    else if (analogRead(5) > 900 && analogRead(4) < 900)
    {

        if (!driveExtraFast)
        {
            leftTurn();
        }

        if (driveExtraFast)
        {
            analogWrite(0, 0);
            analogWrite(2, 90);

        }
    }
    else if (analogRead(5) < 900 && analogRead(4) > 900)
    {

        if (!driveExtraFast)
        {
            rightTurn();
        }
        if (driveExtraFast)
        {
            analogWrite(0, 90);
            analogWrite(2, 0);

        }

    }
    else if (analogRead(5) < 900 && analogRead(4) < 900)
    {
        driveForward();
        Debug.Log("switching to Sonar");
        followBlackLine = false;
        avoidFirstPillar = false;
    }
}

Når driveExtraFast ville blive ”true” ændres vores analog værdi til en højere værdi, der vil få robotten til at køre hurtigere. Når sonaren tændes i sidste else if argument vil driveExtraFast også blive ligegyldig, da man her kontrollere robottens fart med funktionen followRedWall. Man valgte at beholde den gamle kode, da den var mere sikker. Grundet at robotten kun ville støde ind i problemer 1 ud af 50 gange.

Raw Code fra TinkerCAD

#include <Servo.h>
int leftSensorValue;
int rightSensorValue;

const int sonarPin = 4;

Servo myServo;
void setup()
{
  Serial.begin(9600);
  myServo.attach(5);
  
  pinMode(8,OUTPUT);
  pinMode(9,OUTPUT);
  pinMode(10,OUTPUT);
  pinMode(11,OUTPUT);
  
  pinMode(6,INPUT);
  pinMode(7,INPUT);
  
} 

void loop()
{
  rightSensorValue = digitalRead(6);
  leftSensorValue = digitalRead(7);
  
  int duration;
  int cm;

  if(rightSensorValue==HIGH && leftSensorValue==HIGH)
  {
    digitalWrite(8,HIGH);
    digitalWrite(9,LOW);
    digitalWrite(10,HIGH);
    digitalWrite(11,LOW);
  }
  else if(rightSensorValue==HIGH && leftSensorValue==LOW)
  {
    digitalWrite(8,HIGH);
    digitalWrite(9,LOW);
    digitalWrite(10,HIGH);
    digitalWrite(11,HIGH);
  }
  else if(rightSensorValue==LOW && leftSensorValue==HIGH)
  {
    digitalWrite(8,HIGH);
    digitalWrite(9,HIGH);
    digitalWrite(10,HIGH);
    digitalWrite(11,LOW);
   }
  else if(rightSensorValue==LOW && leftSensorValue==LOW)
  {
    digitalWrite(8,LOW);
    digitalWrite(9,HIGH);
    digitalWrite(10,LOW);
    digitalWrite(11,HIGH);
  }
  
  pinMode(sonarPin, OUTPUT);
  digitalWrite(sonarPin, LOW);
  delayMicroseconds(2);
  digitalWrite(sonarPin, HIGH);
  delayMicroseconds(5);
  digitalWrite(sonarPin, LOW);
  
  pinMode(sonarPin, INPUT);
  duration = pulseIn(sonarPin, HIGH);
  
  cm = microsecondsToCentimeters(duration);
  
  Serial.print(cm);
  Serial.print("cm");
  Serial.println();

  delay(100);
  
  myServo.write(90);
  delay(2000);
  myServo.write(180);
  delay(1000);
 delay(100);
} 

int microsecondsToCentimeters(int microseconds) {
  return microseconds / 29 / 2;
}

Raw Code fra Unity

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;

public class ArduinoMain : MonoBehaviour
{
    public Breadboard breadboard;
    public Servo servo;

    public bool followBlackLine = true;
    public bool avoidFirstPillar = true;
    public bool followWall = false;
    public bool redWallDetection = false;

    int leftSensorValue = 0;
    int rightSensorValue = 0;
    //On included/premade Arduino functions:
    //delay(timeInMilliseconds) : use "yield return delay(timeInMilliseconds)", to get similar functionality as delay() in arduino would give you.

    //map() : works exactly as on Arduino, maps a long from one range to another. 
    //If you want to get an int or a float from the map()-function, you can cast the output like this: (int)map(s, a1, a2, b1, b2) or (float)map(s, a1, a2, b1, b2) 

    //millis() : returns the time as a ulong since the start of the scene (and therefore also the time since setup() was run) in milliseconds.

    //If you want to do something similar to serial.println(), use Debug.Log(). 

    //analogWrite() and analogRead() works as they do in arduino - remember to give them correct input-values.
    //digitalRead() and digitalWrite() writes and returns bools. (High = true). 
    //LineSensors have both write-functions implemented, motors/hbridge have both read-functions implemented.
    //The console will display a "NotImplementedException" if you attempt to write to sensors or read from motors. 


    //Additions from 21-04-2020:

    //Distance sensor:
    //The Distance (ultrasonic) sensor is added, if you use "pulseIn()" on the pin it is assigned to, 
    //it will return the time it took sound to travel double the distance to the point of impact in microseconds (type: ulong).
    //This mimics roughly how the HC-SR04 sensor works. 
    //There is no max-range of the distance-sensor. If it doesn't hit anything, it returns a 0.

    //Servo:
    //if you add the servo-prefab to the scene, ArduinoMain will automatically find the servo object, essentially handling "servo.attach()" automatically. 
    //There can be only one servo controlled by this script.
    //servo.write() and servo.read() implemented, they function similar to a servomotor. 
    //The angles that servo.write() can handle are [0:179]. All inputs outside of this range, are clamped within the range.
    //servo.read() will return the last angle written to the servo-arm. 
    //In order to attach something to the servo, so that it rotates with the servo-arm, simply make the object you wish to rotate, a child of either: Servo-rotationCenter or Servo-arm. 
    //Make sure to take into account the position of the object relative to Servo-rotationCenter. The rotated object will rotate positively around the Y-axis (up) of the Servo-rotationCenter gameobject.

    IEnumerator setup()
    {
        //Your code goes here:
        //Element 0: LeftMoterForward
        //Element 1: LeftMoterBackwards
        //Element 2: RightMoterForwards
        //Element 3: RightMoterBackwards
        //Element 4: LineSensorLeft
        //Element 5: LineSensorRight
        servo.write(90);

        //Example of delay:
        Debug.Log("pre-delay log");
        yield return delay(2000); //2 second delay
        //Your code ends here -----

        //following region ensures delay-functionality for setup() & loop(). Do not delete, must always be last thing in setup.
        #region PremadeSetup
        yield return StartCoroutine(loop()); ;
        #endregion PremadeSetup
    }

    IEnumerator loop()
    {
        //Your code goes here:
        //Code for LineFollow
        Debug.Log("Line Sensor Right Value: " + analogRead(5));
        Debug.Log("Line Sensor Left Value: " + analogRead(4));
        Debug.Log("Distance is: " + pulseIn(6));

        if(followBlackLine == true)
        {
            followLine();
        }

        if (avoidFirstPillar == true)
        {
            if(pulseIn(6) < 500 && pulseIn(6) > 1)
            {
                avoidPillar();
                yield return delay(1000);
                leftTurn();
                yield return delay(1000);
                driveForward();
                yield return delay(2000);
                rightTurn();
                yield return delay(850);
                driveForward();
                yield return delay(3000);
                rightTurn();
                yield return delay(1000);
                driveForward();
                yield return delay(1800);
                leftTurn();
                yield return delay(1200);
                driveForward();
            }
        }

        //Sonor Code
        if(followBlackLine == false && avoidFirstPillar == false)
        {
            redWallDetection = true;
        }

        if(redWallDetection == true)
        {
            if (pulseIn(6) < 500 && pulseIn(6) > 1)
            {
                leftTurn();
                yield return delay(1260);
                driveForward();
                yield return delay(2900);
                redWallDetection = false;
                followBlackLine = true;
                yield return delay(3300);
                servo.write(150);
                yield return delay(1600);
                followWall = true;
            }
        }

        if(followWall == true)
        {
            followRedWall();
        }

        goal();

        //Following region is implemented as to allow "yield return delay()" to function the same way as one would expect it to on Arduino.
        //It should always be at the end of the loop()-function, and shouldn't be edited.
        #region DoNotDelete
        //Wait for one frame
        yield return new WaitForSeconds(0);
        //New loop():
        yield return loop();
        #endregion DoNotDelete 
    }

    void followLine()
    {
        if (analogRead(5) > 900 && analogRead(4) > 900) //if LineSensorRight value is greater than 900 && LineSensorLeft is greater than 900 driveForward.
        {
            driveForward();
        }
        else if (analogRead(5) > 900 && analogRead(4) < 900)
        {
            leftTurn();
        }
        else if (analogRead(5) < 900 && analogRead(4) > 900)
        {
            rightTurn();
        }
        else if (analogRead(5) < 900 && analogRead(4) < 900)
        {
            driveForward();
            Debug.Log("switching to Sonar");
            followBlackLine = false;
            avoidFirstPillar = false;
        }
    }

    void driveForward()
    {
        analogWrite(0, 90);
        analogWrite(2, 90);
    }

    void leftTurn()
    {
        analogWrite(0, 0);
        analogWrite(2, 90);
    }

    void rightTurn()
    {
        analogWrite(0, 90);
        analogWrite(2, 0);
    }

    void avoidPillar()
    {
        analogWrite(0, 0);
        analogWrite(2, 0);
        analogWrite(1, 50);
        analogWrite(3, 50);
    }

    void wallDetection()
    {
        analogWrite(0, 0);
        analogWrite(2, 0);
        analogWrite(1, 50);
        analogWrite(3, 50);
    }

    void goal()
    {
        if (analogRead(5) < 900 && analogRead(4) < 900 && followWall == true)
        {
            analogWrite(0, 0);
            analogWrite(2, 0);
            analogWrite(1, 0);
            analogWrite(3, 0);
        }
    }

    void followRedWall()
    {
        if (pulseIn(6) < 800 && pulseIn(6) > 400)
        {
            analogWrite(0, 90);
            analogWrite(2, 90);
        }
        else if (pulseIn(6) > 800 || pulseIn(6) == 0)
        {
            analogWrite(0, 90);
            analogWrite(2, 45);
        }
        else if (pulseIn(6) < 400 && pulseIn(6) > 1)
        {
            analogWrite(0, 0);
            analogWrite(2, 120);
        }
    }

    #region PremadeDefinitions
    void Start()
    {
        servo = FindObjectOfType<Servo>();
        if (servo == null)
        {
            Debug.Log("No servo found in the scene. Consider assigning it to 'ArduinoMain.cs' manually.");
        }
        Time.fixedDeltaTime = 0.005f; //4x physics steps of what unity normally does - to improve sensor-performance.
        StartCoroutine(setup());
    }

    IEnumerator delay(int _durationInMilliseconds)
    {
        float durationInSeconds = ((float)_durationInMilliseconds * 0.001f);
        yield return new WaitForSeconds(durationInSeconds);
    }

    public long map(long s, long a1, long a2, long b1, long b2)
    {
        return b1 + (s - a1) * (b2 - b1) / (a2 - a1);
    }

    public ulong millis()
    {
        return (ulong)(Time.timeSinceLevelLoad * 1000f);
    }

    public ulong abs(long x)
    {
        return (ulong)Mathf.Abs(x);
    }

    public long constrain(long x, long a, long b)
    {
        return (x < a ? a : (x > b ? b : x));
    }


    #endregion PremadeDefinitions

    #region InterfacingWithBreadboard
    public int analogRead(int pin)
    {
        return breadboard.analogRead(pin);
    }
    public void analogWrite(int pin, int value)
    {
        breadboard.analogWrite(pin, value);
    }
    public bool digitalRead(int pin)
    {
        return breadboard.digitalRead(pin);
    }
    public void digitalWrite(int pin, bool isHigh)
    {
        breadboard.digitalWrite(pin, isHigh);
    }

    public ulong pulseIn(int pin)
    {
        return breadboard.pulseIn(pin);
    }
    #endregion InterfacingWithBreadboard
}

Leave a Reply