Latest updated delay vs timer demo

Choose Mode by grounding pin 7 (Delay Mode) or not (Time Mode) with keyboard Enter as pause/unpause scrolling control.

The loop count and status led13 only run in Time Mode.

Serial Monitor info looks very different between modes.

The demo shows how non-blocking code can run as parallel tasks smoothly and quickly, on my Uno R3 I get ? 64000 loops/second.. first second may be a partial count.

// DelayOrNoDelayWithLoopCount V1 by GoForSmoke  6/20/25 --
// LoopCount sketch lines added to run like the Status Blink as a parallel Task
// Serial Monitor output improved and more verbose -- Uno speed up to 64K range.
// DelayOrNoDelay V1 by GoForSmoke  6/3/25 -- some changes to...
// DualActionDelayMillis v1.1 by GoForSmoke 11/18/24 -- made for Uno R3
// expect: ground pin 7 to run delay mode, not grounded runs timer mode
// expect: enter key in Serial Monitor to pause action, unpause action
// code revised 2/7/25 -- now using arrays and function calls + status led

// note that Arduino millis is +/-1 and that printing takes time as well!

// line taken from my 2024 NoBlockLoopCounterLT sketch.
extern volatile unsigned long timer0_millis; // might be faster than millis()
// end taken

const byte jumperPin = 7; // a jumper from pin 7 to GND or pin 7 unterminated

byte jumperStateNow, jumperStatePrev; // to compare what is to what was
const word shortWait = 3; // ms delay for the contacts to settle

const byte statusBlinkPin = 13; // Uno board LED pin13 to run as status led
byte statusBlinkState;  // led13 0=OFF, not-0=ON
unsigned long statusBlinkStart;
const unsigned int  statusBlinkInterval = 500;

const unsigned long interval[ 2 ] = { 3000000, 700000 };   // for 2 timings, both modes wait time
unsigned long start[ 2 ];                            // for 2 timings, timer mode
byte started[ 2 ] = { 0, 0 };
byte runInterval = 0;


void usage()
{
  Serial.println( F( "\n\n    Dual Action Delay Millis  2/7/25 \n" ));  // now shows version
  Serial.println( F( "    Ground pin 7 to run delay mode, not grounded runs timer mode." ));
  Serial.println( F( "    You can jumper pin 7 to ground or even put a button between pin 7 and ground." ));
  Serial.println( F( "    Send Enter key in Serial Monitor to pause action, unpause action." ));
  Serial.println( F( "    Loop count will print, status led13 blink in Time Mode." ));
  Serial.println( F( "    Note that printing Serial can cause timing to be slightly late.\n\n" ));
}

// function taken from my 2024 NoBlockLoopCounterLT sketch.
void LoopCounter() // tells the average response speed of void loop()
{ // inside a function, static variables keep their value from run to run
  static unsigned long count; // only this function sees this
  static bool lastBit10Set; // only this function sees this
  word millis16 = timer0_millis;

  count++; // adds 1 to count after any use in an expression, here it just adds 1.

  bool currentBit10Set = millis16 & 0x0400; // leverage integral to bool implicit promotion
  if (currentBit10Set != lastBit10Set) // 1 second
  {
    //    Serial.print( millis16 ); // 16-bit binary into decimal text, many micros
    //    Serial.write('\t');
    Serial.print( F( "Loops " )); // added for demo, not from the taken code
    Serial.println( count ); // 32-bit binary into decimal text, load of cycles!
    count = 0; // don't forget to reset the counter
    lastBit10Set = currentBit10Set;
  }
}
// end taken

void setup()
{
  Serial.begin( 115200 ); // run serial fast to clear the output buffer fast
  // set Serial Monitor to match

  pinMode( jumperPin, INPUT_PULLUP );
  jumperStateNow = jumperStatePrev = digitalRead( jumperPin );

  pinMode( statusBlinkPin, OUTPUT ); // LOW by default
  statusBlinkState = 0;
  statusBlinkStart = millis();

  usage();
}

void checkForUserJumperPinChange()  //  handles pin bounce, only changes once pin state is stable
{
  jumperStateNow = digitalRead( jumperPin );    // check for mode change

  if ( jumperStateNow != jumperStatePrev )      // if jumperPin changes state, stop and debounce then re-init
  {
    while ( jumperStateNow != jumperStatePrev ) // beginner debounce does block but here that doesn't matter
    {
      jumperStatePrev = jumperStateNow;

      delay( shortWait ); // yes, freakout, user input gets delayed!
      jumperStateNow = digitalRead( jumperPin ); // wait and read again, loops until no change

    } // finished debounce, pin state is stable

    started[ 0 ] = started[ 1 ] = 0;  // initialize for timer mode, doesn't matter in delay mode
    runInterval = 0;             // init Interval
  }
}

void pauseSerialMonitorIfSerialEntry()  // stops the scrolling to allow highlight and copy (ctrl-C) for paste (ctrl-V)
{
  if ( Serial.available())  // this is blocking code that does not matter
  {
    while ( Serial.available())
    {
      Serial.read(); // empty the buffer
      delay( 1 );
    }
    usage();

    while ( !Serial.available());  // wait for unpause

    while ( Serial.available())
    {
      Serial.read(); // empty the buffer
      delay( 1 );
    }

    started[ 0 ] = started[ 1 ] = 0; // re-init timer mode
  }
}

void statusBlinker()  // will not run properly in delay mode
{
  if ( millis() - statusBlinkStart >= statusBlinkInterval )
  {
    statusBlinkState = !statusBlinkState;
    digitalWrite( statusBlinkPin, statusBlinkState );
    statusBlinkStart += statusBlinkInterval;
  }
}

void delayModeTiming( byte interval_number )
{
  Serial.print( F( "Delay " ));
  Serial.print( interval_number );
  Serial.write( ' ' );
  Serial.print( interval[ interval_number ] / 1000 );
  Serial.print( F( " ms  now: " ));
  Serial.println( millis());

  delayMicroseconds( interval[ interval_number ] );

  Serial.print( F( "End Delay " ));
  Serial.print( interval_number );
  Serial.print( F( " End time " ));
  Serial.println( millis());
}

void timerModeTiming( byte interval_number ) // uses micros timing for closer precision, but shows millis time!
{
  if ( started[ interval_number ] == 0 ) // only prints info line at the start
  {
    started[ interval_number ] = 1;
    Serial.print( F( "Timer " ));
    Serial.print( interval_number );
    Serial.print( F( " Wait " ));
    Serial.print( interval[ interval_number ] / 1000 );
    Serial.print( F( " ms  now: " ));
    Serial.println( millis());
    start[ interval_number ] = micros();
  }

  if ( micros() - start[ interval_number ] >= ( interval[ interval_number ] )) 
  {
    started[ interval_number ] = 0;
    Serial.print( F( "Timer " ));
    Serial.print( interval_number );
    Serial.print( F( " End time " ));
    Serial.println( millis());
  }
}

void loop()
{
  if ( jumperStateNow == 1 ) // time mode
  {
    LoopCounter(); // the function runs as a task, the optimizer will inline the code.
  }

  if ( jumperStateNow == 1 ) // run timer mode
  {
    timerModeTiming( runInterval ); // only runs when it should, when pin 7 is grounded
  }
  else
  {
    delayModeTiming( runInterval ); // only runs when it should, when pin 7 is not grounded
  }

  runInterval = !runInterval; // ! changes 0 to 1, changes 1 to 0

  if ( jumperStateNow == 0 ) // run delay mode
  {
    statusBlinker();
  }

  checkForUserJumperPinChange();
  pauseSerialMonitorIfSerialEntry(); // stops Serial Monitor scrolling on key entry, resumes on next key entry
}

Your question and code are not clear, sorry.
Do you think that all of this code needs for testing?

1 Like

What is the point of the post?

@GoForSmoke
Suggestion, lose the mode pin and swap modes by responding to 'M' and 'm' keys, or 'D' and 'T'.

Otherwise, I'll presume you're correct, haven't even parsed the code. What's it look like in Wokwi?

It's not a question, its presenting a demo that shows how-to do what smart beginners may learn and do themselves.

The output shows the difference between using delay-based code and using time-based code with the user being able to toggle which is used. It also shows how time-based code can be added to. It also shows in the Serial Monitor pause-unpause where delay gets proper use. But only to those ready to see it.

This is part of the forum where I see many who could use it.
I'm a bit confused, I thought that you already knew this well.

Sometimes you have to see it work to have a map to the source.
Run the output with INPUT_PULLUP pin 7 unterminated for a bit, 7 to 12 seconds should be enough and hit Ser Mon Enter to pause then scroll up to see 2 tasks run parallel... is it plain that they do?
If you run delay mode the output order is serial. The difference to time mode parallel output should help beginners to get this.

The loop counts are to get beginners aware of how fast/smooth/accurate that void loop is running and if/when they mess with it then how that affects loop speed. That can help/lead-to code that uses iterative cycles to walk through jobs.

This is meant to be stretched, used and grabbed from.
Once you know how to not block you have the key to combining sketches at will. Will you resolve pin, interrupt and name conflicts (edit) and convert delays to timers first? If so then yeah a semi-slap-together code system might work and code toolbox be possible to "grab codez from". :slight_smile:

On my Uno R3 the non-blocking is clocking > 64000 for whole seconds on the second since board startup, the first count will likely be short. The second is when millis() & 0x400 changes.
While making that demo change at one point the text output was heavy and speed was > 47000. Don't print >= 64 chars at once.
That's what loop counter does for me, I trimmed 17K loops!

I have an old demo that shows solutions to delays inside loops and other structures, that one involves state machines. I could update that.

I didn't run it on Wokwi, I have an Uno right here.

Suggestion, lose the mode pin and swap modes by responding to 'M' and 'm' keys, or 'D' and 'T'.

Key pause now just looks for Enter, and everything keyboard has Enter at the end. Still, mode change and pause might work.

Demo code for beginners to experiment with.

After a while of reading threads, it seems like a good idea.
I've been active here for a while.

Then perhaps it should be in 'Tutorials', with appropriate extended verbiage and use cases? In 'Programming', I think it's just lost.

1 Like

Is a mess to wade through.
Appropriate wha? Time sink for what?
Here it will sink in time to the archives.
Those who search, find.

I have saved it, will look into it next week.

Maybe add this to the tutorials you already list in your profile.

What Profile? Izzerabutton? I don't see no steenking button...
okay I find.
Tutorials? Oh, you mean the Nick Gammon fundamentals for experienced beginners links?

Maybe I need to set up a home page. Bother.

There is a section for tutorials in this forum, where people explain topics to beginners.

In my humble opinion your post #1 doesn't qualify as tutorial in the current stage.

For a tutorial you would need to describe what you want to demonstrate and how it is working so a beginner can understand what your code is doing.

1 Like

Yes, those. I suggest you add a link to this there.

Still need to test the newest with serial entry control, do final debug and cleanup. Got V1.1 code ready.

Compiles, untested:

// DelayOrNoDelayWithLoopCount V1.1 by GoForSmoke  6/21/25 --
// replacing jumper for mode selection with serial commands
// cleaned up a lot of variable names and comments
// make sure that your Serial Monitor only sends \n or \r end of line!
// DelayOrNoDelayWithLoopCount V1 by GoForSmoke  6/20/25 --
// LoopCount sketch lines added to run like the Status Blink as a parallel Task
// Serial Monitor output improved and more verbose -- Uno speed up to 64K range.
// DelayOrNoDelay V1 by GoForSmoke  6/3/25 -- some changes to...
// DualActionDelayMillis v1.1 by GoForSmoke 11/18/24 -- made for Uno R3
// expect: ground pin 7 to run delay mode, not grounded runs timer mode
// expect: enter key in Serial Monitor to pause action, unpause action
// code revised 2/7/25 -- now using arrays and function calls + status led

// note that Arduino millis is +/-1 and that printing takes time as well!

// line taken from my 2024 NoBlockLoopCounterLT sketch.
extern volatile unsigned long timer0_millis; // might be faster than millis()
// end line taken


const byte statusBlinkPin = 13; // Uno board LED pin13 to run as status led
byte statusBlinkState = 0;  // led13 0=OFF, not-0=ON
unsigned long statusBlinkStart;
const unsigned int  statusBlinkInterval = 500;

const byte tasks = 2;
byte mode = 1; // 0 runs delay mode, 1 runs time mode
const unsigned long interval[ tasks ] = { 3000000, 700000 };   // for 2 timings, both modes wait time
unsigned long start[ tasks ];                            // for 2 timings, timer mode
byte started[ tasks ] = { 0, 0 };
byte task = 0;

byte pause; // paused if 1


void usage()
{
  Serial.println( F( "\n\n    Dual Action Delay Millis  2/7/25 \n" ));  // now shows version
  Serial.println( F( "    Enter D or d in Serial monitor to run delay mode." ));
  Serial.println( F( "    Enter T or t in Serial monitor to run time mode." ));
  Serial.println( F( "    Enter P or p in Serial monitor to pause data scrolling." ));
  Serial.println( F( "    While paused, P, p, or Enter will end pause." ));
  Serial.println( F( "    Loop count will print, status led13 blink, in Time Mode only." ));
}

// function taken from my 2024 NoBlockLoopCounterLT sketch.
void LoopCounter() // tells the average response speed of void loop()
{ // inside a function, static variables keep their value from run to run
  static unsigned long count; // only this function sees this
  static bool lastBit10Set; // only this function sees this
  word millis16 = timer0_millis;

  count++; // adds 1 to count after any use in an expression, here it just adds 1.

  bool currentBit10Set = millis16 & 0x0400; // leverage integral to bool implicit promotion
  if (currentBit10Set != lastBit10Set) // 1 second
  {
    //    Serial.print( millis16 ); // 16-bit binary into decimal text, many micros
    //    Serial.write('\t');
    Serial.print( F( "Loops " )); // added for demo, not from the taken code
    Serial.println( count ); // 32-bit binary into decimal text, load of cycles!
    count = 0; // don't forget to reset the counter
    lastBit10Set = currentBit10Set;
  }
}
// end function taken

void setup()
{
  Serial.begin( 115200 ); // run serial fast to clear the output buffer fast
  // set Serial Monitor to match

  pinMode( statusBlinkPin, OUTPUT ); // LOW by default
  statusBlinkState = 0;
  statusBlinkStart = millis();

  usage();
}


void SerialEntry()  // change mode or stop the scrolling to allow highlight and copy (ctrl-C) for paste (ctrl-V)
{
  char ch;
  if ( Serial.available())
  {
    switch ( ch )
    {
      case 'D' :  // set delay mode
      case 'd' :
        mode = 0;
        break;

      case 'T' :  // set time mode
      case 't' :
        mode = 1;
        break;

      case 'P' :  // pause toggle
      case 'p' :
        if ( pause == 1 ) pause = 0;
        else              pause = 2; // since after the P there's a \n
        break;

      case '\n' :     // make sure that your Serial Monitor only sends \n or \r end of line!
      case '\r' :
      if ( pause > 0 ) pause--;
    }
    started[ 0 ] = started[ 1 ] = 0; // re-init timer mode
  }
}

// this code came from an older sketch
void statusBlinker()  // will not run properly in delay mode
{
  if ( millis() - statusBlinkStart >= statusBlinkInterval )
  {
    statusBlinkState = !statusBlinkState;
    digitalWrite( statusBlinkPin, statusBlinkState );
    statusBlinkStart += statusBlinkInterval;
  }
}
// end older sketch code

void delayModeTiming( )
{
  Serial.print( F( "Delay " ));
  Serial.print( task );
  Serial.write( ' ' );
  Serial.print( interval[ task ] / 1000 );
  Serial.print( F( " ms  now: " ));
  Serial.println( millis());

  delayMicroseconds( interval[ task ] );

  Serial.print( F( "End Delay " ));
  Serial.print( task );
  Serial.print( F( " End time " ));
  Serial.println( millis());
}

void timerModeTiming( ) // uses micros timing for closer precision, but shows millis time!
{
  if ( started[ task ] == 0 ) // initialize timer only prints info line at the start
  {
    started[ task ] = 1;  // locked
    Serial.print( F( "Timer " ));
    Serial.print( task );
    Serial.print( F( " Wait " ));
    Serial.print( interval[ task ] / 1000 );
    Serial.print( F( " ms  now: " ));
    Serial.println( millis());
    start[ task ] = micros(); // loaded
    return;
  }

  if ( micros() - start[ task ] >= ( interval[ task ] ))
  {
    started[ task ] = 0;
    Serial.print( F( "Timer " ));
    Serial.print( task );
    Serial.print( F( " End time " ));
    Serial.println( millis());
  }
}

void loop()
{
  if ( mode == 1 ) // time mode
  {
    LoopCounter(); // the function runs as a task.
  }

  if ( mode == 1 ) // run time mode
  {
    timerModeTiming( );
  }
  else
  {
    delayModeTiming( );
  }

  // running 1 timer per loop, there could be many so just 1 per
  task = !task; // ! changes 0 to 1, ! changes 1 to 0, ! is NOT
  // started[ task ] and interval[ task ] control the action

  if ( mode == 1 ) // run status blinker
  {
    statusBlinker();
  }

  SerialEntry();
}