Contents[Hide]

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

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.

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
}

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

Here 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" ; 
}

History

The VirtualDelay library is 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 are possible like restart, pause, resume, stop, reset.

VirtualDelay-development
VirtualDelay-development

 

Do you have any comments? Please let me know.
Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.