Arduino/Timers: verschil tussen versies
Geen bewerkingssamenvatting |
k (1 versie) |
(geen verschil)
|
Huidige versie van 26 mrt 2014 om 21:06
Timers
Reactieve programma's
Een programma dat een robot, een auto, of een ander apparaat aanstuurt moet voldoende snel reageren op de verschillende invoer-sensoren. Ook bij een interactief programma zoals een browser speelt dit een rol. In het geval van een interactief programma betekent "niet snel genoeg" dat een gebruiker zich begint te ergeren. Maar voor een programma dat een apparaat bestuurt kan dit betekent dat er echt (fysiek) iets kapot gaat. De robot (of een auto) reageert te laat op een obstakel, en botst er hard tegenaan. Als er op "te laat" een grote straf staat spreken we van een (hard) real time system. Het liefst willen we in zo'n geval kunnen garanderen dat elke reactie op tijd komt.
Een programma dat vooral bestaat uit reacties op een aantal inputs noemen we ook wel een *reactief programma*.
Timers (1)
Bekijk het onderstaande programma:
void loop () {
if (button_pushed(0)) {
led_on(0);
delay(2000);
led_off(0);
}
if (button_pushed(1)) {
led_on(1);
delay(2000);
led_off(1);
}
}
De bedoeling van dit programma is dat zodra er een knop ingedrukt wordt, de bijbehorende LED aangaat gedurende 2 seconden, waarna deze weer uitgaat.
- De functies
button_pushed()
,led_on()
, enled_off()
werken we hier niet verder uit.
Maar het programma werkt niet helemaal zoals bedoeld: als je een knop indrukt terwijl een LED brandt, gebeurt er niets. Gedurende de `delay(2000)` is het programma "doof" voor dergelijke invoer.
We kunnen dergelijke problemen voorkomen door het programma anders op te zetten: het indrukken van een knop resulteert in een *event*; eventueel kunnen we dergelijke events bufferen. We proberen elke event snel af te handelen; bij het afhandelen van een event mogen we *niet wachten*. We mogen geen delay
gebruiken, maar ook bijvoorbeeld niet wachten op de invoer van een randapparaat, of op het resultaat van een database-query.
In plaats van de delay
moeten we ervoor zorgen dat we een event krijgen op het moment dat de LED uitgeschakeld moet worden. Dit kan door middel van een "timer": een conditie die aangeeft dat een bepaalde tijdsduur verstreken is. Een dergelijke timer kunnen we maken in software, aan de hand van de milliseconde-klok. De Arduino-hardware beschikt ook over een aantal timers; deze worden onder andere gebruikt voor de genoemde milliseconde-klok. Ook de "analoge output", in de vorm van pulsbreedtemodulatie (pulse widt modulation) maakt gebruik van een hardware-timer.
De functie millis()
geeft het aantal milliseconden aan sinds de laatste reset van de Arduino, in de vorm van een unsigned long
.
- Het resultaat van
millis()
is eenunsigned long
: een 32-bits getal. De maximale waarde daarvan is 2^32-1, ofwel ca. 4*10^9 (ruim 4 miljard). Verder doortellen resulteert in "overflow": de teller begint weer bij 0. De teller maakt dan een sprong terug: het lijkt dan alsof de tijd terugloopt. Voor een 32-bits milliseconden-timer treedt deze overflow op na `2^32/(1000*60*60*24)` dagen (1000 msec/sec, 60 sec/min, 60 min/uur, 24 uren/dag). Dit is bijna 50 dagen (49,71). In het dagelijks gebruik zul je niet snel tegen dit probleem aanlopen, maar als je een vaste opstelling met een Arduino maakt, bijvoorbeeld voor een home-control toepassing, dan moet je daarmee zeker rekening houden. (Denk bijvoorbeeld aan een thermostaat waarmee je een CV-installatie bestuurt.)
Met behulp van deze millis()
-functie kunnen we het programma op de volgende manier opzetten:
const infinitum = 4294967295ul; // 2^32-1, max unsigned long
unsigned long led_timeout_0 = infinitum;
unsigned long led_timeout_1 = infinitum;
void loop () {
if (button_pushed(0)) {
led_on(0);
led_timeout_0 = millis() + 2000;
}
if (millis() > led_timeout_0) {
led_off(0);
led_timeout_0 = infinitum;
}
if (button_pushed(1)) {
led_on(1);
led_timeout_1 = millis() + 2000;
}
if (millis() > led_timeout_1) {
led_off(1);
led_timeout_1 = infinitum;
}
}
Merk op:
- de volgorde van de verschillende
if
-statements doet er in dit geval niet toe. - je kunt een timer ook gebruiken voor een periodiek event, bijvoorbeeld elke 2 seconden. Dit kun je gebruiken om elke twee seconden een LED om te schakelen. Zie het voorbeeld hieronder.
- een timer voor een niet-periodieke reactie in de toekomst (zoals hierboven) heet ook wel een *one-shot*. (ref).
Het volgende programma is ontleend aan BlinkWithoutDelay
:
const unsigned long infinitum = 4294967295ul; // 2^32-1
const int ledPin = 13;
unsigned long ledTimeOut = infinitum;
int ledState = LOW; // == 0
void setup () {
pinMode(ledPin, OUTPUT); // initially, LOW
ledTimeOut = millis() + 1000;
}
void loop () {
if (millis() > ledTimeOut) {
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
digitalWrite(ledPin, ledState); // switch LED
ledTimeOut = millis() + 1000;
}
}
}
Opmerkingen:
- de omgekeerde waarde van een "logische" waarde
x
voorgesteld door 0 en 1, krijg je door de expressie1-x
. (Ga dit na.) Dit betekent dat we het binnensteif
-statement kunnen vervangen door:ledState = 1 - ledState
.- in het geval van een logische waarde in de vorm van een Boolean, wordt dit:
ledState = !ledState`
(!
staat in C voor de Boolean not-operator).
- in het geval van een logische waarde in de vorm van een Boolean, wordt dit:
Opdrachten:
- pas het programma aan zodat een LED 1 seconde aan staat, en 2 seconden uit.
- gebruik twee LEDs en twee schakelaars: de LEDs knipperen regelmatig, het indrukken van de ene schakelaar halveert de knipperfrequentie, en indrukken van andere verdubbelt de knipperfrequentie.
Vervolg
- Hoe kun je omgaan met overflow? (Dit is van belang voor timers die sneller verlopen dan de milliseconden-timer, die gebruik maken van een kortere representatie (8 of 16 bits), of die echt lang moeten kunnen lopen.)
- combineren van timers.
- timers in hardware. Aftellen naar 0 (voor alle timers gelijk).
- timers en interrupts.
- opmerking over logische (Boolean) waarden en condities in C (en C++).