www.ateliervilvoorde.be DOC20170113A De Arduino programmeertaal Versie 1.1 Introductie Als we in deze tekst verwijzen naar een Arduino dan bedoelen we een Arduino UNO. Een Arduino is in essentie een microcontroller met wat elektroniche componenten errond om die microcontroller te kunnen gebruiken en programmeren. Op een Arduino UNO bord is deze microcontroller een AtMega 328p. Een microcontroller is in essentie een centrale verwerkingseenheid (CPU) die samen met een aantal randapparaten (bijvoorbeeld een seriële poort) in één verpakking is gestopt. De CPU van de microcontroller die gebruikt wordt op een Arduino UNO is een Atmel AVR. De AVR is een 8-bit-CPU omdat hij data bewerkt in groepjes van 8 bits (één byte). Het programma dat de AVR verwerkt is echter gecodeerd in blokken van 16-bits (twee bytes). De AVR CPU is een zogenaamde RISC-CPU. RISC staat voor Reduced Instruction Set Computer. Ontwerpers van RISC-CPU s doen bijzonder veel inspanningen om ervoor te zorgen dat elke individuele instructie van het uit te voeren programma snel wordt uitgevoerd. De ontwerpers van de AVR zijn daar bijzonder goed in geslaagd. Je mag ruwweg stellen dat de AVR op een Arduino UNO 16 000 000 instructies per seconde uitvoert. Computers en microcontrollers en dus ook de AVR-CPU van een Arduino verwerken enkel en alleen machinetaal. Machinetaal is een reeks enen en nullen. Machinetaal is voor de mens slechts met heel veel inspanning leesbaar. Daarom heeft men assemblers uitgevonden. In assembler gebruikt men leesbare afkortingen om een machinetaal instructie voor te stellen. Dat maakt het voor een mens mogelijk om een programma in assembler te schrijven en te lezen. Een programma dat geschreven is in assembler wordt door een programma dat men de assembler noemt omgezet in machinetaal. Wanneer je assembler gebruikt in plaats van machinetaal blijf je 100% in controle van wat de computer uiteindelijk uitvoert. Dat is de belangrijkste reden waarom er héél uitzonderlijk nog gebruik gemaakt wordt van assembler bij het programmeren van microcontrollers. Een programma dat geschreven is in assembler werkt uitsluitend op de CPU waarvoor die assembler bedoeld is. Dat is heel vervelend want dat betekent dat je een programma in assembler moet herschrijven voor elke CPU waarop je het wil gebruiken. Daarom heeft men hogere programmeertalen uitgevonden : BASIC, C, Fortran, Java, Pascal, Python, om er een paar te noemen. Een programma dat geschreven wordt in één van deze hogere programmeertalen wordt door een programma dat men de compiler noemt uiteindelijk toch weer omgezet in machinetaal. Een programma dat geschreven is in een hogere programmeertaal kan op elke CPU uitgevoerd worden waarvoor er een compiler voor die hogere programmeertaal bestaat.
C is de taal die veruit het meest gebruikt wordt om microcontrollers te programmeren. In C heb je zo goed als 100% controle over wat de CPU uiteindelijk verwerkt. De Arduino programmeertaal is, in combinatie met de Arduino programmeer omgeving (in het Engels: Integrated Development Environment of IDE), een geslaagde poging om de bijzonder krachtige programmeertaal C (eigenlijk C++) gemakkelijk toegankelijk te maken. Arduino gebruikt op de achtergrond de C++ compiler uit het GNU project. Dat is een bijzonder krachtige open source C++ compiler die ook de standaard is in een Linux omgeving. Alhoewel het perfect mogelijk is om in de Arduino IDE gebruik te maken van standaard C en C++ programmeertaal beperkt dit document zich tot de Arduino taal zoals die gedocumenteerd staat op de website van www.arduino.cc onder learning => reference. Een belangrijk stuk van de kracht van de programmeertaal C komt van de mogelijkheden die pointers bieden aan de programmeur. Toch zal dit document daar NIET op ingaan. Het is ongebruikelijk voor de modale Arduino programmeur om pointers te gebruiken. Wij beschouwen het gebruik van pointers dan ook als een onderwerp voor een document voor gevorderden. In de Arduino taal noemt men een programma een schets. Dat komt omdat Arduino oorspronkelijk voor kunstenaars werd opgezet. De termen schets, programma en code zijn in dit document uitwisselbare synoniemen. Elke schets bestaat uit een reeks instructies. semicolon ; Elke instructie in uw programma moet je afsluiten met een semicolon ; De compiler (het programma dat je Arduino programma omzet naar de machinetaal die je Arduino verstaat) zoekt naar die semicolon en zolang hij die niet is tegengekomen blijft hij proberen om van alles wat je schrijft één instructie te maken. Een zeer frequent voorkomende fout is het vergeten om de semicolon te plaatsen achter een instructie. Daarom het advies om als je foutboodschappen (in het Engels error messages) krijgt na te gaan of in de directe omgeving (meestal op de regel boven de regel die de foutboodschap genereert) na te kijken of de semicolon er staat. De semicolon wordt ook gebruikt als scheiding tussen de verschillende blokken van de programma verloop bepalende instructie forloop. We gaan daar later dieper op in. Hoofdletters en kleine letters Voor de compiler zijn hoofdletters en gewone letters twee totaal verschillende letters. (mytext en MyText zijn twee totaal verschillende dingen). Let er dus op dat je de hoofdletters en de gewone letters heel consequent gebruikt. Als de compiler je een foutboodschap (in het Engels error message) geeft die klinkt als not defined in this context is het zeer zinvol om na te gaan of je gewone en hoofdletters weldegelijk consequent hebt gebruikt. Commentaar // of ook /* */ Je kan in je programma commentaar schrijven. Die dient als hulp voor degene die probeert om je code te begrijpen. In vele gevallen zal je dat zelf zijn. Doe jezelf dus een plezier en schrijf veel en goed uitgewerkte commentaar. Commentaar wordt door de compiler overgeslagen. Het genereert dus geen bit extra code en heeft geen enkele invloed op de uitvoering van uw programma. In Arduino kan je commentaar toevoegen door een dubbele schuine streep // te schrijven. Alles wat op dezelfde regel volgt ( tot de enter of cariage return) achter de dubbele schuine streep is commentaar. Als je verschillende regels commentaar wenst te schrijven dan kan je op elke regel beginnen met //. Een alternatief is om te starten met /* en te eindigen met */. Bijvoorbeeld :
// Commentaar regel /* Commentaar regel 1 Commentaar regel 2 Commentaar regel 3 */ accolades { In de Arduino taal gebruikt men accolades { (in het Engels curly braces) om stukken code samen te bundelen. Dat is het geval bij een functie, een loop en conditionele verwerking. Functies en setup() en loop() In Arduino (en in C en C++) is elk programma in essentie een verzameling functies. Elke functie voert een mooi afgebakende taak uit. Dat maakt uw programma modulair waardoor het veel gemakkelijker is te onderhouden. Elk C-programma heeft een functie die de naam main heeft. De uitvoering van het programma start door main op te roepen en uit te voeren. Ook Arduino gebruikt de functie main op de achtergrond. In die main staat de oproep (in het Engels call) naar de functies setup en loop. Als je een programma compileert waarin de functie setup ontbreekt krijg je een errorboodschap in function main undefined reference to setup. Een gelijkaardige errorboodschap krijg je als de functie loop niet in je programma is opgenomen. Ook als je in deze functies geen enkele instructie schrijft dan nog moeten deze functies in uw programma opgenomen worden. De minimale code die je kan compileren in Arduino is daarom. void setup(){ void loop(){ In setup zet je de instructies die slechts één keer uitgevoerd moeten worden. In loop zet je de instructies die steeds opnieuw moeten uitgevoerd worden. In vele gevallen is een functie bedoeld om iets uit te rekenen en dan het resultaat van uw berekening terug te sturen naar uw programma. In het Engels noemt men die waarde die terug gestuurd wordt de return value. Als je een functie declareert moet je het data type opgeven. (We gaan het dadelijk uitvoerig hebben over data types.) Als uw functie niet bedoeld is om iets terug te sturen dan declareer je een functie als void. De functies setup() en loop() zijn voorbeelden van functies die geen resultaat terug sturen. Vandaar dat deze gedeclareerd worden als void. In vele gevallen wil je aan een functie een aantal parameters meegeven. Deze parameters worden dan in de functie gebruikt om de taak uit te voeren waarvoor je de functie wil gebruiken. Die parameters worden opgegeven tussen de haakjes () die volgen op de naam die je aan uw functie geeft. Zelfs als uw functie geen parameters nodig heeft dan nog moet je de haakjes plaatsen. Het zijn namelijk deze haakjes die aan uw compiler vertellen dat je een functie gaat omschrijven. De instructies die uitgevoerd worden door uw functie moet je afbakenen met accolades {. De naam die je aan een functie geeft mag je samenstellen uit de kleine letters (a-z) de hoofdletters (A-Z) de cijfers (0-9) en het teken _ (underscore). De naam moet beginnen met een letter of met _. Alleen de eerste 31 tekens worden door de compiler bekeken. Namen die langer zijn dan 31 tekens maar waarvan de eerste 31 tekens identiek zijn zullen geïnterpreteerd worden als identiek. #include #include is geen instructie voor de compiler. Het is een instructie voor de pre-compiler. De pre-compiler stelt de tekst samen die vervolgens aan de compiler wordt doorgegeven. Bijvoorbeeld #include <LiquidCrystal.h>
zal door de pre-compiler vervangen worden door het bestand dat je opgeeft. In het voorbeeld hierboven het bestand LiquidCrystal.h. Het is geen C-instructie en er hoort geen semicolon achter te staan. De Arduino IDE is standaard zo ingesteld dat hij voor een #include <> het bestand gaat zoeken in de folder waar de bibliotheken (in het Engels libraries) staan. Je kan ook de <> vervangen door. In dat geval zal de pre-compiler eerst zoeken in de folder waar uw programma staat en daarna in de folder waar de bibliotheken staan. Als je bestanden wil invoegen die in andere folders staan dan moet je die folder expliciet opgeven. Zozal #include <c:\headers\mycode.h>. Op je vaste schijf c: in de folder headers het bestand mycode.h zoeken en invoegen. #define Geeft aan de pre-compiler de opdracht om een tekst te vervangen door een andere tekst in de rest van het programma. Bijvoorbeeld #define LedPin 3 vervangt in de rest van het programma de tekst LedPin door het getal 3. Ook bij deze pre-compiler instructie hoort er geen semicolon achter te staan. Een veel gemaakte fout is om #define LedPin = 3 te schrijven. Als je dat schrijft zal de precompiler in de rest van uw programma LedPin vervangen door = 3 en dat is allicht niet je bedoeling. Ingebouwde definities Er zijn in de Arduino IDE voor een Arduino UNO een hele reeks definities gemaakt die je in je schetsen kan gebruiken. We zullen hier de belangrijkste daarvan toelichten. Er zijn er echter veel meer. Een belangrijke lijst van extra definities kan je vinden op pagina 428 in de datasheet van de AVR 328P : http://www.atmel.com/images/atmel-42735-8-bit-avr-microcontroller-atmega328-328p_datasheet.pdf HIGH, LOW, INPUT, OUTPUT, INPUT_PULLUP, LED_BUILTIN en PI Deze zijn voor een Arduino UNO gedefinieerd als respectievelijk : 1, 0, 0, 1, 2, 13 en 3.14 Het is de bedoeling dat je deze omschrijvingen gebruikt in je code (in plaats van de eigenlijke waarden). Het voordeel is dat je code dan gemakkelijker te lezen is. Vergeet niet dat hoofdletters en kleine letters twee totaal verschillende letters zijn in Arduino. Alles in hoofdletters dus. true en false Ook true en false zijn standaard gedefinieerd. Daarbij is het opvallend dat dit NIET in hoofdletters is gedaan. Zoals men zal verwachten is false gedefinieerd als 0 (nul) en is true gedefinieerd als 1. Variabelen en data types In de Arduino (en elke digitale computer van tegenwoordig) is de kleinste opslagruimte een bit. In die opslagruimte kunnen we uitsluitend een één of een nul opslagen. In de Arduino is het RAM geheugen georganiseerd in groepjes van 8 bits. Zo een groepje van 8 bits noemt men een byte. In het RAM geheugen bewaart de Arduino alle waarden waarmee ons programma werkt. De waarde in een byte zou een letter kunnen zijn. De letter zou op zich kunnen staan of een onderdeel zijn van een tekst. De waarde zou ook een binair getal kunnen zijn. Dat getal zou op zich kunnen staan (als het in een byte past) of een onderdeel zijn van een groter getal dat meerdere bytes nodig heeft.
Ter illustratie. Als er in een byte (een groep van 8 bits) het binaire getal B1000001 staat dan kan dat het decimale getal 65 zijn maar het zou ook een code kunnen zijn voor de hoofdletter A. De 65 zou een byte van een groter getallen kunnen zijn en de A zou de eerste letter van het woord AAP kunnen zijn. Achtergrond informatie : Het flash-geheugen waarin het programma wordt opgeslagen is georganiseerd is groepen van 2 bytes (16 bits). De AVR CPU die in de Arduino gebruikt wordt is dan wel een 8-bit CPU (omdat hij de data verwerkt in groepjes van 8-bits) maar hij gebruikt wel 16-bit instructies om aan te geven wat er met die data moet gebeuren. De AVR CPU in een Arduino is met het RAM geheugen verbonden met 8 aansluitingen (een 8 bit bus). Om twee bytes uit het RAM geheugen naar de CPU te halen moet de Arduino twee keer uit het RAM geheugen lezen. De AVR CPU in een Arduino is met het programma geheugen verbonden met 16 aansluitingen (een 16-bit bus). Om twee bytes uit het programma geheugen naar de CPU te halen moet de Arduino maar één keer lezen uit het programma geheugen lezen. Om ervoor te zorgen dat de compiler weet hoe hij met de opgeslagen enen en nullen moet omgaan moeten we aan de compiler vertellen wat we willen opslagen. Dat doen we door een variabele te declareren. (In het Engels Variable Declaration). Dat gaat als volgt TypeIdentifier VariableName; of TypeIdentifier VariableName = Value; TypeIdentifier Eén van de types van variabelen die de Arduino kent: boolean, char, unsigned char, byte, int, unsigned int, word, long, unsigned long, short, float of double. boolean Kan alleen de waarden 1 (true) of 0 (false) hebben. Neemt 1 byte in beslag. Dat is niet efficiënt maar wel snel. char (si8) Zal men normaal gebruiken om een teken (in het Engels character) op te slaan. Bijvoorbeeld de letter A. Alhoewel char bedoeld is om tekens op te slaan is het perfect mogelijk om er getallen in op te slaan. Kan de waarden -128 tot 127 bevatten. Neemt 1 byte in beslag. unsigned char (ui8) Is identiek hetzelfde als een byte. Kan de waarden 0 tot 255 opslaan. Neemt 1 byte in beslag. byte (ui8) Is identiek hetzelfde als een unsigned char. Kan de waarden 0 tot 255 bevatten. Neemt 1 byte in beslag. int (si16) Kan de waarden -32768 tot 32767 bevatten. Neemt 2 bytes in beslag. unsigned int (ui16) Dit is identiek hetzelfde als een word. Kan de waarden 0 tot 65535 bevatten. Neemt 2 bytes in beslag. word (ui16) Dit is identiek hetzelfde als een unsigned int. Kan de waarden 0 tot 65535 bevatten. Neemt 2 bytes in beslag. long (si32) Kan de waarden -2147483648 tot 2147483647 bevatten. Neemt 4 bytes in beslag. unsigned long (ui32) Kan de waarden 0 tot 4294967295 bevatten. Neemt 4 bytes in beslag. short (si16) Dit is identiek hetzelfde als een int. Kan de waarden -32768 tot 32767 bevatten. Neemt 2 bytes in beslag. float
Bedoeld voor de opslag van getallen met een zwevende komma. (in het Engels floating point). Kan de waarden 3,4028235X10 38 tot 3,4028235X10 38 opslaan. Neemt 4 bytes in beslag. Een float kan dan wel heel grote en heel kleine getallen opslaan, de precisie is hoogstens 7 decimale cijfers. In nogal wat berekeningen met een float blijkt dat het perfecte juiste resultaat NIET het getal is dat in de float is opgeslagen (het is daarvan een geweldig goede benadering). Dat kan heel vervelend zijn als je een resultaat van een berekening met een float gaat vergelijken met bijvoorbeeld een int. Daarom is het de gewoonte om niet na te gaan of die twee waarden gelijk zijn maar wel om na te gaan of het verschil voldoende klein is om als gelijk beschouwd te worden. Berekeningen met floats zijn aanzienlijk trager dan berekeningen met gehele getallen. Het is dan ook raadzaam om berekeningen met floats tot een minimum te beperken. double Dit is identiek hetzelfde als een float. Kan de waarden 3,4028235X10 38 tot 3,4028235X10 38 opslaan. Neemt 4 bytes in beslag. VariableName De naam die je aan je variable wil geven. Je mag alle hoofdletters (A-Z), alle kleine letters (a-z), alle cijfers (0-9) en _ (underscore) gebruiken. Het eerste teken moet een letter of een _ zijn. Alleen de eerste 31 tekens worden door de compiler bekeken. Namen die langer zijn dan 31 tekens maar waarvan de eerste 31 tekens identiek zijn zullen geïnterpreteerd worden als identiek. Het is raadzaam om een naam te gebruiken die een begrijpelijke betekenis heeft bijvoorbeeld Temperatuur_Sersor_Waarde. Dat helpt om de code te begrijpen en onderhouden. Er zijn programmeurs die graag in de naam van de variabele aangeven welk type werd gebruikt bij de declaratie. Ze gebruiken bijvoorbeeld de naam int_testwaarde voor een variabele die als int werd gedeclareerd. We vermelden dat hier om ervoor te zorgen dat je niet het noorden verliest als je dergelijke code ziet. Value De waarde die we initieel geven aan onze variabele. Het toekennen van een waarde aan een variabele in een declaratie noemt met de variabele initialiseren. Je kan alle variabelen die van hetzelfde type zijn ook in een statement declareren. Bijvoorbeeld : int i, j, k ; Dat kan ook meteen met een initialisatie van de waarden. Bijvoorbeeld : int i = 2, j = 3, k = 4; Arrays In nogal wat situaties heb je een ganse reeks variabelen nodig die je bovendien voor hetzelfde doel gebruikt en die je dus liefst dezelfde naam zou wille geven. Dat kan door gebruik te maken van arrays. Een array declareer je met de volgende instructie TypeIdentifier VariableName[ NrOfElements ]; Daarin is TypeIdentifier en variablename net hetzelfde als bij de normale variabele declaratie die we net behandeld hebben. NrOfElements Een geheel getal dat aangeeft hoeveel variabelen je wenst aan te maken. Het aantal dat je kan opgeven is beperkt door het RAM geheugen van uw AVR 328p (2048 bytes). De verschillende elementen van de array kan je dan aanspreken door een index te gebruiken. Het eerste element in een arry heeft als index 0 (nul). Als je dus een array aanmaakt van 10 elementen dan hebben die als index 0 tot 9. We illustreren dat met de volgende code.
byte myarray[3]; myarray[0] = 10; myarray[1] = 20; myarray[2] = myarray[0]; //myarray[2] = 10 Arrays van het type char (in het Engels character arrays) is de standaard manier om in de C-programma om te gaan met teksten. Je moet al redelijk goed met C en arrays vertrouwd zijn om daar vlot gebruik van te maken. Het Arduino team heeft in de standaard Arduino IDE een object opgenomen dat String noemst (met een hoofdletter S). Daarmee kan je wel op een heel gebruiksvriendelijke manier teksten manipuleren. Als je een schets gaat schrijven waarbij er heel wat tekstmanipulaties nodig zullen zijn moet je zeker de referentie pagina s over het String object doornemen. Die vind je op www.arduino.cc Learning => Reference onder de titel Data Type. Voor we het onderwerp arrays afsluiten willen we erop wijzen dat je geen error boodschap krijgt als je een voor een array een index gebruikt die je niet hebt gedefinieerd. De volgende code illustreert dat. byte myarray[3]; // myarray[0] tot myarray[2] byte mybyte; myarray[3] = 10; // GEEN ERROR BOODSCHAP! Dat kan moeilijk te vinden bugs opleveren. Zeker omdat de index van de elementen begint met 0 maar het voor de beginnende programmeur het heel natuurlijk aanvoelt dat een array van X elementen als laatste element het element heeft met als index X. NIET DUS! De reden waarom er geen error boodschap gegenereerd wordt is omdat het perfect zou kunnen dat je inderdaad achter uw array wil lezen en schrijven. We komen daar dadelijk op terug maar eerst moeten we het hebben over volatile. volatile De C-compiler die gebruikt wordt om uw Arduino schets om te zetten naar machinetaal is een bijzonder slim beestje. Hij weet dat bewerkingen met variabelen die worden uitgevoerd met registers sneller uitgevoerd worden dan bewerkingen met variabelen die in het RAM geheugen worden opgeslagen. Dat is zo omdat de AVR CPU zonder een lees opdracht uit te voeren bewerkingen kan uitvoeren tussen registers. Het gevolg is dat de compiler gaat proberen om alle variabelen die je veel gaat gebruiken op te slaan in registers in plaats van RAM geheugen. De AVR CPU heeft 32 dergelijke registers. Dat is behoorlijk wat en vele kleine schetsen hebben zo weinig variabelen dat ze allemaal in registers kunnen worden opgeslagen in plaats van in het RAM geheugen. De compiler zal dat dan ook doen. Dat is op zich geweldig. Berekening gaan een stuk sneller. Er zullen daarbij nooit problemen veroorzaakt worden zolang de compiler kan voorzien in welke volgorde code gaat uitgevoerd worden. (in het Engels as long as the compiler is in control ) Maar als je gebruik maakt van interrupt routines (een onderbreking van het normale verloop van het programma die veroorzaakt wordt door een extern event) is de compiler niet in control. Dan kan het fout gaan. Daarom is er een manier om de compiler te verplichten om variabelen op te slaan in het RAM geheugen. Dat doe je door voor de declaratie van de variabele het woord volatile te plaatsen. Dat ziet er dan bijvoorbeeld zo uit : volatile byte myarray[3]; Als je gebruik maakt van interrupt routines dan is het slim om gul om te gaan met volatile. Nu kunnen we ook illustreren waarom er geen error boodschap komt als je een array element met een te grote index gebruikt. Het zou perfect uw bedoeling kunnen zijn zoals in de onderstaande code. volatile byte myarray[3]; // myarray[0] tot myarray[2] aangemaakt
volatile byte mybyte; myarray[3] = 20; // mybyte = 20 scope local en global Om te voorkomen dat je per ongeluk variabelen gebruikt die je elders voor iets anders gebruikt zijn variabelen alleen geldig binnen de functie waarin ze gedeclareerd worden. Variabelen die je declareert in setup kan je niet gebruiken in loop (en in geen enkele andere functie). void setup() { byte mybyte; void loop(){ mybyte = 10; // ERROR! Je zou wel een ANDERE variabele kunnen declareren in loop en ze dezelfde naam geven. MAAR HET ZIJN DUS TWEE TOTAAL VERSHILLENDE VARIABELEN. Probeer dat te vermijden. Het maakt je code nodeloos verwarrend. Tegen dit advies mag je vlot tegen zondigen als het om variabelen gaat die je hoe dan ook telkens opnieuw Initialiseert voor je ze gebruikt. Tellers bijvoorbeeld. Voor dat soort variabelen is het ook gebruikelijk om namen te gebruiken die uit één letter bestaan. De letters: i, j, k, l, m en n zijn daarvoor heel gebruikelijk. Als je variabelen wil gebruiken in alle functies van uw schets dan moet je ze declareren buiten een functie. Dat kan gelijk waar maar het is gebruikelijk om dat bovenaan in je code te doen voor de functie setup zoals in dit voorbeeld. byte mybyte; void setup() { mybyte = 10; void loop(){ mybyte = 20; static Om zuinig om te gaan met RAM geheugen worden variabelen op het einde van een functie vernietigd. Daardoor kunnen de RAM geheugenlocaties die voor die variabelen gebruikt werden hergebruikt worden voor andere variabelen. Het gevolg is echter ook dat de waarde die je in een variabele in een functie had opgeslagen niet meer de waarde heeft die ze had toen de functie de vorige keer werd afgesloten. Als je dat wel wil dan kan je gebruik maken van het woordje static bij de declaratie van een variabele in een functie. static TypeIdentifier VariableName; In code ziet er dat dan bijvoorbeeld zo uit static int myint; De volgende code illustreert het nut van static. int sum1(int add){ int total; total = total + add; return total; int sum2(int add){ static int total; total = total + add; return total; void setup(){ void loop(){ int myint1, myint2; myint1 = sum1(10); //myint = 10 even after several loops myint2 = sum2(10); //myint = 10 then 20, 30, 40,... const Soms wil je dat een variabele in je schets een waarde krijgt die nooit kan veranderd worden. Dan kan door in de declaratie gebruik te maken van const. Dat ziet er dan als volgt uit const float euler = 2.7182818;
Als je de waarde van deze constante variabele ergens in je schets probeert te veranderen genereert de compiler een error boodschap. Het gebruik van const is een zinvol alternatief voor #define als je getallen wil definiëren. Als je daarvoor een constante variabele gebruikt dan zijn de regels van scope daarop van toepassing. Dat is niet het geval bij het gebruik van #define. Getallen In een programma kan je rechtstreeks getallen gebruiken. Bijvoorbeeld 123 of 1017. De compiler veronderstelt dat getallen opgegeven worden in het 10-delig getallenstelsel. Het 10-delig getallen-stelsel zijn de gewone getallen zoals we die dagdagelijks gebruiken. Als je wil dat deze getallen door de compiler geïnterpreteerd worden als getallen in een ander getallen stelsel dan kan je dat forceren door een voorzetsel te gebruiken: - B voor binair (base-2), - 0 (nul) voor octaal (base-8) en - 0x (nulx) voor hexadecimaal (base-16) zoals hieronder aangegeven. 123 base-10 (0-9 are valid) B01111011 base-2 (0-1 are valid) 0173 base-8 (0-7 are valid) 0x7B base-16 (0-F are valid) Het is verdorie vervelend dat men het cijfer 0 (nul) gekozen heeft om aan te geven dat een getal octaal is. Je kan dan immers 01017 schrijven. Je denkt dat dit duizendzeventien is maar er staat dus octaal 1017 en dat is 527 10- delig. Het voorzetsel B kan je alleen gebruiken voor Bytes (maximaal 8 bits). Als je dat voor getallen van meerdere bytes wil gebruiken dan kan dat door de bytes te vermenigvuldigen met een veelvoud van 256. Voor een 16 bit getal ziet er dat dan zo uit : word my16bitvar; my16bitvar = (B11111111) X 256 + B0000000; //now my16bitvar is 1111111100000000 Gehele getallen van -32767 tot en met 32768 worden door de compiler opgeslagen als int. Dus ook getallen die in een 8 bit data type (char unsigned char of byte zouden passen). Grotere gehele getallen komen in een long terecht. Het datatype van het resultaat van een bewerking is niet afhankelijk van de variable waarin je het resultaat wil opslagen maar wel van de datatypes van de getallen die verwerkt worden. Dat flink vervelend zijn omdat de compiler twee bewerkingen tussen integers ook zal opslaan als een integer. Zelfs als je aangeeft dat je het resultaat wenst opslaan in een long dan nog zal de compiler eerst de bewerking opslaan in een int en pas daarna die int omzetten naar een long. Daardoor kan je onverwachte resultaten krijgen als het resultaat van uw bewerking niet in een int past. Bijvoorbeeld long i ; i = 16000 * 3; // i = -17536 i = 30000 + 5000; // i = -30536 Om dit op te lossen kan je de compiler forceren om toch die getallen die eigenlijk in een int passen te bewaren als een unsigned int, een long of een unsigned long. Dat doe je door gebruik te maken van het achtervoegsel U, L of de combinatie van beiden UL. (ook kleine letters zijn toegestaan.) Door correct gebruik te maken van het achtervoegsel U en/of L krijg je wel het juiste resultaat. long i ; i = 16000 * 3L; // i = 48000 i = 30000 + 5000U; // i = 35000 Om getallen met een drijvende komma in te geven gebruik je gewoon een punt op de plaats waar wij in ons taalgebied normaal een komma zetten. Bijvoorbeeld 3,1416 schrijf je 3.1416. Voor hele grote en hele kleine getallen met een drijvende komma kan dat ook door gebruik te
maken van een notatie met machten van 10. Dat doe je door de macht van 10 aan te geven met het achtervoegsel E (of e) gevolgd door de gewenste macht. Bijvoorbeeld 476 X 10-6 schrijf je 476E-6 Rekenen (in het Engels arithmetic) Om een waarde aan een variabele toe te kennen (in het Engels assign a value to a variable). Gebruik je de assignment operator =. Bijvoorbeeld int i; i = 5; optellingen, aftrekkingen vermenigvuldigen en delen gebeuren met respectievelijk +, -, * en /. Bijvoorbeeld int i; i = 3 + 2; // i = 5 i = 3 2; // i = 1 i = 3 * 2; // i = 6 i = 6 / 2; // i = 3 Om de rest van een deling te bekomen gebruik je de modulo operator %. Bijvoorbeeld int i; i = 6 % 4; // i = 2 Bij het programmeren komt het heel frequent voor dat je een waarde van een geheel getal met 1 wil verhogen of verlagen. Bijvoorbeeld om aan de int i 1 toe te voegen of af te trekken. Dat zou je kunnen schrijven als : int i = 10; i = i + 1; // i = 11 i = i - 1; // i = 10 In Arduino is het gebruikelijk om dat te noteren met een compound operator. De code hieronder geeft identiek hetzelfde resultaat als de code hierboven. De compiler genereert ook identiek dezelfde machinetaal. int i = 10; i++; // i = 11 i--; // i = 10 Zo een increment (++) of decrement (--) kan je ook schrijven voor een variabele. int i = 10; ++i ; // i = 11 -- i ; // i = 10 De versie i++ en i noemt men respectievelijk post-increment en post-decrement. De versie ++i en i noemt met respectievelijk pre-increment en pre-decrement. Het verschil wordt duidelijk bij een assignatie zoals geïllustreerd hieronder int X = 10, Y; Y = X++; // X = 11 and Y = 10 X = 10 ; Y = ++X ; // X = 11 and Y = 11 In de tweede regel van de code hierboven zal X toegekend worden aan Y en dan verhoogd men X met één (post increment). In de vierde regel zal eerst X verhoogd worden met één en dan wordt X toegekend aan Y (preincrement). Een andere veel voorkomende situatie is dat je bij een variabele een getal optelt en het resultaat weer opslaat in diezelfde variabele. Dat kan je schrijven als : int i = 100; i = i + 10; // i = 110 In de Arduino taal is het gebruikelijk ook dat te noteren met een compound statement. int i = 100; i += 10; // i = 110 Ook hier genereert de compiler exact dezelfde machinetaal. Er bestaat ook een compound operator voor een aftrekking, een vermenigvuldiging, een deling of een restberekening. Dat ziet er dan als volgt uit : int i = 100; i += 10; // i = 110 I -= 20; // i = 90 i *= 2; // i = 180
I /= 3; // i = 60 i% = 50; // i = 10 Machtsverheffing doe je met de functie pow. Bijvoorbeeld 2 3 int i; i = pow(2,3); // i = 8 Zo kan je ook exotische wortels bekekenen. Je kan een wortel namelijk ook schrijven als een macht. Zo is bijvoorbeeld de 3demachtswortel van 8 ook 8 tot de 1/3 de macht. Bijvoorbeeld int i; i = pow (8, (1.0/3)); // i = 2 Vierkantswortels (in het Engels square rooth) doe je met de functie sqrt. Bijvoorbeeld int i; i = sqrt(9); // i = 3 De sinus, de cosinus en de tangens bereken je met de functies sin, cos en tan. Daarbij is het heel belangrijk om te onthouden dat je de hoek opgeeft in radialen. De 360 van een cirkel zijn exact 2 * PI radialen. Om een hoek in GRADEN om te zetten in RADIALEN gebruik je de formule: RADIALEN = (2 * PI * GRADEN) / 360 Bijvoorbeeld float res ; int deg = 30 ; res = sin((2 * PI * deg) / 360); // res = 0.5 res = cos((2 * PI * deg) / 360); // res = 0.87 deg = 45 ; res = tan((2 * PI * deg) / 360); // res = 1 Met abs bereken je de absolute waarde van een getal. Bijvoorbeeld float myfloat, myf2 = -3e-2; myfloat = abs(myf2); // myfloat = 0.03 In de Arduino IDE zijn er verder nog een paar speciale functies opgenomen. Met de functie min en max bepaal je het kleinste of het grootste getal van twee. Bijvoorbeeld : int i = 2, j = 3, k = 4; i = min(j,k); //i = 3 i = max(j,k); //i = 4 Met constrain kan je waarden binnen limieten houden. Dat kan heel handig zijn als je de resultaten van een sensor gaat bewerken. Bijvoorbeeld : int i=2; i=constrain(i,10,150); // i = 10 i = 20; i=constrain(i,10,150); // i = 20 i=200; i=constrain(i,10,150); // i = 150 Met map kan je waarde binnen een bereik omzetten in een waarde binnen een ander bereik. Dat kan heel handig zijn om resultaten van een sensor om te zetten in naar een andere schaal. De code in het voorbeeld hieronder zou je kunnen gebruiken om een waarde 0 tot 1023 (het resultaat van een analogread zoals we later gaan zien) om te zetten in een spanning tussen 0 en 5000 mv. int i=500; i=map(i,0,1023,0, 5000); // i = 2443 Bits en bytes manipuleren In de toepassingen waarvoor een Arduino veel gebruikt wordt komt het frequent voor dat we rechtstreeks operaties op bits en bytes willen uitvoeren. De instructies die daarvoor ter beschikking staan volgen hier. Om de hoogste of laagste byte uit een variabele te isoleren kunnen we gebruik maken van respectievelijk highbyte en lowbyte. Bijvoorbeeld : unsigned int; byte j; i = ((B11110000 * 256) + B00001111); j = lowbyte(i); // j = B00001111 j = highbyte(i); // j = B11110000
Met bitread, bitwrite, bitset en bitclear kunnen we individulele bits in een variabele lezen of schrijven. Bijvoorbeeld : unsigned int; boolean j; i = ((B11110000 * 256) + B00001111); j = bitread(i,0); // j = 1 bitwrite(i,14,0);// i = B1011000000001111 bitset(i,14); // i = B1111000000001111 bitclear(i,14); // i = B1011000000001111 Nog een gelijkaardige functie is bit. De functie bit geeft je de waarde van een bit. Dat is een macht van 2 afhankelijk van de positie van de bit. Je zou dat dus ook kunnen uitrekenen met pow(2,bit). Ziehier het voorbeeld met gebruik van de functie bit. int i; i = bit(7); // i = 128; Er zijn ook een aantal operators voor bit operaties die elke individuele bit van de ganse variabele bewerken. Ze werken zoals logische poorten en we hebben dan ook AND, OR en XOR met de respectievelijke operators &, en ^. Natuurlijk moeten we dan ook NAND, NOR end NXOR kunnen uitvoeren. Dat dat kan met ~. De volgende code illustreert het gebruik van elk van deze operatoren. byte i, j, k; i = B11110000; j = B10000001; k = i & j; k = ~(i & j); k = i j; k = ~(i j); k = i ^ j; k = ~(i ^ j); // k = B11110000 AND // k = B01111111 NAND // k = B11110001 OR // k = B01111111 NOR // k = B01110001 XOR // k = B10001110 NXOR Van AND & en OR bestaat er ook een compound operator : byte i = 1, j = B11110001; i&= j; // i = B00000001 i = j; // i = B11110001 Deze laatste twee operatoren in deze rij zijn de shift left << en shift right >>. Ze verplaatsen alle bits in een variabele één positie naar links of naar rechts. De vrijgekomen bit wordt gevuld met nul. Bijvoorbeeld : byte i, j = B11110000; i= j<<1; // i = B11100000 i= j>>1; // i = B01111000 Booleaanse rekenkunde Voor we kunnen ingaan op conditionele verwerking van instructies moeten we het eerst hebben over het opstellen en evalueren van condities. Zo een conditie evalueert uiteindelijk naar waar of niet waar (ja of neen, true or false). Om dat soort bewerkingen uit te voeren hebben we een hele reeks operatoren ter beschikking: gelijk aan ==, niet gelijk aan!=, kleiner dan <, groter dan > kleiner dan of gelijk aan <= en groter dan of gelijk aan >=. Deze code illustreert het gebruik daarvan. int i = 10, j = 12; boolean k; k= i < j; // k = 1 k= i > j; // k = 0 k= i == j; // k = 0 k= i == 10; // k = 1 k= i <= 10; // k = 1 k= i >= 10; // k = 1 Het is niet uitzonderlijk dat er meer dan 2 waarden moeten vergeleken worden. Om dat mogelijk te maken hebben we de booleaanse operatoren AND &&, OR en NOT!. ter beschikking. Die gebruik je zo : boolean k; k = true && true; // k = 1 k = true && false; // k = 0 k = true false; // k = 1 k =!(true false); // k = 0 Om deze reeks af te ronden hebben we ook nog een hele resem voorgeprogrammeerde testen ter beschikking. Deze zijn : isalphanumeric
isalpha isascii iswhitespace iscontrol isdigit isgraph islowercase isprintable ispunct isspace isuppercase ishexadecimaldigit Het meest gangbare gebruik van deze testen is de controle van input van een gebruiker. De namen van elk van deze functies zijn behoorlijk zelf verklarend. Vandaar dat we ons hier beperken tot de illustratie van het gebruik van isalphanumeric. Alle anderen zijn analoog. isalphanumeric is een functie die nar waar (true) evalueert als het teken dat je doorgeeft aan de functie een alfanumeriek teken is. De alfanumerieke tekens zijn de kleine letters van a tot z, de hoofdletters van A tot Z en de cijfers van 0 tot 9. char mychar = 'P'; boolean myb; myb = isalphanumeric(mychar); //myb = true; mychar = $ ; myb = isalphanumeric(mychar); //myb = false; Conditionele verwerking : if(), else if() en else Normaal wordt een schets uitgevoerd instructie na instructie van boven naar beneden. Men noemt dat sequentiëel. Er zijn nogal wat situaties waarbij je wil dat een stuk code alleen uitgevoerd wordt als er aan een bepaalde voorwaarde (in het Engels condition) wordt voldaan. Dat kan perfect door gebruik te maken van if(). if (condition) { // code if condition is true Het komt ook veel voor dat je een ander stuk code wil uitvoeren als aan een andere voorwaarde wordt voldaan. Dat kan je doen door gebruik te maken van else if(). Dat zou er dan zo uitzien : if (conditiona){ // code when conditiona is true else if (conditionb){ // code when condition is true Je kan in zo een if else if structuur een onbeperkt aantal keer else if() herhalen. Het zal u ook wel overkomen dat je stuk code hebt dat je wil uitvoeren al er aan geen enkele van de voorwaarden werd voldaan. Dat kan dat kan met else() en ziet er dan zo uit : if (conditiona){ // code when conditiona is true else if (conditionb){ // code when condition is true Else { //code when conditiona and //conditionb are false Conditionele verwerking : switch() case en break Een mooi alternatief voor het gebruik van if() krijg je als je op basis van een geheel getal een aantal stukken code wil laten uitvoeren. Dat gaat als volgt. int A=3; switch (A) { case 1: // code if A is 1 break; case 2: // code if A is 2 break; case 3: //code if A is 3 break; default: // code if A is none of the above
Het zal je wel eens overkomen dat je vergeet om een stuk code af te sluiten met break. Als dat gebeurt dan loopt de verwerking van je code door naar de instructies die in de volgende case staan. Dat zou perfect je bedoeling kunnen zijn. Vandaar dat je expliciet break moet gebruiken als je dat niet wil. Ja kan break ook gebruiken in combinatie met if() of in de loops die we dadelijk gaan gebruiken. Als je compiler break tegen komt dan springt hij naar de eerste instructie die volgt op het afsluiten van switch(), if() of de loops die we dadelijk gaan uitleggen. Loops : while() en do while() Als je wil dat een stuk code wordt uitgevoerd zolang er aan een bepaalde voorwaarde wordt voldaan dan kan dat met while(). while (condition) { // Code that will get executed as long // as condition is true Een klassieker in de C-taal is de eeuwige loop. while (1) { // Code that will get executed forever Soms heb je de situatie waarbij er een stuk code hoe dan ook moet uitgevoerd worden en pas daarna wil je dat de code alleen wordt uitgevoerd als er aan een conditie wordt voldaan. Dan kan met do while(). do { // Code that will get executed at least // one time and // that will get executed over and over // again as long as condition is true while ( condition); Loop : for() Misschien wel de meest gebruikte loop is de for-loop. Dat ziet er zo uit : for( initialisation ; condition ; change ) { //Code Met daarin : Initialisation : de plaats waar je een variablele initialiseert met een bepaalde waarde. Condition : de conditie die wordt nagekeken en alleen als daaraan voldaan wordt zal je code uitgevoerd worden Change : de plaats waar je een verandering kan plaatsen. Meestal een increment (plus één). Deze verandering zal uitgevoerd worden telkens nadat de code tussen de accolades { werd uitgevoerd. Een voorbeeld zal hier veel helpen om dat te verduidelijken : for( int i = 1 ; i <10 ; i++ ) { //code De code in het voorbeeld hierboven zal een eerste keer doorlopen worden waarbij de waarde van i in de code 1 zal zijn. Nadat de code doorlopen werd zal i verhoogd worden met 1 en zal er nagegaan worden of i kleiner is dan 10. Als dat waar is wordt de code opnieuw doorlopen waarbij i nu de waarde 2 heeft. Dat gaat zo voort tot de code doorlopen werd met als waarde voor i 9. Ook nu wordt i verhoogd met 1 maar nu is i 10 en dus is i niet meer kleiner dan 10. Het gevolg is dat de uitvoering van uw schets verder gaat naar de eerste instructie die volgt na het sluiten van de accolades. De code hierboven zal dus 9 keer doorlopen worden waarbij i de waarden 1 tot en met 9 zal hebben. Het is gebruikelijk om in de initialisatie het datatype op te nemen. Je had echter ook dat datatype vooraf kunnen aangeven. Gezien er ook wel programmeurs zijn die daar de voorkeur aan geven willen we je tonen hoe dat er dan uit ziet : int i ; for(i = 1 ; i <10 ; i++ ) { //code
Je hoeft de verandering (change) ook niet te beperken tot het verhogen met 1. Je kan ook een heel andere aanpassing doen, bijvoorbeeld verhogen met 10 of gelijk welke andere waarde. Nog straffer! Je hoeft helemaal geen verandering op te nemen in uw if! Maar als je niet wil terecht komen in een eeuwige loop moet er in je code dan wel een instructie staan die een invloed heeft op de conditie. Dit was geen volledige bespreking van alle mogelijkheden van de for-loop. Het zal echter ruimschoots volstaan voor de modale en zelfs redelijk gevorderde Arduino programmeur. continue Je kan de verwerking van de code in en loop afbreken door gebruik te maken van continue. Het gebruik van continue heeft tot gevolg dat de uitvoering van je code wordt verder gezet met het testen van de conditie. We geven een voorbeeld met for() maar je kan dit ook gebruiken in een while() of een do while(). for (int i = 1 ; i < 10 ; i++) { // Some code continue; // Some more code Dat kan erg nuttig zijn als het zinloos is om een aantal instructies in je loop uit te voeren als er aan een bepaalde conditie wordt voldaan. Dat zou er dan zo kunnen uitzien : for (int i = 1 ; i < 10 ; i++) { // Some code if (condition) continue; // Some more code Return en return Value Je kan de verwerking van een functie afbreken door gebruik te maken van return. Als je return gebruikt zonder dat er een value achter staat dan zal de uitvoering van je functie afgebroken worden net zoals dat zou gebeuren al je de laatste accolade sluit. Voor een functie die gedeclareerd werd als void is dat ook de juiste manier. Als je een functie verlaat die werd gedeclareerd met een data type dan MOET je de functie verlaten met een return EN daarachter MOET er een waarde staan van het type dat je hebt opgegeven bij de declaratie van je functie. int checktime(){ if (millis() > 1000) { return 1; else{ return 0; Serial.begin, Serial.print en Serial.println Onze Arduino heeft een seriële interface. Deze gebruiken we om een schets door te sturen naar de Arduino. Je kan hem ook gebruiken om gegevens naar de Arduino te sturen en om gegevens van de Arduino naar de PC te sturen. Er zijn veel instructies om met deze seriële interface te werken. We beperken ons hier tot deze die we nodig hebben om feedback te krijgen van onze Arduino UNO. We beginnen met Serial.begin. Met deze instructie initialiseren we een C++ object dat we daarna kunnen gebruiken om te communiceren via de seriële interface. We gaan hier echter niet verder in op C++ objecten. Beschouw Serial.begin als een functie die we moeten gebruiken voor we met de seriële interface kunnen werken. Serail.begin kan je daarom best plaatsen in de functie setup van uw schets en in ieder geval voor je gebruik maakt van enige andere instructie voor het gebruik de seriële interface. Serial.begin gebruik je als volt : Serial.begin( Baudrate ); of Serial.begin( Baudrate, Configuration );
Met daarin Baudrate De snelheid waarmee je bits doorstuurt. De serial monitor die is ingebouwd in uw Arduino IDE is ingesteld op 9600 baud. Daarom is het handig om ook als baudrate 9600 op te geven als je deze serial monitor gaat gebruiken. Je zou andere waarden kunnen gebruiken en de baudrate van de serial monitor daarop aanpassen. Er is keuze uit een heleboel waarden van 300 tot 25000. Maar 9600 is een goede waarde. Configuration Hiermee kan je de configuratie van de seriële poort van de Arduino instellen. Het gaat over : - Het aantal bits per teken. - Of er al dan niet een pariteit bit is en als die er is of die even of oneven is (zie achtergrond informatie) en - Of je één of twee stop bits wil gebruiken. Achtergrond informatie. De seriële interface van onze Arduino kan tekens doorsturen die bestaan uit 5, 6, 7 of 8 bits. De oude telex gebruikte tekens van 5 bits. De normale ASCII-code heeft tekens die bestaan uit 7 bits. De extended ASCII-code heeft tekens die bestaan uit 8 bits. Een pariteit is een vorm van error controle. De zender telt alle bits die op één staan van het teken dat hij doorstuurt. Hij voegt dan een extra bit toe. Deze bit is de pariteit bit. Als de pariteit is ingesteld op even (in het Engels Even parity afgekort als E) dan zal die extra bit ervoor zorgen dat de telling van alle bits plus de pariteit bit een even getal oplevert. De ontvanger doet hetzelfde. Daardoor weet de ontvanger of er een fout is opgetreden of niet. Dat is natuurlijk alleen waar indien er slechts één bit fout werd doorgestuurd. Je mag er echter op vertrouwen dat een fout van één bit al héél zelden is. De kans op twee foute bits bij het doorsturen van één teken is dan ook virtueel onbestaande. Naar analogie zou je ook een oneven pariteit (in het Engels Odd parity afgekort als O) kunnen instellen. Je hoeft bij een goede verbinding die werkt aan een snelheid die duidelijk onder de limiet van het betrouwbare ligt helemaal geen pariteit bit te gebruiken. (In het Engels no parity afgekort als N). Om de tekens die je doorstuurt te scheiden heb je minstens één stop-bit nodig. Je kan echter ook aangeven dat je er twee wil gebruiken. De seriële monitor in de Arduino IDE is standaard ingesteld op 8 bits per teken, geen pariteit bit en één stop bit. Dat kort men af als 8N1. Als je bij het gebruik van Serial.begin geen configuratie meegeeft dan staat ok de seriële interface van uw Arduino op 8N1. Daarom is het handig om gewoon geen configuratie mee te geven en het kort te houden met Serial.begin(9600). Met Serial.print en Serial.println stuur je gegevens naar de seriële poort. Bij Serial.println voegt de Arduino er een cariage return en linefeed aan toe. (Denk aan een oude schrijfmachine ). Op de serial monitor van de Arduino IDE heeft dat tot gevolg dat wat je stuurt na het gebruik van Searial.print pal achter de vorige tekens komt te staan. Wat je stuurt na een Serial.println komt aan het begin van de volgende regel. In de code hieronder illustreren we het gebruik van deze instructies. We beginnen met de gebruikelijke Hello, world! van elke cursus C-taal. De code hieronder drukt Hello, world! op de serial monitor. Dat kan je testen door de code te kopiëren naar uw schets. Door met je muis te klikken op de grote pijl naar rechts, links bovenaan in de Arduino IDE, compileer je de code naar machinetaal en programmeer je die machinetaal in uw Arduino. Daarna open je de serial monitor door te drukken op het vergrootglas rechts bovenaan in je Arduino IDE interface. void setup() { Serial.begin(9600); Serial.print("Hello, world!");
void loop() { Als het goed is heb je nu Hello World voor je staan op de serial monitor. Mooi zo! Als we met de volgende code twee keer na elkaar afdrukken met Serial.print zien we dat de tekst nu Hello, world!helo, world oplevert. void setup() { Serial.begin(9600); Serial.print("Hello, world!"); Serial.print("Hello, world!"); void loop() { Dat had je allicht liever op twee regels zien verschijnen. Dat kan door gebruik te maken van Serial.println. void setup() { Serial.begin(9600); Serial.println("Hello, world!"); Serial.print("Hello, world!"); void loop() { Als je de resultaten toch liever op één lijn had zien staan maar wel gescheiden van elkaar dan kan dat door een spatie (of meerdere spaties) te plaatsen als laatste teken(s) van de eerste Hello, World. Zo dus : void setup() { Serial.begin(9600); Serial.print("Hello, world! "); Serial.print("Hello, world!"); void loop() { Een alternatief is het gebruik van een tab. Dat kan met de volgende code. void setup() { Serial.begin(9600); Serial.print("Hello, world!"); Serial.print("\t"); Serial.print("Hello, world!"); void loop() { Als je gehele getallen afdrukt dan kan je ervoor zorgen dat die getallen afgedrukt worden in een getallenstelsel van uw keuze. Dat doe je door achter het getal op te geven welk getallenstelsel je wil gebruiken. De code hieronder illustreert dat. Serial.println(78, BIN); // prints "1001110" Serial.println(78, OCT); // prints "116" Serial.println(78, DEC); // prints "78" Serial.println(78, HEX); // prints "4E" Tenslotte willen we nog vermelden dat je voor getallen met een decimaal kan aangeven hoeveel decimalen moeten afgedrukt worden. Dat doe je zo : Serial.println(1.23456, 0); // prints "1" Serial.println(1.23456, 2); // prints "1.23" Serial.println(1.23456, 4); // prints "1.2346" delay en delaymicroseconds Als we de Arduino even willen doen wachten dan kan dat met de functies delay en delaymicroseconds. Met delay geef je de wachttijd op in milliseconden. 1000 milliseconden is één seconde. Met delaymicroseconds geef je de wachttijd op in microseconden. 1000 micoseconden is één miliseconde. De volgende code gebruikt delay om de output te reduceren naar een menselijk tempo. void setup(){ Serial.begin(9600); void loop(){ Serial.println("Hello, world!");
delay(1000); millis en micros Uw Arduino telt het aantal keer dat 4 microseconden verstreken is sinds de vorige reset en houdt die teling bij in een variabele van het type unsigned long. In de functie micros wordt deze variabele vermenigvuldigd met 4. Met de functie micros kan je dus het aantal microseconden lezen dat verstreken is sinds de vorige reset. Die waarde zal juist zijn tot op 4 microseconden tot ongeveer 70 minuten na een reset. Na ongeveer 70 minuten zal de variabele waarop deze functie gebaseerd is immers overlopen. Uw Arduino telt het aantal miliseconden dat verstreken is na een reset. Eigenlijk is er een variabele van het type long die veelvouden telt van 1024 microseconden. De de functie millis corigeert het resultaat zo goed als mogelijk is. Met de functie millis kan je het aantal milliseconden lezen die verstreken zijn sinds de vorige reset. De waarde is om de 1000 milliseconden ook helemaal juist maar kan voor waardes die geen veelvoud zijn van 1000 ook tijdelijk 1 miliseconde fout zijn. Deze telling zal na ongeveer 9 dagen fout lopen omdat de variabel waarop de telling gebaseerd is dan overloopt. Unsigned long timeelapsed; timeelapsed = micros(); Unsigned long timeelapsed; timeelapsed = millis();
Versie 1.0 Het originele document Versie 1.1 Met een aantal taalkundige rechtzettingen op de eerste pagina s met dank aan Kris Carlier.