arduino programmeerboek Davy Wolfs -...

35
arduino programmeerboek - Davy Wolfs

Transcript of arduino programmeerboek Davy Wolfs -...

arduino programmeerboek - Davy Wolfs

arduino programmeerboek herwerkt door Davy Wolfs gebaseerd op: Arduino Programming Notebook door Brian W. Evans (2007) Arduino Programmeer Manual door A. Kompanje (2009) bijkomende informatie verkregen van: http://www.arduino.cc Uitgave: versie 1.0 - februari 2017

Dit werk valt onder de Creative Common licentie Attribution--ShareAlike 4.0 International (CC BY-SA 4.0) Een kopie van de licentie staat op: https://creativecommons.org/licenses/by-sa/4.0/

inhoud opbouw 1

void setup() 1

void loop() 2

functies 2

{ } accolades (gekrulde haakjes) 3

; puntkomma 4

// regelcommentaar 4

/* … */ blokcommentaar 4 variabelen 6

variabelen declareren 7

het bereik van een variabele 7 data types 9

byte 9

int 9

long 9

float 9

double 10

char 10

Overflow 10

arrays 10 wiskunde 12

wiskundige bewerkingen 12

samengestelde toekenning 13

vergelijkende bewerkingen 13

logische bewerkingen 14 constanten 15

true/false 15

input/output 15

high/low 15 controle elementen 16

if 16

if … else 17

switch … case 18

for 19

while 20

do … while 21 digitale I/O 22

pinMode(pin, mode) 22

digitalRead(pin) 22

digitalWrite(pin, waarde) 23 analoge I/O 24

analogRead(pin) 24

analogWrite(pin, waarde) 24 tijd 25

delay() 25

millis() 25 seriële communicatie 26

Serial.begin(baudrate) 26

Serial.println(data) 26

Serial.read() 27

Serial.available() 27

Serial.parseInt() 27 Hulpfuncties 28

map(…) 28

min(…) 28

max(…) 28

constrain(…) 29

voorwoord Dit boek dient als een eenvoudig en makkelijk te gebruiken referentie om een Arduino (Uno) microcontrollerbordje te programmeren. Om het eenvoudig te houden werden hier en daar wat complexere begrippen en commando’s weggelaten. Het resultaat is een beginnershandleiding en bijkomende informatie vindt men genoeg op het internet. We beginnen met de basis opbouw van een Sketch, de Arduino versie van een programma afgeleid van de programmeertaal C++. Daarna komen de basiselementen van de taal aanbod, geïllustreerd met voorbeelden. Mijn dank aan CVO De Verdieping, die het voor mij mogelijk maakte om het Arduino-gebeuren aan anderen te leren. Verder ook nog de cursisten die de tijd namen om alles eens na te lezen. Maar bovenal dank aan de community en de bedenkers van Arduino. Zonder de overvloed aan informatie van hun was dit zakboek niet mogelijk geweest. Het originele materiaal is terug te vinden op http://www.arduino/cc.

opbouw | 1

opbouw De basis van de Arduino programmeertaal is een Sketch. Een tekstversie op het scherm dat beschrijft wat de Arduino moet gaan doen. Een Sketch wordt eerst ‘gecompileerd’ naar een programma dat de Arduino microcontroller kan uitvoeren. De basis opbouw van een Sketch is vrij eenvoudig en bestaat steeds uit minstens twee delen. Deze twee delen, of beter functies, groeperen een aantal opdrachten (Engels: statements). void setup()

{

opdrachten;

}

void loop()

{

opdrachten;

}

Zowel setup() als loop() zijn verplichte functies, zonder zal een

Sketch niet compileren. De setup() functie doet het voorbereidende werk. Deze functie

wordt als eerste uitgevoerd, wordt slechts een keer uitgevoerd en wordt gebruikt om de pinMode en/of seriële communicatie in te stellen. De loop() functie volgt op setup() en bevat de code die de

Arduino continue (in een lus) moet uitvoeren. Hier worden ingangen uitgelezen, eventueel beslissingen genomen om vervolgens de uitgangen aan te sturen. De loop() functie is de motor van de

Sketch waarin al het werk gebeurt. void setup()

De setup functie wordt slechts één keer uitgevoerd, namelijk wanneer de Arduino opstart (of reset). setup() wordt gebruikt om de richting

(in- of uitgang) van pinnen in te stellen en/of om seriële communicatie te initialiseren. Deze functie moet in de Sketch aanwezig zijn, zelfs als er geen opdrachten in staan.

opbouw | 2

void setup()

{

pinMode(13, OUTPUT); // maak pin 13 een uitgang

}

void loop()

Nadat de setup functie eenmaal is uitgevoerd, volgt de loop functie. De loop functie voert alle opdrachten uit in een oneindige lus (Engels: loop). Hier worden de eventuele ingangen ingelezen, beslissingen genomen om tot slot de uitgangen aan te sturen. void loop()

{

digitalWrite(13, HIGH); // zet pin 13 aan

delay(1000); // wacht 1 seconde

digitalWrite(pin, LOW); // zet pin 13 uit

delay(1000); // wacht 1 seconde

}

functies

Een functie is een stukje code met een naam die een groep van opdrachten bevat. Deze opdrachten worden uitgevoerd telkens de functie wordt aangeroepen. De functies void setup() en void

loop() zijn reeds besproken en andere Arduino specifieke functies

komen later aan bod. In een Sketch kan men ook zelf geschreven functies toevoegen om opdrachten, die steeds in dezelfde volgorde en meerdere keren in de loop() functie moeten worden uitgevoerd, te groeperen. Zo blijft de Sketch overzichtelijk en moeten eventuele wijzingen maar op één plaats in de code worden gedaan. Om een functie te kunnen gebruiken, moet je hem onderaan in de Sketch eerst ‘declaren’. type functieNaam (parameters)

{

opdrachten;

}

opbouw | 3

Eerst wordt het type van de functie benoemd. Dit is het type van de waarde die die functie teruggeeft zoals int voor een geheel getal

(Engels: integer). Als er geen waarde wordt teruggegeven, zoals bij

de functies setup() en loop(), is de functie van het type ‘void’. Na

het type declareer je de naam van de functie. Tot slot volgen, tussen ronde haakjes, de parameters die je aan de functie wilt meegeven. Een voorbeeld: We lezen een analoge ingang in maar willen de waarde van de ingang (tussen 0 en 1023) herleiden tot een waarde tussen 0 en 255. Hiervoor moeten we de analoge waarde delen door 4. int delayVal()

{

int v; // maak een tijdelijke variabele 'v'

v = analogRead(A0); // lees de analoge ingang

// ‘A0’ en plaats de waarde

// in ‘v’

v /= 4; // converteer van 0-1023 naar 0-255

return v; // geef de aangepaste waarde terug

}

Als eerste wordt een lokale variabele ‘v’ gedeclareerd. Daarna lezen we de analoge ingang op pin ‘A0’ in en kennen we de waarde (tussen 0 en 1023) toe aan de variabele ‘v’. Vervolgens delen we de variabele ‘v’ door 4 om een definitieve waarde tussen 0 en 255 te bekomen en kennen we de waarde opnieuw toe aan ‘v’. De opdracht v/=4; is een

verkorte vorm voor v=v/4;. Tot slot gebruiken we de ‘return’ opdracht om de waarde van ‘v’ terug te geven aan het hoofdprogramma. { } accolades (gekrulde haakjes)

Accolades geven het begin ({) en einde (}) aan van functies declaraties (setup() en loop()) of opdracht blokken zoals bij if en

for.

type functieNaam (parameters)

{

opdrachten;

}

Iedere beginnende accolade { moet altijd gesloten worden door een eindigende accolade }. Het totaal aantal accolades is dus steeds een

opbouw | 4

even getal en er zijn steeds evenveel { als } accolades. Indien dit niet het geval is, compileert het programma niet en is het soms moeilijk te achterhalen waar er juist een accolade ontbreekt. ; puntkomma

Iedere opdracht moet met een puntkomma worden afgesloten. De puntkomma wordt ook gebruik om de verschillende elementen in de

for lus van elkaar de scheiden.

int som = 0; // opdracht: declareer een ‘integer’

// variabele som met waarde 0

for (int i = 0; i < 10; i++) // scheid de elementen

{

som /= i; // opdracht: tel i op bij de som

}

Als een puntkomma ontbreekt op het einde van een opdracht, compileert het programma (meestal) niet. De fout die de compiler toont staat echter niet noodzakelijk op de regel van de ontbrekende puntkomma. // regelcommentaar

Regelcommentaar begint met de dubbele slash // en eindigt automatisch op het einde van de regel. De compiler negeert commentaar en commentaar neemt geen programmageheugen in beslag. // Dit is een regel met commentaar

Regelcommentaar wordt vaak gebruikt achter een opdracht om bijkomende informatie te verschaffen. Dit kan een woordje uitleg van de opdracht zijn of een herinnering dat er nog iets moet aangepast worden. /* … */ blokcommentaar

Blokcommentaar, of commentaar over meerdere regels, zijn stukjes tekst die niet worden uitgevoerd door het programma en die uitgebreide informatie verschaffen. Blokcommentaar begint met /* en wordt afgesloten met */, waarbij het over meerdere regels kan verspreid zijn.

opbouw | 5

/* Dit is een stukje blokcommentaar

dat over meerdere regels verspreid staat.

Vergeet niet de blok ook terug te sluiten

*/

Blokcommentaar wordt gebruikt om een ‘header’ te voorzien bovenaan de code met daarin uitleg over de Sketch. Blokcommentaar wordt ook gebruikt om stukjes code ‘uit te commentariëren’ als men de code wilt testen of fouten opsporen. Het is perfect mogelijk om regelcommentaar te gebruiken in blokcommentaar. Een tweede blok commentaar starten in een eerste gaat echter niet. /* Blokcommentaar met

//een regel regelcommentaar

- dit gaat –

*/

/* Blokcommentaar met

/* nog een stukje blokcommentaar

*/

- werkt niet: deze en de volgende regels staan

niet meer in commentaar

*/

Het is daarom aangeraden om in de code enkel regelcommentaar te gebruiken voor bijkomende informatie en blokcommentaar om stukken code uit te commentariëren.

variabelen | 6

variabelen Een variabele is een manier om een waarde (bv. een getal of een karakter) te bewaren voor later gebruik in het programma. Zoals de naam variabele al aangeeft, kan de waarde van een variabele regelmatig veranderen. Dit in tegenstelling tot constanten, wiens waarde nooit kan veranderen. Een variabele moet gedeclareerd worden voordat hij gebruikt kan worden. Optioneel kan je bij de declaratie ook al een waarde toekennen. De onderstaande code declareert een variabele met als naam inputWaarde met als initiële waarde 0. Daarna wordt er de waarde van de analoge input pin 2 aan toegekend. int inputWaarde = 0; // declareer een

// variabele en ken

// de waarde toe

inputWaarde = analogRead(A2); // geef de variabele

// de waarde van

// analoge pin 2

‘inputWaarde’ is de variabele. De eerste lijn declareert hem als type int (integer), een geheel getal. De tweede opdracht kent de waarde

van analoge pin 2 toe aan de variabele. Nu kan deze waarde overal in de code gebruikt worden. Eenmaal een variabele (opnieuw) een waarde heeft gekregen, kan men testen of deze waarde voldoet aan bepaalde condities. Men kan de variabele natuurlijk ook rechtstreeks gebruiken. Onderstaand voorbeeld toont drie mogelijkheden om een variabele nuttig te gebruiken. De code test of de waarde van de variabele inputWaarde kleiner is dan 100 en indien zo, zet de waarde op 100. Daarna wordt een delay() gebruikt met deze variabele, waarbij de waarde nu 100

is. if (inputWaarde < 100) // test of kleiner dan 100

{

inputWaarde =100; // indien waar: zet op 100

}

delay(inputWaarde); // gebruik de variabele als

// vertraging

Variabelen krijgen best omschrijvende namen, waardoor de code leesbaarder wordt. Variabelen zoals drukKnop en lichtSensor zorgen ervoor dat men beter begrijpt waarvoor de variabele dient. Variabelen

variabelen | 7

zoals var en waarde zeggen weinig of niks. Een variabele kan eender welke naam krijgen, zolang het maar geen ‘keyword’ van de Arduino programmeertaal is. variabelen declareren

Iedere variabele moet gedeclareerd worden voordat hij gebruikt kan worden. Declareren betekent dat we een type, zoals int, long,

float, etc… definiëren, een naam specifiëren en optioneel een

initiële waarde toekennen. De declaratie moet (en kan) slechts een keer gebeuren in de Sketch, maar de waarde van de variabele kan eender wanneer wijzigen. Onderstaand voorbeeld declareert inputWaarde als type int, of geheel getal, en geeft hem als initiële warde nul. int inputWaarde = 0;

Een variabele kan men op verschillende plaatsen in de Sketch declareren. De plaats waar men dit doet bepaald in welk deel van de Sketch de variabele gebruikt kan worden. het bereik van een variabele

De scope, of bereik, van een variabele vertelt waar in de Sketch de variabele gebruikt kan worden. Variabelen worden meestal gedeclareerd:

in het begin van de Sketch, voor setup(),

lokaal in een functie en

soms in een code blok zoals bijvoorbeeld de for() lus.

De plaats waar men dit doet bepaalt in welk deel van de Sketch de variabele gebruikt kan worden. Een globale variabele is een variabele die overal gebruikt kan worden: in iedere functie en opdracht van de Sketch. Deze variabelen worden in het begin van de Sketch gedeclareerd, voor de setup()

functie. Een lokale variabele is een variabele die gedeclareerd wordt in een functie (dus tussen de accolades ‘{‘ en ‘}’). Deze variabelen zijn enkel zichtbaar en bruikbaar in de functie waarin ze gedeclareerd zijn. Hierdoor is het mogelijk dat je twee of zelfs meer variabelen hebt met dezelfde naam maar met mogelijks verschillende waarde. De enige voorwaarde is dat geen enkele van deze variabelen globaal is en dat ze elk afzonderlijk in hun eigen bereik staan. Door een variabele

variabelen | 8

lokaal te declareren, zorg je ervoor dat enkel in dat stukje code de variabele kan gebruikt worden, wat de leesbaarheid verhoogd en de kansen op fouten verkleint. Het volgend voorbeeld toont hoe je de globale en lokale variabelen kunt declareren en waar ze zichtbaar zijn. int waarde; // ‘waarde’ is zichtbaar

// in iedere functie

void setup()

{

char karakter; // ‘karakter’ is enkel

// zichtbaar in setup()

}

void loop()

{

for(int i=0; i<20;) // ’i’ is enkel zichtbaar

{ // in de for loop

i++;

}

float f; // ‘f’ is enkel zichtbaar in

} // loop(), maar niet in de

// for lus

data types | 9

data types De data types die gebruik worden voor een variabele of constante binnen een Sketch bepalen niet enkel welke waarden we er kunnen insteken, maar ook hoeveel geheugen ze innemen. byte

Een byte is een 8-bit geheel getal (zonder komma). Ze hebben een bereik van 0 tot 255.

byte waarde = 180; // declareer de variabele

//‘waarde’ met als type byte

int

Integer (of gehele getal) is het basis type zonder decimalen bij Arduino. Ze zijn 16-bit groot en hebben een bereik van -32.768 tot 32.767. int waarde = 1500; // declareer de variabele

//‘waarde’ met als type int

long

Een long is een ‘langere’ int en wordt ook gebruikt voor gehele getallen. Ze zijn 32-bit groot en hebben een bereik van -2.147.483.648 tot 2.147.483.647. long waarde = 90000; // declareer de variabele

//‘waarde’ met als type long

float

Een float is een datatype voor rationele of komma getallen met

decimalen. Ze zijn 32-bit groot en hebben een bereik van -3,402823 10

38 tot 3,4028235 10

38. Hun bereik is veel groter dan een int, maar

ze hebben hierdoor wel afrondingsfouten. Hierdoor kan men vreemde resultaten krijgen als men ze vergelijkt. Het vraagt ook meer tijd om met een float te rekenen.

float waarde = 3.14; // declareer de variabele

//‘waarde’ met als type float

data types | 10

double

Een double is nauwkeuriger dan een float. Ze zijn 64-bit groot en

hebben hetzelfde bereik als een float. De extra bits worden

gebruikt om een kleinere afrondingsfout te verkrijgen. Door de verdubbeling van het aantal bits, is de rekentijd nog groter. double waarde = 6.5; // declareer de variabele

//‘waarde’ met als type double

char

Een char is een type dat bedoeld is om karakters in op te slaan. Het type is 8-bit groot en breed genoeg om alle karakters uit de extended ASCII-tabel in op te slaan. char karakter = ‘A’; // declareer de variabele

//‘karakter’ met als type char

Overflow

In C en C++ kan bij de types byte, int en long een overflow optreden. Dit gebeurt zodra we maximum (of minimum) waarde van het bereik overschrijden. Bijvoorbeeld: de integer x = 32767. Vervolgens wordt hier 1 bij op geteld, x= x + 1. x zal dan niet de waarde 32768 hebben, maar er treedt een overflow op waardoor de waarde van x gelijk is aan -32768. arrays

Een array (of reeks) is een verzameling van waarden met hetzelfde type die we kunnen benaderen met een indexnummer of kortweg index. We kunnen eender welke waarde uit de array bereiken door middel van de naam van de array en de index van die gewenste waarde. Een array heeft een index met als basis 0. Dit wil zeggen dat het eerste element in de array de index 0 heeft. Een array moet eerst gedeclareerd worden met de gewenste grootte voordat je hem kunt gebruiken. Eens gedeclareerd, kan je de array vullen met de gewenste waarde op de gewenste index. int mijnArray[5]; // declareer een array van

// integers met 5 plaatsen

mijnArray[3] = 10; // plaats 4 krijgt als waarde 10

data types | 11

Om een waarde uit de array te bekomen, gebruik je de array naam en index om de waarde aan een variabele toe te kennen. x = mijnArray[3]; // x heeft nu als waarde 10

Optioneel kan je ook al waarden toekennen tijden de declaratie. Bij het toekennen van waarden tijdens de declaratie is het niet verplicht om de grootte op te geven: de array is even groot als het aantal toegekende waarden. int haarArray[] = {1, waardeA, 8, waardeB};

// haarArray heeft 4 plaatsen (van 0 tot 3)

Arrays worden vaak gebruikt in lussen, waarbij de teller van de lus gebruikt wordt als index van de array. In het volgend voorbeeld gebruiken we een array om een LED te laten flikkeren zoals een kaars. Met behulp van een for-lus, die begint bij waarde 0, schrijven we verschillende waarde naar een PWM-uitgang waaraan de LED hangt. Hierdoor flikkert de intensiteit van de LED. const int PIN_LED = 10; // de LED hangt op een PWM

// uitgang

byte flikker[] = {180, 30, 255, 200, 10,

90,150, 60};

// een array met 8 verschillende waarden om het

// flikkeren van een kaars na te bootsen

void setup()

{

pinMode(PIN_LED, OUTPUT);

}

void loop()

{

for(int i=0, i<8; i++)// Een lus over al de

{ // waarde van array flikker

analogWrite(PIN_LED, flikker[i]); // zet PWM

delay(200); // wacht even

}

}

wiskunde | 12

wiskunde wiskundige bewerkingen

Wiskundige bewerkingen, zoals optellen, aftrekken, vermenigvuldigen en delen geven respectievelijk de som, het verschil, het product of het quotiënt terug van 2 getallen. y = y + 3;

x = x – 7;

i = j * 6;

r = r / 5;

De wiskundige bewerking gebruikt het data type van de getallen. Met andere woorden 9 delen door 4 resulteert in 2 en niet 2,25 aangezien 9 en 4 beide gehele getallen (integers) zijn. Gehele getallen hebben nu eenmaal geen decimalen. Dit betekend ook dat we een overflow kunnen krijgen als het resultaat groter is dan de maximale waarde die we aan een bepaald type kunnen toekennen. Als de twee getallen van een verschillend type zijn, wordt het ‘grotere’ type gebruikt voor de bewerking. Bijvoorbeeld: als één van de getallen van het type float is en de andere van het type integer,

is het resultaat ook een float. 9.0 delen door 4 resulteert dus wel in

2,25 (net zoals 9 delen door 4.0 of 9.0 delen door 4.0). Gebruik dus steeds een type dat groot genoeg is om het resultaat in te stockeren anders treedt er een overflow op. Overdimensioneer wel niet alle variabelen. ‘Grote’ variabelen nemen meer plaats in het geheugen in en de berekeningen gebeuren trager. Je kan het type van een getal tijdelijk veranderen door het te ‘casten’ naar een ander type. Dit doe je door het gewenste type tussen ronde haakjes vlak voor het getal te zetten. Bijvoorbeeld, (float)9 / 4;

resulteert in 2.25 omdat de geheel getal 9 eventjes wordt omgezet in een kommagetal 9.0 voor de deling.

wiskunde | 13

samengestelde toekenning

Een samengestelde toekenning (Engels: compound assignment), combineert een wiskundige bewerking met een variabele toekenning. Een samengestelde toekenning wordt vooral gebruikt in lussen. De meest voorkomende samengestelde toekenningen zijn: x++ // x = x + 1

x-- // x = x – 1

x += y // x = x + y

x -= y // x = x – y

x *= y // x = x * y

x /= y // x = x / y

Stel x = 5, dan is na x *= 3 de waarde van x gelijk aan 15, of drie keer de waarde die x had. vergelijkende bewerkingen

Het vergelijken van de waarden van een variabele met de waarde van een andere variabele of constante wordt vaak gebruikt bij een if-

statement om te kijken of een bepaalde conditie waar (true) is.

x == y // test of x gelijk is aan y

x != y // test of x niet gelijk is aan y

x < y // test of x kleiner is dan y

x > y // test of x groter is dan y

x <= y // test of x kleiner of gelijk is aan y

x >= y // test of x groter of gelijk is aan y

Merk op dat voor data types met decimalen (float en double) de

test om te zien of iets gelijk is, niet zinvol is omwille van de

afrondingsfouten. Ook de vergelijkingen x <= y en x >= y herwerk

je beter in de overeenkomstige alternatieve vorm y > x en y < x

respectievelijk.

wiskunde | 14

logische bewerkingen

Logische bewerkingen worden meestal gebruikt om twee (of meer) resultaten van vergelijkingen met elkaar te combineren. Het resultaat

is opnieuw true of false. Er zijn drie logische bewerkingen, die

vaak gebruikt worden bij de if-statement. Logische EN (AND) if ( x > 0 && x < 5) // waar (true) indien x groter

// dan 0 en x kleiner dan 5

if ( x > 0 || y > 0) // waar (true) indien x groter

// dan 0 of y groter dan 0

if ( !x > 0 ) // waar (true) indien x niet

// groter dan 0.

Er bestaat ook een alternatieve schrijfwijze, in leesbaar Engels, voor de drie logische operatoren && (and), || (or) en ! (not). Deze kunnen ook beruik worden in de code. if ( x > 0 and x < 5) // alternatief voor &&

if ( x > 0 or y > 0) // alternatief voor ||

if ( not x > 0 ) // alternatief voor !

constanten | 15

constanten De Arduino programmeertaal heeft enkele voorgedefinieerde constanten. Ze worden gebruikt om de code leesbaarder te maken en hebben geen invloed op de grootte of snelheid van de Sketch. We kunnen deze constanten onderverdelen in verschillende groepen. true/false

Dit zijn Booleaanse constanten die overeenkomen met een logische toestand. false is gedefinieerd als 0 terwijl true vaak als 1 wordt

gedefinieerd. Eender welke waarde behalve nul komt in aanmerking voor true.

if ( b == true)

{

doeIets();

}

input/output

Deze constanten worden gebruikt samen met de pinMode() functie

en bepalen of een digitale pin een INPUT (ingang) of OUTPUT

(uitgang) is. pinMode(13, OUTPUT);

high/low

Deze constanten definiëren de niveaus voor de in- en outputpinnen als HIGH of LOW. HIGH of hoog komt overeen met een waarde van 1,

‘aan’ of een spanning van 5 volt aan de pin. LOW of laag komt overeen

met een waarde van 0, ‘uit’ of een spanning van 0 volt. digitalWrite(13, HIGH); // zet pin 13 aan (5V)

controle elementen | 16

controle elementen Soms moet er in een Sketch enkel iets uitgevoerd worden als aan bepaalde voorwaarden voldaan wordt. In andere gevallen moeten we een stukje code een aantal keer uitvoeren. Hiervoor kunnen we controle elementen gebruiken. if

if vergelijkt ‘als’ een bepaalde voorwaarde bereikt is en indien dit zo

is, voert het de opdrachten tussen de gekrulde haakjes uit. Indien de voorwaarde niet waar is, worden de opdrachten tussen de gekrulde haakjes genegeerd. if (eenVariabele ?? eenWaarde)

{

doIets();

}

De variabele kan eender welk type hebben en de waarde kan een variabele of constante zijn.

In onderstaand voorbeeld vergelijken we of de analogeWaarde

groter is dan de grensWaarde. Als de vergelijking tussen de ronde

haakjes waar is, dan wordt de opdracht doIets() uitgevoerd.

if (analogeWaarde > grensWaarde)

{

doIets();

}

Let op

Een veelvoorkomende vergissing is per ongeluk ‘=’ in plaats van ‘==’ te gebruiken in de vergelijking. Bij if (x = 10) wordt in plaats van

te testen of x gelijk is aan 10, de waarde 10 toegekend aan x. Dit is altijd ‘waar’ waardoor de opdrachten tussen de gekrulde haakjes van de if altijd worden uitgevoerd. Als if (x == 10) wordt gebruikt,

dan worden de opdrachten tussen de gekrulde haakjes uitgevoerd als x gelijk is aan 10. Lees ‘=’ als ‘is’ en ‘==’ als ‘is gelijk aan’. Een andere veel voorkomende vergissing is om ‘;’ te plaatsen vlak na de ronde haakjes if (x == 10);. Dit betekend dat als x gelijk is

aan 10, de lege opdracht ; wordt uitgevoerd. Hierna wordt altijd de

controle elementen | 17

opdrachten tussen de gekrulde haakjes uitgevoerd omdat deze haakjes niet meer bij de if horen.

if … else

if…else laat toe om een ‘als-en-anders’ beslissing te maken.

Bijvoorbeeld, als de waarde van een inputPin HIGH is, moeten we iets doen. Anders, als de waarde van de inputPin LOW is, doen we iets anders: if (inputPin == HIGH)

{

doIets();

}

else

{

doIetsAnders();

}

else kan onmiddellijk gevolgd worden door een nieuwe if –test. Op

deze manier kunnen we meerdere testen uitvoeren, waarbij er uiteindelijk steeds slechts één stukje code wordt uitgevoerd.

if (analogeWaarde < 500)

{

doIetsBijLageWaarde();

}

else if (analogeWaarde > 1000)

{

doIetsBijHogeWaarde();

}

else

{

doIetsBijTussenliggendeWaarde();

}

controle elementen | 18

switch … case

Net zoals if en if … else, laat een switch … case toe om

beslissingen te maken over welk stuk code uitgevoerd moet worden afhankelijk van een bepaalde voorwaarde. In dit geval wordt de waarde van een variabele, gespecifieerd in de switch, vergeleken met de waarde die in iedere case wordt gespecifieerd. Zodra de waarde van de case overeenkomt, worden de opdrachten in deze case uitgevoerd. Via de ‘break;’-opdracht kan men een case verlaten nadat al de

gewenste opdrachten werden uitgevoerd. Zonder de ‘break;’-

opdracht, zullen de opdrachten van de volgende case(s) ook uitgevoerd worden totdat een ‘break;’-opdracht wordt gevonden of

het einde van de switch wordt bereikt. Dit wordt ‘falling-through’ genoemd, wat vrij vertaalt als ‘verder tuimelen’. Op het einde wordt vaak ook default: gebruikt als case (zonder het

case ervoor.) Deze ‘case’ vangt alle andere gevallen op indien er geen overeenkomende case erboven was. Het gebruik hiervan is optioneel. switch(eenVariabele)

{

case 1:

doIetsIndien1();

break;

case 2:

doIetsIndien2();

case 3:

doIetsIndien2of3();

break

default: //optioneel

doIetsInAlleAndereGevallen();

break;

}

controle elementen | 19

for

De for-lus wordt gebruikt om een blok van opdrachten, omsloten met

gekrulde haakjes, een exact aantal keren uit te voeren. Een teller wordt hierbij vaak gebruikt om bij te houden welke iteratie uitgevoerd wordt en wanneer de for-lus moet stoppen. Vlak na for volgen

ronde haakjes met daarin drie delen, gescheiden door een puntkomma (;): for (initialisatie; voorwaarde; update)

{

doIets();

}

De initialisatie wordt exact één keer uitgevoerd voor dat de lus begint. Hier initialiseren we de teller als een lokale variabele. Iedere keer voordat de lus begint met het uitvoeren van de opdrachten tussen de gekrulde haakjes, wordt de voorwaarde gecontroleerd. De voorwaarde wordt gebruikt om te kijken of de teller zijn gewenste waarde heeft bereikt. Als deze voorwaarde ‘true’ is, wordt eerst de update uitgevoerd en daarna de lus. Indien de voorwaarde ‘false’ is, eindigt de for-lus. De update dient om de waarde van de teller te

verhogen. Het volgende voorbeeld start met een integer i op waarde 0, test dan of i kleiner is dan 20 en indien dit ‘true’ is, wordt i met 1 verhoogd en worden de opdrachten tussen de gekrulde haakjes uitgevoerd. for (int i = 0; i < 20; i++)

{

digitalWrite(13, LOW);

delay(250);

digitalWrite(13, HIGH);

delay(250);

}

controle elementen | 20

while

Een while-lus loopt onafgebroken oneindig door, totdat de

voorwaarde tussen de ronde haakjes ‘false’ wordt. Hiervoor moet

de geteste variabele binnen de lus (tussen de gekrulde haakjes) ook veranderen, anders stopt de while-lus nooit.

while (eenVariabele ?? eenWaarde)

{

doeIets();

}

Het volgende voorbeeld test of eenVariabele kleiner is dan 200 en indien dit zo is, worden de opdrachten binnen de gekrulde haakjes uitgevoerd. De while-lus blijft zich herhalen tot eenVariabele niet

meer kleiner is dan 200. while (eenVariabele < 200)

{

doIets()

eenVariabele++; // verhoog de variabele met 1

}

Let op

Een veel voorkomende vergissing is om ‘;’ te plaatsen vlak na de ronde haakjes: while (eenVariabele < 200);. Hierdoor wordt

een variabele niet meer verhoogd met 1 en zal de while-lus oneindig

blijven lussen.

controle elementen | 21

do … while

De do-while-lus werkt identiek als de while-lus, met uitzondering

dat de voorwaarde op het einde getest wordt. Hierdoor zal de do-while-lus altijd minstens één keer uitgevoerd worden.

do

{

doIets();

} while (eenVariabele ?? eenWaarde);

In het volgend voorbeeld wordt er in de lus een sensor uitgelezen en 50 milliseconden gewacht. Zolang de uitgelezen waarde kleiner is dan 100, wordt de lus herhaald.

Do

{

x = leesSensor(); // lees een sensor in x

delay(50); // wacht 50 ms

} while (x < 100); // lus als x kleiner is dan 100

digitale I/O | 22

digitale I/O pinMode(pin, mode)

pinMode() wordt in void setup() gebruikt om een pin als INPUT of

als OUTPUT te configureren. pinMode(pin, OUTPUT); // stel ‘pin’ in als output

Standaard staan al de pinnen van een Arduino ingesteld als input pinnen. Deze moeten dus niet expliciet nog als INPUT geconfigureerd worden. Een pin die als INPUT is geconfigureerd bevindt zich in een hoog ohmige toestand, met andere woorden, deze pin heeft een zeer grote ingangsweerstand. De ingangspinnen op een Arduino hebben ook een ingebouwde weerstand die als pull-up weerstand gebruikt kan worden. De waarde van deze weerstand ligt ergens tussen de 20K en de 150K, afhankelijk van de gebruikte microcontroller. pinMode(pin, INPUT_PULLUP);

Aan een pin met een interne pull-up weerstand kan men eenvoudig een schakelaar verbinden die, wanneer ingedrukt, de massa met de ingangspin verbindt. Pinnen die als OUTPUT geconfigureerd zijn, zijn laag ohmig en kunnen 40 mA stroom leveren aan andere componenten. Dit is genoeg om een LED helder te laten op lichten, maar te weinig om bijvoorbeeld een motor te laten draaien. Let op dat je geen kortsluiting maakt met een OUTPUT pin. Hierdoor kan de pin beschadigd raken. Daarom wordt er vaak een weerstand van 470R of 1K in serie geplaatst als men andere componenten met de pin verbindt. digitalRead(pin)

Leest de waarde van een digitale input pin, het resultaat is HIGH of LOW. De gewenste pin wordt gekozen door zijn nummer (0 … 13) als variabele of als constante mee te geven aan de functie. waarde = digitalRead(pin); // lees de waarde van de

// input pin en ken deze

// toe aan ‘waarde’.

digitale I/O | 23

digitalWrite(pin, waarde)

Zet de toestand van een digitale uitgangspin op de logische waarde HIGH (aan of 5V op de pin) of op de logische waarde LOW (uit of 0V op de pin). De gewenste pin wordt gekozen door zijn nummer (0 … 13) als variabele of als constante mee te geven aan de functie. digitalWrite(pin, HIGH); // zet ‘pin’ aan

In het volgend voorbeeld wordt de waarde van een drukknop, verbonden aan een digitale ingangspin, uitgelezen en gebruikt om een LED, verbonden aan een digitale uitgangspin, aan te schakelen. const int PIN_LED = 13; // LED op pin 13

const int PIN_KNOP = 7; // drukknop op pin 7

int waarde = 0; // variabele om de waarde

// van de drukknop te

void setup() //bewaren

{

pinMode(PIN_LED, OUTPUT); // pin 13 als uitgang

pinMode(PIN_KNOP, INPUT); // pin 7 als ingang

}

void loop()

{

waarde = digitalRead(PIN_KNOP); // lees de waarde

// van de knop

digitalWrite(PIN_LED, waarde); // schrijf de

// waarde van de

} // knop naar de

// LED

analoge I/O | 24

analoge I/O analogRead(pin)

Leest de waarde van een analoge pin met een 10-bit resolutie (waarde tussen 0 en 1023). Deze functie werkt alleen bij analoge ingangspinnen (A0 … A5). waarde = analogRead(pin); // lees de waarde van de

// input pin en ken deze

// toe aan ‘waarde’.

analogWrite(pin, waarde)

Schrijft een pseudo-analoge waarde, gebruik makend van hardware gestuurde puls breedte modulatie (PWM), naar een uitgangspin die hier geschikt voor is (gemarkeerd met PWM of ~). Voor de Arduino UNO werkt dit op pinnen 3, 5, 6, 9, 10 en 11. De waarde die geschreven kan worden is een variabele of constante van 0 tot en met 255. analogWrite(pin, waarde); // schrijf waarde naar

// analoge ‘pin’

Een waarde van 0 genereert een constante 0 volt op de uitgangspin. En waarde van 255 genereert een constante 5 volt op de uitgangspin Voor waardes tussen 0 en 255 schakelt de uitgangspin snel tussen 0

en 5 volt. Hoe hoger de waarde, hoe langer de pin HIGH (5 volt) is.

Bijvoorbeeld: bij een waarde van 64 is de uitganspin drie vierde van de tijd LOW en één vierde van de tijd HIGH. Bij een waarde van 128 is

de uitgangspin de helft van de tijd HIGH en de helft van de tijd LOW.

Bij een waarde van 192 is de uitgangspin één vierde van de tijd LOW

en drie vierde van de tijd HIGH.

Doordat de PWM door hardware gestuurd wordt, zal de PWM op de uitganspin blijven na een analogWrite() en dit tot de pin opnieuw

wordt gebruikt voor een analogWrite(), digitalRead() of

digitalWrite().

opmerking: voor analogRead() of analogWrite() moet de pin

niet eerst als INPUT worden gedefinieerd met de pinMode() functie.

Het volgend voorbeeld leest een analoge waarde van een analoge ingangspin, deelt deze waarde door 4 en zet het resultaat als PWM-signaal op een PWM pin. We moeten de waarde door 4 delen omdat

tijd | 25

analogRead() een waarde van 0 tot en met 1023 kan teruggeven,

terwijl analogWrite() een waarde van 0 tot en met 255 verwacht.

const int PIN_LED = 10; // LED met 220R weerstand

// op pin 10

const int PIN_POT = A0; // potentiometer op pin A0

int waarde = 0; // variabele om de analoge

// waarde op te bewaren

void setup() {} // geen pinMode nodig

void loop()

{

waarde = analogRead(PIN_POT);// lees de waarde

// van potentiometer

waarde /= 4; // deel door 4

analogWrite(PIN_LED, waarde);// schrijf een PWM

// signaal naar de

} // LED

tijd delay()

Pauzeert het programma voor het aantal milliseconden dat je mee geeft aan de functie. De waarde die je meegeeft kan een variabele of constante zijn, waarbij 1000 milliseconden overeenkomt met 1 seconde. delay(1000); // wacht 1 seconde

millis()

Deze functie geeft het aantal milliseconden sinds de Arduino is begonnen met het huidige programma uit te voeren. De waarde is een unsigned long. waarde = millis(); // aantal ms sinds start

opmerking: De waarde krijgt een overflow (start terug van 0) na

ongeveer 9 uren.

seriële communicatie | 26

seriële communicatie Serial.begin(baudrate)

Opent een seriële poort en zet de baudrate voor seriële communicatie. Als baudrate voor seriële communicatie met een computer wordt typisch 9600 gekozen, hoewel andere snelheden ook ondersteund worden. void setup()

{

Serial.begin(9600); // open seriële poort

} // zet de baudrate op 9600

opmerking: Wanneer de seriële communicatie gebruikt wordt, zijn

digitale pinnen 0 (RX) en 1 (TX) niet bruikbaar voor andere doeleinde. Serial.println(data)

Verstuurt data naar de seriële poort en voegt automatisch het ‘carriage return’ en ‘line feed’ karakter toe zodat de seriële monitor automatisch op een nieuwe regel staat. Serial.print() werkt

identiek, maar zonder de automatische nieuwe regel. Serial.println("hallo"); // verstuur de tekst

// "hallo"

Je kan ook variabelen en constanten versturen. Serial.println() zorgt er dan voor dat de waarde omgezet wordt in de juiste karakters. opmerking: Voor variabelen van het type float en double kan je

een bijkomend argument meegeven aan de functie namelijk het aantal decimalen na de komma(punt). Standaard worden er twee decimalen getoond. const float pi = 3.14159265

Serial.println(pi); // 3.14

Serial.println(pi, 1); // 3.1

Serial.println(pi, 5); // 3.14159

seriële communicatie | 27

Serial.read()

Leest een byte (karakter) van de seriële poort. Indien er geen seriële data ontvangen is, geeft de functie de waarde -1 terug. char rxKar = Serial.read();

Serial.available()

Geeft het aantal bytes (karakters) terug die in de leesbuffer zitten. Deze functie wordt gebruikt om te voorkomen dat we ongeldige karakters zouden lezen als er nog niks verstuurd werd. opmerking: de leesbuffer kan tot 64 bytes bevatten. Als daarna nog

een byte wordt ontvangen zonder uit te lezen, wordt de eerst ontvangen byte overschreven. void setup()

{

Serial.begin(9600); // open seriële poort

} // zet de baudrate op 9600

void loop()

{

// controleer of er data ontvangen is

if(Serial.available() > 0)

{

// lees een ontvangen byte

char rxKar = Serial.read();

// verstuur wat je ontvangen hebt

Serial.print("Ontvangen data: ");

Serial.println(rxKar);

}

}

Serial.parseInt()

Probeert volgende seriële ontvangen bytes te lezen als een geheel getal:

Beginnende karakters die geen cijfer of ‘-‘ teken zijn worden genegeerd.

De functie stopt zodra er geen karakters meer ontvangen worden na een zeker tijd (time-out) of als het ontvangen karakter geen cijfer meer is.

Indien er geen cijfers ontvangen worden gedurende de time-out periode, geeft de functie 0 terug.

Hulpfuncties | 28

Hulpfuncties map(…)

Herrekent een geheel getal (int, long) van een bereik naar een ander. ledPWM = map(potWaarde, 0, 1023, 0, 255);

De potWaarde van 0 tot en met 1023 wordt herrekend naar een waarde van 0 tot en met 255 voor een PWM-signaal. opmerkingen:

map() is niet begrensd. Als in het voorbeeld de potWaarde groter

zou zijn dan 1023, dan is de berekende ledPWM groter dan 255.

Indien men een variabele moet beperken, gebruik je constrain().

Als ledPWM moet dalen naarmate potWaarde groter wordt, kan men de grenzen omwisselen ledPWM = map(potWaarde, 0, 1023, 255, 0);

De functie werkt met negatieve getallen, maar geen komma getallen. Het deel na de komma wordt afgekapt, er vindt met andere woorden geen afronding plaats. min(…)

Berekent het minimum van twee getallen van eender welk data type en geeft het kleinste getal terug. waarde = min(waarde, 100); // waarde kan nooit

// groter zijn dan 100

max(…)

Berekent het maximum van twee getallen van eender welk data type en geeft het grootste getal terug. waarde = max(waarde, 20); // waarde kan nooit

// kleiner zijn dan 20

Hulpfuncties | 29

constrain(…)

Begrenst een getal binnen een gekozen bereik. Ieder type van variabele wordt ondersteund. x = constrain(x, onderGrens, bovenGrens);

x wordt in bovenstaand voorbeeld begrensd tussen onderGrens en

bovenGrens.

Als onderGrens ≤ x ≤ bovenGrens is het resultaat x. Als x < onderGrens is het resultaat onderGrens. Als x > bovenGrens is het resultaat bovenGrens. // begrens de sensorWaarde tussen 10 en 150

sensorWaarde = constrain(sensorWaarde, 10, 150);