Bewerkingen

invloed van het variabelentype

C++ kent heel wat bewerkingen. De meeste spreken voor zichzelf, maar je moet wel rekening houden met het type van de operanden waarmee een bewerking wordt uitgevoerd.

Als algemene regel geldt dat het resultaat van een bewerking van hetzelfde type zal zijn als dat van de grootste operand. Wanneer de types van de operanden gelijk zijn is het eenvoudig. Ook het resultaat zal van hetzelfde type zijn.

Wanneer een bewerking op operanden van een verschillend type wordt uitgevoerd, zal het resultaat van hetzelfde type zijn als de het type van de operand die meest plaats inneemt.

Valkuil

Denk er aan dat de waarde die in een variabele kan worden opgeslagen beperkt is volgens het type van variabele. Zo zal een variabele x die een waarde 250 bevat na het optellen van 50 bij de variabele een overflow geven. Het resultaat zal dus 44 (300-256) zijn i.p.v. 300.

Via onderstaande code is dit uit te testen.

void setup() {
  Serial.begin(115200);
}

void loop() {
  byte x=250;
  Serial.println(x);
  x=x+50;
  Serial.println(x);
}

In bovenstaand programma maken we gebruik van de seriële verbinding tussen Arduino en PC om de resultaten zichtbaar te maken.

In de loop()-functie wordt een byte x gedefinieerd en geïnitialiseerd met de waarde 250. De inhoud van x wordt doorgestuurd naar de terminal waarop de waarde 250 verschijnt in het terminalvenster.

Daarna wordt x verhoogd met 50 en opnieuw via het serieel kanaal verstuurd. In het terminalvenster verschijnt nu 44.

Ook het combineren van types kan problemen opleveren.

Valkuil

Wanneer we bij byte x een waarde optellen die een float is (dus een kommagetal) lijkt dit in eerste instantie geen problemen op te leveren. De code

void setup(){
  Serial.begin(115200);
  }
 
void loop() {
  byte x=250;
  Serial.println(x);
  x=x+5.0;
  Serial.println(x);
  }

levert in het terminalvenster mooi de waarden 250 en 255 op. Wanneer we van de 5.0 echter 50.0 maken, krijgen we dus overflow, maar het resultaat dat in het terminalvenster verschijnt is nu ook 250 en 255. Bij een overflow met een float krijgen we dus een ander resultaat (255) dan bij een overflow met een byte waar het resultaat 44 was.

samengestelde bewerkingen

C++ kent enkele verkorte notaties voor de gangbare bewerkingen

uitdrukking kan verkort geschreven worden als
i = i + 1 i++
i = i - 1 i--
x = x + y x+ = y
x = x - y x- = y
x = x * y x* = y
x = x / y x/ = y
 

vergelijkingen

betekenis uitdrukking
is gelijk aan ==
is verschillend van !=
is groter dan >
is kleiner dan <
is groter dan of gelijk aan

>=

is kleiner dan of gelijk aan <=

Valkuil

Merk op dat een enkel gelijkheidsteken dient voor een toekenning. Zo is de uitdrukking

x = y;

een toekenning van de waarde van y aan de variabele x, terwijl de uitdrukking

x == y;

een vergelijking is van de waarden van x en y die resulteert in de uitdrukking TRUE (1) of FALSE (0).

logische bewerkingen

C++ kent uiteraard ook de logische functies AND, OR en NOT. Ze kunnen echter onder verschillende vormen voorkomen.

Een logische uitdrukking is een uitdrukking die resulteert in de logische constante TRUE (1) of FALSE (0). Zo zal de code

void setup(){
  Serial.begin(115200);
  }
 
void loop() {
  Serial.println(3==3);
  }

in het terminalvenster een 1 opleveren, terwijl

void setup(){
  Serial.begin(115200);
  }
 
void loop() {
  Serial.println(3==4);
  }

een nul doet verschijnen.

We kunnen met deze logische uitdrukkingen bewerkingen uitvoeren door gebruik te maken van de logische operatoren &&, || en !.

Dit komt uiteraard veel voor in een voorwaardelijke instructie zoals een if-constructie (zie verder)

if (x>0 && x<5) { Serial.print("x ligt tussen 0 en 5"); }

Dit werkt ook met getallen, maar daarbij moeten we rekening houden dat alleen het cijfer 0 als FALSE geïnterpreteerd wordt, terwijl elke andere waarde voor TRUE doorgaat. Zo zal de uitdrukking 3&&2 een 1 (=TRUE) opleveren, terwijl een 3&&0 als resultaat een 0 (FALSE) geeft.

Scope

De plaats binnen het programma waar de variabele gedefinieerd wordt is zeer belangrijk. Deze bepaalt immers de scope of het bereik van de variabele. De scope is de plaats binnen het programma waar de variabele bestaat.

Een variabele bestaat alleen binnen het codeblok waarin hij gedefinieerd werd. We kunnen dit visualiseren door gebruik te maken van het boxmodel.

Het programma vormt de hoofdbox. Binnen dit programma staan twee functies, setup() en loop(). Elk van deze functies vormen een subbox binnen het programma. Binnen deze functies kunnen weer andere functies gedefinieerd zijn, die dan weer een subbox van de subbox zijn.

Zelfs een enkelvoudige instructie van een subbox zijn.

Een variabele die binnen een subbox gedefinieerd werd, bestaat alleen in deze subbox. In de omvattende box van deze subbox bestaat de variabele niet meer.

Bekijken we even onderstaand voorbeeldprogramma.

Een variabele van het type integer, met als naam segA krijgt de waarde 4 toegekend binnen de functie setup. Wanneer we deze variabele gebruiken in de loop-functie krijgen we een foutmelding “segA was not declared in this scope”, mat andere woorden, binnen de loop-functie bestaat er geen variabele met als naam segA.

scope voorbeeld 1

Dit zorgt voor rare toestanden. In het volgend voorbeeldprogramma hebben we opnieuw een variabele segA gedefinieerd in de setup-functie en er de waarde 5 aan toegekend.
In de loop-functie definieren we segA opnieuw, maar deze keer geven we de 4 aan de variabele.

scope voorbeeld 2


Hoewel beide variabelen dezelfde naam hebben, gaat het toch om twee verschillende variabelen. Binnen de functie setup bestaat een variabele segA die de waarde 5 bevat, en binnen de loop-functie bestaat een tweede variabele met de naam segA met een waarde 4.
Als we heel exact willen zijn moeten we ook vermelden dat deze twee variabelen nooit tegelijkertijd zullen bestaan. De eerste segA zal bestaan zolang de setup-functie wordt uitgevoerd. Eenmaal de functie doorlopen is, wordt de eerste segA vernietigd. Bij het starten van de loop-functie wordt een nieuwe variabele aangemaakt met als naam segA.

scope voorbeeld 3


 

En het kan nog erger. In dit voorbeeld wordt een variabele segA gedefinieerd buiten de beide functies. Deze variabele behoort dus tot de hoofdbox, en zal ook bestaan binnen beide functies.
In de setup functie is er geen probleem. segA bestaat en heeft de waarde 5 zoals gedefinieerd in de hoofdbox.
Binnen de loop functie wordt een variabele met dezelfde naam gedefinieerd. Binnen loop zijn er nu dus twee variabelen met naam segA, de hoofd-segA en de sub-segA. In zo’n geval heeft de subvariabele (ook locale variabele genoemd) voorrang op de hoofdvariabele. Wanneer we segA gebruiken binnen de loop functie zal deze dus de waarde 4 hebben. Merk op dat de hoofdvariabele ook blijft bestaan en dat deze de waarde 5 houdt.

scope voorbeeld 4

 

Oefeningen

Wanneer we dit programma willen compileren krijgen we een foutmelding. Kun je voorspellen waar die foutmelding zal optreden?

Oplossing

Binnen de setup functie wordt een variabele i gebruikt in de for-lus. Deze wordt netjes als integer gedefinieerd.
Binnen de loop vinden we twee for-lussen terug. In de eerste wordt opnieuw netjes een integer i gedefinieerd. Deze i bestaat dus enkel binnen de for-lus.
Bij de tweede for-lus gaat het verkeerd. Daar wordt een variabele i gebruikt die niet voordien werd gedefinieerd. Daar zal dus een “i was not declared in this scope” fout optreden.

 

variabalen

numeriek

In ons programma willen we gebruik maken van waarden die tijdens en door het programma aangepast worden. Daarvoor hebben we variabelen nodig. Afhankelijk van welke waarden we wensen op te slaan bestaan variabelen in verschillende types.

  • Een byte neemt 1 byte geheugen in beslag en kan waarden van 0 tot en met 255 opslaan.
  • Voor grotere gehele waarden kunnen we een integer gebruiken. Deze beslaat 2 geheugenbytes maar kan in de signed versie waarden opslaan van -32.768 tot +32.767, en in unsigned versie waarden van 0 tot en met 65.537.
  • Als dit nog niet genoeg zou zijn kunnen we een variabele van het type long gebruiken. Deze neemt 4 byte geheugen in, maar kan signed lopen van -2.147.483.648 tot en met +2.147.483.647. De unsigned versie heeft een bereik van 0 tot en met 4.294.967.295.
  • Wanneer we kommagetallen nodig hebben maken we gebruik van de types float en double. Deze komen overeen met single precision floating point en double precision floating point getallen, zoals gezien in het vak digitale elektronica.

Bewerkingen uitvoeren gaat veel gemakkelijker en sneller met gehele getallen. Bij kommagetallen zal je zien dat je programma veel trager werkt.

alfanumeriek

We moeten ook tekst als een variabele kunnen opslaan. Daarvoor hebben we type char wanneer we slechts één karakter moeten opslaan of string wanneer het om woorden of zinnen gaat.

Om een waarde toe te kennen aan een variabele van het type char kunnen we het teken tussen enkele aanhalingstekens plaatsen, of de waarde van de ASCII code toekennen.

Bij een string is de lengte variabel. De tekst van de string wordt afgesloten met een null-teken. Daardoor beslaat een string in het geheugen 1 byte meer dan er karakters zijn in de string.

Er kunnen ook reeksen van variabelen gedefinieerd worden. De reeksen zijn zero-indexed, dus het eerste element van de reeks heeft als index 0. Bij de definitie worden na de variabelenaam vierkante haakjes geplaatst, of worden de elementen opgesomd tussen accolades.

Structuur en syntax

Structuur

Programmeren van de Arduino gebeurt in C/C++. Deze taal is zeer gestructureerd en is opgebouwd uit functies.

Een C-programma bevat altijd een functie main(). Bij de Arduino bestaat er geen main() functie, maar is deze vervangen door twee andere functies: setup() en loop().

de basis van elk Arduino programma

 

Wanneer een Arduino wordt opgestart zal hij altijd eerst de setup()-functie uitvoeren, om daarna de loop()-functie blijvend te herhalen.

Het is natuurlijk heel eenvoudig om te zeggen dat een programma uit functies bestaat, maar wat is nu eigenlijk een functie?

Een functie is een groep van instructies die met een eigen naam aangeduid worden. Een functie geeft altijd een resultaat van een bepaald type weer. Indien geen resultaat wordt teruggegeven door de functie is het resultaat van het type void (=leegte). De functies setup() en loop() zijn typisch twee functies die niks teruggeven. Ze zijn dan ook allebei void.

Het type van de teruggegeven waarde is de eerste aanduiding waarmee een functie beschreven wordt. Zo kan een functie bijvoorbeeld een som van twee gehele getallen opleveren. De definitie van deze functie begint dan met de type-aanduiding int.

Na de type-aanduiding krijgen we de naam van de functie. Deze naam kan naar believen gekozen worden, zolang hij maar begint met een letter. Merk op dat namen in C hoofdlettergevoelig zijn.

Na de naam komt een paar haakjes. Deze haakjes kunnen eventuele parameters bevatten die aan de functie worden doorgegeven. Ook als er geen parameters worden doorgegeven moeten de haakjes na de functienaam komen. Dat is terug het geval bij zowel setup() als loop().

We weten nu al wat het resultaat van de functie zal zijn, wat zijn naam is, en met welke parameters de functie zal rekenen, maar hoe bepalen we welke instructies er wel en niet tot de functie behoren?

Daarvoor worden accolades gebruikt. Alles wat binnen de accolades staat behoort tot de functie. Elke instructie wordt van de volgende gescheiden door een puntkomma.

Terug naar de basisstructuur

Zoals reeds eerder vermeld bestaat de basis van een arduino programma uit twee functies:
  • Setup()
  • Loop()

Beide functies zijn van het type void, omdat ze geen informatie teruggeven aan de aanroepende functie. Die is er immers niet.

De setup() functie wordt éénmaal doorlopen en dient dus hoofdzakelijk voor initialisatie. De loop()-functie wordt steeds opnieuw herhaald en vormt een oneindige lus. Hieruit kun je al concluderen dat een microcontroller nooit stopt met werken.

voorbeeldprogramma: Blink

 

Het eerste programma dat gebruikt wordt is zo goed als altijd het blink-programma. Met dit programma kijken we of de arduino omgeving werkt, en of we in staat zijn om een programma op het arduino-bord te laten uitvoeren.

We zien duidelijk de twee basisfuncties setup() en loop().

In de setup wordt pin 13 van het arduino bord geconfigureerd als een output. Op het bord zit een SMD-led die met deze pin 13 verbonden is. Door deze te laten knipperen kunnen we het  bord testen zonder dat we extra hardware nodig hebben.

Het laten knipperen gebeurt in de loop-functie. Met behulp van een digitalWrite instructie wordt pin 13 achtereenvolgens hoog en laag gezet. De controller werkt aan een zeer hoge snelheid, waardoor het nodig is om na elke digitalWrite instructie een delay-functie te gebruiken om het knipperen zodanig te vertragen dat het voor een mens nog te volgen is.

De waarde die als parameter bij de delay-functie wordt meegegeven is een tijd, uitgedrukt in milliseconden. De delay(1000) instructie zal de controller dus 1 seconde doen pauzeren.

syntax

Een programma zal slechts succesvol gecompileerd worden als de syntax van het programma klopt. Dit betekent dat alle code volgens strikte regels moet geschreven worden.

Het groeperen van een aantal instructies gebeurt met behulp van accolades. Dit kan zowel bij de definitie van een functie voorkomen, als bij een instructie zelf, zoals te zien in de for-loop.

gebruik van accolades om statements te groeperen.

Een instructie wordt altijd beëindigd met een puntkomma. Een einde-regel teken heeft geen enkele betekenis binnen een programma. Je kan dus perfect alle instructies op één lijn schrijven, zolang ze maar van elkaar gescheiden zijn door middel van een puntkomma.

In sommige gevallen wil men tekst aan het programma toevoegen om iets te verduidelijken voor iedereen die het programma leest. Het is niet de bedoeling dat deze tekst aan de syntax voldoet. Het gaat hier dus om commentaar, dat door de compiler moet worden overgeslagen.

Het gebruik van commentaar is dan ook zeer belangrijk om in je programma aanwijzingen op te nemen over het verloop of het doel van het programma. Deze wordt door de compiler genegeerd, en zal ook geen invloed hebben op de snelheid of grootte van je programma.

Een eerste manier is de blok commentaar. De combinatie forward slash en sterretje geeft aan dat alles wat hierna komt commentaar is. Je kunt hiermee dus grote stukken commentaar plaatsen tussen de code in. De commentaar wordt afgesloten met de combinatie sterretje-forward slash. Deze combinatie geeft het einde van de blokcommentaar aan. Alles erna wordt weer behandeld als programmacode.

Wanneer we korte commentaar willen toevoegen kunnen we ook gebruik maken van regel commentaar. Deze wordt aangegeven door een dubbele forward slash en wordt niet afgesloten. Alles wat na de dubbele slash staat op deze ene regel wordt als commentaar behandeld. Door naar een volgende regel te gaan wordt de commentaar beëindigd.