Getting rid of delay() and for loops; un-delay2 is out

I shaved off a few bytes and added names to the cases. Easy to add more, in any order, as their numeric representation isn't used. The names make it easier when debugging:

// NoBlockUndelayDemo2 2022 by GoForSmoke @ Arduino.cc Forum
// Free for use, May 10, 2022 by GFS. Compiled on Arduino 2.1.0.5
// This sketch shows a general method to get rid of delays in code.
// You could upgrade code with delays to work with add-a-sketch.
// .. adding looped cases

// #include <avr/io.h>  ---  remove this line per Railroader, it's no longer needed
#include "Arduino.h"

enum {LED_ON_500, LED_OFF_500, LED_ON_250, LED_OFF_250, LED_ON_1_SEC, LED_OFF_1_SEC};
const byte ledPin = 13;
unsigned long delayStart;
word delayWait; //65535ms max
const byte indexMax = 12;
byte index;


void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Un-Delay Example, free by GoForSmoke\n" ));
  Serial.println( F( "This sketch shows how to get rid of delays in code.\n" ));

  pinMode( ledPin, OUTPUT );
};


/* The section of the original sketch with delays:
 * 
 * digitalWrite( ledPin, HIGH );   --  0
 * delay( 500 );
 * digitalWrite( ledPin, LOW );    --  1
 * delay( 500 );
 * for ( i = 0; i < 12; i++ )
 * (
 *   digitalWrite( ledPin, HIGH );   --  2
 *   delay( 250 );
 *   digitalWrite( ledPin, LOW );    --  3
 *   delay( 250 );
 * }
 * digitalWrite( ledPin, HIGH );   --  4
 * delay( 1000 );
 * digitalWrite( ledPin, LOW );    --  5
 * delay( 1000 );
 */

byte blinkStep; // state tracking for BlinkPattern() below

void BlinkPattern()
{
  // This one-shot timer replaces every delay() removed in one spot.  
  // start of one-shot timer
  if ( delayWait > 0 ) // one-shot timer only runs when set
  {
    if ( millis() - delayStart < delayWait )
    {
      return; // instead of blocking, the undelayed function returns
    }
    else
    {
      delayWait = 0; // time's up! turn off the timer and run the blinkStep case
    }
  }
  // end of one-shot timer

  // here each case has a timed wait but cases could change Step on pin or serial events.
  switch( blinkStep )  // runs the case numbered in blinkStep
  {
    case LED_ON_500 :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "Case 0 doing something unspecified here at " ));
    Serial.println( delayStart = millis()); // able to set a var to a value I pass to function
    delayWait = 500; // for the next half second, this function will return on entry.
    blinkStep = LED_OFF_500;   // when the switch-case runs again it will be case 1 that runs
    break; // exit switch-case

    case LED_OFF_500 :
    digitalWrite( ledPin, LOW );
    Serial.println( F( "Case 1 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 500;
    blinkStep = LED_ON_250;
    break;

    case LED_ON_250 :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "Case 2 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    blinkStep = LED_OFF_250;
    break;

    case LED_OFF_250 :
    digitalWrite( ledPin, LOW );
    Serial.println( F( "Case 3 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    // this replaces the for-loop in non-blocking code.
    if ( index++ < indexMax ) // index gets incremented after the compare
    {
      blinkStep = LED_ON_250;
    }
    else
    {
      index = 0;
      blinkStep = LED_ON_1_SEC;
    }  // how to for-loop in a state machine without blocking execution.
    break;

    case LED_ON_1_SEC :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "Case 4 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 1000;
    blinkStep = LED_OFF_1_SEC;
    break;

    case LED_OFF_1_SEC :
    digitalWrite( ledPin, LOW );
    Serial.print( F( "Case 5 doing something unspecified here at " ));
    Serial.println( delayStart = millis());
    delayWait = 1000;
    blinkStep = LED_ON_500;
    break;
  }
}


void loop()  // runs over and over, see how often
{            
  BlinkPattern();
}

That's NOT a solution, it's a crappy work-around that'd need a whole special code.
I can get a vt-100 emulator but that's not beginner friendly.

Still not initialized (cf my previous comment) - even more important if you use an enum (which you could force as byte rather than int, and possibly make use it as a type for the state)

Add your name to the comments then and enums cost 2 bytes by default, there's a compiler directive to make them 1 byte.

If there's an enum, it can be the type for blinkStep that then has to be initialized.

// NoBlockUndelayDemoV2R01 2022 by GoForSmoke @ Arduino.cc Forum
// Free for use, May 10, 2022 by GFS. Compiled on Arduino 2.1.0.5
// This sketch shows a general method to get rid of delays in code.
// You could upgrade code with delays to work with add-a-sketch.
// .. adding looped cases
// revisions with forum help:
// dlloyd --  added state enums May 11. <--- changed as needed.


const byte ledPin = 13;
unsigned long delayStart;
unsigned long delayWait; 
const byte indexMax = 12;
byte index;

enum blinkStates {LED_ON_500, LED_OFF_500, LED_ON_250, LED_OFF_250, LED_ON_1_SEC, LED_OFF_1_SEC};
blinkStates blinkStep; // state tracking for BlinkPattern() below


void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Un-Delay Example, free by GoForSmoke\n" ));
  Serial.println( F( "This sketch shows how to get rid of delays in code.\n" ));

  pinMode( ledPin, OUTPUT );

  blinkStep = LED_ON_500;  // actual value is 0
};


/* The section of the original sketch with delays:
 * 
 * digitalWrite( ledPin, HIGH );   --  0
 * delay( 500 );
 * digitalWrite( ledPin, LOW );    --  1
 * delay( 500 );
 * for ( i = 0; i < 12; i++ )
 * (
 *   digitalWrite( ledPin, HIGH );   --  2
 *   delay( 250 );
 *   digitalWrite( ledPin, LOW );    --  3
 *   delay( 250 );
 * }
 * digitalWrite( ledPin, HIGH );   --  4
 * delay( 1000 );
 * digitalWrite( ledPin, LOW );    --  5
 * delay( 1000 );
 */


void BlinkPattern()
{
  // This one-shot timer replaces every delay() removed in one spot.  
  // start of one-shot timer
  if ( delayWait > 0 ) // one-shot timer only runs when set
  {
    if ( millis() - delayStart < delayWait )
    {
      return; // instead of blocking, the undelayed function returns
    }
    else
    {
      delayWait = 0; // time's up! turn off the timer and run the blinkStep case
    }
  }
  // end of one-shot timer

  // here each case has a timed wait but cases could change Step on pin or serial events.
  switch( blinkStep )  // runs the case numbered in blinkStep
  {
    case LED_ON_500 :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "LED_ON_500, time " ));
    Serial.println( delayStart = millis()); // able to set a var to a value I pass to function
    delayWait = 500; // for the next half second, this function will return on entry.
    blinkStep = LED_OFF_500;   // when the switch-case runs again it will be case 1 that runs
    break; // exit switch-case

    case LED_OFF_500 :
    digitalWrite( ledPin, LOW );
    Serial.println( F( "LED_OFF_500, time " ));
    Serial.println( delayStart = millis());
    delayWait = 500;
    blinkStep = LED_ON_250;
    break;

    case LED_ON_250 :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "LED_ON_250, time " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    blinkStep = LED_OFF_250;
    break;

    case LED_OFF_250 :
    digitalWrite( ledPin, LOW );
    Serial.println( F( "LED_OFF_250, time " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    // this replaces the for-loop in non-blocking code.
    if ( index++ < indexMax ) // index gets incremented after the compare
    {
      blinkStep = LED_ON_250;
    }
    else
    {
      index = 0;
      blinkStep = LED_ON_1_SEC;
    }  // how to for-loop in a state machine without blocking execution.
    break;

    case LED_ON_1_SEC :
    digitalWrite( ledPin, HIGH );
    Serial.println( F( "LED_ON_1000, time " ));
    Serial.println( delayStart = millis());
    delayWait = 1000;
    blinkStep = LED_OFF_1_SEC;
    break;

    case LED_OFF_1_SEC :
    digitalWrite( ledPin, LOW );
    Serial.print( F( "LED_OFF_1000, time " ));
    Serial.println( delayStart = millis());
    delayWait = 1000;
    blinkStep = LED_ON_500;
    break;
  }
}


void loop()  // runs over and over, see how often
{            
  BlinkPattern();
}

Where else can a delay() or serial commands

readBytes()
readBytesUntil()
readString()
readStringUntil()

be hard to unblock?

I often see asks for led strip animations where the animation is just one big function with delays

How baroque does it get?

Based on the topic title, I was a little disappointed in your opening post; I thought that somebody finally explained how to change a for-loop with delay() to something that uses millis().

I did attempt to do so in e.g. Multiple SK6812RGBW controlled by different pins - #17 by sterretje.

typical ask I've seen is that the sketch offers multiple animations and being able to switch between animations at any point in time with a button/remote control/sensor/ or do other stuff whilst the animation goes on

typical state machine based solution

I don't know what change in definition you mean.

Can you post a small example? Perhaps a loop that has a delay in a single case or just runs the same case (over and over, driven by "big wheel" void loop) until a valid input arrives? That will be in the parallel serial text task..

You do know that the 1-shot timer that the function begins with is the millis part?

A lot of very short delays or inputs on interrupts?

Cringe code?

here is an example

(quick search in the forum, I've seen many)

I'm a little confused if this is addressed to me. If so, I'm missing the question; the link that I provided should be the example where a for-loop with delay is converted to a non-blocking approach.

If you leave the mix of mostly println and a few print comands you get sloppy output like this:

LED_ON_250, time 
7005
LED_OFF_250, time 
7255
LED_ON_1000, time 
7505
LED_OFF_1000, time 8505
LED_ON_500, time 
9505
LED_OFF_500, time 
10005

If you change them to use one line per report you get this output:

  Un-Delay Example, free by GoForSmoke

This sketch shows how to get rid of delays in code.

LED_ON_500, time 4
LED_OFF_500, time 504
LED_ON_250, time 1004
LED_OFF_250, time 1254
LED_ON_250, time 1504
LED_OFF_250, time 1754
LED_ON_250, time 2004
LED_OFF_250, time 2254
LED_ON_250, time 2504
LED_OFF_250, time 2754
LED_ON_250, time 3004
LED_OFF_250, time 3254

With the one-report-per-line it demonstrates the precision of the timing much more clearly.

// NoBlockUndelayDemoV2R01 2022 by GoForSmoke @ Arduino.cc Forum
// Free for use, May 10, 2022 by GFS. Compiled on Arduino 2.1.0.5
// This sketch shows a general method to get rid of delays in code.
// You could upgrade code with delays to work with add-a-sketch.
// .. adding looped cases
// revisions with forum help:
// dlloyd --  added state enums May 11. <--- changed as needed.


const byte ledPin = 13;
unsigned long delayStart;
unsigned long delayWait; 
const byte indexMax = 12;
byte index;

enum blinkStates {LED_ON_500, LED_OFF_500, LED_ON_250, LED_OFF_250, LED_ON_1_SEC, LED_OFF_1_SEC};
blinkStates blinkStep; // state tracking for BlinkPattern() below


void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Un-Delay Example, free by GoForSmoke\n" ));
  Serial.println( F( "This sketch shows how to get rid of delays in code.\n" ));

  pinMode( ledPin, OUTPUT );

  blinkStep = LED_ON_500;  // actual value is 0
};


/* The section of the original sketch with delays:
 * 
 * digitalWrite( ledPin, HIGH );   --  0
 * delay( 500 );
 * digitalWrite( ledPin, LOW );    --  1
 * delay( 500 );
 * for ( i = 0; i < 12; i++ )
 * (
 *   digitalWrite( ledPin, HIGH );   --  2
 *   delay( 250 );
 *   digitalWrite( ledPin, LOW );    --  3
 *   delay( 250 );
 * }
 * digitalWrite( ledPin, HIGH );   --  4
 * delay( 1000 );
 * digitalWrite( ledPin, LOW );    --  5
 * delay( 1000 );
 */


void BlinkPattern()
{
  // This one-shot timer replaces every delay() removed in one spot.  
  // start of one-shot timer
  if ( delayWait > 0 ) // one-shot timer only runs when set
  {
    if ( millis() - delayStart < delayWait )
    {
      return; // instead of blocking, the undelayed function returns
    }
    else
    {
      delayWait = 0; // time's up! turn off the timer and run the blinkStep case
    }
  }
  // end of one-shot timer

  // here each case has a timed wait but cases could change Step on pin or serial events.
  switch( blinkStep )  // runs the case numbered in blinkStep
  {
    case LED_ON_500 :
    digitalWrite( ledPin, HIGH );
    Serial.print( F( "LED_ON_500, time " ));
    Serial.println( delayStart = millis()); // able to set a var to a value I pass to function
    delayWait = 500; // for the next half second, this function will return on entry.
    blinkStep = LED_OFF_500;   // when the switch-case runs again it will be case 1 that runs
    break; // exit switch-case

    case LED_OFF_500 :
    digitalWrite( ledPin, LOW );
    Serial.print( F( "LED_OFF_500, time " ));
    Serial.println( delayStart = millis());
    delayWait = 500;
    blinkStep = LED_ON_250;
    break;

    case LED_ON_250 :
    digitalWrite( ledPin, HIGH );
    Serial.print( F( "LED_ON_250, time " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    blinkStep = LED_OFF_250;
    break;

    case LED_OFF_250 :
    digitalWrite( ledPin, LOW );
    Serial.print( F( "LED_OFF_250, time " ));
    Serial.println( delayStart = millis());
    delayWait = 250;
    // this replaces the for-loop in non-blocking code.
    if ( index++ < indexMax ) // index gets incremented after the compare
    {
      blinkStep = LED_ON_250;
    }
    else
    {
      index = 0;
      blinkStep = LED_ON_1_SEC;
    }  // how to for-loop in a state machine without blocking execution.
    break;

    case LED_ON_1_SEC :
    digitalWrite( ledPin, HIGH );
    Serial.print( F( "LED_ON_1000, time " ));
    Serial.println( delayStart = millis());
    delayWait = 1000;
    blinkStep = LED_OFF_1_SEC;
    break;

    case LED_OFF_1_SEC :
    digitalWrite( ledPin, LOW );
    Serial.print( F( "LED_OFF_1000, time " ));
    Serial.println( delayStart = millis());
    delayWait = 1000;
    blinkStep = LED_ON_500;
    break;
  }
}


void loop()  // runs over and over, see how often
{            
  BlinkPattern();
}

There's a led strip animation change sketch highlighted on Wokwi under Wokwi - Online Arduino and ESP32 Simulator :

https://wokwi.com/arduino/libraries/Adafruit_NeoPixel/buttoncycler

I fiddled with it some, trying to recode some of the animations as state machines:

" I was a little disappointed in your opening post; I thought that somebody finally explained how to change a for-loop with delay() to something that uses millis() ".

I did except that as per the delay code in comment there, the for-next has 2 delays.

You have a different situation

void colorWipe(uint32_t color, int wait)
{
  for (int i = 0; i < strip.numPixels(); i++)
  {
    strip.setPixelColor(i, color);
    strip.show();
    delay(wait);
  }
}

okay, how's this? It can be set to start after a period by giving delayWait a value.

unsigned long delayStart;
unsigned long delayWait; 
int ledNum = 0;

// ........... snippety snip

void colorWipe(uint32_t color, int wait)
{
  // This one-shot timer replaces every delay() removed in one spot.  
  // start of one-shot timer
  if ( delayWait > 0 ) // one-shot timer only runs when set
  {
    if ( millis() - delayStart < delayWait )
    {
      return; // instead of blocking, the undelayed function returns
    }
    else
    {
      delayWait = 0; // time's up! turn off the timer and run the blinkStep case
    }
  }
  // end of one-shot timer

  strip.setPixelColor( i, color );
  strip.show();
  delayStart = millis(); // starting now
  delayWait = wait;     // wait this long to make the next step

  if ( ledNum++ >= strip.numPixels() )
  { 
    ledNum = 0;
  }
}

I embrace that functions inside the "Big Driving Wheel" void loop() are written to be driven at any time. Treat the world as asynchronous and less can go wrong.

1 Like

Great work.


In a final version maybe show other millidelay polling.


  if ( delayWait > 0 ) // one-shot timer only runs when set
  {
    if ( millis() - delayStart >= delayWait )
    {
      delayWait = 0; // time's up! turn off the timer and run the blinkStep 

     bla bla bla
    }
  }
. . .
  if ( delayWait2 > 0 ) // one-shot timer only runs when set
  {
    if ( millis() - delayStart2 >= delayWait2 )
    {
      delayWait2 = 0; // time's up! turn off the timer and run the blinkStep 

     bla2 bla2 bla2
    }
  }

Every "millis timer" has to store its own values. Even button debounce uses a timer.
Timing millis to less than 65.535 seconds can be done with 16 bit type unsigned time variables to save 4 bytes per timer.

One-shots have to turn themselves off.

Repeaters have to reset their start times.

I didn't invent these concepts, they were old when I learned.

The delay code that undelay demo fixes

/* The section of the original sketch with delays:
 * 
 * digitalWrite( ledPin, HIGH );   --  0
 * delay( 500 );
 * digitalWrite( ledPin, LOW );    --  1
 * delay( 500 );
 * for ( i = 0; i < 12; i++ )
 * (
 *   digitalWrite( ledPin, HIGH );   --  2
 *   delay( 250 );
 *   digitalWrite( ledPin, LOW );    --  3
 *   delay( 250 );
 * }
 * digitalWrite( ledPin, HIGH );   --  4
 * delay( 1000 );
 * digitalWrite( ledPin, LOW );    --  5
 * delay( 1000 );
 * ------------------------------------------------------------add next or "run at the same time"?
 * for (int i = 0; i < strip.numPixels(); i++)
 * {
 *   Serial.print( "pixel " );
 *   Serial.println( i );
 *   delay( wait );
 * }

 */