Track 3

Jullie zijn aanbeland bij de laatste stof van deze site. In deze laatste track gaan we een tweetal pittige laatste onderdelen behandelen. We gaan kijken naar interrupts en timers op de Arduino, om zo onze Arduino nog meer te kunnen controleren.

Interrupts

Tot nu toe hebben jullie leren werken met de setup() en de loop() functie. In de setup() functie zetten we alle code die we één keer willen draaien bij het opstarten van de Arduino en in de loop() functie staat alle code die keer-op-keer moet worden uitgevoerd. Als je wilt uitlezen of een knopje wordt ingedrukt, moet je in de loop() functie telkens de status van dat knopje opvragen. Naarmate je project groter wordt, kan het voorkomen dat je loop() functie een tijd duurt voordat deze is afgelopen, of er zitten een aantal delay() functies in, die ervoor zorgen dat je zomaar een input van de gebruiker kan missen. Om dit probleem op te lossen, gaan we kijken naar interrupts.

Wellicht weet je nog wat interrupts zijn uit eerdere vakken die je gehad hebt. Voor de zekerheid hier ook nog even een uitleg. Een interrupt is een signaal dat op de Arduino binnenkomt en direct afgehandeld wordt. Effectief betekend dit dus dat de Arduino de loop() functie onderbreekt, de code uitvoert die je aan het interrupt hebt gekoppeld, en de loop() functie weer oppakt op het punt waar deze was onderbroken. Hieronder staat een stuk code die gebruik maakt van een interrupt. Onder de code leggen we uit hoe deze werkt.

const int BUTTON = 2;
const int LED1 = 3;
const int LED2 = 4;

void setup() {
 pinMode(BUTTON, INPUT_PULLUP);
 pinMode(LED1, OUTPUT);
 pinMode(LED2, OUTPUT);

 attachInterrupt(digitalPinToInterrupt(BUTTON), blink, RISING);
}

void loop() {
 digitalWrite(LED1, HIGH);
 delay(1000);
 digitalWrite(LED1, LOW);
 delay(1000);
}

void blink() {
 digitalWrite(LED2, !digitalRead(LED2));
}

Het eerste gedeelte van de code moet inmiddels vertrouwd voorkomen. Bovenaan de code geven we aan op welke pinnen we een knopje en twee leds aansluiten. Vervolgens zetten we deze pinnen in de setup() functie in de juiste stand: in of output, maar onderaan de setup() functie gebeurd er iets nieuws. Met de attatchInterrupt() functie gaan we een nieuw interrupt aanmaken. Het eerste wat we meegeven aan de functie is de uitkomst van een andere functie, namelijk de digitalPinToInterrupt() functie, waaraan we het nummer van de pin meegeven, waar we de interrupt op willen afvangen. Hierna geven we aan de attachInterrupt() functie de naam van de functie die we willen aanroepen als er een interrupt wordt gegenereerd mee. Dat is dus de functie naam, ZONDER de ronde haken! Onderaan het code fragment zie je bijvoorbeeld de blink() functie staan. Dit is de functie die wordt uitgevoerd als er een interrupt op pin 2 wordt gegenereerd. En kijk eens goed naar de blink() functie, waar we in slechts een enkel statement de huidige waarde van de LED opvragen en de geïnverteerde waarde weer terugsturen. De huidige status van de LED2 pin wordt namelijk uitgelezen en vervolgens door het uitroepteken geïnverteerd. Deze geïnverteerde waarde wordt weer naar de pin weggeschreven, waardoor de LED van status veranderd als er op de knop wordt gedrukt. Worden jullie ook niet heel gelukkig van zulke elegante code . Het enige wat we nog aan de attachInterrupt functie mee moeten geven is wanneer er een interrupt gegenereerd moet worden. Daarvoor hebben we de volgende mogelijke waarde:

Voordat we jullie aan de slag laten gaan met deze stof, moeten we jullie op nog een paar dingen wijzen. Het eerste is dat op de Ardiuno Nano alleen op pin 2 en 3 een interrupt gegenereerd kan worden. Welke pinnen dat op andere boards zijn en meer uitleg over interrupts, is te vinden op:

https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

Daarnaast is het zo dat wanneer er een interrupt wordt uitgevoerd, de code uit de loop() functie NOOIT loopt en de Arduino naar geen enkel interrupt meer luistert. Zorg er dus altijd voor dat de code in een interrupt zo klein mogelijk is! Ook werkt de delay() functie niet in een interrupt, omdat deze onder water zelf gebruikt maakt van een interrupt (!). In het onderstaande filmpje demonstreren we wat de gevolgen zijn van bovenstaande eigenschapen.

Goed, dit is inmiddels weer meer dan genoeg uitleg. Tijd om al deze kennis eens te gaan toepassen in een eigen schakeling.

Download de interrupt opdracht

Timers

In het vorige blok hebben we jullie geleerd hoe je externe interrupts op de Arduino kunt opvangen, maar de Arduino kan ook een aantal interne interrupts genereren. Een dergelijke soort interrupt is een timer. Dit is een interrupt dat op een vast gesteld moment wordt aangeroepen door de Arduino zelf en wordt gebruikt op het moment dat exacte timing cruciaal is. Op de site van Arduino Playground worden twee libraries gegeven. De Timer1 en de Timer3 library:

http://playground.arduino.cc/Code/Timer1

Navigeer naar de bovenstaande site en klik op de link: 'TimerOne Google Code download'. Download van de pagina die wordt geopend de meest recente .zip file van de timer en zet deze in dezelfde map als je project. Op het moment dat je een Arduino project opslaat, maakt de IDE een map aan met daarin een .ino file met dezelfde naam als de map. Pak de .zip file uit in deze map. In de .zip file zitten wat bestanden en een nieuwe map, maar de belangrijkste bestanden voor nu zijn de 'TimerOne.h' en de 'TimerOne.cpp' files. De code van de timer is in de Object-georiënteerde taal C++ geschreven. Wees niet bang, die hoef je niet te kennen voor het gebruiken van de library, maar er is wel wat uitleg nodig over het gebruiken van dergelijke libraries.

Zo meteen gaan we de TimerOne.h file includen in ons project. De TimerOne library bevat de code van de TimerOne class. In de .h file staat een overzicht van alle functies en variabelen die we kunnen gebruiken en in de .cpp file staat de volledige code van de functies. Dit is standaard voor C++ classes. Door de .h file in onze code te includen, pakt de compiler tijdens het compileren de .cpp file er automatisch bij. Een ander verschil is dat we nu voor het eerst werken met een lokale library. Vaak gebruik je het < en het > teken om libraries te includen. Wanneer het < en het > teken om een library naam heen staan, zoekt de compiler in de set met libraries die standaard met de IDE worden meegeleverd of door middel van de IDE zijn geïnstalleerd. In dit geval gaan we twee dubbele accolades om de naam heen zetten, waarmee we tegen de compiler zeggen dat hij moet gaan zoeken in de map waar ook de .ino file in staat. Met de volgende code kunnen we vervolgens een Timer aanmaken.

#include "TimerOne.h"

const int LED1 = 3;
const int LED2 = 4;

void setup() {
 pinMode(LED1, OUTPUT);
 pinMode(LED2, OUTPUT);

 Timer1.initialize(1000000);
 Timer1.attachInterrupt(change);
}

void loop () {
 digitalWrite(LED1, HIGH);
 delay(500);
 digitalWrite(LED1, LOW);
 delay(500);
}

void change() {
 digitalWrite(LED2, !digitalRead(LED2));
}

De bekende code gaan we niet meer uitleggen, maar we focussen ons even op de nieuwe code. Allereerst zien we dat we de Timer1 moeten initialiseren. Hier geven we de interval periode in microseconden mee, dus om de hoeveel microseconde de Timer af moet gaan. In ons geval is dit een seconde. Vervolgens herkennen we de attachInterrupt methode, waar we de functie naam (wederom zonder haakjes) meegeven van de functie die moet worden aangeroepen. In de loop() wordt aan het begin een ledje aangesproken dat om de halve seconde knippert, om zo het verschil tussen de twee ledjes te zien. In de change() functie wordt, net zoals in het vorige code fragment, de status van het ledje geïnverteerd. De hoogste tijd om weer eens een mooie opdracht te maken!

Download de timers opdracht

Wat laatste woorden over interrupts

Nu je geleerd hebt over interrupts is het goed om te weten dat interrupts meer voorkomen dan je wellicht zou denken. In een aantal Arduino functies of libraries die je van het internet haalt, kan je tegenkomen dat er onder water gebruik gemaakt wordt van interrupts. De processor die op de Arduino zit (een type Atmega processor) bied een aantal mogelijke interrupts aan. Voorbeelden hiervan zijn: een interrupt die wordt aangeroepen als de SPI verbinding klaar is met verzenden, of als de seriële verbinding klaar is met verzenden of ontvangen. Als je in de toekomst met interrupts gaat werken en je Arduino reageert anders dan je verwacht, kan het zijn dat er ergens anders in de code ook van een interrupt gebruik wordt gemaakt.

Track 3 eindopdracht

Download de Track 3 eindopdracht