I think the concepts are intimately connected. There's a lot of tricky stuff hidden in the details of the BWOD sketch, and it's difficult to see the necessary differences between the blocking and non-blocking methods.
Rather than the setup()-loop() separation between the two systems, it might be more parallel if one started with slightly more complicated base example. Perhaps this one with a fast, controlled count, a pause, and repeat:
void setup() {
Serial.begin(115200);
}
void loop() {
const unsigned long quickInterval = 100; //
const unsigned long slowInterval = 1000;
for (int i = 0 ; i < 10 ; ++i) {
Serial.print(i);
Serial.print(" ");
delay(quickInterval);
}
Serial.println("-- blocking");
delay(slowInterval);
}
And with the non-blocking code shown in parallel in the same sketch:
void setup() {
Serial.begin(115200);
}
void loop() {
const bool doBlocking = true; // choose between blocking or non-blocking code
const unsigned long quickInterval = 100; //
const unsigned long slowInterval = 1000;
if (doBlocking == true) { // use delay() and for(;;)
for (int i = 0 ; i < 10 ; ++i) {
Serial.print(i);
Serial.print(" ");
delay(quickInterval);
}
Serial.println("-- blocking");
delay(slowInterval);
} else { // unblocked loop() by:
// * replacing delays with millis() edge-detectors/state machines
// * replacing for(;;) loop with a counter state machine
// state variables to persist values past the end of loop()'s scope
static int i = 0; // digit to print
static unsigned long lastDigitMs = -quickInterval; // backdate by -quickInterval for immediate operation
static unsigned long lastLineMs = 0; // state variable for edge-detecting slow delay
unsigned long now = millis(); // remember the loop time
// edge detecting state machine {i,now-las}
if (i < 10 && now - lastDigitMs >= quickInterval) { // edge-detect interval change
lastDigitMs = now; // measure digit delay from last digit print
Serial.print(i);
Serial.print(" ");
++i;
lastLineMs = now; // measure line ending delay from last print time
}
// edge-detecting state machine
if (i >= 10 && now - lastLineMs >= slowInterval) {
Serial.println( "-- nonblocking");
i = 0; // reset the count
}
}
}
I think I like the comparison between the two schemes in the same script.
The results of these two schemes aren't quite identical, since the EOL message comes before the delay in the blocking case, and after the pause in the non-blocking case. An additional i==10 case could fix that
if( i == 10) {
Serial.println( "-- nonblocking");
++i;
}
if (i > 10 && now - lastLineMs >= slowInterval) {
...
...but this difference shows that it is trickier to explicitly handle the interacting state variables and their transitions than it is to depend on the Instruction Pointer to keep track of program state.
It isn't a simple task to make the switch from blocking code to non-blocking code.
Edit: This is a nice thread on transforming a for(;;) to use state variables with loop()
