En rapport af: Lasse Fisker (lafis17) og Alexander Rol (alrol17)

Demonstration af robottens gennemkørsel
NB: Timeren er gået i stykker, og viser derfor ikke tid.
Tiden er ikke en del af projektets formål og er derfor ikke forsøgt fikset.

Komponentliste

Til udarbejdelsen af denne robot skal der bruges følgende komponenter:

  • Arduino UNO microcontroller
  • 2 DC-motorer
  • L293D integreret kredsløb
  • 1 servomotor
  • 1 HC-SR03
  • 1 9V batteri
  • 2 hvide LED
  • 2 LDR
  • 2 32 ohm modstande
  • 2 10 kilo-ohm modstande
  • Ledninger

Derudover skal der bruges et udviklingsmiljø til at skrive Arduino kode i, eksempelvis Arduino IDE eller Visual Studio Code, samt robottens skelet, eksempelvis chassis og hjul.

Opbygning i TinkerCad

Robottens opbygning er skitseret i Tinkercad, som vist nedenfor på figur 1. For at overskueliggøre de forskellige dele af kredsløbet, er dette blevet delt op på to forskellige breadboards, som dog stadig bliver styret af én enkelt Arduino. Hvis systemet skulle produceres som endeligt produkt, ville dette naturligvis ikke være lavet på to forskellige boards, men hele systemet ville være integreret.

Figur 1 – Robottens sammensætning i TinkerCad

Den samlede opbygning kan deles op i flere delkomponenter, som kan ses på figur 2 nedenfor.

Figur 2 – Opdeling af sensorer, aktuatorer og controller

De tre delkomponenter er sensorer, aktuatorer og controlleren. På sensor-komponenten, findes en HC-SR03, som sender ultrasoniske bølger ud ved aktivering og måler hvor lang tid der går før den modtager de udsendte bølger. Derudover er der to hvide lysdioder (LED) og to lysmodstande (LDR). De to LED’ers formål er at sikre at der er konstant det samme udgangspunkt for at måle underlagets reflektion. LDR reagerer på reflekteret lys og vil derfor reagere på bølgelængden af det lys der bliver reflekteret i gulvet i lyset fra de to LED’er og justerer modstanden i henhold til det. Da mørke overflader og lyse overflader ikke reflekterer lige meget af lyset, vil de to LDR kunne få robotten til at følge den sorte linje, ved hjælp af Arduino koden.
Både LED og LDR har modstande enten før eller efter sig i deres respektive kredsløb. LED’erne har en modstand før sig for at der ikke løber for meget spænding igennem dem, så de sprænger. Disse modstande er beregnet i forhold til en hvid LED, som er fundet i et datablad (http://www1.futureelectronics.com/doc/EVERLIGHT%C2%A0/334-15__T1C1-4WYA.pdf). Det er beregnet at for at LED’erne får den mængde spænding (3,3V) som er midt mellem minimum- og maksimumsværdierne i databladet, i stedet for de 5V som Arduinoen giver den, skal der bruges en modstand på 32 ohm. Dette er beregnet på følgende måde:

Da vi ved at LED’en i sig selv bruger 3,3V og der er en spænding på 5V i kredsløbet, skal modstanden bruge 1,7V for at bruge resten i kredsløbet. Det er vores U-værdi i dette tilfælde. Da strømmen målt i kredsløbet ved LED’erne er 0,0535A, er dette værdien på variablen I.

R=U/I=1,7/0,0535=31,78 ohm

Dette rundes i dette tilfælde op til 32 ohm, og derved er modstandsværdien for de to resistorer til LED’erne beregnet.
Derudover er strømmen gået fra rundt regnet 53,5 mA til 34,3 mA. LED’ernes normale anbefalede mængde strøm er 20 mA, så den bliver ikke overbelastet nok til at der sker andet end at levetiden på dioden vil forringes en smule.

Grunden til at der skal være en modstand efter hver enkelt LDR i kredsløbet er at Arduino ikke kan aflæse modstand. Derimod kan den registrere spændingsforskelle. Ved at sætte en ekstra modstand efter en LDR, et såkaldt spændingsdeler-kredsløb, omdannes modstanden til en spændingsforskel, ved hjælp af Ohms første lov, som siger at spænding = modstand * strøm. Og denne værdi kan Arduino registrere og derved også ændre adfærd ud fra, ved hjælp af koden.

Hvis man kigger på aktuator-boardet, er der en servomotor, to DC-motorer og en H-bro ved navn L293D (Se figur 4), som sørger for at de to DC-motorer kan køre i begge retninger, alt efter hvilken vej strømmen løber.

Servomotorens opgave er at kunne dreje HC-SR03, således at vi kan sørge for at måle både til venstre, højre og ligefrem for selve køretøjet. I et virkeligt system, ville sensoren være fastspændt direkte på servomotoren, i stedet for at være på to forskellige breadboards her. Servomotoren styres ved hjælp af Arduino-boardet.

De to DC-motorer sørger for at drive robottens hjul for at skabe fremdrift og motorerne vil give et højere omdrejningstal jo mere spænding de får gennem sig. Da Arduino-boardet ikke kan konvertere digitale signaler til analoge signaler ved hjælp af en DAC (Digital-to-Analog Converter), bliver hastigheden på motorerne styret ved hjælp af PWM (Pulse Width Modulation). PWM fungerer på den måde at man skriver digitale signaler ud fra Arduinoen, som enten er HIGH eller LOW, hvor HIGH er maks hastighed og LOW er slukket. Ud fra hvor længe outputtet er HIGH i forhold til hvor længe det er LOW, får man en gennemsnitlig spænding. Eksempelvis vil man få et spændingsniveau på 50% af det maksimale hvis man har en duty cycle på 50%, det vil sige at PWM-signalet er lige lang tid på HIGH som det er på LOW. Da dette signal er vekslende, skal der noget til at jævne det ud for at DC-motoren kan fungere optimalt. En spole eller en transistor kan omdanne indkommende spænding til et magnetfelt, som lades op og derefter kan aflade jævnt igen, således at spændingen derefter er helt jævn. Dermed kan man også nemt implementere blød opstart eller generelle hastighedsændringer, ved at regulere PWM-signalerne til en lavere eller højere duty cycle En illustration af PWM vises herunder i figur 3.

Figur 3 – Illustration af PWM-signaler ved forskellige duty cycles
Figur 4 – L293D – H-Bro

L293D består af 16 forskellige terminaler, hvoraf nogle deler funktionalitet og kan drive to outputs på én gang.
Vcc 1 står for at have strømforsyning til den interne logik i chippen, mens Vcc 2 står for at modtage strøm som kan sendes videre ud i systemet, eksempelvis for at drive en DC-motor.
Enable-terminalerne står for at låse op for at et signal kan nå fra input-terminalerne til den interne logik i kredsløbet.
Input-terminalerne bruges til at sende et signal til kredsløbet, som beskriver en opgave man vil have udført. Dette vil blive behandlet internt og signalet om opgaven sendes videre til den respektive output-port, som eksempelvis sender signalet videre til en DC-motor for at drive den.
GND er for at sørge for at kredsløbet har jordforbindelse, så der ikke er overgang og for at fuldende kredsløbet (https://www.ti.com/lit/ds/symlink/l293d.pdf?ts=1588425062000).

Derudover er der brugt et 9V batteri, for at sørge for at kredsløbet havde nok strøm. Uden batteriet, ville der ikke være strøm nok til både aktuatorerne og sensorerne. Normalt er det ikke anbefalet at køre bare DC-motorer direkte fra Arduino boardet og da vi i dette tilfælde skal bruge tre forskellige motorer – 1 servomotor og 2 DC-motorer – Er det nødvendigt at have et 9V batteri til at agere strømforsyning til disse tre. Det skal også nævnes at disse motorer oftest bruger mere strøm i opstarten end når de er ved den ønskede hastighed, så der vil være mere pres på strømforsyningen indtil motorerne kommer op i fart.

Opbygning af program

Følgende kodestykke er skrevet i Visual Studio ved brug af C#, da situationen med Covid-19 gør det umuligt at kunne fremstille en fysisk version af robotten. Der er derfor blevet udleveret en bane i Unity vi bruger til at teste robottens opførsel.

Banens opbygning kan ses i videoen i toppen af denne blog.
Vi vil ikke komme nærmere ind på Unity og dets implementering, da dette ikke er en del af kurset.

Grundprincipperne ved kørslen af robotten er, at den altid vil starte med at forsøge at følge linjen på gulvet. Mens dette sker, vil HC-SR03 holde øje med om der er forhindringer forud for robotten. Skulle dette ske, vil sensoren giver udslag og robotten vil forsøge at undgå forhindringen ved hjælp af en serie af sving på 90 grader samt forsøge at komme tilbage på stregen igen.
Når robotten opfanger sort på begge LDR, vil den slå disse sensorer fra, og gå i sonar-mode. I denne mode vil den holde øje med væggen til højre resten af vejen til målet.

HC-SR03 stying:
I Arduino er HC-SR03 sat op til at sende et signal ud i 10 mikro-sekunder for derefter at lytte efter et svar på dette signal. Dette køres over 1 pin på Arduinoen, hvilket gør at vi er nødt til at ændre signaltypen på den valgte pin fra at forvente et output til et input efter vi har sendt signalet i 10 mikro-sekunder.
Resultatet vi får tilbage er af datatypen long som vi herefter evaluerer på i forhold til at bedømme om vi er over eller under vores tolerancegrænse som bestemmes efter for stor en afstand vi ønsker robottenhar før en undvigelse.

long readDistance(int triggerPin, int echoPin) {
  	pinMode(triggerPin, OUTPUT);  //Clear the trigger
 	digitalWrite(triggerPin, LOW);
  	delayMicroseconds(2);
  	//Sets the trigger pin to HIGH state for 10 microseconds
  	digitalWrite(triggerPin, HIGH);
  	delayMicroseconds(10);
  	digitalWrite(triggerPin, LOW);
  	pinMode(echoPin, INPUT);
  	long result = pulseIn(echoPin, HIGH);
  	digitalWrite(echoPin, LOW);
  	pinMode(triggerPin, OUTPUT);  //Clear the trigger
  	
  	//Reads the echo pin, and returns the sound wave travel time in microseconds
  	return result;
}

Servo styring:
Ved brug af servo benytter vi et import i Arduino “#include <Servo.h>” som gør os i stand til at kommunikere med vore servo gennem simple kommands. Det eneste vi skal gøre er at give vores servo et variabel navn (i dette tilfælge “myServo” og derefter kalde dette navn med den grad du gerne vil have den skal vende (“myServo = 45″ sætter servo til 45 grader). Hvis ikke en værdi sættes på denne servo, vil den pr. automatik gå til 90 grader som er midterste værdi (minimum på 0 og maximum på 180 grader).

Motor styring:
Vores motor er forbundet 2 steder i vores Arduino. Den ene er til bevægelse fremad og den anden er til bevægelse bagud. Dette er gjort, da vi benytter os af en L293D, som du kan læse mere om under “Opbygning i TinkerCad”. Kort sagt, kan vi styre motorens retning afhængigt af hvilken vej strømmen føres gennem motor-komponenten. Dette styres gennem vores pin(s) og forbindelser til L293D.
Det vi gør herfra er at fortælle Arduino hvilken pin vi har sat motoren på, og derefter fortælle om output’et på den/de valgt(e) pin(s) skal være “HIGH” eller “LOW”. Disse er en indikation på om vi vil have bevægelse i den valgte retning eller ej.
Det er altid en god idé at navngive nummeret på den pin der bruges, på en måde, så man kan læse sig til hvad den pågældende pin kontrollerer (f.eks. “leftForward“).

NB: Koden beskriver HC-SR03 ved sonar-mode. Dette er ikke faktuelt, da sonar primært forholder sig til undervands-afstandsmåling. Funktionerne er meget ens, men teknologiens navn er forskellig.

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

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

    ulong rightDist;
    ulong middleDist;
    ulong leftDist;
    ulong distanceThreshold = 400;

    int servoAngle = 0;
    bool maxServoAngle = false;
    bool sonarMode = false;
    bool initialMeasurementDone = false;

    bool avoided = false;
    bool wallFound = false;

    ulong readDistance()
    {
        ulong result = breadboard.pulseIn(2);
        Debug.Log("ReadDistance: " + result);
   
        return result;
    }

    void reverse(int speed)
    {
        Debug.Log("tilbage");
        breadboard.analogWrite(3, 0);
        breadboard.analogWrite(4, 0);
        breadboard.analogWrite(5, speed);
        breadboard.analogWrite(6, speed);
    }

    void driveForward(int speed)
    {
        Debug.Log("Frem");
        breadboard.analogWrite(5, 0);
        breadboard.analogWrite(6, 0);
        breadboard.analogWrite(3, speed);
        breadboard.analogWrite(4, speed);
    }

    void turnLeft(int speed)
    {
        Debug.Log("venstre");
        breadboard.analogWrite(3, 0);
        breadboard.analogWrite(4, speed);
    }

    void turnRight(int speed)
    {
        Debug.Log("højre");
        breadboard.analogWrite(3, speed);
        breadboard.analogWrite(4, 0);
    }

    void stop()
    {
        breadboard.analogWrite(3, 0);
        breadboard.analogWrite(4, 0);
        breadboard.analogWrite(5, 0);
        breadboard.analogWrite(6, 0);
    }

    void followLine()
    {
        Debug.Log("Followline");
        int leftSensor = analogRead(0);
        int rightSensor = analogRead(1);

        if(servoAngle != 90)
        {
            servo.write(90);
        }
        
            if (leftSensor > 945 && rightSensor > 945)
            {
                //Go straight
                breadboard.analogWrite(3, 50);
                breadboard.analogWrite(4, 50);
            } else if (leftSensor < 945 && rightSensor > 945)
            {
                //Left correction
                breadboard.analogWrite(5, 5);
                breadboard.analogWrite(3, 0);
                breadboard.analogWrite(4, 50);
            } else if (leftSensor > 945 && rightSensor < 945)
            {
                //Right correction
                breadboard.analogWrite(6, 5);
                breadboard.analogWrite(3, 50);
                breadboard.analogWrite(4, 0);
            } else if (leftSensor < 945 && rightSensor < 945)
            {
            Debug.Log("Entered STOP");
                //Stop
                breadboard.analogWrite(3, 0);
                breadboard.analogWrite(4, 0);
                breadboard.analogWrite(5, 0);
                breadboard.analogWrite(6, 0);
                sonarMode = true;
                servo.write(90); //preparing servo for forward motion and detection.
                Debug.Log("sonarmode " + sonarMode);
            }
    }

IEnumerator setup()
    {
        //Your code goes here:
        servoAngle = 90;
        servo.write(servoAngle);

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

 IEnumerator loop()
    {
        Debug.Log("Sonarmode: " + sonarMode);
        if (!sonarMode)
        {
            ulong result = readDistance();
            //Distance sensor returns 0 if no obstacle is found
            if(result == 0)
            {
                followLine();
            } else if (result > distanceThreshold) 
            {
                followLine();
                Debug.Log("If result");
            } else
            {
                if (avoided == false)
                {
                    Debug.Log("avoidobst");
                    Debug.Log(middleDist);
                    avoided = true;
                    //AVOID OBSTACLE
                    reverse(25);
                    yield return delay(200);
                    turnLeft(50);
                    yield return delay(2600);
                    driveForward(25);
                    yield return delay(1800);
                    turnRight(50);
                    yield return delay(2300);
                    driveForward(25);
                    yield return delay(2000);
                    turnRight(50);
                    yield return delay(2300);
                    driveForward(25);
                    yield return delay(200);
                    turnLeft(50);
                    yield return delay(2300);
                    reverse(25);
                    yield return delay(1400);

                    stop();

                    Debug.Log("DONE");
                }
            }
            
        } else
        {
            breadboard.analogWrite(3, 25);
            breadboard.analogWrite(4, 25);

            if (!wallFound)
            {
                if (readDistance() >= 900 && readDistance() <= 950)
                {
                    wallFound = true;
                    turnLeft(50);
                    servo.write(180);
                    yield return delay(2300);
                }
            } else if(wallFound)
            {
                if(readDistance() > 950)
                {

                    Debug.Log("højre");
                    breadboard.analogWrite(3, 50);
                    breadboard.analogWrite(4, 15);
                   // turnRight(50);
                    yield return delay(6500);

                    Debug.Log("Complete 180");

                    breadboard.analogWrite(3, 50);
                    breadboard.analogWrite(4, 50);

                    yield return delay(8000);

                    breadboard.analogWrite(3, 0);
                    breadboard.analogWrite(4, 0);

                    yield return delay(9999999); //Stop when passing finish line (Infinite delay)
                }
            }
        }
    }

Forhindringen:
For at sikre at vi undgår forhindringen på samme måde, hver gang, har vi valgt at kode ruten rundt om. Dette er ikke det bedste valg, men da forhindringen var så smal som den var så vi på nogle af de tidlige tests, at HC-SR03 ikke så forhindringen under omkørslen.
Denne måde at gøre det på tog lang tid at implementere, men har ført til et resultat der gør at der er minimal afvigelse fra de forskellige kørsler, og vi kan derfor regne med at forhindringen passeres korrekt hver gang. Dog er koden til dette ikke det flotteste at kigge på.
Der er lagt en forsinkelse ind efter hver bevægelse, for at sikre, at robotten når at udføre denne operation førend vi beder den udføre den næste. Uden forsinkelserne, vil robotten gennemføre alle disse linjer på mindre end 1 sekund og stadig ikke være forbi forhindringen.
Samme princip er også benyttet ved andre forsinkelser i koden.

Debug.Log("avoidobst");
                    Debug.Log(middleDist);
                    avoided = true;
                    //AVOID OBSTACLE
                    reverse(25);
                    yield return delay(200);
                    turnLeft(50);
                    yield return delay(2600);
                    driveForward(25);
                    yield return delay(1800);
                    turnRight(50);
                    yield return delay(2300);
                    driveForward(25);
                    yield return delay(2000);
                    turnRight(50);
                    yield return delay(2300);
                    driveForward(25);
                    yield return delay(200);
                    turnLeft(50);
                    yield return delay(2300);
                    reverse(25);
                    yield return delay(1400);

                    stop();

                    Debug.Log("DONE");

Samspil – mekanik, elektronik og software

I dette afsnit vil det blive illustreret og beskrevet hvordan denne robot udnytter både mekanik, elektronik og software til at udføre den ønskede opgave. Til illustrationer er der udarbejdet forløbsdiagrammer der viser forløbet i de tre forskellige tilstande robotten kan være i. Disse faser er avoidObstacle, hvor robotten skal navigere udenom en forhindring, som i dette tilfælde er en søjle, followLine hvor robotten følger den sorte linje i gulvet og til sidst sonarMode, hvor robotten navigerer ved hjælp af HC-SR03.

Samspillet mellem mekanik, elektronik og software foregår generelt sådan at vi får en værdi indlæst gennem noget elektronik, eksempelvis de to lysmodstande, som Arduinoen (software) processerer og ved hjælp af dens logik, eksempelvis sender signal til L293D og derved får de to DC-motorer til at udføre noget mekanisk arbejde.

Herunder følger forløbsdiagrammer for de tre forskellige faser der er nævnt ovenover.

Figure 5 – AvoidObstacle
Figur 6 – FollowLine
Figur 7 – SonarMode

Konklusion

I vores personlige mening, kører robotten bedre end forventet. Mængden af iterationer pr. millisekund er langt langt højere end først antaget, hvilket kommer til fordel for kørslen, da robotten er meget hurtigere til at registrere når den rammer linjen på gulvet. Dette er også grunden til at vi har valgt at flytte de to lysmodstande tættere på hinanden, for at få en mere præcis og ensartet kørsel hver gang.
Overordnet klarer robotten opgaven, da den kan navigere gennem banen uden at støde ind i forhindringerne og benytter dens sensorer på en måde der gør at den har nemmere ved at komme igennem banen end hvis den ikke havde dem.

Eventuelle forbedringer

Grundet Covid-19, har vi været nødsaget til at simulere hele dette miljø gennem Unity og C#. Brugen af C# har gjort at vi ikke har kunnet følge den normale praksis for udførelse af loops mv. som man normalt skal gøre i Arduino, men da vi ikke har haft mulighed for at benytte delay i void-metoder, var det nødvendigt at bryde den normale praksis.

I sidste del af banen ville det være mere smart hvis robotten kunne navigere udelukkende ved hjælp af HC-SR03, som hele tiden scanner miljøet omkring den og ikke kun bruge den til at finde en væg for at udløse at event i robottens logik.

I det hele taget kunne robotten være mere autonom, eksempelvis ved at bruge HC-SR03 til at navigere rundt om forhindringen, i stedet for en kodet række operationer, der kun gælder lige præcis den ene forhindring.

Leave a Reply