Arduino cursus/Dag 2
Dag 2 - cursus Arduino
Input: sensoren
We hebben te maken met verschillende soorten inputs: signalen en events.
Signalen
Een signaal heeft op elk moment een waarde. Meestal is dit een weergave ("spoor") van een fysisch proces.
- Enkele voorbeelden van signalen: geluid; elektrisch signaal van de hartslag; lichtniveau (via LDR); temperatuur; snelheid; versnelling;
Periodieke signalen
Sommige signalen bestaat uit een herhalend patroon. We spreken dan over een periodiek signaal; de periode is de duur van het (kleinste) deel dat steeds herhaald wordt.
- Enkele voorbeelden van periodieke signalen: sinus (toon); PWM-signaal (bijvoorbeeld voor het aansturen van een LED of van een motor);
Digitaliseren: bemonsteren en quantiseren
Een analoog signaal is continu: voor het werken met een signaal in een computer moet je dit discreet maken, door middel van bemonstering en quantisatie met een A/D omzetter. Bemonstering (sampling) betekent dat je periodiek de waarde van het signaal meet. Deze gemeten waarde zet je vervolgens om in een geheel getal (quantiseren). Voor een 10-bits A/D omzetter is dat een getal tussen 0 en 1023. Het digitale signaal beschrijf je dat als een reeks getallen. Als je deze bemonstering vaak en precies genoeg doet, kun je deze reeks waarden beschouwen als een betrouwbare benadering van het oorspronkelijke signaal.
- Hoe vaak is "vaak genoeg"? Dat hangt van het signaal af. Als een signaal snel verandert, met andere woorden: als het signaal hoge frequenties bevat, dan zul je dit sneller moeten bemonsteren. Voor een periodiek signaal geldt dat je dit tenminste tweemaal in een periode moet bemonsteren (https://en.wikipedia.org/wiki/Nyquist_rate); in de praktijk doen we dat meestal iets vaker.
- voorbeeld: de temperatuur in een kamer hoeven we niet elke 1/10 seconde te bemonsteren: zo snel verandert deze niet. In een vriezer of in een koelkast zal de temperatuur nog minder snel veranderen (door de isolatie).
- Hoorbaar geluid heeft frequenties tot ca. 15 kHz; dit zul je dan met tenminste 30 kHz moeten bemonsteren.
- Hoe precies is "precies genoeg"? Ook dat hangt van het signaal af - en wat we ermee willen doen. Voor het regelen van de temperatuur in de woonkamer is 0,5 graden precies genoeg, we hebben dan aan een 10-bits A/D omzetter ruim voldoende. Voor muziek hebben we een veel grotere precisie nodig. Het discretiseren van een analoge waarde levert afrondingsfouten op: quantisatieruis. Bij muziek moet de discretisatie-ruis kleiner zijn dan de ruis in het oorspronkelijke analoge signaal.
Events
Een event is een gebeurtenis die op een bepaald moment plaatsvindt. Hierbij speelt de duur van een event geen rol: een event is "momentaan".
- Voorbeelden van events: het indrukken van een toets; een muisklik; het aflopen van een kookwekker; een val; een stap; een hartslag;
Ook events kunnen (semi-)periodiek zijn: denk bijvoorbeeld aan de stappen van iemand die loopt; of aan een reeks opeenvolgende hartslagen. In deze voorbeelden kan er een kleine variatie in de periode tussen de opeenvolgende events zitten.
Opmerking: als de duur op de een of andere manier wel van belang is, dan kun je gebruik maken van afzonderlijke start- en stop-events.
Event-detectie
Met behulp van signaalverwerking kunnen we een gebeurtenis, zoals een hartslag, herkennen in een signaal, zoals een electrocardiogram (ECG). Een ander voorbeeld is het herkennen van een stap in het signaal van een 3D-versnellingsopnemer (accelerometer).
- Een eenvoudig voorbeeld hiervan: in het signaal van een drukknop kunnen we de overgang van laag naar hoog herkennen: dit komt overeen met het indrukken van de drukknop.
Sensoren
Sommige sensoren meten een signaal:
- temperatuursensor
- lichtsensor (LDR)
- potmeter (potentiometer)
- versnellingsopnemer (accellerometer)
- microfoon
- afstandssensor
Andere sensoren detecteren een event:
- bewegingsmelder
- deursensor (openen/sluiten van een deur)
- brandmelder
Soms gebruiken we een stukje programma om een event in een signaal te herkennen:
- indrukken van een toets
- hartslag in een hartsignaal
Voorbeelden van deze sensoren - voor signalen en events - behandelen we in de opdrachten verderop.
Signalen: van input(sensor) naar output(actuator)
Sensoren met een signaal als output kun je koppelen aan actuatoren met een signaal als input. Soms moet je het signaal daarvoor aanpassen - bijvoorbeeld door middel van schaling (map-functie), filtering, of andere berekeningen. De tabel hieronder geeft voorbeelden van sensoren en actuatoren die je op een dergelijke manier direct kunt koppelen. Sommige combinaties zijn nuttig, andere vooral creatief.
in (signaal) | bewerking | output (signaal) |
---|---|---|
input = analogRead(pin); |
map(in, inFrom, inTo, outFrom, outTo) |
analogWrite(pin, out);
|
potmeter | ...filter... | buzzer (toonhoogte) |
LDR (lichtniveau) | ... | LED (helderheid) |
temperatuursensor | ... | RGB-LED (kleur) |
kracht/gewichtsensor | display (waarde) | |
oriëntatiesensor (kompas) | motor (snelheid) | |
versnellingopnemer | servo (hoek) | |
geluidsniveausensor | ... | |
afstandssensor | ... | |
... | timer (periode) | |
... | -> tikker, enz. | |
... | ... |
De Arduino-code voor deze directe koppeling van input- en outputsignalen kan er als volgt uitzien:
input = analogRead(sensorPin);
output = map(input, inFrom, inTo, outFrom, outTo);
analogWrite(outputPin, output);
Dit kunnen we nog directer weergeven:
analogWrite(outputPin, map(analogRead(sensorPin), inFrom, inTo, outFrom, outTo));
Opdrachten - van sensorsignaal naar actuator-input
input (sensor) | output (actuator) | opdracht(en) | library/function | voorkennis |
---|---|---|---|---|
analoge temperatuursensor | serial plotter; display | Arduino cursus/Analoge temperatuursensor | map(...)
|
display |
digitale temperatuursensor | serial plotter; display | Arduino cursus/Digitale temperatuursensor | OneWire, DallasTemperature | display |
afstandssensor | serial monitor; display; | Arduino cursus/Afstandssensor | pulseIn()
|
display |
hartslagsensor | serial plotter; LED | Arduino cursus/Hartslagsensor | ||
LDR (lichtsensor) | serial plotter; buzzer (tone) | |||
potmeter | servo | Arduino cursus/Servo-0 |
Nog wat ideeën:
- afstandssensor met buzzer (kliksnelheid - vgl. parkeersensor?)
- afstandssensor met servo
- afstandssensor met buzzer (tone)
- potmeter met RGB-LED (kleur)
Events en de afhandeling daarvan
Een event is een gebeurtenis die op een bepaald moment plaatsvindt. Een event is momentaan: deze heeft geen duur. Een voorbeeld is het indrukken van een button. Dit kunnen we herkennen in het (digitale) ingangssignaal, aan de overgang LOW->HIGH. Om deze te detecteren, hebben we zowel de vorige waarde van de input nodig, als de huidige. Deze vorige waarde van de input bewaren we in een globale variabele.
Het optreden van een event kunnen we nagaan aan de hand van de event conditie, bijvoorbeeld: is button A ingedrukt? We kunnen het optreden van de event koppelen aan een actie, de event handler, door in de "forever loop" te controleren op de event-conditie, en afhankelijk daarvan de event handler uit te voeren. Deze event-handler resulteert vaak in een output-actie.
if (eventCondition) {
eventHandler();
}
Voorbeeld. Als event-conditie om na te gaan of een button ingedrukt is, detecteren we de "laag" naar "hoog" overgang in het button-signaal. De output-actie is het omschakelen van de LED. We krijgen dan (in de "forever loop"):
void loop() {
prevInput = input;
input = digitalRead(buttonPin);
if (prevInput == LOW && input == HIGH) {
switchLedState();
}
}
Op deze manier koppelen we input-events direct aan output-acties. (Zoals we eerder input-signalen direct koppelden aan output-signalen.)
input (event) | output (event) | |
---|---|---|
button (indrukken) | digitalWrite(...)
| |
timer | LED aan/uit | |
bewegingsdetector | motor aan/uit; richting | |
relais aan/uit |
Event-opdrachten
Sensor | detectie | output (event) | opdracht(en) | library | voorkennis |
---|---|---|---|---|---|
button | indrukken van button | omschakelen van een LED | Arduino cursus/Button-event | ||
bewegingsdetector | beweging | omschakelen van een LED | Arduino cursus/Bewegingsdetector | ||
hartslagsensor | hartslag (signaalniveau) | knipperen van een LED | Arduino cursus/Hartslagsensor | display; LED |
Meerdere taken "tegelijk"
We hebben tot nu toe steeds gewerkt met een enkele taak - zoals het knipperen van een LED, of het aannsturen van een servo met een potmeter. In een realistische toepassing wil je meerdere taken kunnen uitvoeren. Elke taak heeft daarbij zijn eigen tijdseisen: de LED moet op tijd knipperen, de aansturing van de servo vanuit de potmeter moet "direct" zijn. Computers zijn snel genoeg om meerdere taken "tegelijk" uit te voeren, dat wil zeggen: door snel te wisselen tussen de verschillende taken lijkt het alsof deze tegelijk uitgevoerd worden. Dit noemen we multitasking.
Om snel te kunnen wisselen tussen de deelacties van de verschillende taken is het van belang dat deze deeltaken niet te lang duren: anders kunnen de andere taken niet aan hun tijdseisen voldoen. Voorbeelden van deelacties die lang kunnen duren, en daarmee de voortgang kunnen blokkeren:
delay(t)
- blokkeert de processor gedurende t milliseconden;- wachten op de invoer van een sensor, van de host, e.d.
Een voorbeeld van het gebruik van delay hebben we gezien bij Blink: het knipperen van een LED. Een voorbeeld van het (langdurig) wachten op de input van een sensor zien we bij de digitale temperatuursensor: het kan 750 msec. duren voordat het meetresultaat beschikbaar is.
Een gevolg van het gebruik dan delay bij Blink is dat we geen andere taken tegelijk kunnen uitvoeren. We kunnen bijvoorbeeld niet een tweede LED tegelijk laten knipperen, met een andere frequentie. Dit probleem kunnen we wel oplossen als we timers gebruiken, in plaats van delay: een timer is een soort kookwekker: als deze afloopt, voeren we de uitgestelde actie uit. Terwijl de timer loopt kunnen we andere acties uitvoeren.
Timers
Een timer is een soort kookwekker: je start een timer met een bepaalde duur; als de timer afloopt, voer je een uitgestelde actie uit. Een timer kan ook periodiek zijn, om met een vaste regelmaat een actie uit te voeren. Timers kunnen zowel in hardware als in software uitgevoerd zijn. We behandelen hier een software timer: daarbij maken we gebruik van de "systeemklok": de functie millis()
die het aantal milliseconden telt sinds de laatste reset van de Arduino.
We geven hier het voorbeeld van een knipperende LED met behulp van een timer. Dit is een vereenvoudigde versie van "Blink without delay", zie Bestand->Voorbeelden->02. Digital->BlinkWithoutDelay.
const int ledPin = LED_BUILTIN;
int ledState = LOW;
unsigned long timerPeriod = 1000L;
unsigned long timerStart = 0;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
unsigned long now = millis();
if (now - timerStart >= timerPeriod) {
timerStart = now;
// timer action:
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState);
}
}
Voor de timer is het volgende van belang:
Een timer heeft de volgende onderdelen:
timerStart
- een variabele met het begintijdstip van de timertimerPeriod
- een variabele met de periode van de timer- een conditionele actie, uitgevoerd als de timer afloopt:
if (millis() - timerStart >= timerPeriod) {
timerStart = millis(); // restart timer
.... // timer action
}
- gebruik
unsigned long
voor alle waarden en berekeningen met software timers- het bereik van
int
is -32768..32767; van0..4294967295
- het bereik van
- de test heeft altijd de vorm:
millis() - start >= period
- deze conditie werkt ook bij "overflow" van de milliseconden-teller.
- de opdracht
timerStart = now;
start een volgende periode van de timer. - je stop de timer met de opdracht
timerPeriod = infinity
, metconst unsigned long infinity = 4294967295
Nog enkele opmerkingen:
- een herstartende timer is een periodiek signaal - dit kun je gebruiken voor het genereren van andere periodiek signalen;
- het verschil
start + periode - millis()
geeft de resterende tijd van de timer aan. Dit kun je bijvoorbeeld gebruiken als PWM-parameter, om een LED geleidelijk te laten dimmen.
Timer-opdrachten
Omschrijving | opdracht | voorkennis |
---|---|---|
knipperende LED (duty cycle 50%)) | Arduino cursus/Blink met timer-0 | Blink (LED) |
knipperende LED (duty cycle instelbaar) | Arduino cursus/Blink met timer-1 | Blink (LED) |
twee onafhankelijk knipperende LEDs | Arduino cursus/Blink-2-LEDs | Blink met timer-0 |
tikker (met instelbare periode) | Arduino cursus/Tikker | Blink met timer-0 |
reactietijdmeting | Arduino cursus/Reactietijd | Blink met timer-0 |
Automaten
We hebben hiervoor gezien dat je een event (input) kunt koppelen aan een actie (output). Soms wil je dat de actie niet alleen afhangt van de event, maar ook van de toestand. Deze aanpak kunnen we goed beschrijven met behulp van eindige automaten.
- Een eindige automaat heeft een eindig aantal toestanden, bijvoorbeeld genummerd 0, 1, ..N,
en een eindig aantal overgangen, waarbij elke overgang gelabeld is met een input- en een output-symbool. Eén van de toestanden (meestal: 0) is de begintoestand.
Als inputsymbolen gebruiken we hier events; als outputsymbolen gebruiken we acties. We geven hier een aantal voorbeelden.
Aan/uit schakelaar
Met deze automaat kunnen we met een enkele knop een LED aan- en uitschakelen. De automaat heeft 2 toestanden:
- 0: de LED is uit
- 1: de LED is aan
en 2 overgangen:
- 0->1: input: button A ingedrukt; output: zet LED aan
- 1->0: input: button A ingedrukt; output: zet LED uit
(Met "button A ingedrukt" bedoelen we hier de event van het indrukken (en loslaten) van een drukknop, niet de toestand waarin deze knop ingedrukt is.)
We kunnen deze automaat op een systematische manier omzetten in een programma:
- de variabele
state
stelt de toestand voor; initieel is deze 0; - voor elke overgang krijgen we een combinatie van een conditie en een actie:
- de conditie bestaat uit een toestand en een event-conditie;
- de actie bestaat uit de output-actie en het toekennen van de nieuwe toestand.
Merk op dat de conditie bestaat uit twee elementen: de state en de event-conditie. We kunnen deze dubbele conditie op verschillende manieren uitsplitsen:
- steeds uitschrijven van de dubbele conditie;
- eerst testen op de state, en dan op de event-conditie
- eerst testen op de event-conditie, en dan op de state.
We gebruiken hieronder de laatstgenoemde aanpak. Deze heeft vooral voordeel als het niet eenvoudig is om de event-conditie herhaald uit te rekenen, bijvoorbeeld omdat dit een neveneffect heeft.
We geven het complete Arduino-programma hieronder.
const int buttonA = 10;
const int led = 11;
int state = 0;
int prevInput = 0;
void setup () {
pinMode(led, OUTPUT);
pinMode(buttonA, INPUT);
state = 0;
}
void loop () {
int thisInput = digitalRead(buttonA);
if (prevInput == LOW && thisInput == HIGH) {
// L->H transition: button A pressed
if (state == 0) {
digitalWrite(led, HIGH);
state = 1;
} else if state == 1) {
digitalWrite(led, LOW);
state = 0;
}
}
prevInput = thisInput;
}
Meerdere taken tegelijk
Om meerdere taken "tegelijk" uit te kunnen voeren moet elke taak in stukken opgeknipt worden die voldoende klein zijn om de voortgang van andere taken niet te blokkeren. Een handig model hiervoor is de eindige automaat (finite state machine; meestal kortweg "state machine"/automaat). Een automaat wordt gekenmerkt door:
- een eindig aantal toestanden; we geven elke toestand een naam (of een nummer);
- één van deze toestanden is de begintoestand van de automaat.
- overgangen tussen de toestanden; een toestand hangt af van invoersymbool (in ons geval: een event).
- bij een overgang hoort ook vaak een uitvoer: het zetten van een digitale output, of het genereren van een event die elders gebruikt wordt.
Vaak tekenen we een automaat in de vorm van een diagram ("ballenplaatje"):
Een erg eenvoudig voorbeeld van een eindige automaat is de knipperende LED. Deze heeft 2 toestanden: aan en uit. Het diagram hiervoor ziet er als volgt uit:
We geven de begintoestand van een automaat hier aan door een los inkomend pijltje.
Van diagram naar Arduino-code
We kunnen een dergelijk diagram op een systematische manier vertalen naar Arduino-code:
- we gebruiken een (int) variabele om de toestand vast te leggen.
- we kunnen hiervoor ook een enum-type gebruiken; zie het verkeerslichten-voorbeeld;
- in de setup-functie geven we deze variabele de waarde van de begintoestand
- we moeten in de setup ook de uitvoer-actie voor de begintoestand uitvoeren
- voor elke overgang (pijl) van toestand A naar toestand B, met conditie (event)
eventCondition
, en overgangs-actietransAction
krijgen we in de loop-functie:
if (state == A && eventCondition) {
state = B;
transAction;
}
- de volgorde van deze overgangen-code in de loop is niet van belang: elke overgang is onafhankelijk van de andere overgangen.
Voorbeeld van een automaat: knipperende LED
Voor de knipperende LED wordt dit (zie ook Arduino cursus/Blink met timer-1):
- toestands-variable:
int ledState;
- deze heeft de waarden
LOW
(0) enHIGH
(1).
- deze heeft de waarden
- begintoestand, in
setup
ledState = LOW;
digitalWrite(ledPin, ledState);
- de event-conditie voor de overgangen bestaat uit de timer-conditie:
if (ledState == LOW && now - timerStart >= offPeriod) {
ledState = HIGH; // invert LED
digitalWrite(ledPin, ledState);
timerStart = now;
} else if (ledState == HIGH && now - timerStart >= onPeriod) {
ledState = LOW; // invert LED
digitalWrite(ledPin, ledState);
timerStart = now;
}
Merk op dat we door deze aanpak voor de beide overgangen verschillende condities kunnen kiezen. Dit maakt het mogelijk om de tijd voor de beide periodes onafhankelijk te kiezen.
Voor een groter voorbeeld, zie Arduino cursus/Verkeerslichten
Andere problemen die we met een automaat kunnen oplossen:
- het herkennen van een reeks inputs, bijvoorbeeld een pincode o.i.d.
Automaat-opdrachten
(Mini)projecten
Temperatuurbewaking
Maak een schakeling die een LED laat branden als de temperatuur langer dan 60 s boven een bepaalde temperatuur gebleven is. (Toepassing: bijvoorbeeld koelkast/vrieskast-alarm.)
- met een button moet je de schakeling kunnen resetten;
- voeg eventueel een buzzer toe als alarm
- verfijning: houd op een display de maximale en de actuele temperatuur bij
Voorkennis:
- temperatuursensor (analoog of digitaal)
- button (button-event)
- LED
- timer
- verfijning: display
Hal-lamp
Maak een lamp die na inschakelen dia een drukknop (button) een korte periode blijft branden.
- verfijning: maak de periode dat de lamp blijft branden instelbaar met een potmeter
- verfijning: aan het eind van de periode (bijv. de laatste 30 sec.) kan de lamp geleidelijk gedimd worden - ook als waarschuwing dat de lamp bijna uit is.
Voorkennis:
- button (button-event)
- timer
- dimmer
Automatische nachtlamp
Maak met een bewegingsdetector, een timer, een LDR en een (verlichtings)LED een automatische nachtlamp:
- het licht gaat branden zodra er beweging gedetecteerd wordt en er geen (of nauwelijks) licht is;
- het licht gaat uit nadat er 2 minuten geen beweging gedetecteerd is.
- als je een digitale LED gebruikt, kun je kiezen voor het weglaten van blauw licht: dit verstoord de nachtrust het meest;
- je kunt de verlichting geleidelijk laten dimmen, als er geen beweging meer gedetecteerd wordt
Voorkennis:
- LDR
- LED (zo mogelijk digitale LED)
- bewegingsdetector
- timer
Afstand(ver)klikker
Maak met een afstandsdetector en een tikker een verklikker die door tikken aangeeft wat de afstand tot de detector is. Hoe korter de afstand, des te hoger de tik-frequentie.
Voorkennis:
- tikker
- afstandsdetector
Bestellijst
Naast de "normale" onderdelen, zoals LEDs, drukknoppen, weerstanden en een display, zijn de volgende onderdelen nodig voor de bovenstaande opdrachten:
Naam | figuur | opmerking(en) | leverancier |
---|---|---|---|
PIR bewegingsdetector | https://www.tinytronics.nl/shop/nl/sensoren/optisch/ir-pyroelectrische-infrarood-pir-motion-sensor-detector-module | ||
Ultrasound afstandssensor HC-SR04 | https://www.tinytronics.nl/shop/nl/sensoren/afstand/ultrasonische-sensor-hc-sr04 | ||
Hartslagsensor | https://www.tinytronics.nl/shop/nl/sensoren/optisch/hartslagsensor-xd-58c-met-accessoires | ||
Analoge temperatuursensor LM35DZ | LM35DZ | https://www.eoo-bv.nl/temperatuur-sensors/910-lm35dz.html | |
Analoge temperatuursensor LM35DZ | LM35DZ | (Alternatieve leverancier) | https://www.tinytronics.nl/shop/nl/sensoren/temperatuur-lucht-vochtigheid/lm35-to-92-thermometer-temperatuur-sensor |
Digitale temperatuursensor DS18B20 | DS18B20 | Nodig: weerstand 4k7 als pull-up NB: je kunt deze sensor ook krijgen in een waterdichte versie met een lange kabel |
https://www.tinytronics.nl/shop/nl/sensoren/temperatuur-lucht-vochtigheid/ds18b20-to-92-thermometer-temperatuur-sensor |