PaulS' comment on another thread got me thinking...
PaulS:
There is no magic bullet - no delayWithoutBlocking() function that you can use in place of the blocking delay() function.
I'm posting as a fresh topic because this is probably not the best answer for that OP and might distract from a better answer.
I turned this into a personal challenge to write a function as similar as possible to delay() while being non-blocking. I do already realize it's an oxymoronic task, but fun nonetheless. I am also aware that there are plenty of very well featured timer libraries available which are generally the best solution if hand coding a millis() watcher doesn't cut it.
I think the closest a non-blocking function can semantically get to delay() is in this format:
if ( delayWithoutBlocking(<number of ms>) ) {
//delayed code here
}
Here's my first working attempt at a basic solution:
/* delayWithoutBlocking is an attempt to create a non-blocking timer function
as semantically similar as possible to delay()
It has 2 major weaknesses:
1. Each usage must have a unique interval value because the timers are
indexed by interval value.
2. A constant MaxTimers value must be declared and made equal or greater
than the number of unique timers used.
*/
const int dwbMaxTimers = 5;
void setup() {
Serial.begin(250000);
}
void loop() {
// delayWithoutBlocking usage examples:
if (delayWithoutBlocking(2000)) {
//do this not more than once every 2 seconds
Serial.println("2 second report");
}
// do other stuff while waiting
// ...
// for example:
if (delayWithoutBlocking(555)) {
//do this other thing not more than once every 555ms
Serial.println("thing!");
}
//...or:
bool someEventOrCondition;
if (someEventOrCondition) {
if (delayWithoutBlocking(33)) {
someEventOrCondition = false;
//whenever someEventOrCondition happens
//wait 33ms and then do this once
}
}
}
bool delayWithoutBlocking(unsigned long interval) {
static unsigned long dwbInterval[dwbMaxTimers];
static unsigned long dwbStart[dwbMaxTimers];
static bool dwbExpired[dwbMaxTimers];
static int dwbCount;
unsigned long now = millis();
for (int i = dwbCount; i-- > 0;) {
if (dwbInterval[i] == interval) {
if (dwbExpired[i]) {
dwbStart[i] = now;
return dwbExpired[i] = false;
}
if (now - dwbStart[i] >= dwbInterval[i]) {
return dwbExpired[i] = true;
}
else {
return false;
}
}
}
if (dwbCount >= dwbMaxTimers) {
Serial.println("Error: Trying to use more timers than are available.");
Serial.println("Increase dwbMaxTimers to the number of timers required.");
return false;
}
dwbInterval[dwbCount] = interval;
dwbStart[dwbCount] = now;
dwbCount++;
return false;
}
Interesting? Bad Idea?
Maybe a suitable implementation could serve a purpose in the space between learning the right way to code a timer yourself and using a timer library?
So, have a class with a static method, and have loop() poll it. That class holds a list closures that have to be invoked and the time at which they have to be invoked. There's a few linked-list implementations that will do the job.
Tricky bits are
1 - managing the roll-over arithmetic
2 - explaining to people that these closures can't reference stack variables that go out of scope
From there, it's reasonably easy to build a class that executes a closure repeatedly until some condition is met.
but I think it's much better to just use millis() instead in the BWoD fashion. The use of yield() is not very beginner friendly because it's harder to understand the program flow when you have this function being called seemingly out of nowhere and if you're not a beginner then you won't be using delay() in any code where blocking will be an issue. However, I see beginners unnecessarily using interrupts just so they can still use delay so I think this is an improvement over that at least.
I often use the elapsedMillis library. It's just blink without delay type thinking wrapped up in a nice box, and saves a bit of coding.
edit: but I don't recommend using it until one fully understands blink without delay type thinking, since that's a very important thing to understand conceptually. Then just use elapsedMillis to ease the coding a bit, while fully understanding what it's doing inside.
When I threw that together I was focusing on providing a timing alternative. I didn't fully consider the implied sequential state machine inherent to some uses of delay(), Blink being a prime example. My 3rd example combines a timer with a conditional in a way that could complement various state machine schemes.
Clearly this isn't a drop in replacement for all delay() uses, or even most. Thanks for the feedback.
I'm surprised that code will even compile for a Teensy or a Due, since those platforms do implement a yield() function. I can't be bothered reaching for a board to check if it actually runs though. Even if it does run, it seems a spectacularly bad idea. Just use the Scheduler library like it's supposed to be used.
I'm no comp sci type and nowhere did I say that Delay and FSMs are related but my lay understanding is that FSMs and timers aren't mutually exclusive. Regardless, it's very safe to assume that when I talk about state machines I'm talking in the broadest sense.
So here's how I would apply the OP method to Blink.
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
}
void loop() {
static int LEDstate = HIGH;
if (delayWithoutBlocking(1000)) {
LEDstate = LEDstate == HIGH ? LOW : HIGH;
digitalWrite(LED_BUILTIN, LEDstate);
}
// do other things
}
We have to record the current state of the sequence so other things can be done while checking if it's time for the next transition.
MorganS:
I'm surprised that code will even compile for a Teensy or a Due, since those platforms do implement a yield() function. I can't be bothered reaching for a board to check if it actually runs though. Even if it does run, it seems a spectacularly bad idea. Just use the Scheduler library like it's supposed to be used.
* This function is intended to be used by library writers to build
* libraries or sketches that supports cooperative threads.
*
* Its defined as a weak symbol and it can be redefined to implement a
* real cooperative scheduler.
You're welcome to define it anyway you like, as was done in the Scheduler library. The official Arduino Scheduler library is for SAM or SAMD only. The vast majority of Arduino users are exclusively AVR.
Again, I'm not recommending someone use any of these techniques, just dump delay() altogether if you want non-blocking code, but the topic of the thread is about using delay without blocking and that's the exact reason why Arduino added the yield() call.
I'm surprised that code will even compile for a Teensy or a Due, since those platforms do implement a yield() function. I can't be bothered reaching for a board to check if it actually runs though. Even if it does run, it seems a spectacularly bad idea. Just use the Scheduler library like it's supposed to be used.
How many threads have there been complaining that the Scheduler library doesn't work on the Due?