Superior solarising

gruppemedlemmer: Ajithan Christian, Jeppe Olesen og Mathias Katz Sørensen.

Intro

I Danmark står solen op i øst og går ned i nordvest. Når solen står op i øst om morgenen, går sollyset næsten parallelt med jordens overflade, og igennem dagen står sollyset mere og mere vinkelret til jordoverfladen, indtil solen igen går ned i nordvest. DC-motoren er placeret, således at “solcellen” roterer omkring x-aksen. På denne måde kan solcellen orientere sig vinkelret i forhold til solen, igennem sollysets dagscyklus.

Systemet er bygget på denne måde for at kunne opfange mest sollys. Med den nuværende opstilling kan systemet bevæge sig over 180◦ og sikre at mest muligt af platformen vender direkte mod solen, dog kun i én akse. (det vides ikke om dette er mere effektivt end det som er afbilledet i PF1-pdf´en, men det kunne tænkes at være bedre omkring ækvator, hvor solen bevæger sig mere lige over himlen.)

Anvendte komponenter  

  • 2 * 10K Ω modstande
  • 4 * 1K Ω modstande
  • 3 * 220 Ω modstande
  • 1 * Arduino Uno 
  • 1 * 238-9642 3V DC motor 
  • 2 * LDR-modstand 
  • 4 * IRLZ34N transistor
  • 4 * IN4007 dioder
  • 3 * LED dioder

Diagramtegning

Teori

På DC-motorens rotationsakse er der placeret en plade, som skal agere som “solcellen”. I enderne væk fra rotationsaksen, er der placeret en LDR modstand. I mellem de to LDR modstande er der placeret en skillevæg, der skal skygge for solen, der kommer parallelt med solcellen. Skillevæggen gør, at den ene LDR-mostand vil give et større output end den anden, hvis solcellen står parallelt med sollyset. Forskel imellem outputtet fra de to LDR-mostande, registreres af mikrocontrolleren (Arduinoen), som efterfølgende vil få motoren til at rotere solcellen indtil modstandenes output når ligevægt. Rotationsretningen af solcellen afhænger af hvilken LDR-modstand, der giver det største output. Afhængigt af hvilken modstand der giver det største output vil få motoren til enten at køre den ene vej eller den anden vej, afhænger af de if-statements, der er erklæret i koden.

Motoren sættes igang, når der løber strøm igennem H-broen. Hvilken retning som strømmen løber igennem DC-motoren styres af koden, hvor der er lavet tre forskellige funktioner. Funktionerne styrer hvilke MOSFET’s, der er åbne og lukket, som er bestemt af hvilket MOSFET, der modtager strøm på deres Gate-pin. Føres der dog ikke nogen strøm til nogle af MOSFET’ene, kan der ikke løbe strøm igennem dem, og motoren vil ikke køre rundt.


Systemet kan befinde sig i tre forskellige faser; bevægende fremad, bevægende bagud, eller stillestående. Afhængigt at hvilken af de tre faser, som systemet befinder sig i, vil 1 af de 3 lysdioder lyse. Hvis den grønne lysdiode lyser, så kører motoren fremad, og de andre dioder vil være slukket. Lyser den gule lysdiode, vil motoren køre bagud, og de andre dioder vil være slukket. Hvis motoren ikke bevæger sig, vil den røde lysdiode være tændt, og de andre vil være slukket.

Hvordan er gearingen og hvorfor?

Gearingen er valgt for at mindske rotationen fra DC-motoren, så platformen kører langsommere. Dette er gjort via 5 gear med 12 tænder og 5 med 48 tænder. Nedenfor er et billede for at vise opstillingen af gearene. For at finde ud af hvor meget gearingen sænker eller øger hastigheden, skal gear ratioen findes. Dette gøres her, ved at tage tænderne fra det ”drivende gear” over tænderne fra det ”drevne gear”, da radius af gearene i dette tilfælde er afstemt.

Dette giver derfor følgende:

Hvilket vil sige, at for hver gang det første gears tænder roteres 1024, rykkes tænderne på det sidste gear 1 gang. Da platformen placeres ved det sidste gear er hastigheden, som den kan flyttes altså nedsat med dette.

Hvordan er h-broen opbygget inkl begrundelser og beregninger? Hvordan er robottens hardware sammensat?

Superior solarising er opbygget af to LDR modstande, en DC motor og en H-bro, som er styret via en Arduino. 

LDR modstande er lavet af halvledende materiale, der giver den en høj modstand. Når lys fald over modstanden vil dets modstand ændres, hvormed modstandsværdien kan anvendes til yderligere behandling. LDR’erne opsættes simpelt i en spændingsdeler, hvor LDR værdien kan læses til Arduinoen. Til spændingsdeler er anvendt en modstand på 100k ohm for begge, hvormed analogværdierne for de to sensorer vil være tæt på hinanden. Dette er ønsket for bedre at kunne styre Superior solarising. 

Spændingsdeleren er givet ved følgende. 

Hvor R2 er sensoren og R1 = 100k ohm 

Kredsløbet er opsat på følgende vis. 

Signalet fra LDR’erne kan læses af en Arduino, hvor der via software kan tændes en DC motor. En DC motor kræver dog mere strøm, end en Arduino kan forsyne fra dets outputpins, derfor skal motoren sættes op med en transistor. Den anvendte DC motor trækker 1.05 A, hvor Arduinoen maks kan give 40mA. 

Transistor: 

Transistoren er en elektrisk komponent, typisk anvendes den som switch eller som forstærker. I denne portfolie anvendes den som forstærker. Transistoren tager en lille input strøm, hvor denne strøm forstærkes og giver en større output strøm. Typisk vil en IC-driver anvendes til at forsyne den nødvendige strøm, udover denne kan en H-bro konfigurering med transistor anvendes. Da Superior solarising skal følge solen horisontalt, skal den kunne bevæge sig både til øst og vest. H-broen muliggøre motorstyringen i begge retninger. 

Superior solarisings H-bro er opstillet vha. MOSFET transistor, da disse er spændingskontrolleret. Ved spændingskontrollerede transistor er drain strømmen kontrolleret af spændingen over gate terminalen. Til h-broen anvendes MOSFET af N-kanal med “enchancement mode”, hvor konduktiviteten kan kontrolleres, dermed kan strømmen til motoren kontrolleres. Hvis spændingen  på gate terminalen er lav, vil MOSFET’ten ikke lede en strøm og omvendt, er spændingen på gaten høj, vil MOSFET’ten lede.

En spænding over gate terminalen, vil inducere en kanal, som tillader en spænding over Rds modstanden, dermed løber en Id strøm. Rds modstanden sidder fra sources to drain, og er lav ved aktivering (Rds(on)) og høj ved deaktivering, hvormed strømmen kan varieres.  

Gate spændingen, som skaber kanalen, skal være større end en bestemt tærskelværdi, som er markeret med Vgsth, i databladet. 
Værdien vil være mindre end 5V for logic gate, hvilket gør dem optimale i kombination med en mikrokontroller. 
H-boren opsættes med 4 MOSFET, som er spændingskontrolleret. Disse kan styres via Arduinoen, som kan sætte terminalen til høj eller lav via de digitale indgange. Broen skal yderligere have en modstand på gaten, som kan regulere skiftetiden. Denne er dog ikke afgørende for dette forsøg.


 

H-broens kredsløb har yderligere 4 flyback dioder. Disse sikrer afladning af eventuel gemt energi i motor vindingerne, da en DC motor er induktiv. Den induktive energi vil generere en flyback spænding, når strømmen afbrydes. Dioden sørger for sikker afledning til forsyningen, så denne ikke vil skade transistorene. På figuren kan opstillingen ses. 

Til H-broen bruges 

Med databladets beskrivelse, kan drain strømme regnes ved følgende. 

Hvor VGS er gate-source spændingen og VGSth er tærskelværdien. K kan defineres som følgende.

For IRLZ34N kan følgende drain strøm regnes. Ved Vgs = 4 er Id = 14A og Vgsth = 1,5 V 

Med den udregnede k værdi kan Id strømme findes ved en forsyning på 5 volt. 

Ud fra beregningerne og diagrammet, kan det ses, at MOSFET’ene kan forsyne motoren med 1.05 A uden problem. 

Analogværdien fra LDR sensorerne kan nu bruges til at styre motorens retning. 

Har det været problemfrit at opbygge robotten eller har den ændret form/sensor-placeringer flere gange?

Der er under opbygning ikke blevet ændret på placeringen af sensorerne, dog blev der sat tape mellem sensorerne for at få en større forskel på input fra sensorerne, når der blev lyst på dem. Der blev under opbygningen forsøgt at ændre på placering og type af ledninger. Dette blev gjort fordi at de mere stive ledninger blev revet ud af breadboardet, når platformen roterede. Dog endte vi med at gå tilbage til de originale ledninger, da de andre ikke virkede. Problemet blev i stedet løst ved at tape ledningerne fast på platformens stander.

Har der været nogle problemer ift. at få hardwaren til at virke?

Der opstod enkelte problemer i forhold til motoren og gearingen. Hvis gearingen var for hård, kunne motoren ikke trække. På et tidspunkt kørte motoren ikke som ønsket. Forskellige input fra sensorerne menes at kunne have været årsagen

Hvilke udfordringer har der været ift. til at få koden til at virke?

Det var besværligt at vurdere de hvad tærskelværdien som skulle overskrides for at motoren kørte skulle være. For høj og motoren kørte ikke og for lav så kørte den for hurtigt. Derudover skulle styrken på DC-motoren også reguleres efter gearingen, som blev ændret undervejs.

Flow chart over systems opførsel

Kode
I koden er de to modstande navngivet henholdsvis, som eLDR og wLDR og udskriver et analogt output. Der er opsat følgende if-statements, der udføres hvis kravene opfyldes. Hvis eLDR  er større end wLDR og forskellen mellem dem er større end 80, sættes motoren til at køre fremad.
Hvis wLDR ligeledes er større end eLDR og forskellen mellem dem er større end 80, sættes motoren til at køre baglæns. Er der ingen af kravene, der opfyldes, standses motoren.

Lysdioderne styres af de 3 funktioner i koden; Forward(), Backward(), og Break(), Her vil de tilsvarende lysdioder enten lyse eller være slukket, afhængigt af den funktion der køres af programmet. Forward()=grøn diode lyser, Backward()=gul diode lyser og Break() = rød diode kører.

const int eastLDR = A0;
const int westLDR = A1; 
const int BreakLight = 10;
const int BackwardLight = 9;
const int ForwardLight = 11;
int eLDR = 0; // Den ene LDR
int wLDR = 0; // Den anden LDR

const int forward1 = 5;
const int forward2 = 4;
const int backward1 = 3;
const int backward2 = 2;

int dif = 0; //difference mellem de to LDR

void setup() 
{
 
 pinMode (forward1, OUTPUT);
 pinMode (forward2, OUTPUT);
 pinMode (backward1, OUTPUT);
 pinMode (backward2, OUTPUT);
 pinMode (ForwardLight, OUTPUT);
 pinMode (BackwardLight, OUTPUT);
 pinMode (BreakLight, OUTPUT);
 pinMode (ForwardLight, LOW);
 pinMode (BackwardLight, LOW);
 pinMode (BreakLight, LOW);

Serial.begin(9600);
}

void loop() 
{
eLDR = analogRead(eastLDR);
wLDR = analogRead(westLDR); 

float eLDRV = eLDR*(5.0/1024.0); 
float wLDRV = wLDR*(5.0/1024.0);
 

dif = eLDR - wLDR //finder forskellen mellem de 2 LDR værdier

if(eLDR > wLDR && abs(dif) > 150){  
  forward();  //kør denne funktion hvis overstående krav opfyldes
  }
else if (eLDR < wLDR && abs(dif) > 150) {
  backward();   //kør denne funktion hvis overstående krav opfyldes
}
else {
  breaks();   //kør denne funktion hvis ingen af kravene opfyldes
}

Serial.print("east ");
Serial.println(eLDR);
Serial.print("west ");
Serial.println(wLDR);
  Serial.print("sensorValue: ");  //udskriver værdierne i serial monitor
  Serial.println(eLDRV);
  Serial.print(' ');
  Serial.print("sensorValue1: ");    
  Serial.println(wLDRV);
  Serial.print('\n');
  Serial.println(dif);
  Serial.print('\n');
  delay (1000); 
}


void forward() //sætter motor til at køre fremad
{
   digitalWrite(forward1, HIGH); //sætter hastigheden til 100 via PWM signal
   digitalWrite (forward2, HIGH);
   digitalWrite (backward1, LOW);
   digitalWrite (backward2, LOW);
   digitalWrite (ForwardLight, HIGH); //tænder lysdioden
   digitalWrite(BreakLight, LOW);   //slukker lysdioden
   digitalWrite(BackwardLight, LOW);    //slukker lysdioden
  
}

void backward() //sætter motor til at køre baglæns
{
  digitalWrite(backward1, HIGH);   //sætter hastigheden til 100 via PWM signal
   digitalWrite (backward2, HIGH);  
   digitalWrite (forward1, LOW);
   digitalWrite (forward2, LOW);
   digitalWrite(BackwardLight, HIGH);//tænder lysdioden
   digitalWrite(BreakLight, LOW);//slukker lysdioden
   digitalWrite(ForwardLight, LOW);//slukker lysdioden
  
}

void breaks() //stopper motoren
{
  digitalWrite(forward1, LOW);   //sætter hastigheden til 0 via PWM signal
   digitalWrite (backward2, LOW);  
   digitalWrite (forward1, LOW);
   digitalWrite (forward2, LOW); 
   digitalWrite(BreakLight, HIGH); //tænder lysdioden
   digitalWrite(BackwardLight, LOW); //slukker lysdioden
   digitalWrite(ForwardLight, LOW); //slukker lysdioden
 }

Serial monitor

Testkørsel

Det var desværre meget overskyet, da vi kørte vores test. så data er fra lyset fra en mobiltelefon over en kortere periode.

Grafen viser inputs fra begge LDR modstande. Disse afgør om motoren kører og i hvilken retning, dette bestemmes når differencen krydser en af tærskelværdierne, som ses i grafen nedenfor. de første 450 samples bevæger Superior Solarising mod “vest” efter ca. 500 samples bevæger den sig mod “øst”.

Video

More

Solcellesystem V1 Beta 2.2.1, Alpha Gamma Zeus, Final final 3, Primo 2021.

Anders Bjørn Øbro, Anubr19

Rasmus Bundsgaard, Rabun19

systemet/robotten opbyggning

Målet med robotten er at måle sollysets indfaldsvinkel for derefter at reagere på lyset. Ved at placere 2 photoresistorer/LDR på hver side af solcellen bliver det muligt at bestemme hvilken en af de to LDR der modtager mest lys, for derefter at bevæge solcellen hen imod den LDR, for at optimere soludbyttet. Vi har derfor placeret de 2 LDR på en servomotor, for at kunne ændre vinklen og måle solens position løbende. Hvilket betyder at solcellen bevæger sig mod solen, så der hele tiden måles på solens lys. Hvis de var fast placeret havde det ikke været muligt.  Motoren har til funktion at rykke pladen med solcellen og LDR og den er derfor placeret fast vinkelret på en plade med bunden nedad, så den har mulighed for at rotere solcellen mod solen. En servomotor er blevet brugt da denne giver mulighed for præcis styring, i modsætning til en DC motor der ville kræve et gearingssystem for at få samme mængde kontrol. 

Vi har benyttet 2 photoresistorer/LDR (model KLS6-3537), som er placeret midt på ‘solcellen’ med en skærm imellem. Vi startede med at placere LDR-sensorerne i hver sin side af solcellen, uden en skærm imellem, men efter få minutters test observerede vi en minimal ændring i målingerne. 

Vi vurderede dette skyldtes den minimale forskel der er på lyset over så kort afstand, og placerede derfor en skærm imellem, således at lyset ville skygge på den ene LDR-sensor hvis de ikke var placeret helt vinkelret mod lyset. Vi observerede med en markant forbedring efter ændringen.

Robottens hardware

 

Vi forsøgte først at bygge en H-bro til en DC-motor, uden at bruge L293D motor driveren. Dog blev dette for kompliceret til os, og vi havde svært ved at få adgang til lego-dele eller en 3D printer som kunne lave gearing. Derfor valgte vi at bruge servomotoren. 

Vores største ulempe ved dette valg, er motorens 180° begrænsning, som har betydet vi skulle være meget opmærksomme på opsætningen af motoren inden test, for at sikre den havde nok bevægelighed til at følge solen. Til gengæld kunne vi få en meget præcis feedback om motorens vinkel, grundet motorens indbyggede infrarøde målinger.

Ud fra resultaterne af vores endelige test kan der påpeges en mulig problematik i balanceringen af top-delen på servomotoren. På trods for at vi forsøgte at være omhyggelige med balanceringen, observerede vi en hældning på top-delen ved testens afslutning, som har ændret sig siden starten af testen. Dette kan være med til at forklare nogle markante forskelle i målingerne af servomotorens vinkel, eftersom den muligvis skulle dreje lidt mod solens retning, for at tilpasse LDR-sensorernes vinkel på solen.

Robottens opførsel

#include <Servo.h> 

Servo tracker; // servo vinkel

// LDR - lys dioder som peger mod øst og vest
int eastLDRPin = 1;
int westLDRPin = 0;
// variabler med lys diodernes værdier
int eastLDR = 0; 
int westLDR = 0;

int difference = 0; // lysdiode forskel
int trackerPos = 90;    //gem servomotors placering

// dioder som indikerer motorens 'state'
int green = 8;
int yellow = 7;
int greenLED = 0;
int yellowLED = 0;

// efter ~1min skal den kun logge hvert 3min
int minutteCounter = 0;

void setup() 
{
  Serial.begin(9600);
  tracker.attach(5);  // attaches the servo on pin 11 to the servo object
  
  pinMode(yellow, OUTPUT);
  pinMode(green, OUTPUT);
} 
 
 
void loop() 
{ 
  // mål LDR værdier
  eastLDR = analogRead(eastLDRPin);
  westLDR = analogRead(westLDRPin);
  difference = eastLDR - westLDR;

  // tjek om der er forskel (med en lille buffer, for en sikkerheds skyld)
  if(difference>10 || difference<-10)
  {
    // gul lys tænder - indikerer den bevæger sig
    yellowLight();

    // tjek hvilken retning motoren skal dreje, og om den kan dreje
      // drej mod øst 
    if(difference>10 && trackerPos<=160)
    {
      trackerPos++;
      tracker.write(trackerPos);
      
    } 
      // drej mod vest
    else if(difference<-10 && trackerPos>20)
    {
      trackerPos--;
      tracker.write(trackerPos);
    }
    
  } else {
    // der er ikke nogen forskel, derfor skal grøn pære tænde - indikerer ingen bevægelse
    greenLight();
  }

  // hvis LDR værdier er X er det nat, og tracker skal bevæge sig mod øst (start pos)
  if(eastLDR<15 && westLDR<15)
  {
    resetSolar();
  }

  // skriv værdier i Serial monitor hvert 3. minut
  if(minutteCounter >= 180) {
    logState();
    // reset minutteCounter
    minutteCounter = 0;
  } else {
    minutteCounter += 1;
  }
  delay(900);
}

void yellowLight() {
  digitalWrite(yellow, HIGH);
  digitalWrite(green, LOW);
}

void greenLight() {
  digitalWrite(yellow, LOW);
  digitalWrite(green, HIGH);
}

void resetSolar() {
  if(trackerPos <= 140) {
    trackerPos++;
    tracker.write(trackerPos);
  }
}

void logState() {
  Serial.println(trackerPos);
  Serial.print(eastLDR);
  Serial.print(" ");
  Serial.println(westLDR);
  Serial.println(" ");
}

På nedenstående graf ses værdierne fra vores endelige test, som blev foretaget d. 15. marts, fra kl. 11:09 til kl. 15:52. Vi valgte at logge værdier i Serial monitoren hvert 3. minut, for at undgå for mange ens værdier. Vi valgte at printe værdierne for servomotorens vinkel, den østlige- og vestlige LDR. 

Nedenstående ses graferne hver for sig.

Grafen over servomotorens værdier indikerer en tydelig tendens, med en gradvis øgning af vinklen, hvilket svarer til en gradvis drejning mod øst. Da robotten er placeret spejlvendt, er øst og vest byttet rundt, og derfor bevæger den sig fysisk mod vest, med i koden bekrives det som øst.

Der kan tilføjes den information, at det begyndte at regne fra kl 13 til kl 14.

https://www.dmi.dk/vejrarkiv/

læse ud fra tallene

LDR-værdierne og servomotorens vinkel har ikke nødvendigvis en lineær sammenhæng, hvilket også kan ses på graferne som er sat sammen. Dette skyldtes at LDR-værdierne ikke behøver at ændre sig, hvis styrken fra lyskilden ikke ændrer sig. Dog skal de skal de sørge for at at ændre servomotorens vinkel, således at LDR-sensorerne har samme værdier. 

Derfor kan de store udsving i LDR-grafen skyldtes skyer, eller skiftende vejr, som også kan påvirke servomotorens vinkel. Generelt set ligger LDR-værdierne mellem 600 og 700, med en gradvis lille stigning. På servomotoren kan der dog aflæses en tydelig ændring af servomotorens vinkel, som har en markant gradvis øgning. 

Der kan derfor aflæses en relativ ens lysstyrke fra solen, med rimelig tydelig ændring af lyskildens placering over tid.

Under udarbejdelsen af koden har vi primært brugt tid på at balancere en difference mellem øst- og vest LDR. Eftersom vi startede med at placere LDR-sensorerne i hver sin side, uden en skærm, har vi haft svært ved at finde en tilstrækkelig difference som var præcis nok til at få servomotoren til at rykke sig. Efter vores ændring af LDR placering og tilføjelse af skærm løste vi denne problematik. 

Konklusion

Robotten formår at rotere solcellen mod lyskilden ved hjælp af LDR, og som det kan ses på grafen formår robotten at følge med solen, dog med udsving. Samtidig formår robotten at signalere hvilket stadie den er i. På baggrund af dette mener vi at robotten fungere som den bør, og at den løser problemformuleringen. Vi har valgt den mere simple løsning ved ikke at bruge DC-motoren, men robotten fungere stadig som den skal. Dog har den kun mulighed for at dreje 180 grader og det giver visse udfordringer i placering af robotten i forhold til solen. Vi har konstrueret en struktur som gør at solens lys bør ramme vinkelret ind på solcellen på visse årstider, men robotten giver ikke mulighed for at skifte vinkel på vertikalt plan. Dette var dog ikke med i problemformuleringen. Robotten tager også hensyn til om det er mørkt, ved at bevæge sig mod startposition, så den er klar til det bliver dag. Med alt dette i mente konkluders det at robotten løser problemformuleringen. 

En mulighed for forbedring ville være at robotten selv ville kunne bestemme hvilken vinkel solen havde på toppen af solcellen. Solen har forskellig indgangsvinkel alt afhængigt af årstiden. Derfor ville det være fordelagtigt hvis robotten kunne tage højde for dette. Dette kunne på samme måde som med solens retning, måles med PCR, og en motor ville derefter skifte vinkel på solcellen. 

Video

More

Robot solcelle

Af Louise Sjodsholm og Ricco Flyckt
_____________________________________________________________________________________

OBS: Gruppen havde glemt deres modstande uden mulighed for at hente dem inden fristen for genaflevering. Det var derfor ikke muligt at anvende andet end hvad gruppen havde liggende.
Der er derfor anvendt 200 Ohms modstande istedet for 220 Ohm modstande, som der blev anvendt ved første aflevering.

Hvordan ser systemet ud?

Systemet består af en kasse lavet i pap hvor to photoresistorer er placeret på ydersiden. Inden i kassen er et breadboard, samt Arduino Uno’en. I bunden af kassen er der hul til ledningen til Arduinoen således det er muligt at opsamle data løbende. Hvis dette ikke var nødvendigt, kunne den køre på batteri, og på denne måde kunne ledningen undgås. Der er under kassen placeret en vandret papflade, da dette gør det muligt at montere kassen ovenpå motoren, således den kan rotere frit uden forhindringer. Desuden er der bag papkassen placeret et breadboard med to statuslysdioder, der har til formål at fortælle hvilket stadie systemet er i. Når lysværdien er under grænsen for hvad der vurderes til at være nat, vil begge disse være slukket. Når der er mere lys på højre photoresistor lyser den blå, mens den gule lyser når der mere lys på venstre. Er forskellen på lys indenfor den angivne margin vil begge dioder lyse, hvilket betyder at motoren ikke bevæger kassen og dermed fastholder sin position.

Hvorfor er systemet/robotten opbygget som den er ift. at løse opgaven.

Hvilke tanker ligger bag ved opbygningen?

Photoresistorerne er placeret på fladen, for at indfange lyset. Placeringen i hjørnene skyldes breadboardets placering i kassen. Derudover skyldes det at det på denne måde vil være muligt at aflæse forskellige værdier fra de to photoresistorer, således det er muligt at beslutte i hvilken retning kassen skal bevæges, for at der er mest muligt sollys på fladen hele tiden.  Det vil være muligt at flytte den mod henholdsvis højre og venstre når det detekteres hvilken LDR der registrer den højeste værdi og flytte den i denne retning således den anden LDR vil måle en tilsvarende værdi. Dette vil betyde at solcellen nu har flyttet sig så meget at den er rettet optimalt mod lyset. Det blev besluttet at opbygge systemet med både breadboard og Arduinoen inden i kassen for at forhindre udfordringer med ledningerne, som opstod ved rotation. Desuden er solcellen på skrå, for at indfange så meget sollys som muligt.

Hvor er motoren placeret og hvorfor dér?

Motoren er placeret under papfladen, hvorpå breadboardet med dioder og kassen med Arduinoen er placeret. Denne placering er valgt da det giver fri bevægelighed for motoren, således den kan gå fra udgangspositionen og rotere 180 grader uden hindring. Motoren er placeret på denne flade, så det samtidigt er muligt at vinkle selve kassen som det ønskes, da denne ikke vil være afhængig af motoren, når motoren er under kassen og ikke er i direkte kontakt med denne. Desuden er motoren placeret ovenpå et glas, dette er den for at hæve den over bordet, således det er nemmere at fastgøre den, og samtidigt undgå problemer med at stikket til Arduinoen vil støde på ved rotation.

Hvilke sensorer er benyttet og hvor er de fysisk placeret på robotten

Der er anvendt to photoresistorer som er placeret i hver deres hjørne af solcellen. Formålet det dette er at detektere hvor der er mest sollys således solcellen kan rettes ind efter dette, og på den måde stå bedst muligt placeret ift. solens stråler. Det betyder samtidigt at afhængigt at målingerne fra sensoren vil motoren bevæges enten mod højre eller venstre indtil den optimale position er fundet.

Har det været problemfrit at opbygge robotten eller har den ændret form/sensor-placeringer flere gange?

Projektets forløb foregik ved den iterative tankegang. Blandt andet blev der løbende ændret i robottens fysiske udseende, da b.la. ledninger røg ud, når solcellen bevægede sig. Ved første aflevering blev der kun anvendt én photoresistor, samtidigt var kassen placeret direkte på motoren. Der var flere udfordringer ved dette. Eftersom der kun blev anvendt én photoresistor, skulle motoren bevæges meget, hvergang værdien for mængden af lys blev lavere end grænseværdien. Dette medførte at motoren roterede mere end hvad der var nødvendigt. Dette betød også at der ved indirekte sollys, som ville opstå hvis det var overskyet, ville være meget bevægelse af motoren. Her ville den, hvis skyen ikke kun var der ganske kortvarig blive ved med at rotere til den nåede slutpositionen hvorefter den returnerede til start, og begyndte rotation forfra til den igen fandt lys. Det vil sige, er der skyet længe ville den kunne risikere at rotere fra start til slut mange gange. Derudover gav det udfordringer at kassen var placeret direkte på motoren, da motoren til tider kunne have svært ved at rotere korrekt uden at hænge fast i kassen.

Desuden blev der løbende lavet forsøg, til at bestemme hvilke grænseværdier der var mest optimale for at fortælle mikrocontrolleren hvornår det var nat, og hvornår der var sollys. Ved genafleveringen er der tilføjet yderligere en photoresistor samt to lysdioder, hvilket har ændret designet af robotten. Den nye photoresistor er tilføjet i modsatte hjørne af den der var der tidligere, mens der er blevet tilføjet en papplade under kassen, hvor lysdioderne er placeret. Dette har gjort at motoren i stedet er placeret under kassen og breadboardet med lysdioderne. Dette har den fordel at motoren ikke længere risikere at hænge fast i kassen, og giver derfor en bedre og mere ren bevægelse, når motoren skal rotere.

Hvordan er robottens hardware sammensat?

Ovenstående ses en diagramtegning samt systemets kredsløbsdesign udfærdiget i Tinkercad. Systemet er opdelt over to breadboards, da der både er placeret et i kassen, der agerer solcellen, og et bag kassen, således det er muligt at se de to statuslysdioder. På breadboardet længst til højre, hvilket i den fysiske model er placeret inden i kassen er servo motoren sat til ground samt 5 V. Desuden er motoren sat til pin 9, hvorfra den kontrolleres. På breadboardet er ligeledes placeret to photoresistorer samt 200 Ohm modstande. Dette er ligeledes sat til GND og 5 V. Signalet fra de to photoresistorer læses ved de analoge pins A1 og A2. På breadboardet over Arduinoen er to LED lysdioder samt to 200 Ohm modstande placeret. Disse er forbundet til GND, og hver diode er forbundet til henholdsvis pin 2 og pin 4, dette gør det muligt at bestemme hvornår en eller begge lysdioder skal lyse. Alle modstande der er anvendt er 200 Ohm modstande da gruppen ved genaflevering udelukkende havde 200 Ohms modstande til rådighed. For at vurdere om en 200 Ohm modstand er tilstrækkelig at sætte foran lysdioderne udføres beregninger med Ohms lov. Da spændingen og modstanden er kendt udregnes strømmen.

Strømmen der løber gennem lysdioderne vil være 25 mA, dette kan de godt holde til samtidigt med at det er tydeligt at se hvornår de lyser og ikke lyser. Det vurderes derfor at være en fin løsning, når der ikke var andre modstande til rådighed. En resistor med en lidt højere modstand vil medføre lidt lavere strøm gennem lysdioden, hvilket vil være tættere på de optimale forhold som for de fleste er 20 mA[1].

For spændingsdelen med LDR modstandene er der kigget i datasheetet, hvor det er angivet at lys modstanden varierer mellem 18 og 50 k Ohm. Til at beregne den spænding som vil kunne måles efter photoresistoren, og som er den der aflæses, til at vurdere hvor meget lys der er, anvendes spændingsdelerformlen

Det vil sige, hvis der tages udgangspunkt i 50 k Ohm modstanden

Det vil sige at spændingen som der måles, kan variere op til næsten 5 V, dette giver stor variation da hvert step vil være 0,0049, og det dermed er muligt at adskille værdierne meget præcist da hvert enkelt step er meget småt. Step størrelsen udregnes som følgende, hvor det signal som der måles kan variere mellem 0 og 1023, og der divideres derfor med 1024.


[1] Forward Voltage and KVL | All About LEDs | Adafruit Learning System


Tag et billede af robotten og sæt pile og beskrivelser på de enkelte delkomponenter

Arduino Uno: Arduino uno’en er microcontrolleren der anvendes til at styre motoren på baggrund af det signal der kommer fra photoresistoren. Denne forsyner samtidigt systemet med spænding fra 5V pinen.

Servo motor: Er en micro servo SG90. Denne sørger for rotation på baggrund af signalet fra photoresistoren. Da det er en servo motor kan den maksimalt rotere 180°, hvor den udover at være sat til 5V og GND har kontrolledningen sat til pin 9, således motorens position kontrolleres via signalet fra denne, på baggrund af den indbyggede styring.

Photoresistor: Er sensoren der anvendes til at detektere lysintensiteten. Afhængigt af hvor meget lys der er på komponenten ændres modstanden, hvilket er denne egenskab der udnyttes til at bestemme ved hvilke værdier der er henholdsvis sollys, skygge og mørke.

Modstand: Der anvendes en 200 Ω modstand, da denne medførte store variationer i signalet fra photoresistoren ved de tre forskellige stadier.


Status LED: Der anvendes to Status LED pærer som har til formål at fortælle hvor i scriptet at vi befinder os. Henholdsvist en blå og en gul. Før status LED’en er der en 200 Ohm’s modstand, der sikrer at der ikke kommer en for stor strøm ind i status LED’en og ødelægger den.

Ledninger: Disse anvendes for at forbinde kredsløbet. Udover ledninger fra 5V og GND er der en ledning der forbinder kontrol på servo motoren med Arduino’en og en ledning der forbinder Arduinoen med photoresistoren, således værdien herfra kan anvendes.

Breadboard: Anvendes for at opbygge kredsløbet. Photoresistoren samt modstanden er sat i breadboardet sammen med ledningerne fra Arduinoen som sørger for spænding og forbindelse til ground. Ligeledes er der ledninger til de signaler som der anvendes fra photoresistoren.

OBS: Der er flere modstande, én ekstra photoresistorer og LED pærer som der ikke er pil til, da billedet ellers ville være uoverskueligt.

Har der været nogle problemer ift. at få hardwaren til at virke?

Der var under udviklingen af designet til første aflevering problemer med ledninger der faldt ud ved bevægelse. Derfor blev designet af prototypen ændret for at forhindre at nogle af ledningerne kom i kontakt med motoren. Ved genaflevering er der tilføjet en ekstra photoresistor, hvilket der var plads til på breadboardet indeni kassen, og som derfor ikke medførte yderligere problemer at tilføje. Der er samtidigt også blevet tilføjet to LED status lysdioder, samt ledninger fra Arduinoen indeni kassen hertil, som gav udfordringer i form af ledningernes længde, dette gjorde at designet ændrede sig. Der var problemer i forbindelse med opbygningen af hardwaren, da gruppen udelukkende havde 200 Ohm modstande til rådighed ved genafleveringen. Det havde været optimalt at kunne anvende andre modstande, men løsningen fungerede, som også ses af beregningerne.

Hvordan er robottens opførsel implementeret

Hvad er de overordnede opførsler / metoder for robotten

Robotten har fire primære opgaver. Disse er som følgende

  1. Læs LDR værdier
    1. Find differencen af LDR-værdierne. Både differencen, samt venstre og højre LDR-værdi gemmes i en integer datatype.
  2. Er LDR værdierne mindre end grænseværdierne for nat?
    1. Hvis den er, bevæger servo motoren solcellen til startpositionen.
  3. Er differencen som blev opsamlet i step 1 mindre eller lig med 60? Hvis den er dette, betyder det altså at der er cirka lige meget sol på begge LDR-modstande
    1. Vi holder derfor positionen for servo motoren.
  4. Vi ved nu at det ikke er nat, og der er mere sol på en af LDR-modstandene.
    1. Vi tester hvilken LDR-modstand der har mest sol på sig. Hvis venstre har mest på sig, bevæges der mod højre, indtil differencen er mindre end 60.
    1. Går nu tilbage til step 1.

Alt dette kan ses i flowdiagrammet nedenstående.

Flowdiagrammet ovenstående starter ved den blå boks.
Dette er starten af loopet, hvor vi læser de to LDR-værdier. Hernæst opsamler vi både venstre, højre og den absolutte difference af de to LDR-modstande.

I vores første if statement tjekker vi om det er nat, hvis det er nat, går vi tilbage til start position, og slukker begge statuslamper.

Efterfølgende ved vi det ikke er nat, og tester nu om solen står imellem de to LDR-modstande, med et margin på 60. Hvis de gør, står vi stille, men ellers tester vi om venstre LDR-modstand er større end højre LDR modstand. Hvis den er, betyder det at vi skal bevæge solcellen mod højre, for at komme ind for den absolutte difference på 60. Hertil tændes den gule status LED, så brugeren kan se at der nu bevæges mod venstre.
Hvis venstre ikke er større, betyder det at højre er større, og derfor opfanges dette i else sætningen, at vi skal bevæge os mod venstre.  Hertil tændes den blå status LED, så brugeren kan se at der nu bevæges mod højre.

Arduino kode

#include "Servo.h"

Servo minServo;

int sensorPin = A1;
int sensorPin2 = A2;
int servoPin = 9; //9'ende port
int blueLED = 2;
int yellowLED = 4;

int lysVaerdiV; // den analoge opsamlede lys værdi.
int lysVaerdiH;// den analoge opsamlede lys værdi.
int diff; //Differencen
const int NATVAERDI = 150; // grænse værdi for om det er nat.
const int STARTPOS = 180; // start position for motor
const int SLUTPOS = 0;
int pos = STARTPOS; // integer der tracker servo motorens position, sættes til start positionen

void setup() 
{
  pinMode(blueLED,OUTPUT);
  pinMode(yellowLED,OUTPUT);
  minServo.attach(servoPin); //attacher servo'en til 9'ende port
  Serial.begin(9600); // start seriel kommunikation
  minServo.write(STARTPOS); // sætter servo motor til start position - 180 grader
  digitalWrite(blueLED,LOW);
  digitalWrite(yellowLED,LOW);
}

void loop() 
{
  lysVaerdiV = analogRead(sensorPin); // opsamler lys værdi
  lysVaerdiH = analogRead(sensorPin2); // opsamler lys værdi
  diff = abs(lysVaerdiV - lysVaerdiH); //Tager den absolutte værdi af forskellen på venstre og højre
  
  if (lysVaerdiV <= NATVAERDI && lysVaerdiH <= NATVAERDI) // Er vores opsamlede værdi mindre end grænseværdien for nat? Kør tilbage til start position. 
  {
      pos = STARTPOS;//Sætter positionen til start positionen
      digitalWrite(blueLED,LOW); //Slukker for begge LED
      digitalWrite(yellowLED,LOW);
  }
   else if (diff <= 60) 
   {
      digitalWrite(blueLED,HIGH); //Tænder for begge LED
      digitalWrite(yellowLED,HIGH);
   }
   else
   {
    if (lysVaerdiV > lysVaerdiH)
      {
        pos--;
        digitalWrite(blueLED,LOW); 
        digitalWrite(yellowLED,HIGH); // Tænder for den gule LED, slukker for den blå.
      }
     else
      {
        pos++;
        digitalWrite(blueLED,HIGH);
        digitalWrite(yellowLED,LOW); // Tænder for den blå LED, slukker for den gule.
      }

   }

   if (pos > STARTPOS) //Position er større end 180, sæt den til 180
   {
    pos = STARTPOS;
   }
   else if (pos < SLUTPOS) // Position er mindre end 0, sæt den til 0. 
   {
    pos = SLUTPOS;
   }
   
   minServo.write(pos); //Får nu servo motoren til at bevæge sig, er det nat, bevæger den sig til start positionen, hvis venstre LDR er større, bevæger den sig mod højre, og omvendt mod venstre. 
  
/* Serial.print("V "); 
   Serial.println(lysVaerdiV);  
   Serial.print("H ");
   Serial.println(lysVaerdiH);
   Serial.print("P ");
   Serial.println(pos); */ //Til at lave grafer

   delay(1000); //Delay på ét sekund mellem hvert loop.
  
} 

I Arduino scriptet er der tre især vigtige funktioner for at solcellen agerer som forventeligt.  

  1. Først testes der om venstre og højre LDR er mindre end grænseværdien for om det er nat. Hvis begge sensorer er mindre eller lig med denne, betyder det at det er nat, og servo motoren flytter solcellen tilbage til startpositionen, hvor begge LED pærer bliver slukket.
  2. Efterfølgende testes der om differencen af de to LDR-modstande er mindre eller lig med 60, dette sikrer sig at der er lige meget sol på begge sider.  Grunden til at der først testes om det er nat i koden, er at hvis det er nat, ville begge LDR-modstande have en difference mindre end 60, hvilket ville få koden til at tro at der er lige meget sol på hver side, hvis der ikke blev testet for om det var nat først.
  3. Til sidst når vi til else sætningen. Det er altså her at vi nu er klar over det hverken er nat, eller differencen for de to LDR-modstande er større end 60. Et eksempel på dette kunne være at der var en værdi på 400 på højre, og en værdi på 50 for venstre. Dette betyder at vi skal dreje mod venstre, indtil at højre og venstre har en difference på mindre end 60.


Som nævnt vil vi altså i denne sætning teste om den venstre LDR-modstand er større end højre, hvis den er det bevæger vi solcellen mod højre.  Yderligere slukker vi for den blå LED og tænder for den gule LED.

Omvendt hvis den højre LDR-modstand er større end den venstre, skal vi altså bevæge os mod venstre. Efterfølgende tænder vi for den blå LED, og slukker for den gule LED.

Vis grafer for testkørsel med rigtig sol

Det var ikke muligt at udføre forsøg med rigtig sollys, i stedet blev der udført en test over 2 minutter, der simulerer hvordan robotten reagerer, ved lys, skygge og nat. Da forsøget udføres over kort tid er der indført et kort delay, som ville øges ved forsøg med rigtig sollys, ligesom delayet var større ved første aflevering, hvor forsøg med rigtigt sollys blev udført. Af grafen ses det at værdier målt fra den venstre LDR er angivet med blå, værdier fra den højre er angivet med orange, og servo motorens position er angivet med gul. Den sorte linje er grænsen for hvornår mængden af lys registreres som nat. Heraf ses det at ved de forhold forsøget er foretaget under er dette en høj værdi. Dette er dog ikke et problem da begge værdier fra LDR’erne skal være under før den kører tilbage til start. Det ses at mens begge værdier er under grænsen ændres positionen ikke, den fastholder udgangspositionen ved 180 grader. Så snart der påføres lys, men der ikke er en værdi med en difference på 60 mellem de to photoresistorers målinger roterer motoren, indtil værdien er indenfor en difference på 60. Af grafen ses de vertikale grønne linjer, som illustrer at positionen fastholdes, og dette skyldes målinger fra venstre LDR og højre LDR er indenfor differencen. For at illustrere at solcellen kan rotere tilbage mod udgangspunktet, hvis den eksempelvis har roteret fordi der kom en sky, og nu er for langt i den ene retning, påføres kraftigere lys på den højre LDR, herved ses det at motoren roterer tilbage. Her finder den ganske kort optimalt lys på begge, og ændre derved ikke position, dette er ikke markeret med grønne linjer, da det er meget kort. Derefter roteres igen, indtil der er tilsvarende lys på de to sensorer som betyder den står i den optimale position. Efterfølgende er det illustreret hvad der sker når det bliver nat. Selvom motoren på den korte tid forsøget er udført ikke har ændret positionen kraftigt, fjernes lyskilden alligevel, for at vise at den kører tilbage til start som er positionen angivet med 180 grader.

Ved et længere forsøg med rigtigt sollys vil positionen i længere tid fastholdes, mens det bør kunne ses den roterer både frem og tilbage, hvis der kommer skygge.

Herunder ses et forsøg over 10 minutter, men stadig med en kunstig lyskilde

Hvilke udfordringer har der været ift. til at få koden til at virke?

Under udviklingen af scriptet var der især problemer med at få servo motoren til at virke. Da projektgruppen ikke har tidligere erfaring med servo motoren. Mere specifikt var det bevægelsen af servo motoren, og hvordan dette skrives rent kode mæssigt. Desuden opstod der mindre problemer under udviklingen af koden, hvilket hurtigt blev løst, ved at anvende Serial.print(), og få et output på den serielle monitor, og dermed teste fejl i koden. Herunder var der b.la. grænseværdierne, for hvornår der er solskin, hvornår der ikke er solskin og hvornår det er nat, dette blev dog hurtigt løst, ved at lave forsøg. Til rigtig brug vil der skulle laves nye tests med rigtig sollys, og logge eventuelt en gang i minuttet. Herefter kan denne data analyseres for at forstå hvilke tal der beskriver sollys, og hvilke der beskriver nat. Grundet tidspres, var det kun muligt at lave kortvarige forsøg.
Yderligere blev der arbejdet med to LDR-modstande. Gruppen ville gerne have at solcellen først stoppede når der cirka var lige meget sollys på hver af de to LDR-modstande, så det betød at solen var cirka imellem de to LDR-modstande, og der derfor var sol på hele solcellen.
Gruppen brainstormede og lavede forsøg, og fandt ud af hvis den absolutte difference af de to LDR-modstande blev fundet, og der blev sat en margin på 60 (fundet ud fra forsøg), ville sensorene kunne detekte hvornår solen er imellem de to LDR-modstande.

Konklusion

Aktuatoren bevæger sig 180 grader efter sollyset der falder på LDR modstandene, og går tilbage til den oprindelige position, når det er nat. Dog er disse grænseværdier fundet ved hjælp af delvist simulerede forhold, og grænseværdierne vil derfor muligvis ikke være i overensstemmelse med grænseværdierne der ville fås ved at lave langvarige tests med sol lys (den absolutte difference mellem de to LDR-modstande), og nat. Der kan overvejes om opgaveformuleringen ville løses endnu bedre ved hjælp af en motor som er styret alt efter årstiderne. Herved kan solcellens vinkling ændres, da solen står højere på himlen om sommeren.

Desuden kan der konkluderes at ved hjælp af to LDR-modstande er der mindre tabt energi, i det at der løbende testes om den ene sides output er større end den anden, og retter sig hertil efter, for at få lige meget sol på hver LDR-modstand.  

Desuden anvendes der nu en absolut difference mellem de to LDR-modstande, i stedet for en grænseværdi for om der er sol. Dette sikrer at selv hvis det skulle blive overskyet, vil solcellen ikke bevæge sig efter solen. Det kunne eventuelt forbedre solcellen, hvis der sættes noget mellem de to photoresistorer, således det er nemmere at detekterer mængden af lys på hver af de to photoresistorer. Når solen ikke står helt lige, vil en plade eller lignende mellem, medfører en skygge, hvorved den en LDR vil måle en lavere værdi, sammenlignet med den værdi den vil måle når lyset blot er vinklet, men der ikke opstår en skygge. Dette vil altså kunne gøre at motoren hurtigere roterer solcellen, således den står i den optimale position.

Demo video

Der er ved genafleveringen optaget en ny video af robottens funktion, dette kan ses nedenstående.

Hvis link ikke virker : https://youtu.be/s9Y4Ry5BJVc

More

Solar Panelet Wall-E

Gruppemedlemmer

Jens Kristian Vitus Bering – jberi18@student.sdu.dk

Karl Amadeus Finsson Hansen – karlh18@student.sdu.dk

Pradeep Thayaparan – prtha18@student.sdu.dk

Systemets opbygning

Der var fokus på at starte med et fundament for vores servomotor, så denne kunne stå stabilt og sikkert. Derefter måtte gruppen lave en vinkel, som breadboard kunne ligge på, så gruppen fik sollyset vinkelret ind. Under breadboard er vores servomotor, der kan dreje til den ene og anden side. Vores breadboard fungerer som vores solcelle flade, og på den er der placeret 2 LDR en i hver side. LDR er vores solsensor som indfanger lyset, og er skilt ad ved hjælp af 2 stykker pap, således at lysets indfald fra den ene side ikke forstyrre den anden side. Derudover er der 2 led lys, en i hver side, deres funktion er at lyse op når servomotoren drejer deres retning. Servomotoren håndterer selv gearing, men hvis den ikke været det kunne gruppen evt. have brugt lego til at lave en gearing.

Gruppen har programmeret Arduinoen således, at hvis de indlæste værdier fra LDR-sensorerne på den ene side er større en tolerancen får den gruppens servomotor til at dreje sig i den retning. Der er anvendt en tolerance for at undgå, at robotten bevæger sig for meget omkring samme værdi. Når det er mørkt begynder servomotoren at bevæge sig til startpositionen, så den er klar til næste dag.

Anvendte komponenter 

Følgende komponenter blev anvendt i forbindelse med opsætningen af systemet 

  • 2 x 220 Ω – Som fungere til at regulere modstanden
  • 2 x 1000 Ω – Som fungere til at regulere modstanden
  • 1 x Arduino Uno – Kontrollere systemet
  • 14 x Man-Man ledninger 
  • 1 x Servomotor – En motor der kan bevæge sig 180 grader
  • 2 x LDR-modstand – Lyssensor
  • 2 x LED – Lys

Figure 1 – Opbygning af Wall-E ved hjælp af TinkerCAD

Figur 1 viser gruppens opstilling af komponenterne. Jord og strøm er forbundet til strømskinnerne af breadboard således, at det kan anvendes af de andre komponenter. 

Servomotoren er forbundet til strømskinnerne og Arduinos #D9 som input. Arduinoen får servomotoren til at dreje ved hjælp af PWM signaler. Arduino anvender #D11 og #D12 at tænde LED. LDR-modstande er forbundet til analog pin#A0 og pin#A1, hvor Arduinoen læser deres værdier.  I opsætningen er der også anvendt 4 resistorer med modstande på henholdsvis 2 x 220 Ω og 2 x 1k Ω.  Der er blevet anvendt en modstand på 1k Ω til LDR-modstandene og 220 Ω til LED pærerne for at undgå at de ikke bliver brændt af.   

Ændring af form/sensor-placeringer

Gruppen har foretaget løbende ændringer i robottens struktur for at forbedre lysindfaldet, såsom at sætte LDR-modstande helt til højre og venstre side. Derudover er der også blevet  brugt pap til at skelne dem yderligere.

Problemer ift. hardwaren 

Gruppen havde ikke nogen forforståelse af hardware og elektronik før opgaven og derfor var der en stejl læringskurve i starten. I starten måtte gruppen sætte sig ind i, hvordan elektroniske komponenter skal sættes sammen og hvordan de interagerer. Det eneste der er værd at nævne er opsætningen af systemet ikke skete problemfrit, da servomotoren havde svært ved at bære vægten af breadboard hvilket resulterede i at hovedet på servomotoren faldt af et par gange. Derudover gjorde det lidt vanskeligt at benytte materialer fra hjemmet frem for legoklodser som SDU ville have udleveret hvis ikke det havde været for pandemien. Dette ville have gjort opsætningen af hardwaren noget lettere.   

Figur 2 – Diagramtegning af Wall-E

Diagramtegning

Figur 2 viser diagram tegningen af systemet, som er lavet ved hjælp af Fritzing. Tegningen viser kommunikationen mellem Arduino, Servomotor, LDR, LED-lysene og modstanderne. 

 

Figur 3 – Billede af Wall-E med og uden pap

Figure 3 viser opsætningen af systemet samt de komponenter der er blevet anvendt. Ydermere ses det hvordan papstykkerne er blevet brugt. Gruppen har benyttet lim og tape for at kunne holde pap stykkerne, servomotoren og breadboard stabil.

Robottens Opførsel

Figur 4 – Flowdiagram af Wall-E

Ud fra flowdiagrammet kan det ses, at LDR værdierne har en afgørende faktor om hvorvidt servomotoren skal dreje til højre eller venstre alt efter lysets indfald. Værdierne på LDR-sensorerne vil blive aflæst og dernæst vil Arduino give instruktioner til servomotoren om, hvad den skal gøre.

  1. Hvis det er mere lys på venstre side dvs. værdierne på venstre LDR er højere end den på højre vil den bevæge sig til venstre.
  2. Hvis det er mere lys på højre side dvs. værdierne på højre LDR er højere end den på venstre vil den bevæge sig til højre.
  3. Hvis solen er gået ned, vil det sige at værdierne på LDR er meget lave og dermed vender den tilbage til startpositionen, så den er klar til den næste dag, når solen stiger op igen.

Der er anvendt en tolerance for at forhindre servomotoren i at bevæge sig når der er en små differens mellem de to indlæste LDR-værdier og dermed gør den mere stabil. 

Kode 

For at arduinoen kan have en bestemt opførsel skal der også skrives noget kode. Gruppen startede med at erklære de forskellige pins som fik den tilsvarende værdi som var forbundet til på Arduinoen. De værdier som benyttes af digital signal er primært LED lysene, som kun kan have to værdier, nemlig HIGH and LOW, mens LDR-modstandene kan have flere forskellige værdier og derfor har et Analogt signal. Eftersom, at værdierne hurtigt kan ændre sig og dermed have svingninger er der blevet implementeret et deadband på 40. 

I koden er der to forskellige funktioner: Setup() og Loop(). Setup() er funktionen, som bliver kaldt når et sketch startes. I funktionen findes forskellige PinMode() kald, som er blevet brugt til at konfigurere de forskellige pins til at fungere, som enten et output eller input. myServo, som er en instans af Servo bliver også konfigureret til  D9. For at kunne aflæse tallene og dermed plotte dataene til en graf benyttes der Serial. I funktionen sættes Serial til at kunne aflæse værdierne med en baud rate på 9600 bit pr sekund. 

Loop() funktionen indeholder den logik, som sørger for at aflæse værdierne på LDR modstandene og vurderer om den enten skal dreje til højre eller venstre ved hjælp af nested if statements. Den yderste if statement vurderer om sensor værdien på den første LDR er større end sensor værdien på den anden LDR minus tolerancen. Hvis dette er tilfældes så drejes servomotoren, mens LED pæren som servoen drejer hen mod bliver tændt. 

Sidst i loop() funktionen bliver Serial kaldt for at printe værdierne. Der er blev sat et delay på 1 minut, så resultaterne er mere overskuelige.

#include <Servo.h>

int sensorPin = A0;

int sensorPin2 = A1; 
int led1 = 12;
int led2 = 11;
int servoPin = 9;
int sensorValue = 0;

int sensorValue1 = 0; 
int sensorValue2 = 0; 

int servoGrad = 180;
int tolerance = 40;
int minuteCounter = 0;

int totalSensorValue = 0;

Servo myservo;

void setup()
{

    pinMode(sensorPin, INPUT);
    pinMode(sensorPin2, INPUT);

    myservo.attach(servoPin);
    myservo.write(servoGrad);
    Serial.begin(9600);
}

void loop()
{
  
    sensorValue1 = analogRead(sensorPin); // 200
    sensorValue2 = analogRead(sensorPin2); // 200 
    totalSensorValue = sensorValue1 + sensorValue2;

    
    
    if (sensorValue1 < sensorValue2-tolerance)
    {
        if (servoGrad <= 180 && servoGrad > 0)
            servoGrad--;
            digitalWrite(led2, HIGH);
            digitalWrite(led1, LOW);
    }
    else if (sensorValue1 >= sensorValue2+tolerance )
    {
        if (servoGrad >= 0 && servoGrad<180)
            servoGrad++;
            digitalWrite(led1, HIGH);
            digitalWrite(led2, LOW);
    }

    else if(sensorValue1 < 20 && sensorValue2 < 20){
      if(servoGrad < 180){
       servoGrad++; 
      }
       digitalWrite(led1, LOW);
       digitalWrite(led2, LOW);
    }
    minuteCounter++;

    
    myservo.write(servoGrad);
    
    Serial.print("Minute: ");
    Serial.print(minuteCounter);
    Serial.print("; TotalValue: ");
    Serial.print(totalSensorValue);
    Serial.print("; SensorValue1: ");
    Serial.print(sensorValue1);
    Serial.print("; SensorValue2: ");
    Serial.print(sensorValue2);
    Serial.print("; Servograd: ");
    Serial.print(servoGrad);
    
    Serial.println();
    delay(60000); // changes each minute
}

Testkørsel med rigtig sol

Solcellen blev testet den 17. marts 2021 mellem 12:00 og 21:30. Om morgenen var det ikke muligt at placere robotten ud på grund af vind og tåge, men klokken 12 kom solen, og gruppen greb chancen. Umiddelbart gik testperioden meget godt uden nogen mærkbare vanskeligheder.

Figur 4. Diagram over testperioden af solcellen Wall-E

Tiden på grafen er beskrevet i minutter hvoraf 0 minut er kl 12 og den sidste minut er 21:30. Intervallet i loopen i Arduinoen var 1 minut dvs. at sensorerne blev kun læst engang om minuttet. Testperioden tog 560 minutter, hvilket vil sige, at experimentet endte omkring kl 21:30.

Blå Linje

TotalValue er den samlede værdi fra begge LDR-sensorer og er den blåfarvede linje. Grunden til at gruppen valgte at anvende den samlede værdi over to separate værdier pga på grafen var, at den samlede værdi påviste bedre det samlede lysindfald i løbet af dagen så gruppen kan se, hvornår det begynder at blive mørkt. Man kan se, at allerede fra starten er lysindfaldet højt og dette skyldes, at gruppen startede kl 12 da solen allerede var steget op. Det er også muligt at se svingninger i tidsintervallet 100 min – 130 min, som gruppen mener skyldes flere skyer der var omkring dette tidspunkt. Solen begyndte så småt at gå ned efter 300 minutter ie 5 timer (kl 17 ), men der var stadig lyst indtil omkring 19.

Rød Linje

Den røde linje viser graden servomotoren er på, hvoraf 180 er startpositionen og 0 er slutpositionen. Gruppen kan umiddelbart se en nedadgående lineær linje fra start til omkring 100 minutter ind i testperioden. Grunden til dette er, at robotten prøver at indhente solen fordi experimentet startede kl 12, men grundet intervallet vil servomotoren kun nå at dreje 60 grader om timen. Omkring 100 min inde i experimentet begynder servomotoren at stabilisere omkring positionen 90 grader og derefter følger solen stabilt. Servomotoren når sit laveste punkt på 200 minutter, og derefter begynder det langsomt at gå den anden vej igen, fordi solen har nået sit højeste punkt.. Kl 21:30 er den nået tilbage til start positionen igen og klar til næste dag.

Diskussion

Udfordringer ift. koden

Arduino Strings

Der var nogle problemer med at anvende arduino strings, fordi de ikke kunne append de indlæste værdier. Måden gruppen tacklede dette problem var at lave en “fix” ved for at kunne nå at lave experimentet på dagen. Et fix var at anvende “Serial.print()” som som en slags append i stedet for append direkte på Arduino stringen.

CSV

Set i bakspejlet burde gruppen have formateret Serial Output som en CSV, men dette havde gruppen ikke tænkt på før gruppen gik i gang med experimentet. Dette medførte i at gruppen måtte konvertere dataen fra experimentet til CSV format bagefter.

Tolerance

Gruppen frygtede med for lav tolerance ville servo motoren dreje sig for meget. At finde den rigtige tolerance var besværligt, da gruppen ville have et stabilt system og derfor anvendte tolerance for at undgå svingninger. Gruppen eksperimenterede med nogle få tolerancer før den endelig tolerance blev valgt for experimentet. 

Opbygning af robotten

Da der skulle foretages et experiment udendørs medførte det så breadboardet samt computeren skulle også stå udenfor. Dette kunne være risikabelt da experimentet ville tage flere timer og at det ikke kan garanteres at det ikke vil regne. Gruppen overvejede at decouple breadboard fra overfladen af solcellen ved at forlænge ledningerne ved at sammensætte flere Female-to-Male ledninger og måske sætte alt andet end solcellen under ly. Gruppen valgte dog at gå med breadboardet. Denne opsætning havde dog den ulempe at servomotoren bar al vægten af breadboard hvilket resulterede i at servomotoren kunne falde af.

Statusindikator 

Gruppen anvendte lys til at illustrere, når robotten bevægede sig. En anden mulig løsning er at bruge lyspærer til at illustrere, hvilken position servomotoren befinder sig. Det kunne eventuelt være opsat ved hjælp af 7 lysdioder, hvor den midterste altid lyser. Hvis værdien er under 76 grader vil lysdioderne fra midten til venstre side begynde at lyse. Når værdien når helt ned til 0 grader vil hele venstre side lyse. Samme er gældende for højre side, hvor man maksimalt kan nå op til 180 grader. Hvis dette er tilfældet, så vil alle lysdioder fra midten til højre side lyse.

Konklusion

Der er lavet et simpelt solpanel, der følger solen rundt, som styres af en Arduino ved hjælp af LDR-modstande. Systemet lever op til kravene i beskrivelsen, men har stadig rum for forbedring såsom at forbedre koden således, at den formaterer output som en CSV fil. Gruppen valgte at bruge breadboardet som solcelle overfladen og dette havde også nogle konsekvenser såsom, at det var tungt for servomotoren at have al den vægt på således at hovedet på servomotoren kunne falde af. En forbedring af systemet ville muligvis at  decouple breadboardet af solarpanelet.

Video

Det ses på videoen den flytter sig i lysets retning og når det er mørkt drejer mod venstre for at komme tilbage til et udgangspunkt – i den her video er intervallet kun 500 millisekunder og derfor meget hurtigere end det gruppen brugte til solen.

More

Clean Queen Energy

Juul, Simon Helsted 
Rasmussen, Frederik Lau 
Rasmussen, Simon Møller 
Rudolf, Tom Lars Bo 

Opbygning 

Efter aftale med Jacob fik vi lov til at arbejde fire sammen, hvis vi lavede en solcellestyring med et 2-akse-system. Vi vurderede derfor, at vi ville få den mest præcise styring, hvis vi inkorporerede fire LDR-modstande, da det gav os mulighed for at beregne lysintensiteten på begge akser. Vi valgte at bruge en servo-motor til at styre tilten på solcellestyringen, da det ikke ville være muligt at skulle bevæge den mere end 180 grader. Til at rotere styringen valgte vi at bruge en DC-motor, da det gav os mulighed for at roterer 360 grader rundt. Vi valgte derfor at bygge vores platform høj nok til, at den kunne rumme en gearing under sig. 

Vi byggede gearing til DC-motoren ud fra vores krav om at kunne dreje solcelleplatformen langsomt nok til at styre den med en nogenlunde god præcision. I første iteration af gearingen brugte vi alle tandhjul vi havde og kom op på et gearingsforhold af 675:1. Det viste sig dog at det ikke var nok. Vi prøvede os lidt frem og fandt ud af at ormgear var løsningen på vores problem. I anden iteration af gearingen kom vi op på et gearingsforhold af ca. 130000:1, som vi testede og dømte alt for stort. Vi fjernede fire af tandhjulene og endte med et gearingsforhold af 5184:1, da vi vurderede at det gav os god kontrol over hastigheden som styringen roterede med. 

Motoren blev placeret i den ene ende af platformen, da det gav os plads til at lave gearingen på tværs af hele platformen. Yderligere placerede vi motoren med fronten mod jorden, da dette gav os mulighed for at placere gearene under platformen. 

Til styringen af DC-motoren brugte vi et H-bro komponent, L293D. Ved hjælp af H-broen kan man bestemme hvilken retning motoren kører, ved at styre strømmens retning. Hvis solen var til venstre for solcellen, kunne man dreje den lidt til venstre frem for at skulle dreje til højre hele vejen omkring sig selv. 

Solcellen består af 4 lys sensorer (LDR), som er monteret på et stativ som yderligere er sat fast på en servomotor. Stativet består af en plade med 2 vægge sat sammen i et kryds, således at stativet er opdelt i 4 segmenter. I hvert segment er der monteret én LDR. 

Vi brugte en del tid på at måle komponenterne op, da vi ville 3D-printe dele af vores solcellestyring, hvilket betød at vi havde en god ide om, hvordan vi skulle samle hele robotten. Derfor har robotten ikke ændret markant udsende i udviklingsprocessen. Vi opdagede dog at, vi kunne få en større præcision, hvis vi forlængede væggene mellem vores LDR-sensorer, hvilket var den eneste ændring vi har lavet på robotten. 

Hardware 

Diagramtegning

Opførsel 

Robottens primære opførsel er at bevæge sig mod de LDR-sensorer, som har den højeste modstand. For at gøre det, skal robotten bevæge sig funktion. Når programmet starter op, aflæses alle fire LDR-sensorer, hvorefter at “ServoControl” bliver kaldt. Her lægges værdierne for LDR-sensor 1,4 og 2,3 sammen, hvorefter differencen mellem de to værdier findes. Dette gøres for at bestemme om servoen skal øge eller mindske sin vinkel. Efterfølgende kaldes “DirectionControl”, hvor værdierne for LDR-sensor 1,2 og 3,4 lægges sammen. De bruges på samme måde som værdierne i “ServoControl”, men her bestemmer de bare hvilken retning at DC-motoren skal køre. Herefter køres loopet igen, for hele tiden at tjekke at sensorerne er inde for den godkendte difference, ellers flytter robotten sig igen. Yderligere er der en timer, som sørger for at streame data for hver LDR-sensor samt servoens vinkel til Excel hvert minut. 

void loop() {
  servoController();
  directionControl();
  controlLED();

  /*
   * I den sidste del af vores loop, tjekker vi hvor lang tid der er gået. Hvis den er lige så lang eller længere tid
   * end en periode variable kører vi vores readSensors funktion, som gemmer vores data fra sensorene. 
   * Så resetter vi vores counter, som gør at vi sender den dataen efter x sekunder.
   */
  
  currentMilli = millis();

  if(currentMilli - startMilli >= period)
  {
    readSensors();
    startMilli = currentMilli; 
  } 
}

/*
 * I directionControl finder vi to summer af to sensorer på hver side af vores solsensor. 
 * Så minusser vi dem med hinanden hvorefter vi finder differensen. Hvis den er større eller mindre 
 * end vores diff variable, skal den køres enten til højre eller venstre. Det sker ved hjælp af en H-bro. 
 * Hvis den er større kører DC-motoren til højre, og hvis den er mindre end vores -diff kører den til venstre.
 * Det skulle den blive ved med indtil at summen af de to sider er inde for vores differens. 
 * Når den ikke rykker sigere mere kan vi gå ud fra at DC-motoren er i den optimale position.
 */
void directionControl() {
  analogWrite(enB, 255);
  int diff = 7;
    
  sensorVal_1 = analogRead(sensorPin_1);
  sensorVal_2 = analogRead(sensorPin_2);
  sensorVal_3 = analogRead(sensorPin_3);
  sensorVal_4 = analogRead(sensorPin_4);

  int sensorSum_12 = sensorVal_1 + sensorVal_2;
  int sensorSum_34 = sensorVal_3 + sensorVal_4;

  if (sensorSum_12 - sensorSum_34 > diff) {
    isMoving = true;
    controlLED();
    digitalWrite(in3, HIGH);
    digitalWrite(in4, LOW);
    delay(500);
    digitalWrite(in3, LOW);
    digitalWrite(in4, LOW);
    isMoving = false;
  }
  else if (sensorSum_12 - sensorSum_34 < -diff) {
    isMoving = true;
    controlLED();
    digitalWrite(in3, LOW);
    digitalWrite(in4, HIGH);
    delay(500);
    digitalWrite(in3, LOW);
    digitalWrite(in4, LOW);
    isMoving = false;
  }
}

/*
 * I vores readsensors funktion, tager vi alle værdierne fra vores sensorer og nogle variabler vi har defineret.
 * Vi printer dem sp til Serial, hvilket gjorde vi kunne sende vores data til Excel til grafer og analyse.
 */
void readSensors() {
  sensorVal_1 = analogRead(sensorPin_1);
  sensorVal_2 = analogRead(sensorPin_2);
  sensorVal_3 = analogRead(sensorPin_3);
  sensorVal_4 = analogRead(sensorPin_4);

  //Data der udskrives til EXCEL
  Serial.print(sensorVal_1);
  Serial.print(",");
  Serial.print(sensorVal_2);
  Serial.print(",");
  Serial.print(sensorVal_3);
  Serial.print(",");
  Serial.print(sensorVal_4);
  Serial.print(",");
  Serial.print(sensorVal_1 + sensorVal_4);
  Serial.print(",");
  Serial.print(sensorVal_2 + sensorVal_3);
  Serial.print(",");
  Serial.println(servoPos);
}

/*
 * I controlLED har vi en bool variable, som bestemmer hvilken LED der er tændt.
 * Vi ændre den variable i både directionControl og servoController
 */
void controlLED() {
  if (isMoving) {
    digitalWrite(moveLED, HIGH);
    digitalWrite(onPointLED, LOW);
  }
  else {
    digitalWrite(moveLED, LOW);
    digitalWrite(onPointLED, HIGH);
  }
}

/*
 * Princippet i Servocontroller er den samme som med DC-motoren. Vi finder to summer af sensornes værdier. 
 * I servoen er det så toppen og bunden af vores solsensor. Vi kører den op eller ned hvis differensen er for stor
 * eller for lille.
 */

void servoController() {
  int diff = 7;
  int servoPosMin = 0;
  int servoPosMax = 180;
  
  sensorVal_1 = analogRead(sensorPin_1);
  sensorVal_2 = analogRead(sensorPin_2);
  sensorVal_3 = analogRead(sensorPin_3);
  sensorVal_4 = analogRead(sensorPin_4);

  int sensorSum_14 = sensorVal_1 + sensorVal_4;
  int sensorSum_23 = sensorVal_2 + sensorVal_3;

  if (sensorSum_14 - sensorSum_23 > diff) {
    isMoving = true;
    servoPos = servoPos - 1;

    if (servoPos < servoPosMin) {
      servoPos = servoPosMin;
    }
    
    servo.write(servoPos);
    delay(50);
  }
  else if (sensorSum_14 - sensorSum_23 < -diff) {
    isMoving = true;
    servoPos = servoPos + 1;

    if (servoPos > servoPosMax) {
      servoPos = servoPosMax;
    }
    servo.write(servoPos);
    delay(50);
  }
  else{
    isMoving = false;
  }
}

Implementeringen af nulstilling efter solen er gået ned, blev aldrig korrekt lavet. Den primære grund af dette er vores opbygning af robotten. For at løse opgaven var tanken at lade robotten finde sin start position når solen stod op. Herefter ville have to tællere som talte hvilken retning og hvor mange gange den flyttede sig samt værdien for servoen. Når solen gik ned eller at LDR værdierne kom under en bestemt værdi, skulle den back tracke til sin start position ved at loope igennem rotationsfunktionen. Problemet med vores opsætning var at motoren ikke præcist flyttede sig det samme hver gang den kørte, hvilket ville medføre at position kunne være forkert i forhold til start positionen. Problemet var pga. modstand fra ledninger og i gearingen, hvilket medførte at den somme tider ville køre langsommere eller helt stoppe.

Test 

Vi har logget data fra systemet under en test på ca. 4,5 time – herunder værdier for lyssensorer og servopositionen. Dataene er efterfølgende blevet opstillet i forskellige grafer. 

I ovenstående diagram er de fire værdier fra lyssensorerne indsat. Baseret på vores kode og opsætning, bør de alle fire ligge forholdsvis tæt, I det at en nogenlunde ensartet værdi på tværs af sensorerne skulle betyde at servomotoren er vinklet imod solen. Dette er også hvad vi kan se på grafen, at alle fire sensorer ligger nogenlunde ens. De store udsving der ses I grafen, er grundet skyer som har blokeret for solen. 

Yderligere har vi logget vores servo position, og sammenholdt denne med den reélle elevationsvinkel for solen. Generelt passer vores servoposition inden for ca. 2 – 5 grader, med den egentlige vinkel på solen. Her ses også store udsving I vores servoposition. Dette er grundet skyer foran solen, hvilket har resulteret I at himlen lige over systemet har afgivet mere lys, og servoen har derfor rettet sig ind efter dette, og tilbage til solen når skyerne har passeret. 

Efter vi havde samlet og testet vores solcelle, fandt vi nogle mangler ved vores produkt. Et af de største problemer vi stødte på under vores test var, hvis skyer var til stede på himlen, fungerede solcellen ikke optimalt. Hvis der gik en sky for solen, kunne det resultere i at andre skyerne reflekterede lyset fra solen, hvorefter robotten opfattede disse skyer som den stærkeste lyskilde. Derfor ville det kræve direkte sollys fra en skyfri himmel, hvis den skal fungere optimalt.  

Vi observerede også diverse problemer med vores opstilling af robotten. Ledninger kunne komme I vejen hvis solcellen skulle dreje for meget om sig selv. Vi mener også at gearene skulle stabiliseres mere end de allerede var, da man blev nødt til at lægge vægt på bestemte steder, før de fik rigtig fat i hinanden. Sammen med opgraderinger til hardware, kunne vi også gøre vores software mere effektiv. Måden lysene fungere skulle ændres da de blinker mellem rød og grøn, selvom den stadig bevæger sig. 

Konklusion 

På trods af nogle mangler, vurderer vi at der er blevet udviklet et tilfredsstillende produkt, som formår at finde den stærkeste lyskilde. Hvis man ser på grafen for servoens vinkel i forhold til solens vinkel på himlen, kan man se at den hovedsageligt følger solens bane inden for et par grader. Hvis man sørgede for at ledningerne til sensorerne og servoen ikke var snoet rundt om solcellen, fungerede gearingen af DC-motoren som den skulle, og vi havde god kontrol over roteringen af solcellen. 

More

Ultra mega solarsystem

Navne på gruppemedlemmerne

Peter BrændgaardPebra18@student.sdu.dk
Malte BukrinskiMabuk18@student.sdu.dk
Jajoe18@student.sdu.dkJajoe18@student.sdu.dk

Hvorfor er systemet/robotten opbygget som den er ift. at løse opgaven.

  • Hvilke tanker ligger bag ved opbygningen

System er opbygget omkring en servomotor SG90 1PC, som bliver styret af en arduino UNO R3. På Motoren er montoreret en drejearm hvor der er tapet et V formet stykke pap på.
I hver ende af pappet er der montoret et stykke LDR-modstand til at detekterer sollysets retning.
Ideen bagen vores kode, er at vi får et input fra begge vores LDR modstande, i tilfælde af at der er en difference mellem de to tal, ved vi at de ikke modtager lige meget sollys, og der derfor skal sendes et signal ud til servo motoren om at den skal roterer.

  • Hvor er motoren placeret og hvorfor dér?

Motoren er placeret i bunden, i midten af det V-formet pap, sådan at en 1 grads rotation på motoren resulterer i en graders rotation af systemet.

  • Hvordan er h-broen opbygget inkl begrundelser og beregninger?

Der benyttes ikke H-bro da der benyttes servo-motor.

  • Hvordan er spændingsregulatorkredsløbet opbygget inkl begrundelser og beregninger?

Vi har en 10k ohms modstand i forlængelse af vores photoresistor, for at lave en spændingsdeler af de to dele. Det resulterer i den største spændingsforskel når resistor modstanden ændre sig. 

  • Hvilke sensorer er benyttet og hvor er de fysisk placeret på robotten

Der er benyttet to photoresistorer, som sidder på hver side af robotten med et stykke pap imellem for at få større forskel imellem dem.

  • Har det været problemfrit at opbygge robotten eller har den ændret form/sensor placeringer flere gange?

Vi havde den udfordring at forskellen mellem de to sensorer, var svær og sporer, løsningen på dette problem var at opsætte et stykke pap i mellem de to sensorer.

Hvordan er robottens hardware sammensat?

  • Lav en diagramtegning

  • Tag et billede af robotten og sæt pile og beskrivelser på de enkelte delkomponenter
  • Har der været nogle problemer ift. at få hardwaren til at virke?

Der var udfordringer med at måle forskelle mellem photoresitorerne inden der blev sat et stykke pap imellem dem, da de svingede meget.

Hvordan er robottens opførsel implementeret

  • Hvad er de overordnede opførsler / metoder for robotten

Robotten drejer til højre eller venstre alt efter forskellen i modstanden på de to photoresistorer. Når den når enden af servomotoreren, i.e. 0 eller 180 grader, så kører den tilbage. 

  • Lav et flow-diagram, der beskriver robottens opførsel.
  • Vis grafer for testkørsel med rigtig sol.
  • Hvilke udfordringer har der været ift. til at få koden til at virke?

Vi skulle lige have fundet ud af hvordan servomotoren virkede og hvilke gradantal den kunne tage.

Konklusion

  • Løser robotten opgaven ift. opgaveformuleringen?

Robotten kan bevæge sig efter solen uden problemer.

  • På hvilke punkter kan den forbedres og evt. hvordan?

Robotten kunne godt have en bedre evne til at placere sig rigtigt til at fange solen når den står op. Den kunne eventuelt huske sidste dags start position eller have en måde at konfigurere startpositionen på, så den kunne tilpasses forskellige breddegrader.

Video

Denne video viser hvordan den følger lyset rundt.
Denne video er et kort timelapse over ca. 2 timer.

Setup:

Code:

#include <Servo.h>

Servo servo;

int currentPosition = 10;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  servo.attach(3);
   pinMode(10, OUTPUT);
    pinMode(11, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  int lightLevelLeft = analogRead(A0);
  int lightLevelRight = analogRead(A1);



  int difference = lightLevelLeft - lightLevelRight;
  Serial.println("Difference: ");
  Serial.println(difference);
  if(difference > 2){
    servo.write(++currentPosition);
    digitalWrite(11, HIGH);
    digitalWrite(10, LOW);
  }else if(difference < -2){
    servo.write(--currentPosition);
    digitalWrite(11, LOW);
    digitalWrite(10, HIGH);
  }else{
   digitalWrite(11, LOW);
   digitalWrite(10, LOW);
 }
  Serial.println("Currentposition:");
  Serial.println(currentPosition);

  if(currentPosition > 179){
    currentPosition = 0;
  }else if(currentPosition < 1){
    currentPosition = 180;
  }

  delay(1000);
}
More

Solarising

Martin Bjerkov Hansen og Mads Bergholdt Sørensen

Figur 1

Til start var der udtænkt at systemet skulle være baseret på dc motor med tilkoblet gearing og h-bro for at kompensere for den manglende strøm arduino porten normalt levere. Dette blev desværre ikke muligt at udføre eftersom begge vores dc motorer stod af under øvelsen. Derfor blev der i stedet benyttet en servo motor, mens der er redegjort for begge opstillinger.

Systemet som vist på billedet ovenfor er den endelige system, som består af en 3d printet fod til servomotor. På den er der sat et stykke pap, som holder et lille breadboard på plads. Ved hjælp af to papirclips, bliver breakboarded holdt i en mere skrå vinkel. Herpå er der sat to LDR sensorer som er skilt med et stykke pap så det illustrerer en øst og en vest side. På et større bread board ved siden af er der sat 3 LED til, som henholdsvist lyser hvis programmet opfanger ændring i lys indfald, ændring i rotation på servomotor og den sidste lyser hvis systemet er i stilstand. 

Gearing

Gearingen var tænkt at skulle være som vist på figur 1, men grundet ødelagte dc motorer blev det kasseret til fordel for servostyringen. Gearingen skulle fungerer som hastighedsnedsættende, ved at havde 3 forskellige tandhjul hvor det første som var sat direkte på motoren skulle være mindst og det sidste tandhjul, hvis akse solcellen skulle tilkobles, værende det største. 

Figur 2

H-bro

Til styringen af dc motoren, er der som beskrevet tidligere, brug for en anden strøm end den som arduinoen kan levere på sin udgangsporte. Hertil kan en h-bro bruges, som gør det muligt at bevæge motoren i begge retninger. Der kan benyttes et IC som L293D eller der kan opbygges en selv, som ville bestå af fire transistorer, hver især knyttet til en arduino port. Eksemplet på figur 3 benytter sig af fire MOSFET n-kanal transistorer. Der kan yderligere forbindes fire dioder fra drain til source terminalerne for bedre at forhindre “back flow” fra motoren i det den skifter retning. 

Figur 3 – H-bro

For at aktivere motoren til at køre den ene retning, ville Port 1 og Port 2 aktiveres, og for den anden retning vil Port 3 og Port 4 aktiveres. Hvis man ønsker at bremse motoren, kan Port 4 og Port 2 aktiveres. 

Servo motor

Til den endelige opstilling, blev der som nævnt benyttet en servo motor i stedet. Den specifikke model brugt er i stand til at bevæge sig 180 grader frem og tilbage, hvoraf der ved hjælp af programmering kan vælges og aflæses vinkler placering præcist. Der kobles tre ledninger til arduinoen, en til 5V, en til GND og en til en PWM tilgængelig udgang. PWM signalet aflæses af motoren, som oversætter det til en vinkel ved brug af dens reguleringskredsløb. Ved brug af det indbygget library servo.h, sendes der forskellige PWM signaler til motoren, alt efter hvilken vinkel man bruger som input.

Sensore

Til at finde ud af hvor solcellen vil kunne generere mest strøm bruges to LDR sensorer, til at finde ud af om solcellen skal drejes til enten øst eller vest. LDR sensoren er som sådan bare en variabel modstand, som ændres ved påvirkning af lys . Modstanden ændre sig henholdsvis sådan at jo mere lys der er, jo mindre er modstanden, og jo mindre lys jo større modstand. 

Da der ikke er angivet et specifikt serienummer på LDR sensoren, er det valgt at bruge en simpel spændingsdeler opstilling, med en 10K resistor og LDR’en. Som udgangspunkt ønskes der gerne en stor variation i input spændingen ved lyspåvirkning, således det er nemmere at se udslag.

Figur 4

Til programmering skal der simpelt aflæses på en analog port, hvoraf der modtages en værdi mellem 0 til 1023 (0 – 5V). Værdierne fra de to LDR’er sammenlignes, og bruges til at bestemme hvornår servomotoren skal aktiveres. Det blev noteret ved afprøvning af de to sensorer, at den ene havde en delvis større standardværdi ved samme belysning. Den med en højere værdi, blev placeret i den side der skulle pege mod øst, så motoren ikke blev aktiveret konstant. 

Hardware

Til den endelige opstilling med servomoter, blev der brugt forskellige komponenter til systemet. Heriblandt modstande, LED dioder og LDR sensore. Modstandende brugt til LED’erne var 220 ohms og dem til LDR sensorene var 10k. Dette er vist på hardware diagrammet set nedenfor. 

Hardware diagram for DC-motor:

Hardware diagram for Servo-motor:

Flow diagram

For et hurtigt overblik over koden, er der blevet opstillet et flowdiagram. Arduinoen læser med et sekunds interval LDR værdierne, hvor den sammenligner de to med et if-statement. Hvis East er større end West, er det sandt og servomotoren rykker sig 2 grader. Følgende går den tilbage til at læse værdier igen. Et simpelt if statement sidst i koden, tjekker desuden om den maksimale rotation på 180 grader er nået, hvor den efter et delay går tilbage til udgangspunktet. Dette er opstillet til at simulere en solnedgang.

Figur 5

De tre bokse vil her gennemgås yderligere, med et tættere blik på selve programmeringen.

LDR aflæsningen er meget ligetil. De to LDR værdier knyttes til de to variabler, henholdsvis East og West, og udskrives på Serial monitoren. Desuden aflæses og udskrives vinklen af servomotoren, som kan gøres ved brug af header filen Servo.h og de inkluderet funktioner. I dette tidsrum er en gul status diode aktiveret, for at indikere stilstand.

Hvis if-statementet opfyldes, slukkes den gule diode, og der aktiveres en blå diode for at indikere motoren skal til at rykke sig. 

Der bliver benyttet read() funktionen til at aflæse hvilken vinkel motoren befinder sig i, som så adderes med to for en lille rotation. Under rotationen af motoren, aktiveres en rød diode også. 

Test med rigtig sol

Til testen med solen som lyskilde til systemet, blev forsøgsopstillingen placeret udenfor på en altan på en lidt overskyet dag. Dette gav lidt usikkerhed ift data, men som det fremgår af grafen var der en vinkel ændring på lidt over 20 grader i løbet af 2 timer. 

Figur 6
Figur 7

Billederne er taget med to timers interval, det første billede er derfor taget fra start, og billedet til sidst er taget to timer efter ved slut. 

Figur 8 – Billede af grafen, hvoraf Servomotorens vinkel placering ses op af y-aksen og antal minutter i forsøget ses langs x-aksen

Konklusion

Den endelige konstruktion opfylder umiddelbart de opstillet krav fra problemformuleringen. Den er i stand til rotere baseret på solens placering og rotere med små vinkler. Der er desuden opsat statusdioder, der indikere hvilket stadie programmet befinder sig i. Det er beklageligt at der ikke blev benyttet DC motorer og den 3D printet gearing der blev lavet, men servomotoren fungere fint til formålet. De implementeret delays kan simpelt øges og tilpasses, til at fungere i længere tidsperioder. 

Der er nogle punkter som kunne forbedres, hvis det nu f.eks skulle forestilles at implementeres et konkret sted. Først og fremmest, kunne der have været et større fokus på kalibreringen af LDR’erne. Den ene havde en større værdi end den anden ved samme belysning. Dette kunne tages højde for, ved at opsamle værdier fra dem begge ved ens belysning og vurdere den gennemsnitlige værdi den ene var højere end den anden. 

Der kunne være brugt mere tid på at opstille et mere stabilt stel, således ledninger ikke gik i vejen. Andet materiale end pap kunne være brugt, men fungerede fint til den simple opstilling. Der kunne være sat meget tid af til 3D design, men blev ikke vurderet til at være den bedste brug af tid. Stativet der blev brugt til servomotoren tog lang nok tid i sig selv, så fokuset forblev på programmeringen og en mock-up opstilling.

Endelige program

#include <Servo.h>
Servo myservo;
int LDR = A1;
int East = 0;
int West = 0;
int LDR2 = A2;

int LEDB = 3;
int LEDG = 2;
int LEDR = 4;

int readservo = 0;

void setup() {
  // put your setup code here, to run once:
myservo.attach(A0);

  pinMode(LEDG, OUTPUT);
  pinMode(LEDR, OUTPUT);
  pinMode(LEDB, OUTPUT);
  
Serial.begin(9600);
myservo.write(0);

}

void loop() {

  digitalWrite(LEDG, HIGH);
 East = analogRead(LDR2);
 West = analogRead(LDR);
  Serial.println("East is: ");
  Serial.println(East);
  Serial.println("West is: ");
  Serial.println(West);
  Serial.println("Servo Vinkel - "), Serial.println(readservo);
  delay(1000);

   if(East < West) {

    digitalWrite(LEDB, HIGH);
    digitalWrite(LEDG, LOW);

readservo = myservo.read();
myservo.write(readservo+2);
  digitalWrite(LEDR, HIGH);
  delay(500);
  
  digitalWrite(LEDR, LOW);
  delay(500);
  digitalWrite(LEDB, LOW);
  }
//Et if statement der siger hvis servo = 180, og East<West -> Reset til 0 grader, Readservo sættes til 0 igen
if(East < West && myservo.read() == 180){
  delay(1000); //Kunne være meget stort og tage højde for hvornår solen står helt ned'
  myservo.write(0);
  readservo = 0; 
}

}

Video af opstillingen

More

SunSucc

SunSucc (ikke sponsoreret af Tuborg Påskebryg)

Hardware og Robotteknologi (F21)
Porteføljeopgave 1

Kevin Lavlund Hansen – kehan18@student.sdu.dk
Rasmus Sjøholm Stamm – rasta17@student.sdu.dk
Benjamin Clement Klerens – bekle18@student.sdu.dk

Løsningen, praktisk set

Løsningens hovedsagelige dele består af en servomotor som, baseret på aflæsninger fra to photoresistorer, drejer enheden til at stå imod lysets (solens) retning. De to photoresistorer peger i hver sin retning, ca. med en vinkel på 45°. Dette betyder, at hvis lyskilden ikke står lige i mellem dem, opstår en resistansforskel, og denne kan bruges til at dirigere servoen på rette vej. Denne logiske reaktion håndteres naturligvis af en Arduino-enhed, som står for at aflæse sensorer og dreje servoen.

Komponentliste
  • Arduino UNO
  • SG90 servo motor
  • 2x photoresistor / LDR-modstand
Grov skitse af konceptet

Den faktiske konstruktion af systemet er udført med lockdown-venlige materialer såsom pap, tomme øldåser, gaffa-tape og universallim.

På billedet for neden ses et overblik over systemets komponenter, og hvor de sidder på Arduinoen (servoen sidder under, og er derved gemt). Billedet viser:

  • Servo motoren (dog ikke synlig)
  • PWM signalet, som styrer servoen
  • RBGLED’en, som fungerer som statuslysdiode
  • Photoresistorene, som aflæser belysning
Komponent overblik

Endeligt, en video-demonstration af systemet i aktion. Videon viser tydeligt hvordan servoen hele tiden drejer, så enheden “peger” på kameraet, som er den mest betydelige lyskilde.

Systemet i aktion

Løsningen, teknisk set

Det overordnede diagram for systemets kredsløb ser således ud:

SunSucc kredsløbsdiagram

Photoresistor / Light-dependent resistor

En photoresistor, eller LDR, er som navnet foreslår, en modstand, der afhænger af belysning. Kort fortalt er det en variabel modstand, som varierer efter belysning, hvor mere lys = mindre modstand. Denne funktionalitet er specielt smart, når man har et system der skal reagere på lys, såsom projektet her.

En Arduino kan som udgangspunkt ikke aflæse resistansværdier direkte, dog kan den aflæse spændingssignaler via analoge input. Derfor er LDR1 og LDR2 del af hver sin spændingsdeler, hvor Arduionen måler spændingen i mellem disse, på pins A0 og A1, som så:

På Arduinoen vil disse aflæses mellem 0-5V i en opløsning af 10 bit (0-1023). Dvs. at et analogt signal på 2.5V vil aflæses som ~512 fordi 2.5 / 5 * 1023 = 511.5. Disse aflæsninger kan derved bruges i Arduinoens kode til at
1) afgøre hvilken sensor, som modtager mest lys
2) signalere servoen til at dreje mod denne

Servo motor – SG90

En servo motor er, kort fortalt, en DC motor med indbygget gearing, hvilket tillader præcis positionsindstilling. Dette er især smart i vores tilfælde, hvor vi skal indstille enheden baseret på sensor-læsninger. Hvor en DC motor har to pins, positiv og negativ, har en servo motor i stedet typisk tre, nemlig positiv, negativ og signal. Den tredje pin, signal, bruges til at styre servoen, vha. pulse width modulation. I denne kontekst er det pulsbredden af det indgående PWM-signal, der afgør servoens position. Per komponentens datasheet, vides det at servoen drejer mellem 0-180° (eller -90-90°) over pulsbredder mellem 1-2ms.

På Arduionen sidder adskillige pins, som er i stand til outputte PWM-signaler. I vores tilfælde er servoen forbundet til pin 6, som på diagrammet er noteret med “PWM”, hvilket betyder at den har førnævnte PWM-egenskab.

RGBLED, statusdiode

For at gøre fejlfinding og testing af systemet nemmere, har vi sat en RGBLED (red-green-blue LED) i kredsløbet, som blinker i én af tre farver, alt efter hvilken tilstand Arduinoen befinder sig i. Alternativt kunne vi i stedet havet brugt tre individuelle LED’er, men vi fandt det smartere med én RGBLED, da det både var en nemmere og smartere løsning, eftersom en RBGLED principielt set kan lyse i et uendeligt antal farver.

Dioden styres med PWM, på pins 9, 10 og 11, hvor signalets duty cycle mellem 0-100% afgør den tilsvarende farves intensitet i vidden 0-255. Derved kan vi lave farver i typisk RGB-format ved at skrive forskelligt til de individuelle pins.

Programmering

Arduinoen huser al logik og programmering, som bestemmer systemets uadvendte opførsel. Filosofisk set er Arduinoen hjernen mellem sensor og aktuator. Koden, som driver systemet, er således:

#include <Servo.h>

// Photoresistors
const int sens1Pin = A0;
const int sens2Pin = A1;
// Servo
const int servoPin = 6;
// RBG LED
const int redPin = 9;
const int greenPin = 10;
const int bluePin = 11;

Servo servo;

int sens1Val;
int sens2Val;

int sens1Volt;
int sens2Volt;

int pos = 90;

void setup() {
  Serial.begin(9600);
  
  // Photoresistors
  pinMode(sens1Pin, INPUT);
  pinMode(sens2Pin, INPUT);
  // Servo
  servo.attach(servoPin);
  servo.write(pos);
  // RGB LED
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);

  Serial.println("sens1Val,sens2Val,difference,average,pos");

  //servo.write(90);
  //delay(10000);
}

void loop() {
  setColor(255, 0, 0);
  readSensors();
  
  if (sens1Val + sens2Val <= 200) {
    reset();
  } else if (abs(sens1Val - sens2Val) >= 100) { 
    correctServo();
  }
  
  setColor(0, 0, 0);
  delay(50);
}

int mapValue(int value) {
  return 180.0 / 500.0 * value;
}

// Prints a line per reading, following the format:
// [sens1Val],[sens2Val],[difference],[average],[position]
void printReadings() {
  Serial.print(sens1Val);
  Serial.print(",");
  Serial.print(sens2Val);
  Serial.print(",");
  Serial.print(abs(sens2Val - sens1Val));
  Serial.print(",");
  Serial.print((sens2Val + sens1Val) / 2);
  Serial.print(",");
  Serial.println(pos);
}

void readSensors() {
  sens1Val = analogRead(sens1Pin);
  sens2Val = analogRead(sens2Pin);
  printReadings();
}

// Align servo in current lighting environment
void correctServo() {
  bool isCorrected = false;
  int changeDeg = 6;
  int deadzone = 50;

  while (!isCorrected) {
    setColor(0, 0, 255);
    readSensors();
    
    if (abs(sens1Val - sens2Val) <= deadzone) {
      isCorrected = true;
    } else if (sens1Val > sens2Val) {
      changePos(changeDeg);
    } else if (sens1Val < sens2Val) {
      changePos(-changeDeg);
    }
    
    servo.write(pos);
    setColor(0, 0, 0);
    delay(100);
  }
}

// Return servo to baseline (i.e. sun has gone down)
void reset() {
  setColor(0, 255, 0);
  pos = 90;
  servo.write(pos);
  delay(5000);
}

// To ensure pos never goes above 180 or below 0
void changePos(int change) {
  pos += change;

  if (pos < 0) {
    pos = 0;
  } else if (pos > 180) {
    pos = 180;
  }
}

// Changing color of RGB LED
void setColor(int red, int green, int blue) {
  analogWrite(redPin, red);
  analogWrite(greenPin, green);
  analogWrite(bluePin, blue);
}

Programmets logiske flow kan med fordel beskrives med nedenstående flow-diagram.

Flow-diagram over SunSucc’s opførsel

Diagrammet beskriver de logisker operationer, som sker i programmets loop() metode, og hvordan aktuatoren (servoen) reagerer herpå. Kort fortalt “venter” programmet på, at forskellen mellem de to photoresistorer er større end en sat grænse, hvorved den inkrementelt drejer servoen mod photoresistoren med lavest belysning, indtil photoresistor-forskellen igen er under en sat grænsevidde, hvorefter programmet vil gå tilbage til at vente.

Der er også tage højde for, at systemet skal nulstille (i.e. centrere servo), når solen er gået ned, så photoresistorene kan fange solen, når den kommer op på den modsatte side. Dette er gjort ved at tjekke om summen af aflæsning af begge photoresistorer er under, eller lig 200, hvorved servoen vil centreres på 90°.

Diagrammet viser også hvilken farve statuslysdioden har under programmets forskellige stadier. F.eks. vil den lyse blåt, mens servoen indstiller sig efter en lysændring, og rødt mens den venter.

Servo library

Til at gøre interaktion med servoen nemmere, findes der et Arduino library kaldt Servo. Denne simplificerer indstilling vha. generaliserede metoder, som kan kaldes direkte på et Servo-objekt, instansieret med servo-komponentens PWM-pin. Eksempelvis her, hvor attach() metoden “tilslutter” servo-objektet til pin servoPin, og indstiller servoen til gradtallet pos:

  servo.attach(servoPin);
  servo.write(pos);

Dette betyder at vi ikke selv behøver at håndskrive PWM-signaler til servoen, men i stedet bare give metoden det ønskede gradtal, hvorefter den selv beregner og sender det nødvendige signal. Nice.

Logging

For at få data på, hvor vidt systemet faktisk følger efter en lyskilde, er programmet skrevet til at printe en masse data som Serial output. Disse data inkluderer aflæsningsværdier af begge photoresistorer, deres forskel, gennemsnit, og servoens nuværende position i gradtal.

// Prints a line per reading, following the format:
// [sens1Val],[sens2Val],[difference],[average],[position]
void printReadings() {
  Serial.print(sens1Val);
  Serial.print(",");
  Serial.print(sens2Val);
  Serial.print(",");
  Serial.print(abs(sens2Val - sens1Val));
  Serial.print(",");
  Serial.print((sens2Val + sens1Val) / 2);
  Serial.print(",");
  Serial.println(pos);
}

Outputtet er skrevet til at følge et standard CSV-format, hvilket vil sige at dataen kan importeres direkte i et databehandlingsprogram såsom Microsoft Excel eller Google Sheets, hvoraf grafer kan genereres. For at gemme det serielle output, er det nødvendigt at bruge en seriel aflæser med logging-funktionalitet, såsom programmet PuTTY. F.eks:

Test

Under optimale forhold ville systemet blive testet under rigtig sollys, over en hel dag, da dette scenarie er systemets faktiske use case. Desværre, pga. årstidens vejrforhold, har dette vist sig at være besværligt, så i stedet er ægte solskin forsøgt simuleret vha. lygte, over et par minutter. Mens det betyder at systemet ikke er testet i realistiske forhold, giver det dog et overordnet indtryk af dets funktionalitet, da data vil vise om lyskilden bliver fulgt som ønsket. Testen resulterede i følgende diagram:

Grafen viser servoens position i grader (blå) ift. photoresistorenes gennemsnitlige læsninger, dvs. lysindfald (rød). Her håber vi at se en nogenlunde konstant rød linje, i takt med at den blå ændrer sig; dette betyder at servoen drejer for at følge lyskilden, for at holde lysindfaldet maksimalt. Dette viser diagrammet umiddelbart, så derved kan testen vurderes en success.

More

RoboSchneider 2.0

Mads Kildegaard Kristjansen & Lucas Brynk Jensen

I denne tutorial vil vi gennemgå hvordan man bygger en robot, som ved hjælp af diverse sensorer kan køre efter en streg på gulvet indtil stregen ophører, hvorefter robotten vil køre efter lydbølger for at undgå mulig kollision. Robotten i denne tutorial er navngivet RoboSchneider (efter skuespilleren) og den er udarbejdet af Lucas Brynk og Mads K. Kristjansen. Det færdige produkt, samt forventninger til produktets opførelse kan ses på nedenstående videoer.

Video af testkørsel i Unity
Video af test i TinkerCAD

Vi starter med at åbne TinkerCAD. For at kunne forstå hvad der sker med den færdige robot i Unity, er vi nødt til at opbygge RoboSchneider i TinkerCAD først. De nødvendige elementer kan ses på nedenstående billede samt en liste med navne på elementerne.

Komponentlist:

  • Breadboard x2
  • Arduino Uno
  • 9v Batteri
  • DC Motor x2 [H16930]
  • Micro Servo [SG90]
  • Ultrasonic Distance Sensor [HC-SR04]
  • H-Bridge Motor Driver [L293D]
  • 5C Regulator [LM7805]
  • 100 ohm Resistor x2
  • 10kOhm Resistor x2
  • LED-Resistor [2542 Standard White LED] x2
  • LDR-Resistor [f.eks NORPS-12]
  • Slideswitch

Hvordan selve elementerne bliver sat i forhold til hinanden er stort set lige meget i dette projekt, da TinkerCAD modellen ikke skal bruges andre steder. For forståelsens skyld opfordres der til at opsætte elementerne som skulle man bygge en “bil”.

Første skridt er at sætte 9V batteriet til breadboardet så vi kan få strøm rundt i kredsløbet. Lad plus køre igennem en sliderswitch for at kunne slukke og tænde for RoboSchneider.

Det første breadboard bliver vores H-bridge board. Sæt L293D H-Bridge (H-bro) fast i midten af breadboarded og forbind de to motorer på hver side af H-broen. Grunden til at anvende en H-bro er for at styre strømmens polaritet gennem motorerne. Dette gør at vi kan bestemme hvilken vej hjulene skal dreje. For den ene motor vil vi lade Input 1 og 2 fra H-broen gå til pin 10 og 11 på arduinoen. Man kan bruge alle digital pins til dette, men da Arduino Uno er begrænset i antallet af Digitale pins (især PWM pins), og vores servo kommer til at optage pin 9 eller 10, så er vi nødt til at bruge hvad der er tilbage. Ligeledes for den anden motor vil køre Input 3 og 4 fra H-broen hen til pin 5 og 6 på arduinoen.

På første breadboard er der lidt plads tilbage, nede i den anden ende, til at sætte en 5V regulator [LM7805]. Fra 5V Regulatorens output trækker vi en plus og minus videre til næste breadboard. Fra andet breadboard starter vi med at trække en plus og en minus til hhv Arduinoens 5V pin og GND pin for at kunne gøre Arduino Uno uafhængig af USB-forbindelsen til en PC.

Andet breadboard bliver LDR-LED boarded. Her sætter vi 2 LDR-resistorer samt 2 modstande. Modstandene skal være på 10 kOhm grundet følgende beregninger:

Da vi kender Ohms lov kan vi stille den om og finde den ønskede modstandsværdi ved den benyttede spænding. Hertil skal det siges at vi antager fra tidligere projekt at LDR-modstandene ved maksbelysning er 20-230kOhm, samt ved totalt mørke, vil være 2MOhm.

Vout = Vin * (R2/R1+R2)

Ved at sætte den valgte modstand ind, samt indsætte de kendte værdier, kommer formlen til at se sådanne ud ved 20kOhm, 230kOhm og 2MOhm:

5V * (20000/20000+10000) = 3,33V
5V * (230000/230000+10000) = 4,79V
5V * (2000000/2000000+10000) = 4,98V

Lad LDR-resistorerne køre til hhv. pin A2 og A3. (Du kan i princippet benytte enhver A pin i dette projekt, det er dog kun indgangene markeret med ‘A’ der kan bruges her). Grunden til at vi benytter pins markeret med ‘A’ for LDR-resistorerne, er fordi vi her ikke får et digitalt signal, men et analogt signal, det vil sige at det ikke bliver bearbejdet på samme måde som Digitale signaler. Hertil skal det også siges at det signal vi får fra LDR-resistorerne bliver konverteret i Arduinoen fra Volt til en målbar enhed (altså værdien fra 0-1023).

Sæt herefter 2 LED-resistorer direkte til 5V med en modstand imellem. Størrelsen af modstanden vælges ud fra den givende LED’s data. LED-resistor Data kan findes i et Datablad. Da dette projekt er virtuelt har vi ikke ægte LED-modstande at undersøge, derfor må vi gå ud fra at vi har at gøre med en standard hvid LED 5mm (3542serialnumber). Hvis man undersøger en sådanne LED-modstand i et datablad vil man finde ud af at den maximalt kan håndtere 25mA (dog kun 20mA i TinkerCAD) før det begynder at gå ud over levetiden på LED-modstanden, og da den bliver tilført 5V (minus spændingsfaldet på 3,1V) kan vi regne os frem til, med hjælp fra Ohms lov, at en 100 Ohms modstand vil være at fortrække i TinkerCAD.

U=R*I
R=U/I 
R=(5-3,1)/0,02 = 95 Ohm 

Vi runder op således vi anvender en 100 Ohms modstand. 

For en LED udenfor TinkerCAD, som max håndterer 25mA, sætter man 0.025 ind i formlen og får dermed en beregnet modstand på 

R=(5-3,1)/0,025 = 76 ohm (rundet op til 80 ohm) 

Beregningerne er lavet ud fra data fundet på følgende hjemmeside: (https://www.kitronik.co.uk/blog/led-resistor-value-calculator/

Nu skal Micro Servo’en sættes til. Her sætter vi Power samt GND til LDR-LED Breadboardet. Micro Servo’ens Signal skal køre til pin 9. Den skal altid sættes til enten pin 9 eller 10, da det er de eneste to pins der er registreret i Arduinosprogets Servo.h bibliotek som er integreret i TinkerCAD. På en fysisk prototype undenfor TinkerCAD kan man bruge enhver PWN pin til Servoen. Måden vi fandt ud af dette på, var ved at den venstre motor stoppede med at fungere korrekt, samt højre motor kørte med fuld hastighed når andre end pin 9 eller 10 blev benyttet. Servoen kørte heller ikke korrekt. Efter lidt debugging (test af systemet slavisk) fandt vi frem til at det måtte være et problem som er opstået efter servoen blev implenteret. Hertil måtte vi ind og se i dokumentationen i Servo.h biblioteket, hvor vi fandt ud af at pin 9 og 10 er de eneste som er supporteret og integreret i Servo.h biblioteket til TinkerCAD.  

Ultrasonic Distance Sensor’s TRIG, og ECHO skal køre til henholdsvis pin 3 og pin 12 (TRIG skal altid forbindes til en PWM digital pin, og ECHO skal altid forbindes til en normal digital pin). Ultrasonic Distance Sensor’s Power og GND skal også kører hen til LDR-LED Breadboardet. Hvis systemet er opsat korrekt burde det se nogenlunde ud som på billedet nedenfor.

Et PWM-signal er et signal som ikke kun kan gå fra tændt til slukket (0-1), men har et spektrum fra 0 og op til en given værdi. Et PWM-signal fungerer lidt som en kurve. Dette kan anvendes til eksempelvis en motor, for at få en blød opstart og/eller en blød nedbremsning. Dette kan også bruges i servoen, hvor vi ikke vil have servoen til at køre hele vejen rundt, men gerne vil have en til at stille sig i et bestemt punkt hvortil den bliver stående. Dette bliver også brugt på Ultrasonic Distance Sensor hvor vi ikke kun kan nøjes med et signal der går fra 0-1, men har brug for at kende den nøjagtige værdi der bliver givet fra komponentet, altså Distance sensoren.

TinkerCAD Code 

I vores Arduino kode, som kan ses forneden, vil vi først og fremmest declare(erklære) vores variabler vi gør brug af igennem programmet. En vigtig ting at have i mente er, at når man vil gøre brug af en servo, så skal man huske at anvende komponentens bibliotek via #include <Servo.h>. Uden dette vil der opstå fejl i programmet. Foruden vores variabler anvender vi også to bools, der agerer som “triggers” for at programmet ikke springer over nogle af vores funktioner. Inde i vores setup() angiver vi vores pins og servo så vi kan bruge dem i programmet, og sætter vores motorer i en stillestående tilstand. 

#include <Servo.h>
//declaring the names for the pins
//Motor
const int RightPlus = 11;
const int RightMinus = 10;
const int LeftPlus = 6;
const int LeftMinus = 5;

//Distance Sensor
const int TRIGPin = 3;
const int ECHOPin = 12;


//Declare the needed variables
int left = A5;
int right = A4;
int sensorValue1;
int sensorValue2;
int minDiff = 50; //Maximum allowed difference between left and right sensor values

bool onLine = true;
bool firstDrive = false;

int dis = 100;
long tid;
int distance;
int servoLeft = 0;
int servoFront = 0;
int servoRight = 0; 

Servo theServo;

//Setup what needs to be stated before the robot begins its Loop
void setup()
{
  pinMode(RightPlus, OUTPUT);//right plus
  pinMode(RightMinus, OUTPUT);//right minus
  pinMode(LeftPlus, OUTPUT);//left plus
  pinMode(LeftMinus, OUTPUT);//left minus
  
  pinMode(TRIGPin, OUTPUT); // Sets the trigPin as an OUTPUT
  pinMode(ECHOPin, INPUT); // Sets the echoPin as an INPUT
  
  theServo.attach(9); // The Servo
  
  //Make sure motors are off
  digitalWrite (RightPlus, LOW);
  digitalWrite (RightMinus, LOW);
  digitalWrite (LeftPlus, LOW);
  digitalWrite (LeftMinus, LOW);
  
  Serial.begin(9600);
  
}

I vores loop(), hvor hele programmet egentlig kører, starter vi med at aflæse værdierne fra vores to LDR-modstande. Disse værdier bruger vi til at vurdere om RoboSchneider følger linjen, eller om vi skal rette kursen. Her har vi også vores første bool (onLine), som sørger for at denne del af programmet kun kører imens RoboSchneider faktisk kører på linjen.

void loop()
{
  //Assigning values to var's
  sensorValue1 = analogRead(left);
  sensorValue2 = analogRead(right);
  
  int diff = abs(sensorValue1 - sensorValue2);
  
  //"If" robot is following the ground-line; Do this:
  if (onLine == true)
  { ...

Værdierne fra LDR-modstandene angiver det lys der bliver reflekteret fra ”gulvet”. LDR-modstandene har en værdi mellem 0-1023. Denne værdi er målt som mængden af strøm der passerer igennem LDR-modstanden, i forhold til hvor stor en modstand der udvikles i selve LDR-modstanden. Det vil med andre ord sige, at det hvide gulv reflekterer meget lys, og opfanges som en høj værdi, og den sorte streg reflekterer lidt lys, der opfanges som en lav værdi.

Vi bruger værdierne fra vores LDR-modstande til at bestemme hvilke motor pins der skal tilføres strøm. Dette gør vi ved at angive den pin som er tildelt motoren og indsætter en værdi mellem 0-255, eksempel: analogWrite(pin, speed). Da motoren er tilsluttet Arduinoen via en H-bro giver det 4 pins at holde styr på. Der er en analogWrite(pin,speed); for hver “pol” af de 2 motor; RightPlus, RightMinus, LeftPlus, LeftMinus. Så hvis den ene LDR-modstand måler for lavt, altså måler ind over den sorte streg, så retter vi op på kursen ved at ændre vores motor inputs. 

//"If" robot is following the ground-line; Do this:
  if (onLine == true)
  {
    // If the robot follows the line fine:
    if (diff <= minDiff && sensorValue1 >= 100 && sensorValue2 >= 100)
    {
      	//Driving Forward
        analogWrite(LeftPlus, 100);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 100);
        analogWrite(RightMinus, 0);
        delay(1000);
      	firstDrive = true;
    }
    //If the robot is turning right, it turns left slightly
    else if (sensorValue1 < sensorValue2)
    {
      	//Turn Right for 1 sec at speed (50/255)
    	analogWrite(LeftPlus, 0);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 50);
        analogWrite(RightMinus, 0);
      	delay(1000);
    }
        // Vice Versa
    else if (sensorValue2 < sensorValue1)
    {
      	//Turn Left for 1 sec at speed (50/255)
        analogWrite(LeftPlus, 50);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 0);
        analogWrite(RightMinus, 0);
      	delay(1000);
    }
    ...
  

Vi er lidt påpasselige med vores konstruktions hastighed, og efter noget testning fandt vi frem til nogle passende værdier for vores motor inputs, som gav en stabil performance i vores opstilling. Eksempelvis, hvis RoboSchneider har for meget fart på, uden noget nedbremsning inden svinget, så kan den risikere at køre for hurtigt til at vores sensorer kan opfange og justere i programmet, således den ender med at køre over og væk fra stregen. Vi valgte derfor en lavere og mere passende hastighed for at undgå mulige fejl i adfærden. 

Når vi rammer den første målstreg, hvilket sker når begge LDR-modstande måler under den angivet minimumsværdi, ændrer vi vores onLine bool til at være false for at komme videre til den næste del af programmet. 

...

    //When the robot hits the ending of the ground line it stops and shifts over to "Off-Line" mode 
    else if (sensorValue1 <= 100 && sensorValue2 <= 100 && firstDrive == true)
    {
      	//Motor Stop for 1 sec
        analogWrite(LeftPlus, 0);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 0);
        analogWrite(RightMinus, 0);
        onLine = false;
      	Serial.print("online is now false");
      	delay(1000);
    }

Den næste del af koden foregår kun efter vi har ramt den første målstreg. Her anvender vi vores Ultrasonic Distance Sensor (afstandsmåler) til at navigere den resterende del af vores opstillede bane. Vi starter med at aktivere pin 3, altså Distance Sensorens TRIG Pin, som sender en puls ud der bliver opfanget igen af Distance Sensoren. Den opfangede værdi er tiden det tager for denne puls at vende tilbage til vores sensor, målt i mikrosekunder. Denne værdi omregner vi til en målbar enhed. Lydens hastighed er 340 m/s, eller 0,034 cm/µs. Vi tager værdien fra afstandsmåleren, ganger den med 0,034, og deler med 2 da vi skal have afstanden til væggen, og ikke fra sensor til væg til sensor igen. Denne værdi gemmer vi i en variabel ”distance” som vi kan bruge til at vurdere afstanden fra sensoren på RoboSchneider og væggen forud.  

      //THIS IS OFF-LINE MODE
        //If the end of the groundline is reached
  if (onLine == false)
  {
    //Distance Sensor Start
    digitalWrite(TRIGPin, LOW);
  	delayMicroseconds(2);
  	digitalWrite(TRIGPin, HIGH);
  	delayMicroseconds(10);
  	digitalWrite(TRIGPin, LOW);
    //Read signal from TRIGPin and store as "tid"
  	tid = pulseIn(ECHOPin, HIGH);
    //Convert "tid" to "distance"
  	distance = tid * 0.034 / 2;
...

Hvis afstanden til væggen foran RoboSchneider ikke er under den angivet minimumsværdi, så kører den bare ligeud indtil den registrerer en genstand inden for minimumsafstanden. Når afstandsmåleren registrerer at vi er under minimumsgrænsen, så stopper vi RoboSchneiders fremgang for at undgå kollision. I praksis ville vi placere vores afstandsmåler ovenpå vores servo således vi kan rotere den og måle i forskellige retninger (dette kan ses på et billede af RoboSchneider længere nede i tutoriallen). 

//if the SonarSensor signal is bigger than the chosen minimum distance it goes forward 
  	if (distance >= dis)
    {
      	//Driving Forward
    	analogWrite(LeftPlus, 100);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 100);
        analogWrite(RightMinus, 0);
    }
    //if signal is smaller:
    else if (distance <= dis)
    {
      	//Motor Stop
      	analogWrite(LeftPlus, 0);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 0);
        analogWrite(RightMinus, 0);
  ...

Med servo.write(), i vores eksempel har vi navngivet vores for myServo, så kan man indsætte hvilken vinkel man vil indstille servoen til, eksempelvis 0, 90, eller 180 grader. Så hvis vi vil dreje servoen med 90 grader, så skriver vi i koden: myServo.write(90). Ud fra servoens placering og orientering kan disse værdier variere. I vores eksempel antager vi at 0 grader kigger servoen til venstre, 90 grader kigger den ligeud, og 180 grader til højre. 

//The check consists of 4 steps
        //Step 1 check distance to nearest wall in front of robot
        servoFront = distance;
      	//Turning the Servo to the Right
        theServo.write(180);
        delay(2000);
        //Step 2 check distance to nearest wall left of robot
        servoLeft = distance;
      	//Turning the Servo to the Left
        theServo.write(0);
        delay(4000);
        //Step 3 check distance to nearest wall right of robot
        servoRight = distance;
      	//Turning the Servo Back to facing for forward
        theServo.write(90);
        delay(2000);
...

Vi måler de tre retninger og gemmer dem i hver deres variabel. Disse variabler kan vi bruge til at sammenligne og vurdere hvor der er en væg, og hvor der er fri bane. Afhængig af hvilken retning der er fri, så justerer vi RoboSchneiders retning, hvorefter vi indstiller den til at køre fremad indtil der bliver registreret en ny væg/forhindring. 

//Turn to left wall
        if (servoLeft >= servoFront && servoLeft >= servoRight && servoRight != 0 || servoLeft == 0)
        {
          	//Turning Left (on the spot)
        	analogWrite(LeftPlus, 0);
            analogWrite(LeftMinus, 55);
            analogWrite(RightPlus, 55);
            analogWrite(RightMinus, 0);
            delay(1000);
          	//Stopping
            analogWrite(LeftPlus, 0);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 0);
            analogWrite(RightMinus, 0);
            delay(1000);
          	//Driving Forward Again
            analogWrite(LeftPlus, 130);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 130);
            analogWrite(RightMinus, 0);
        }
        //Turn to right wall
        if (servoRight >= servoFront && servoRight >= servoLeft && servoLeft != 0 || servoRight == 0)
        {
          	//Turning Right (on the spot)
        	analogWrite(LeftPlus, 55);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 0);
            analogWrite(RightMinus, 55);
            delay(1000);
          	//Stopping
            analogWrite(LeftPlus, 0);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 0);
            analogWrite(RightMinus, 0);
            delay(1000);
          	//Driving Forward Again
            analogWrite(LeftPlus, 130);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 130);
            analogWrite(RightMinus, 0);
		}
        //Continue forward
        if (servoFront >= servoLeft && servoFront >= servoRight)
        {
          	//Driving Forward
        	analogWrite(LeftPlus, 130);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 130);
            analogWrite(RightMinus, 0);
          	delay(1000);
        }

Med denne opsætning er RoboSchneider i stand til at køre igennem en bane som starter med en streg i gulvet, som naturligvis skal følges til ende.

Når enden af stregen er nået starter RoboSchneider på at køre efter Ultrasonic Distance Sensor (afstandsmåler) for at navigere igennem resten af banen, til målstregen bliver mødt. Hvis alt er opsat korrekt vil RoboSchneider opføre sig som vist på den første video oppe i toppen af bloggen her på siden.  

Complete TinkerCAD code

#include <Servo.h>
//declaring the names for the pins
//Motor
const int RightPlus = 11;
const int RightMinus = 10;
const int LeftPlus = 6;
const int LeftMinus = 5;

//Distance Sensor
const int TRIGPin = 3;
const int ECHOPin = 12;


//Declare the needed variables
int left = A5;
int right = A4;
int sensorValue1;
int sensorValue2;
int minDiff = 50; //Maximum allowed difference between left and right sensor values

bool onLine = true;
bool firstDrive = false;

int dis = 100;
long tid;
int distance;
int servoLeft = 0;
int servoFront = 0;
int servoRight = 0; 

Servo theServo;

//Setup what needs to be stated before the robot begins its Loop
void setup()
{
  pinMode(RightPlus, OUTPUT);//right plus
  pinMode(RightMinus, OUTPUT);//right minus
  pinMode(LeftPlus, OUTPUT);//left plus
  pinMode(LeftMinus, OUTPUT);//left minus
  
  pinMode(TRIGPin, OUTPUT); // Sets the trigPin as an OUTPUT
  pinMode(ECHOPin, INPUT); // Sets the echoPin as an INPUT
  
  theServo.attach(9); // The Servo
  
  //Make sure motors are off
  digitalWrite (RightPlus, LOW);
  digitalWrite (RightMinus, LOW);
  digitalWrite (LeftPlus, LOW);
  digitalWrite (LeftMinus, LOW);
  
  Serial.begin(9600);
  
}

//The Loop Begins

void loop()
{
  //Assigning values to var's
  sensorValue1 = analogRead(left);
  sensorValue2 = analogRead(right);
  
  int diff = abs(sensorValue1 - sensorValue2);
  
  //"If" robot is following the ground-line; Do this:
  if (onLine == true)
  {
    // If the robot follows the line fine:
    if (diff <= minDiff && sensorValue1 >= 100 && sensorValue2 >= 100)
    {
      	//Driving Forward
        analogWrite(LeftPlus, 100);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 100);
        analogWrite(RightMinus, 0);
        delay(1000);
      	firstDrive = true;
    }
    //If the robot is turning right, it turns left slightly
    else if (sensorValue1 < sensorValue2)
    {
      	//Turn Right for 1 sec at speed (50/255)
    	analogWrite(LeftPlus, 0);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 50);
        analogWrite(RightMinus, 0);
      	delay(1000);
    }
        // Vice Versa
    else if (sensorValue2 < sensorValue1)
    {
      	//Turn Left for 1 sec at speed (50/255)
        analogWrite(LeftPlus, 50);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 0);
        analogWrite(RightMinus, 0);
      	delay(1000);
    }
    
    //When the robot hits the ending of the ground line it stops and shifts over to "Off-Line" mode 
    else if (sensorValue1 <= 100 && sensorValue2 <= 100 && firstDrive == true)
    {
      	//Motor Stop for 1 sec
        analogWrite(LeftPlus, 0);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 0);
        analogWrite(RightMinus, 0);
        onLine = false;
      	Serial.print("online is now false");
      	delay(1000);
    }
  }
        //THIS IS OFF-LINE MODE
        //If the end of the groundline is reached
  if (onLine == false)
  {
    //Distance Sensor Start
    digitalWrite(TRIGPin, LOW);
  	delayMicroseconds(2);
  	digitalWrite(TRIGPin, HIGH);
  	delayMicroseconds(10);
  	digitalWrite(TRIGPin, LOW);
    //Read signal from TRIGPin and store as "tid"
  	tid = pulseIn(ECHOPin, HIGH);
    //Convert "tid" to "distance"
  	distance = tid * 0.034 / 2;
  
  //if the SonarSensor signal is bigger than the chosen minimum distance it goes forward 
  	if (distance >= dis)
    {
      	//Driving Forward
    	analogWrite(LeftPlus, 100);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 100);
        analogWrite(RightMinus, 0);
    }
    //if signal is smaller:
    else if (distance <= dis)
    {
      	//Motor Stop
      	analogWrite(LeftPlus, 0);
        analogWrite(LeftMinus, 0);
        analogWrite(RightPlus, 0);
        analogWrite(RightMinus, 0);
    
        //The check consists of 4 steps
        //Step 1 check distance to nearest wall in front of robot
        servoFront = distance;
      	//Turning the Servo to the Right
        theServo.write(180);
        delay(2000);
        //Step 2 check distance to nearest wall left of robot
        servoLeft = distance;
      	//Turning the Servo to the Left
        theServo.write(0);
        delay(4000);
        //Step 3 check distance to nearest wall right of robot
        servoRight = distance;
      	//Turning the Servo Back to facing for forward
        theServo.write(90);
        delay(2000);

        //And step 4
        //Compare the 3 checks and turn to the wall farest away.

        //if the wall at left and the wall at right is not existing, it means the end is reached. The robot will turn right and drive off the ground.
        if (servoLeft == 0 && servoRight == 0)
        {
          	//Turning Right (On the spot)
        	analogWrite(LeftPlus, 55);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 0);
            analogWrite(RightMinus, 55);
            delay(1000);
          	//Stopping
            analogWrite(LeftPlus, 0);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 0);
            analogWrite(RightMinus, 0);
            delay(1000);
          	//Driving Forward again
            analogWrite(LeftPlus, 255);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 255);
            analogWrite(RightMinus, 0);
            delay(1000);
        }
        //Turn to left wall
        if (servoLeft >= servoFront && servoLeft >= servoRight && servoRight != 0 || servoLeft == 0)
        {
          	//Turning Left (on the spot)
        	analogWrite(LeftPlus, 0);
            analogWrite(LeftMinus, 55);
            analogWrite(RightPlus, 55);
            analogWrite(RightMinus, 0);
            delay(1000);
          	//Stopping
            analogWrite(LeftPlus, 0);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 0);
            analogWrite(RightMinus, 0);
            delay(1000);
          	//Driving Forward Again
            analogWrite(LeftPlus, 130);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 130);
            analogWrite(RightMinus, 0);
        }
        //Turn to right wall
        if (servoRight >= servoFront && servoRight >= servoLeft && servoLeft != 0 || servoRight == 0)
        {
          	//Turning Right (on the spot)
        	analogWrite(LeftPlus, 55);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 0);
            analogWrite(RightMinus, 55);
            delay(1000);
          	//Stopping
            analogWrite(LeftPlus, 0);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 0);
            analogWrite(RightMinus, 0);
            delay(1000);
          	//Driving Forward Again
            analogWrite(LeftPlus, 130);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 130);
            analogWrite(RightMinus, 0);
		}
        //Continue forward
        if (servoFront >= servoLeft && servoFront >= servoRight)
        {
          	//Driving Forward
        	analogWrite(LeftPlus, 130);
            analogWrite(LeftMinus, 0);
            analogWrite(RightPlus, 130);
            analogWrite(RightMinus, 0);
          	delay(1000);
        }
     }
  }
}

Unity

Nu tænder vi for Unity. Det første der skal gøres når Unity er åbent, er at importere den udleverede Unity.package så vi har et udgangspunkt at starte fra. Den bane vi gerne vil have RoboSchneider til at køre igennem kan ses på billedet nedenfor.

Vi starter med at trække “Robot root” ind i scenen: “PF2-afleveringsbane”. På Robot root sætter vi Wheels, Breadboard, Servo og Linesensor, som “child” til “CarChassis”. Her kan man lege lidt rundt med hvordan ens Robot skal se ud. Vi har valgt at lade Robotten se ud som på billedet nedenfor.  

Her er det vigtigt at huske at gøre følgende:  

  •  Inde i Left Motor, nede i komponentet “Configurable Joint” skal CarChassis sættes ind i “Connected Body”. Ellers vil hjulene ikke følge med Robotten. Gør det samme for Right Motor.  
  • På Breadboard skal H-bridge LeftMotor og H-bridge RightMotor sættes som “child” af Breadboard. Herefter skal Left Motor sættes ind i Komponentet H Bridge (Script) for at den har en reference. Det samme skal gøre for RightMotor (med Right Motor i stedet for Left Motor selvfølgelig) 
  • På Breadboard skal der tilføjes 7 elementer i komponentet Breadboard(Script). (Man ændrer størrelsen af Arduino Object Pins ved at sætte “Size” til den ønskede størrelse). Indsæt H-bridge forwards pin LeftMotor til Element 0. Indsæt H-bridge backwards pin LeftMotor til Element 1. Indsæt H-bridge forwards pin RightMotor til Element 2. Indsæt H-bridge backwards pin RightMotor til Element 3. Indsæt DistanceSensor til Element 4. Indsæt LineSensorLeft til Element 5 samt LineSensorRight til Element 6. Disse 7 Elementer er referencer for Breadboardet, og virker som “pins” i virkeligheden.  

En kort bemærkning til placeringen af ens LDR-modstande samt hjul. Hvis sensorerne er placeret for langt fra hinanden, kan der opstå en uhensigtsmæssig adfærd når den forsøger at rette sin kurs i forhold til den sorte streg. Den vil altså lave store og lange sving hver gang den justerer. Ligeledes hvis de er for tæt på hinanden, så kan sensorerne have svært ved at vurdere som konstruktionen er på stregen eller uden for den, og i værste fald, forårsage at maskinen hele tiden hakker frem og tilbage eller fuldstændig forbiser når stregen svinger. Det anbefales at afprøve og justere i ens eget projekt mht. skalering og position af sensorerne samt hjul. Dertil kan det også anbefales at hjulene sættes foran på robotten, da dette giver en bedre intuitiv styring under opsætningen. I det endelige produkt vil placeringen af hjul og sensorer være afgørende for robottens navigations evne, og dermed hvor nøjagtigt robotten kan følge stregen i gulvet. 

Nu er Robotten bygget, så nu skal der skrives kode, så RoboSchneider bliver funktionel. Man kan med fordel tage koden fra TinkerCAD og bruge som skabelon i Unity. Desværre er kodesproget ikke det samme for Arduino, som det er for Unity, så vi skal skrive noget af koden om, samt tilføje mere kode. Vi vil i dette afsnit ikke gå for meget i dybden med kodeeksempler, men blot redegøre for nogle af de justeringer der skal til for at ”oversætte” koden til Unity. Den fulde kode kan ses forneden. Gå ind i ArduinoMain.cs. 

I Unity (C#) skal man ikke Declare (erklære) de forskellige pinModes, da vi har referencerne liggende ude i selve Editoren. #include <Servo.h> skal ikke indgå i C# koden, derfor heller ikke “Servo theServo; og theServo.attach(pin);”. Måden vi refererer til Servo’en i Unity er gennem Editoren hvor Servo’en bliver sat ind som en public Servo servo; i ArduinoMain.cs scriptet. Desuden skal “long” ændres til “ulong”. Serial virker heller ikke i C#, derfor bruger vi Debug.Log();. Dette er et meget nyttigt værktøj, som vi benyttede til at udradere ikke nævnelsesværdige fejl med, og det kan eksempelvis bruges til at opdele sin kode og se hvor eventuelle problemer opstår når man kører programmet. 

Den første del der skal tilføjes i C# koden, er en bool samt en “if” statement til at fortælle os hvornår Robotten kommer til den første forhindring. Dette uddyber vi længere nede. Til sidst skal alle “delay()” ændres til “yield return new WaitForSeconds(); da “delay()” funktionen ikke er en del af C# dokumentationen. 

Ideelt set ville vi gerne have RoboSchneider til selv at navigere rundt om denne forhindring ved hjælp af afstandsmåleren, da vi ikke længere havde en streg at køre efter udenom forhindringen, men dette voldte os store problemer og uregelmæssig adfærd, hvor robotten ikke kunne finde tilbage på rette kurs igen. Vi var derfor nødsaget til at navigere den manuelt ved at “hardcode” bevægelsen rundt om forhindringen før vi fortsætte resten af programmet til at navigere via stregen og derefter afstandsmåleren. Koden til den første forhindring er resultatet af en række afprøvelser i forhold til vores opbygning og valg af hastigheder for motorerne. 

else if (firstBlock == true)
                    {
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 0);
                        analogWrite(1, 55);
                        analogWrite(2, 55);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(1.1f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 100);
                        analogWrite(1, 0);
                        analogWrite(2, 100);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.7f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 55);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 55);
                        yield return new WaitForSeconds(1f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 100);
                        analogWrite(1, 0);
                        analogWrite(2, 100);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(1.5f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 55);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 55);
                        yield return new WaitForSeconds(1f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 100);
                        analogWrite(1, 0);
                        analogWrite(2, 100);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.6f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 0);
                        analogWrite(1, 55);
                        analogWrite(2, 55);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(1f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                    }

Det er ikke en implementering vi anbefaler, men vores metode kan ses her foroven og længere nede i den komplette Unity kode. Da man kun støder på én forhindring i denne bane følte vi det var en acceptabel løsning i forhold til dette projekt. Hvis der var flere forhindringer i banen, så ville man være nødsaget til at lave en funktion i stedet for at skrive hele omkørslen hver gang RoboSchneider skulle forbi en forhindring.

Til sidst når vi anvender afstandsmåleren til at navigere den resterende del af banen, så er de værdi vi anvender til vores motor inputs for at dreje RoboSchneider igen noget vi har testet os frem til. Selve målingen og beregning af hvilken retning der giver fri passage er ”korrekt”, men når robotten drejer er det en delvist manuel styring i forhold til de værdier og hastigheder vi har angivet for vores motor inputs. Med lidt måling og finjustering skulle det gerne give robotten en næsten nøjagtig rotation hvis banen blev udvidet, men efter for mange retningsskift kan der potentielt opstå nogle udfordringer med robottens retning. 

Hvis koden er skrevet korrekt skulle den gerne ligne noget i stil med den kode der står herunder. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class ArduinoMain : MonoBehaviour
{
    public Breadboard breadboard;
    public Servo servo;
    
    //Setup what needs to be stated before the robot begins its Loop
    IEnumerator setup()
    {
        //Set Servo Motor on 90 degrees (look forward)
        servo.write(90);
        Debug.Log("GO");
        #region PremadeSetup
        yield return StartCoroutine(loop()); ;
        #endregion PremadeSetup
    }
    //Declare the needed variables
    int left = 0;
    int right = 0;
    int minDiff = 50;
    
    bool onLine = true;
    bool firstBlock = false;
    ulong dis = 900;
    ulong servoLeft = 0;
    ulong servoFront = 0;
    ulong servoRight = 0;
    //The Loop Begins
    IEnumerator loop()
    {
        //Read the LDR-Sensores
        left = analogRead(5);
        right = analogRead(6);
        int diff = Mathf.Abs(left - right);
        
        //Read the SonarSensor
        ulong tid = pulseIn(4);
        //Read the ServoMotor
        int servoPos = servo.read();
        
        //As long as we are on the groundline
        if (onLine == true)
        {
            //If the robot is turning right, it turns left slightly
            if (left < right)
            {
                analogWrite(0, 0);
                analogWrite(1, 0);
                analogWrite(2, 50);
                analogWrite(3, 0);
            }
            // Vice Versa
            else if (right < left)
            {
                analogWrite(0, 50);
                analogWrite(1, 0);
                analogWrite(2, 0);
                analogWrite(3, 0);
            }
            // If the robot follows the line fine:
            else if (diff <= minDiff)
            {
                //it goes forward
                if (tid >= dis || tid == 0)
                {
                    analogWrite(0, 100);
                    analogWrite(1, 0);
                    analogWrite(2, 100);
                    analogWrite(3, 0);
                }
                // else it hits an obsticle
                else if (tid <= dis)
                {
                    //There is a fail in the program that makes it delay just enough for the robot to bug on start-up. This "if" prevents it from stopping
                    if (firstBlock == false)
                    {
                        firstBlock = true;
                        Debug.Log("FirstBlockPassed");
                    }
                    //After the bug, the robot avoits absticles but driving around them as shown below.
                    else if (firstBlock == true)
                    {
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 0);
                        analogWrite(1, 55);
                        analogWrite(2, 55);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(1.1f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 100);
                        analogWrite(1, 0);
                        analogWrite(2, 100);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.7f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 55);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 55);
                        yield return new WaitForSeconds(1f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 100);
                        analogWrite(1, 0);
                        analogWrite(2, 100);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(1.5f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 55);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 55);
                        yield return new WaitForSeconds(1f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 100);
                        analogWrite(1, 0);
                        analogWrite(2, 100);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.6f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                        analogWrite(0, 0);
                        analogWrite(1, 55);
                        analogWrite(2, 55);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(1f);
                        analogWrite(0, 0);
                        analogWrite(1, 0);
                        analogWrite(2, 0);
                        analogWrite(3, 0);
                        yield return new WaitForSeconds(0.5f);
                    }
                }
            }
            //When the robot hits the ending of the ground line it stops and shifts over to "Off-Line" mode 
            if (left <= 1000 && right <= 1000)
            {
                analogWrite(0, 0);
                analogWrite(1, 0);
                analogWrite(2, 0);
                analogWrite(3, 0);
                onLine = false;
            }
        }
        //THIS IS OFF-LINE MODE
        //If the end of the groundline is reached
        if (onLine == false)
        {
            //Debug.log("Offline") to make it obvious for the spectators
            Debug.Log("OffLine");
            //if the SonarSensor signal is bigger than the chosen minimum distance it goes forward 
            if (tid >= dis)
            {
                analogWrite(0, 130);
                analogWrite(1, 0);
                analogWrite(2, 130);
                analogWrite(3, 0);
            }
            //if the SonarSensor signal is smaller than the chosen minimum distance it stops and makes a check 
            else if (tid <= dis)
            {
                analogWrite(0, 0);
                analogWrite(1, 0);
                analogWrite(2, 0);
                analogWrite(3, 0);
                yield return new WaitForSeconds(2);
                //The check consists of 4 steps
                //Step 1 check distance to nearest wall in front of robot
                servoFront = pulseIn(4);
                Debug.Log("Front Servo =" + servoFront);
                servo.write(0);
                yield return new WaitForSeconds(2);
                //Step 2 check distance to nearest wall left of robot
                servoLeft = pulseIn(4);
                Debug.Log("Left Servo =" + servoLeft);
                servo.write(180);
                yield return new WaitForSeconds(4);
                //Step 3 check distance to nearest wall right of robot
                servoRight = pulseIn(4);
                Debug.Log("Right Servo =" + servoRight);
                servo.write(90);
                yield return new WaitForSeconds(2);
                //And step 4
                //Compare the 3 checks and turn to the wall farest away.
                //if the wall at left and the wall at right is not existing, it means the end is reached. The robot will turn right and drive off the ground.
                if (servoLeft == 0 && servoRight == 0)
                {
                    Debug.Log("ENDGAME");
                    analogWrite(0, 55);
                    analogWrite(1, 0);
                    analogWrite(2, 0);
                    analogWrite(3, 55);
                    yield return new WaitForSeconds(1f);
                    analogWrite(0, 0);
                    analogWrite(1, 0);
                    analogWrite(2, 0);
                    analogWrite(3, 0);
                    yield return new WaitForSeconds(1f);
                    analogWrite(0, 255);
                    analogWrite(1, 0);
                    analogWrite(2, 255);
                    analogWrite(3, 0);
                    yield return new WaitForSeconds(10f);
                }
                //Turn to left wall
                if (servoLeft >= servoFront && servoLeft >= servoRight && servoRight != 0 || servoLeft == 0)
                {
                    analogWrite(0, 0);
                    analogWrite(1, 55);
                    analogWrite(2, 55);
                    analogWrite(3, 0);
                    yield return new WaitForSeconds(1f);
                    analogWrite(0, 0);
                    analogWrite(1, 0);
                    analogWrite(2, 0);
                    analogWrite(3, 0);
                    yield return new WaitForSeconds(1f);
                    analogWrite(0, 130);
                    analogWrite(1, 0);
                    analogWrite(2, 130);
                    analogWrite(3, 0);
                }
                //Turn to right wall
                if (servoRight >= servoFront && servoRight >= servoLeft && servoLeft != 0 || servoRight == 0)
                {
                    analogWrite(0, 55);
                    analogWrite(1, 0);
                    analogWrite(2, 0);
                    analogWrite(3, 55);
                    yield return new WaitForSeconds(1f);
                    analogWrite(0, 0);
                    analogWrite(1, 0);
                    analogWrite(2, 0);
                    analogWrite(3, 0);
                    yield return new WaitForSeconds(1f);
                    analogWrite(0, 130);
                    analogWrite(1, 0);
                    analogWrite(2, 130);
                    analogWrite(3, 0);
                }
                //Continue forward
                if (servoFront >= servoLeft && servoFront >= servoRight)
                {
                    analogWrite(0, 130);
                    analogWrite(1, 0);
                    analogWrite(2, 130);
                    analogWrite(3, 0);
                }
            }
        }
        
        #region DoNotDelete
        //Wait for one frame
        yield return new WaitForSeconds(0);
        //New loop():
        yield return loop();
        #endregion DoNotDelete 
    }
    
    #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
}

Konklusion 

RoboSchneider fuldfører den opstillede bane på omtrent 87 sekunder. Den navigerer rundt ved hjælp af en række sensorer, som var påkrævet af opgavebeskrivelsen, nemlig LDR-modstande samt ultralydafstandsmåler. Den er i stand til at nå banens ende og undgå al kollision undervejs. 

Perspektivering  

RoboSchneider er ikke i stand til at fuldføre banen fuldstændig på egen hånd. Med dette menes, at vi var nødsaget til at implementere en lille smule “hard coding” til at navigere rundt om den første forhindring, hvilket vi også belyser tidligere i indlægget. Ydermere har RoboSchneider ikke nødvendigvis den hurtigste tid, men den formår at opnå en sikker og stabil gennemførelse af banen. Med lidt mere justering og testing kan denne tid forbedres, men vi har valgt at beholde den i dens nuværende tilstand med en solid performance for opgavens og forståelsens skyld. 

Afslutningsvist nævner vi, at delay() funktionen ikke virker i C#. Da dette teknisk set er sandt, så er vi opmærksomme på, at Jamie og Christian var med til at udarbejde en IEnumerator “delay” funktion, men da vi havde problemer med at implementere den, så var vi nødsaget til at bruge WaitForSeconds() i stedet. 

More

Roomba’s Overlegen

Demonstration af Roomba’s Overlegen i Unity
Figur 1: Opbygning af Roomba’s overlegen på Tinkercad

Af Kim Dinh Le & Osman Bilal Khassouk

Introduktion

Dette blogindlæg er et forsøg på at lave en robot der kan gennemføre et specifikt forhindringsbane. Det vil siges at den ikke er beregnet til at kunne gennemføre en anden bane med tilhørende elementer. Komponenter der bliver brugt er følgende:

  • Arduino Uno R3
  • 1 H-Bridge L293D
  • 9 V batteri
  • 2 Hobby Gearmotor
  • 2 Photoresistor
  • 1 Mirco Servo
  • 1 Ultrasonic Distance Sensor
  • 1 220 Ohm resistor
  • 4 Red LED
  • 2 10k Ohm resistor

Målet med denne robot er at kunne gennemføre denne bane uden at rører væggene, og komme igennem med den hurtigeste tid.

L293D i sammenspil med Arduino og DC motor.

H-Bridge Motor Driver bruges som en central komponent i vores opbyggelse af Roomba. Komponenten benyttes som en dobbelt H-bro, som gennem sammenspil med vores Arduino Uno kontrollere de 2 DC Motor som er monteret på Roomba.

Sammenkoblingen mellem L293D og de 2 DC-motor styre Roombas, hvilken retning der skal køres. For at kunne bruge motorerne har vi koblet de positive og negative poler med vores L293Ds 4 outputs, de fire outputs i figuren for neden er 1Y, 2Y, 3Y, 4Y.

1,2 EN-stiften tænder eller slukker den ene motor der er tilkoblet h-broen, den skal sættes til HIGH for at motoren kan starte. 3,4 EN-stiften gør det samme ved den anden motor. (Se figur. 2)

Vcc1 giver strøm til L293Ds interne system. Hvor Vcc2 er den strømkilde motoren bruger. Derfor er 9V batteriet tilkoblet til Vcc 2 for at styrke de motore som bruges.

Figur 2: Datablad af vores L293D

1A, 2A, 3A, 4A er L293Ds fire inputs, de er alle koblet sammen til Arduinoens pins. De fire inputs er dem som afgør, hvilken handling Arduinoen styre de 2 DC motorers handling.

Kodeudsnit 1: Pins fra Arduinoen til L293D

LDR Modstande
Fotomodstandene som bruges til Roomba benyttes for at måle forskellen på jordoverfladen og kende forskel på hvid og sort. For at øge chance for at fotomodstandene kan opfange dette ville 4 LED’er implementeres for at belyse jorden under Roomba. Der skal dog implementeres en lille væg eller lignende mellem LED’erne og fotomodstandene så de ikke påvirker de værdier som opfanges.  De værdier som opfanges af fotomodstandene, bestemmer hvordan vores Roomba agere og hvilken handling den skal påtage sig.

Hvis man kigger på kodeudsnittet på kodeudsnit 2. Så bruger Roomba 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 den sorte linje.

Gennem LDR-modstandene som er sammenkoblet med vores Arduino registrer værdierne, dette gøres ved brug af analogRead(sensor1Value) og analogRead(sensor2Value). Arduino registrerer disse værdier og checker hvilken If statement er sand, dette bestemmer handling vores Roomba skal påtage sig.

sonarSensor og Servo-Motor

Vi benytter denne sensor til at måle afstand på robotten og de forhindringer som kommer foran den. Sensoren måler afstanden ved brug af en sonar. Der sendes en ultralyd impuls fra sensoren, og afstanden der skal måles bestemmes ved at måle den tid det tager for lydens ekko at komme tilbage, sonaren måler dette i meter per sekund.
Der er derfor en funktion er lavet som konvertere de mikrosekunder som Sonaren kommer med til cm, dette gøres for at give en bedre forståelse af afstanden mellem Sonaren og objektet.
Dette er i vores tilfælde ideelt for robotten i den sidste forhindring med væggene, hvor sensoren bruges for at robotten ikke skal køre ind i forhindringerne, men dreje i stedet.

Kodeudsnit 3: Målingens konvertering fra meter i sekunder til Cm

Sensorens kode fungerer på den måde at sonarSensor skifter mellem “LOW”, “HIGH” også “LOW”. Sonarsensore reagere på “HIGH” pulse, der tilføjet “LOW” i starten og sat en lille tid som afstand, og en “LOW” i slutningen. At sætte en lille pause mellem “LOW” og “HIGH” i starten kan resultere i en mere effektiv “HIGH” pulse.
Dette kan give en forbedring i registreringen af objekter. Efter denne process registrere den kun ”HIGH” værdien, hvorefter konverteringen til cm finder sted. Dette kan ses i kode afsnit 4.

Kode afsnit 4: Koden som styre vores sonarSensor

Servomotoren fungere ved at sende et servo et PWM-signal (pulsbredde-modulering). Der er en minimal puls, en maksimal puls og en gentagelsesfrekvens. En servomotor kan normalt kun dreje 90 ° i begge retninger i alt 180 ° bevægelse.

Servoen benyttes til at dreje vores Sonar sensor. Sonaren er sammenkoblet ovenpå for at kunne registrere de kommende forhindringer før tid, dette bruges i den sidste del af forhindringen hvor den servoen drejer, hvilket gør sonaren i stand til at registrere muren og følge den til mål.

Man kan i Kodeafsnittet 5 se hvordan vores servomotoren drejer, den begynder med at dreje 90 grader. Dette gøres for at sonarSensoren som er monteret i toppen, kan registrere om der befinder sig objekter ved Roombas højre side.

Efter delay på 1 sekund drejer vores Roomba endnu 90 grader til 180 grader for at kunne registrere om der befinder sig objekter bag den.

Kode afsnit 5: Servo motoren

Opbygning af Program

Figur 3: Forhindringsbanen set fra fugleperspektiv

Koden er skrevet op sådan så det er delt op i to forskellige måder. Hvis man ser på banen (figur 3). Så kan man se at den første del af banen er tegnet op med en sort streg. hvoraf den sidste del af banen er frit løb. Koden er sat op i små bidder, så det er nemmere overskueligt, og giver mulighed for at skifte stadier, efter hvorhenne Roomba’s overlegen er placeret. Den første del af banen er hvor den skal følge efter den sorte streg. Ved hjælp af fotomodstander, er det blevet muligt at kunne måle værdien af roomba’s overlegen kører på enten en hvid eller sort plan, og derved roterer sig alt efter hvad der er nødvendigt.

 void followLine()
    {
        sensorLeft = analogRead(4);
        sensorRight = analogRead(5);
        if (sensorLeft > 900 && sensorRight > 900)
        {
            driveForward();
        }
        else if (sensorLeft > 900 && sensorRight < 900)
        {
            //Debug.Log("turning Right");
            turnRight();
        }
        else if (sensorLeft < 900 && sensorRight > 900)
        {
            //Debug.Log("turning Left");
            turnLeft();
        }
        else if(sensorLeft < 900 && sensorRight < 900)
        {
            driveForward();
            linefollower = false;
        }
    }

Hvis man ser videre på figur 3, kan man se at der er en forhindring ved den første del af banen. Dette er en rød mur som robotten skal kører rundt om. Det er gjort muligt at kører rundt om ved brug af afstandssensoren, som registerer at den nærmer sig en mur. Derefter kører koden ind til næste stadie hvor den vælger at undgå den første mur.

 if(wallCounter == 1)
        {
            if(pulseIn(6) < 500 && pulseIn(6) > 1)
            {
                Debug.Log("something here");
                stopDriving();
                yield return delay(1000);
                turnBack();
                yield return delay(1000);
                turnLeft();
                yield return delay(1000);
                driveForward();
                yield return delay(2500);
                turnRight();
                yield return delay(1000);
                driveForward();
                yield return delay(2500);
                turnRight();
                yield return delay(700);
                driveForward();
                yield return delay(900);
                turnLeft();
                yield return delay(1000);
                wallCounter++;
                servoMode = true;
                Debug.Log(wallCounter.ToString());
            }

Når den så har gennemførst den første del af banen, sætter vi en af vores booleske værdier til falsk. Dette gøres ved at fotomodstandene ikke længere måler at der er en sort plan. Når den når hen til sin første mur, drejer roomba’s overlegen statisk 90 grader i den rigtige retning, og servomotoren drejer 150 grader så den peger til højre for sig. Derefter går den til wallMode, hvor den så følger langs muren. Dette gøres ved at måle afstanden mellem roomba’s overlegen og muren.

   void wallMode()
    {
        if (readDist() < 800 && readDist() > 400)
        {
            driveForward();
        }
        else if (readDist() > 800 || readDist() == 0)
        {
            analogWrite(0, 90);
            analogWrite(2, 45);
        }
        else if (readDist() < 400 && readDist() > 1)
        {
            analogWrite(0, 0);
            analogWrite(2, 120);
        }
    }

Mekanik

Roomba er opbygget på en måde hvorpå en realistisk version ville være ligetil, der er opsæt LED’er tæt på de LDR modstande vi har lagt sammen. Dette gøres for at illusterer, hvordan sammensætningen ville se ud, der vil dog være noget fysisk som blokere for LED’erne så de ikke har indflydelse på vores værdier

Elektronik

Elektronik delen blev hvor den alle komponenterne skulle sammenspille med hindanden og skabe et funktionelt kredsløb som vi kalder Roomba. Gennem elektronikken har vi forskellige signaler fra vores kompenenter i form a lyd, lys osv. som giver information i form af værdier såsom afstand fra forhindringer, og lys som kan hjælpe med at navigere os til at forblive på den sorte linje.

Der blev brugt modstande af 10k ohm resistorer til LDR-sensorerne i vores TinkerCad. Dette bliver brugt da i vores mållinger med fotomodstande har den en minimum modstand på 20K ohm og ved maksimale modstand have en modstand på 2 Mohm.

Ved brug af Ohms lov bruges det til at finde fotomodstanden ud fra lysintensiteten.

Vi starter med at udregne når modstanden er 20K ohm. Hvilket er hvor lyset er maksimalt.

Derefter regner vi for når den rammer den sorte streg og lyset er minimalt.

Herudover kan vi så se at den synlige rækkevidde er rundt regnet 1,65V (4,98V-3,33V). Det vil altså siges, da inputtet bliver læst gennem analogRead() funktionen, så vil værdierne ligge mellem 0 og 1023. Arduino kører på 5V så det betyder at 1 værdi vil svare til 0,005V. Med dette kan vi så udregne de mulige værdier ved brug af målbart lys.

Dette burde være en tilstrækkeligt rækkevidde. Der skal dog lægges mærke til at 10k ohm, ikke er nødvendigt, og der måske er mulighed for at have en højere eller lavere modstand, så længe den mulige rækkevidde af målt lys ikke er for højt eller for lavt.

Software

Software fungere som hjernen af Roomba’s overlegen, og har et tæt sammenspil med elektronikken. Da Roomba’s overlegen funktionaliteter, gøres brug af værdierene som komponenterne registerer. Foreksempel anvender softwaren, brug af fotomodstanende værdier til at kunne måle om den er i en sort eller hvidt plan, og derved kører efter det bestemte fart. Et andet eksempel ville være afstandssensoren, som måler om Roomba’s overlegen er ved at ramme en mur, og derved justere sine handlinger alt efter hvilke stadie den er nået til.

Konklusion

Roomba’s overlegen klarer banen på 100 sekunder, som også kan ses i videon. For at kunne klarer denne forhindring er der blevet brugt en kombination af mekanik, elektronik og software, som giver den de nøvendige funktionaliteter for at kunne gennemføre banen. Dog er dette en kombination af vores Unity projekt og Tinkercad, og derfor ikke svarer til en fysisk plan. Derfor vil dette være en mere reference til en fysisk model og ikke direkte.

Perspektivering

Roomba’s overlegen klarer banen, dog har den kun mulighed for at klare denne specifikke bane, da mange af de forhindringer der blev lagt op, blev løst ved hjælp af “hardcoding”. Dette er gjort, da vores kompetenceniveau ikke er god nok til at kunne lave en selvtænkede AI. Hvis noget skulle ændres ville det være at den løser forhindringer i en mere selvtænkende forstand.

Raw code i Tinkercad

#include <Servo.h>

//LDR-Resistor
int sensor1Pin = A0;
int sensor1Value = 0;

int sensor2Pin = A1;
int sensor2Value = 0;

int cm, duration;

//Servo
Servo servoMotor;

//Sonar
const int sonarSensor = 2;


void setup()
{
  Serial.begin(9600);
  
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  digitalWrite(12, HIGH); //Sæt denne pin til High for at enable motor 1
  

  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  digitalWrite(7, HIGH);// Sæt denne pin til High for at enable motor 2
  
  pinMode(sonarSensor, OUTPUT);

  servoMotor.attach(3);
}

void loop()
{
  sensor1Value = analogRead(sensor1Pin);
  Serial.println(sensor1Value);
  
  sensor2Value = analogRead(sensor2Pin);
  Serial.println(sensor2Value);

  
  
  //Test Servo
  servoMotor.write(90);
  delay(200);
  servoMotor.write(180);
  
  
if(sensor1Value > 850 && sensor2Value > 850)
  {
    digitalWrite(10,HIGH);
    digitalWrite(11,LOW);
    digitalWrite(5,HIGH);
    digitalWrite(6,LOW);
  }
  else if(sensor1Value > 850 && sensor2Value < 100)
  {
    digitalWrite(10,HIGH);
    digitalWrite(11,LOW);
    digitalWrite(5,HIGH);
    digitalWrite(6,HIGH);
  }
  else if(sensor1Value < 100 && sensor2Value > 850)
  {
    digitalWrite(10,HIGH);
    digitalWrite(11,HIGH);
    digitalWrite(5,HIGH);
    digitalWrite(6,LOW);
   }
  else if(sensor1Value < 100 && sensor2Value < 100)
  {
    digitalWrite(10,LOW);
    digitalWrite(11,HIGH);
    digitalWrite(5,LOW);
    digitalWrite(6,HIGH);
  }

   
  //Test Sonar
  digitalWrite(sonarSensor, LOW);
  delayMicroseconds(2);
  
  digitalWrite(sonarSensor, HIGH);
  delayMicroseconds(5);
  
  digitalWrite(sonarSensor, LOW);
  
  pinMode(sonarSensor, INPUT);
  
  duration = pulseIn(sonarSensor, HIGH);
  
  cm = mikrosekunderTilCm(duration);
  
  Serial.print(cm);
  Serial.print("cm");
  Serial.println();
  
  }
  
  int mikrosekunderTilCm(int mikrosekunder){
   return mikrosekunder / 29 / 2;
  
}

Raw code i Unity

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

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

    int sensorLeft;
    int sensorRight;
    int wallCounter = 1;
    bool linefollower = true;
    bool servoMode;

    //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:
        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:
        if (linefollower == true)
        {
            followLine();
        }
        if(wallCounter == 1)
        {
            if(pulseIn(6) < 500 && pulseIn(6) > 1)
            {
                Debug.Log("something here");
                stopDriving();
                yield return delay(1000);
                turnBack();
                yield return delay(1000);
                turnLeft();
                yield return delay(1000);
                driveForward();
                yield return delay(2500);
                turnRight();
                yield return delay(1000);
                driveForward();
                yield return delay(2500);
                turnRight();
                yield return delay(700);
                driveForward();
                yield return delay(900);
                turnLeft();
                yield return delay(1000);
                wallCounter++;
                servoMode = true;
                Debug.Log(wallCounter.ToString());
            }
        }

        else if(wallCounter > 1 && servoMode == true)
        {
            ulong read = readDist();
            if(read < 700 && read > 1)
            {
                Debug.Log("Wall Ahead, adjusting");
                turnLeft();
                yield return delay(1500);
                servo.write(150);
                yield return (1600);
                servoMode = false;
            } 
        }
        else if(servoMode != true)
        {
            wallMode();
        }

        //Debug.Log("Distance wall: " + pulseIn(6));


        //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 driveForward()
    {
        analogWrite(0, 60);
        analogWrite(2, 60);
    }
    

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

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

    void turnBack()
    {
        analogWrite(0, 0);
        analogWrite(2, 0);
        analogWrite(1, 30);
        analogWrite(3, 30);
    }

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

    void wallMode()
    {
        if (readDist() < 800 && readDist() > 400)
        {
            driveForward();
        }
        else if (readDist() > 800 || readDist() == 0)
        {
            analogWrite(0, 90);
            analogWrite(2, 45);
        }
        else if (readDist() < 400 && readDist() > 1)
        {
            analogWrite(0, 0);
            analogWrite(2, 120);
        }
    }

    public ulong readDist()
    {
        ulong read = breadboard.pulseIn(6);
        Debug.Log("Distance from wall: " + read);
        return read;
    }

    void followLine()
    {
        sensorLeft = analogRead(4);
        sensorRight = analogRead(5);
        if (sensorLeft > 900 && sensorRight > 900)
        {
            driveForward();
        }
        else if (sensorLeft > 900 && sensorRight < 900)
        {
            //Debug.Log("turning Right");
            turnRight();
        }
        else if (sensorLeft < 900 && sensorRight > 900)
        {
            //Debug.Log("turning Left");
            turnLeft();
        }
        else if(sensorLeft < 900 && sensorRight < 900)
        {
            driveForward();
            linefollower = false;
        }
    }




    #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
}
More