Af Orhan Hadzihasanovic, Peter Obling og Rune Kristiansen

Produktbeskrivelse:

I denne opgave skal vi konstruere en autonom linje-følge robot, der kan fragte kasser rundt i et netværk bestående af sorte streger på hvid baggrund, med tydelige markeringer for opsamlings- og afleveringssteder.

Billede af banen

Produktkrav:

De to første baner, A & B, er obligatoriske, mens resten af banerne er valgfrie.

Dvs. robotten skal som minimum kunne starte i ”start området” og uden menneskelig indflydelse, kunne aflevere kasse A og B i ét run.

Dertil kommer at robotten skal påmonteres x antal LEDs (én for hvert state robotten kan befinde sig i (eks. turn left/right, go straight, make a u turn) – den enkelte LED skal lyse op mens robotten er i det korresponderende state).

Derudover er det et krav at der fremstilles et PCB der skal fungere som bindeled mellem Arduinoen og LED’erne.

Komponentliste:

  • Motorshield
  • Arduino UnPCB Board
  • 2xDC lego motorer
  • 2xLine-sensor breakout (QRE113)
  • 5xLED
  • Ledninger
  • Lego
  • 240 Ω modstand

Systemets opbygning:

Billede af robotten, Scorpio

Vores to sensorer er med til at registrere, om robotten kører på banen. Sensorerne er placeret under motorerne (ved de forreste hjul), da vi med det samme så ville kunne registrere, når robotten skulle dreje. Derudover, gav det også den optimale afstand mellem jorden og sensorerne. Gruppen har efterfølgende fundet ud af, at sensorplaceringen kunne optimeres yderligere, men dette beskrives i afsnittet “Perspektivering”.

Sensorerne reflekterer væsentligt mere infrarødt lys, når den kører over lyse overflader, end hvis den kører over mørke overflade. Det er præcis som vi kender det fra fysiktimerne i folkeskolen: des lysere en overflade, des mere lys bliver reflekteret. Des mørkere en overflade, des mere lys absorberes.

Line sensorerne består af to dele: en infrarød LED og infrarød fototransistor. Fototransistoren ændrer hvor meget spænding, der sendes til output pinnen. Jo mindre infrarødt lys, der reflekteres, jo højere output får vi. Omvendt, når der reflekteres meget infrarødt lys, vil vi få et lavere output.

Størrelsen på hjulene er ændret undervejs, da robotten ellers ville have været for høj til at kunne løse opgaven tilstrækkeligt. I den første iteration, blev hjulenes størrelse derfor halveret, så de p.t. er ca.4 cm. høje. Dette gav også mulighed for bedre at dreje robotten. Ligeledes kunne vi også placere sensorerne tættere på jorden.

Vi bruger motor shield i stedet for kun Arduino fordi, at motor shieldet nemt styre motorhastigheden og retningen af motoren, vha. dens indbyggede chip (også kaldet H-broen). Det giver også nok strøm til at vi kan køre op til to DC-motorer med vores Arduino.

De to DC motorer, vi benytter, er placeret ved de forreste hjul, da vi på den måde kunne undgå gearing. En af problematikkerne ved dette er, at robotten bliver en anelse for bred. Ved at gøre den smallere, ville det have effektiviseret dens forløb på banen, da den så ville have været mere agil.

Da de to DC motorer og hjulene er placeret overfor hinanden, er der er ingen gearing på robotten, da det ikke er nødvendigt for at kunne løse opgaven.

Legofladen der er øverst på robotten er til at opbevaring af arduino, motorshield m.m., så dette ikke ville falde af.

Fangarmenes størrelse blev også ændret i den sidste iteration, så den havde bedre mulighed for at fange kassen samtidig med, at den havde større areal at bevæge sig på.

Sammensætning af hardware:

Vi har vores to line sensorer sat hhv. til A2 og A3. Her kan vi nemlig få værdier mellem 0 – 1023. Vores line sensorer viser os også værdier mellem 0 – 1023. Jo højere tallet er, jo mere har underlaget absorberet IR lyset. De to sensorers VCC er sat sammen på et linje breadboard, så vi kan tilslutte dem til 5V på vores motor shield. Ligeledes gælder det samme for GND.

LED’erne er sat på PCB’en, og er tilsluttet hhv. digital 4, 5, 6,7 og 10. GND fra PCB er også sat til motorshieldets GND.

De to A og B motorer er også sat til minus og plus i motorshieldet.

Hardwarens opbygning
Diagramtegning

Udfordringer med hardware:

Vores robot har gennemgået et par iterationer undervejs, så den er optimeret bedst muligt til at kunne løse opgaven. Iterationerne inkluderer bl.a. en reduktion i hjulenes størrelse, sensorernes placering samt sensorernes afstand til jorden for bedst mulig måling. Til at starte med, havde vi placeret sensorerne for langt fremme og for højt oppe.

Til at starte med, brugte vi et lego hjul bagerst på robotten, men det gav bedre mening at benytte et rundt hjul til at styre robottens rotation.

Gruppen startede også med at bruge et 9V DC batteri, men da det gav for ujævne resultater, brugte vi i stedet strøm direkte fra computeren.

Til at starte med, havde vi ikke lavet noget hul til modstanden i vores PCB. Vi var dog nødsaget til efterfølgende at lave dette, så vi kunne nedsætte strømmen, der blev tilføjet til LED’erne.

Gruppen har brugt oceaner af tid på at få robotten til at køre korrekt, da både sensorernes værdier og robottens hastighed var for uregelmæssig. Dette skyldtes to defekte arduino motorshields.

Sidst, så har robottens opførsel generelt været meget “selvstændig” i den forstand, at den ikke altid fulgte de states og conditions, vi havde fremsat. Dette skyldtes, at måtten var meget beskidt grundet skomærker, kaffepletter m.m. Dette kunne også påvises, når sensorerne af og til kørte på den hvide del, men ikke registrerede særlig meget IR lys.

Robottens opførsel:

Robottens opførsel er, at som opgaven lød på: “at konstruere en model (Finite State Machine), der er baseret på relevante transition conditions, kan bevæge sig stadie til stadie, hvor hvert stadie står for at udføre en given handling/opførsel”, vha. switch cases. Se nærmere i kode afsnittet.

De 4 overordnede opførsler for robotten er følgende funktioner: Forward(), Left(), Right() og Forwardblack(). Se nærmere på flow-diagram og kodeafsnit for yderligere beskrivelse.

Flow diagram

Gennem flere iterationer, fandt vi ud af mindre motorkraft løste problemer med at dreje i krydsene på banen. Disse transition conditions blev modificeret gennem trial and error, alt efter hvor godt de fungerede med passende if statements. Se nærmere under kodeafsnittet.

Koden:

Beskrivelse af koden er noteret i nedenstående kode ud for hver variabel, funktion og lignende.

#include "Arduino.h"

//Variabler
const int sensorPin1 = A2; //venstre sensor
const int sensorPin2 = A3; //højre sensor
int Sensor1 = 0;
int Sensor2 = 0;
int threshold = 300; //grænseværdi for line sensor 
int motorTurnA = 12; 
int motorSpeedA = 3; //hastighed motor A (venstre)
int motorTurnB = 13;
int motorSpeedB = 11; //hastighed mortor B (højre)
int speedForward = 255; //Max hastighed
int speedTurn = 0;

static unsigned int state = 1; // Første state

void setup() {
  Serial.begin(9600);
  pinMode(sensorPin1, INPUT);
  pinMode(sensorPin2, INPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(10, OUTPUT);
  


  //Opsætning af porte for motorer
  pinMode(motorTurnA, OUTPUT);                              // Erklærer at motorTurn er output
  pinMode(motorSpeedA, OUTPUT);                             // Erklærer at motorSpeed er output
  pinMode(motorTurnB, OUTPUT);                              // Erklærer at motorTurn er output
  pinMode(motorSpeedB, OUTPUT);                             // Erklærer at motorSpeed er output

  state = 1;

OpgaveAB();
  
}


void loop() {

/*
  digitalWrite(4, HIGH);
  digitalWrite(5, HIGH);
  digitalWrite(6, HIGH);
  digitalWrite(7, HIGH);
  digitalWrite(10, HIGH);
  */

//Aflæser input for lyssensor
  
  Sensor1 = analogRead(sensorPin1); //Får værdi af venstre sensor
  Sensor2 = analogRead(sensorPin2); // Får værdi af højre sensor

// Begge værdier bliver printet i serial monitor:
  
  Serial.print(Sensor1); // Venstre1
  Serial.print(", ");
  Serial.println(Sensor2); // Højre1
  Serial.print(", ");

//Initializer koden for opgave A + B hvor vi har hardcoded de forskellige states den går igennem

OpgaveAB();
  
}


// Kode for at køre fremad og korrigerer hvis en af sensorene opfanger en værdi over threshold
void Forward() {

//Kode til PCB board hvor LED er tilkoblet, dette skifter for hver state

     digitalWrite(4, HIGH);
  digitalWrite(5, LOW);
  digitalWrite(6, LOW);
  digitalWrite(7, LOW);
  digitalWrite(10, LOW);
  
    //Motor A Backward er venstre motor
  digitalWrite(12, HIGH);
  analogWrite(3, 150);

  //Motor B forward er højre motor
  digitalWrite(13, HIGH);
  analogWrite(11, 150);

   Serial.println("jeg kører fremad"); 

    
  
    if (analogRead(sensorPin1) > threshold) {
  
  digitalWrite(11, 30);
  
  analogWrite(3, 0);  

  Serial.println("drejer venstre");
}

   

if (analogRead(sensorPin2) > threshold) {
  digitalWrite(3, 30); 

  analogWrite(11, 0);

  Serial.println("drejer højre");
    }


}


// State hvor scorpion står stille
void State0(){
  digitalWrite(12, HIGH);
  analogWrite(3, 0);  

  digitalWrite(13, HIGH);  
  analogWrite(11, 0); 
}

// State hvor scorpion kører fremad hvis begge sensorer opfanger en værdi over threshold (Kører igennem sort kryds)
void Forwardblack() {

    digitalWrite(4, LOW);
  digitalWrite(5, HIGH);
  digitalWrite(6, LOW);
  digitalWrite(7, LOW);
  digitalWrite(10, LOW);
  
  Serial.println("hej");
  
  digitalWrite(12, HIGH);
  analogWrite(3, 150);  

  digitalWrite(13, HIGH);  
  analogWrite(11, 150); 


  
Serial.println("forwardblack");
}

// State hvor scorpiuon drejer til venstre ved at venstre motor har mindre kraft end højre motor
void Left(){

    digitalWrite(4, LOW);
  digitalWrite(5, LOW);
  digitalWrite(6, HIGH);
  digitalWrite(7, LOW);
  digitalWrite(10, LOW);
  
  //Motor A Backward
  digitalWrite(12, LOW);
  analogWrite(3, 180);

  //Motor B forward
  digitalWrite(13, HIGH);
  analogWrite(11, 230);

  Serial.println("Left");
}

//Omvendt af venstre

void Right(){
  digitalWrite(4, LOW);
  digitalWrite(5, LOW);
  digitalWrite(6, LOW);
  digitalWrite(7, HIGH);
  digitalWrite(10, LOW);
  
  //Motor A Backward
  digitalWrite(12, HIGH);
  analogWrite(3, 255);

  //Motor B forward
  digitalWrite(13, LOW);
  analogWrite(11, 170);

  Serial.println("Right");
}

//Dette er koden for opgave A og B, hvor den skifter alt afhængig af pågældende if statement

void OpgaveAB() {

  
  switch (state)
  {
    case 1:
      Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
    state = 2;
    }
   break;

    case 2:
    Forwardblack();
    if((analogRead(sensorPin1) < threshold) || (analogRead(sensorPin2) < threshold)){
      state=3;
    }
     break;
     
     case 3:
      Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
    state = 4;
    }
   break;


case 4:

    Forwardblack();
    if((analogRead(sensorPin1) < threshold) || (analogRead(sensorPin2) < threshold)){
      state=5;
    }
break;

case 5:

Forward();
if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
  state = 6;
}
break;

 case 6:
  Right();
if((analogRead(sensorPin1) > threshold) ){
      
  state = 7;
}
break;


case 7:
      Right();
    if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
    state = 8;
    }
   break;

   
   case 8:
  Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
    state = 9;
break;

case 9:
   Forwardblack();
    if((analogRead(sensorPin1) < threshold) || (analogRead(sensorPin2) < threshold)){
      state=10;
    }
break;

      case 10:
  Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
    state = 11;
break;

   case 11:
    Left();
if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
    state = 13;
    }
    
    
     break;

   case 12:
  Left();
      if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) > threshold)){
  state = 13;
}

break;

 case 13:
  Forward();
if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
  state = 14;
}
break;


case 14:
  Left();
if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
  state = 16;
}
break;

case 15:
Left();
if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
  state = 16;
}
break;

case 16:
Forward();
if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
  state = 17;
}
break;

case 17:
Left();
if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
  state = 19;
}
break;

case 18:
Left();
if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
  state = 19;
}
break;

case 19:
Forward();
if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
  state = 20;

  }
  break;

case 20:
Left();
if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
  state = 22;
}
break;

case 21:
Left();
if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
  state = 22;
}  
case 22:
Forward();
if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
  state = 23;
  
}
break;

case 23:
   Forwardblack();
    if((analogRead(sensorPin1) < threshold) || (analogRead(sensorPin2) < threshold)){
      state = 24;
    }
    break;

    case 24:
    Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
      state = 25;
  }
  break;


  case 25:
   Forwardblack();
    if((analogRead(sensorPin1) < threshold) || (analogRead(sensorPin2) < threshold)){
      state = 26;
    }
break;

    case 26:
    Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
      state = 27;
  }
  break;

  
case 27:
      Right();
    if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
    state = 28;
    }
   break;

   
   case 28:
  Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
    state = 29;
    }
break;

 
case 29:
      Right();
    if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
    state = 30;
    }
   break;

   
   case 30:
  Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
    state = 31;
    }
break;

case 31:
      Right();
    if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
    state = 32;
    }
   break;

   case 32:
  Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
    state = 33;
    }
break;

case 33:
Left();
if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
  state = 34;
}  
break;

case 34:
Forward();
if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
  state = 35;
  
}
break;


case 35:
   Forwardblack();
    if((analogRead(sensorPin1) < threshold) || (analogRead(sensorPin2) < threshold)){
      state = 36;
    }
break;

    case 36:
    Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
      state = 37;
  }
  break;


case 37:
      Right();
    if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
    state = 38;
    }
   break;

   case 38:
  Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
    state = 39;
    }
break;


case 39:
      Right();
    if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
    state = 40;
    }
   break;

   case 40:
  Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
    state = 41;
    }
break;


case 41:
      Right();
    if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
    state = 42;
    }
   break;

   case 42:
  Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
    state = 43;
    }
break;


case 43:
Left();
if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
  state = 44;
}
break;

case 44:
Forward();
if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
  state = 45;
  
}
break;


case 45:
      Right();
    if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
    state = 46;
    }
   break;

   case 46:
  Forward();
    if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
    state = 47;
    }
break;


case 47:
Left();
if((analogRead(sensorPin1) < threshold) && (analogRead(sensorPin2) < threshold)){
  state = 48;
}  
break;

case 48:
Forward();
if((analogRead(sensorPin1) > threshold) && (analogRead(sensorPin2) > threshold)){
  state = 49;
  
}
break;

case 49:
State0();
break;

    }
    }
    }
}

Udfordringer med koden:

I stedet for at benytte batterier, var robotten tilsluttet en computer via USB indgangen, og fik strøm derfra. Motoren kørte ikke ret hurtigt, men var lettere at administrere.

Derudover var der også små fejl som breaks, brackets m.m., der forårsagede compile errors. I en længere kode uden error highlights, skulle vi manuelt ind og kigge, hvor fejlen lå, og det kan tage lidt tid.

Der var også gentagelser af de samme states, som resulterede i, at vi fik forskellige resultater hele tiden.

Sidst, så skulle variablerne også ændres konstant, så vi fik de helt korrekte værdier til fx threshold, speed m.m. Det var nok det, der tog mest tid, men de vi fik de helt korrekte værdier, besluttede robotten sig også for at være samarbejdsvillig.

Perspektivering:

Robotten er i sig selv fin nok til at løse opgaven, men der er altid plads til forbedringer.

Bl.a. skulle sensorerne have været placeret mere mod robottens midte, så det ville give et bedre omdrejningspunkt.

Robottens hjul skulle også have været placeret tættere mod hinanden, da det også ville give en bedre rotation, og dermed gør det nemmere at dreje.

Set i retrospektiv, så ville det også have været bedre at bruge gearing, så robotten ikke ville være så bred. Dette ville have gjort det meget nemmere at følge banerne.

Konklusion:

Robotten kan aflevere kasse A og B i ét run, og har jf. problemformuleringen løst de to første baner og dermed også den obligatoriske del af opgaven.

Gruppen løb undervejs ind i en del problemer både ift. hardware og kodning.

Ift. hardware var det primære problem, at de to første motor shields, vi benyttede, var defekte. Gruppen forventede ikke, at hele to motor shields var defekte, og vi brugte derfor alt for mange unødvendige timer på at forsøge at lave ændringer i kodningen, da vi troede fejlen lå der. Derudover var problemerne også, at robottens opførsel generelt var meget “selvstændig” i den forstand, at den ikke altid fulgte de states og conditions, vi havde fremsat.

Ift. kodningen, så var det primære problem, at stort set alle værdier hele tiden skulle tilpasses, så robotten kunne køre optimalt. Derudover var der også mindre fejl som manglende breaks og brackets. Derudover var der også gentagende states som virkede i tide og utide. Disse skulle løses, da robottens opførsel var uregelmæssig pga. skidt på banen.

Rute C, D, F, E, G og H er ikke forsøgt at blive løst. Skulle robotten forbedres yderligere, ville gruppen selvsagt også forsøge at gennemføre de resterende ruter.

Robotten kan aflevere kasse A og B i ét run, og har jf. problemformuleringen løst de to første baner og dermed også den obligatoriske del af opgaven.

Gruppen løb undervejs ind i en del problemer både ift. hardware og kodning.

Ift. hardware var det primære problem, at de to første motor shields, vi benyttede, var defekte. Gruppen forventede ikke, at hele to motor shields var defekte, og vi brugte derfor alt for mange unødvendige timer på at forsøge at lave ændringer i kodningen, da vi troede fejlen lå der. Derudover var problemerne også, at robottens opførsel generelt var meget “selvstændig” i den forstand, at den ikke altid fulgte de states og conditions, vi havde fremsat.

Ift. kodningen, så var det primære problem, at stort set alle værdier hele tiden skulle tilpasses, så robotten kunne køre optimalt. Derudover var der også mindre fejl som manglende breaks og brackets. Derudover var der også gentagende states som virkede i tide og utide. Disse skulle løses, da robottens opførsel var uregelmæssig pga. skidt på banen.

Rute C, D, F, E, G og H er ikke forsøgt at blive løst. Skulle robotten forbedres yderligere, ville gruppen selvsagt også forsøge at gennemføre de resterende ruter.

Videoer:

Video af Scorpio, der gennemfører bane A og B.

Leave a Reply