Non-blocking Virtual Delay timer for the Arduino
Introduction
The standard Arduino delay() function blocks the Arduino, that is not always allowed. The standard delay has limitations too; it is not possible to use multiple delays at the same time. So I decided to developed a VirtualDelay library which has many advantages:
Advantages of the VirtualDelay library
- The delay is virtual, during the delay, the code execution is continued
- We can use multiple delays sequentially in a loop.
- We can use multiple delays simultaneously and independent of each other.
- The delay time can set in micro-seconds or milli-seconds.
- No hardware timers are used
-
The library is timer-rollover safe
VirtualDelay library download
You can download the library from GitHub.
Questions
Please post any questions at the Arduino forum.
Notes
- The VirtualDelay must always run inside a loop which is executed continuously.
- In contrast with the standard delay function, we need a start() and an elapsed() function.
-
The delay time value can be between 0 and 2147483647 (2^31-1).
Simple blinking LED sketch with the VirtualDelay library
#include <Arduino.h> #include "avdweb_VirtualDelay.h" const byte ledPin = 13; bool b; VirtualDelay singleDelay; // default = millis void setup() { pinMode(ledPin, OUTPUT); } void loop() { singleDelay.start(400); // calls while running are ignored if(singleDelay.elapsed()) digitalWrite(ledPin, b=!b); // blink the onboard LED 400ms, 400ms off }
Micro seconds VirtualDelay example
#include "avdweb_VirtualDelay.h" VirtualDelay delay_us(micros); void setup() { Serial.begin(9600); } void loop() { DO_ONCE ( delay_us.start(1000000)) // 1s if(delay_us.elapsed()) Serial.print(micros()); }
Standard Arduino blocking delay
Here a standard blinking LED sketch. Open the serial port and you can see that printing goes very slowly.
#include <Arduino.h> #include <Streaming.h> const byte ledPin = 13; int i; void setup() { pinMode(ledPin, OUTPUT); Serial.begin(9600); } void loop() { digitalWrite(ledPin, 1); // this comes after the 700ms delay delay(100); digitalWrite(ledPin, 0); // this comes after the 100ms delay delay(700); Serial << " " << i++; // since the cpu is being blocked, printing goes slowly }
Deadlock
With a sequence of VirtualDelays, each delay will wait on the foregoing by which the sequence can’t start. This is a so-called deadlock. To break the deadlock, one of the delays has to start one-time. To do so, I have built a macro DO_ONCE. This is carried out only once in a loop, see the following example:
Using 3 VirtualDelays in sequence
Note: You can use the virtual delay to generate continuously timing signals like the example below. But this is not the most appropriate way. You better use a timer and link it to an interrupt service routine.
We need three separate VirtualDelay instances: delay1, delay2 and delay3. The line with the macro DO_ONCE ensures that the sequence is started. You may use any instance e.g. delay2.start(0).
#include <Arduino.h> #include <Streaming.h> #include "avdweb_VirtualDelay.h" VirtualDelay delay1, delay2, delay3; void setup() { Serial.begin(9600); Serial << "\ntestSequence"; } void loop() { if(delay1.elapsed()) // this sequence has a deadlock { Serial << "\ndelay1 200ms " << millis(); delay2.start(100); } if(delay2.elapsed()) { Serial << "\ndelay2 100ms " << millis(); delay3.start(400); } if(delay3.elapsed()) { Serial << "\ndelay3 400ms " << millis(); delay1.start(200); } DO_ONCE(delay1.start(200)); // breaks the deadlock, you can start with any delay object you want e.g. delay2.start(0); }
Here is the serial output:
delay1 200ms 200 delay2 100ms 300 delay3 400ms 700 delay1 200ms 900 delay2 100ms 1000 delay3 400ms 1400 delay1 200ms 1600
Using multiple delays at the same time
In this example, we use 6 VirtualDelays at the same time, this is not possible with the standard Arduino delay function. It is also showed how we can use the macro DO_ONCE multiple times in a loop.
#include <Arduino.h> #include <Streaming.h> #include "avdweb_VirtualDelay.h" void setup() { Serial.begin(9600); } void loop() { static VirtualDelay delay1, delay2, delay3, delay4, delay5, delay6; DO_ONCE ( Serial << "\nDO_ONCE 1"; delay1.start(200); // start sequence delay1 delay2 delay3 delay4.start(550); // start one-shot delay4 delay5.start(1250); // start one-shot delay5 ) if(delay4.elapsed()) Serial << "\nONE-SHOT 550ms " << millis(); if(delay5.elapsed()) Serial << "\nONE-SHOT 1250ms " << millis(); if(millis()>2250) DO_ONCE(Serial << "\nDO_ONCE 2 2250ms " << millis()) // test a second DO_ONCE delay6.start(750); if(delay6.elapsed()) Serial << "\n Repeat delay6 750ms " << millis(); if(delay1.elapsed()) // sequence with deadlock { Serial << "\nsequence delay1 200ms " << millis(); delay2.start(100); } if(delay2.elapsed()) { Serial << "\nsequence delay2 100ms " << millis(); delay3.start(400); } if(delay3.elapsed()) { Serial << "\nsequence delay3 400ms " << millis(); delay1.start(200); } }
Here is the serial output:
DO_ONCE 1 sequence delay1 200ms 200 sequence delay2 100ms 300 ONE-SHOT 550ms 550 sequence delay3 400ms 700 Repeat delay6 750ms 750 sequence delay1 200ms 900 sequence delay2 100ms 1000 ONE-SHOT 1250ms 1250 sequence delay3 400ms 1400 Repeat delay6 750ms 1500 sequence delay1 200ms 1600 sequence delay2 100ms 1700 sequence delay3 400ms 2100 Repeat delay6 750ms 2250 DO_ONCE 2 2250ms 2251 sequence delay1 200ms 2300 sequence delay2 100ms 2400 sequence delay3 400ms 2800
One-shot example
To create a one-shot, start() may only be called once during the loop, to do so, the macro DO_ONCE is used.
#include <Arduino.h> #include <Streaming.h> #include "avdweb_VirtualDelay.h" VirtualDelay delay1; void setup() { Serial.begin(9600); Serial << "\ntestOneShot 2s\n"; } void loop() { DO_ONCE(delay1.start(2000)) // do only one time in the loop if(delay1.elapsed()) Serial << millis() << "ms" ; }
The library has to be timer-rollover proof
The 32 bit millis() and micros() timers counts from 0, 1, 2 to 4294967295 (2^32-1) and than rollover to 0 again. The millis() timer is going to roll over after roughly 49.7 days, the micros() rolls over every 71.6 minutes. We have to take care about this rollover, because it may happen that we start the virtual-delay just before the rollover time. After the rollover the timestamps will be messed up.
The time compare part in the VirtualDelay library is tricky, it compares 2 timestamps like this:
if(millis() >= timeOut)
However, this won’t work, we have to use:
if((millis()-timeOut)>=0)
Believe it or not, in this situation, both lines are not identical! You can read all about this in the detailed story from Edgar Bonet.
History
The VirtualDelay library looks quite simple, but the development has cost me a lot of time.
The first library from 10-1-2016 didn’t use a start function. The library seemed to work well but after a year it turned out that there were situations in which it failed. The reason was that I had used a simple LED test, which caused that a serious shortcoming was not discovered.
I had to make new library from scratch. This new library seemed to work well, also with a sequence of two delays.
Later tests showed that there were problems with three and more delays sequentially. This again required a completely different approach. But now it turned out that there were problems with using multiple delays simultaneously and again a complete new library had to be made.
Finally, I came to the conclusion that to solve all problems, it was necessary to use a start function and a macro DO_ONCE.
Future expansions
Extra functions may be implemented like restart, pause, resume, stop, reset. Who wants to expand the library on github?
Simple microseconds VirtualDelay