Replace delay() with milllis()

Just investigated a little bit in MegunoLink. They say

Cmd_CommandFunction is the Arduino function in your program that will be called whenever the command message is found. It can have any name you like, but it must take one parameter, a CommandParameter & .

So one change you have to do is to add 'CommandParameter @Parameters' to the calls of Cmd_Stop and Cmd_Start.

Start and stop work as they should. the setvalues command also returns "The SetValues command is handled" but when I start it the values are the same as they were before. I'm testing it with IDE sending commands in serial monitor baud9600 and /r. Uno with leds in pins 2, 3 and 4.

I think it's better to add the (CommandParameter &Parameters) to Cmd_Stop and Cmd_Start as it says

You need to include the parameter, even it is not used in the command handler function. Otherwise your program won’t build.

See here https://www.megunolink.com/documentation/arduino-libraries/serial-command-handler/

But to your last post; could you change the Cmd_SetValues like this

void Cmd_SetValues(CommandParameter &Parameters)
{
  s1 = Parameters.NextParameterAsUnsignedLong();
  s2 = Parameters.NextParameterAsUnsignedLong();
  s3 = Parameters.NextParameterAsUnsignedLong();
  v1 = Parameters.NextParameterAsInteger();
  v2 = Parameters.NextParameterAsInteger();
  v3 = Parameters.NextParameterAsInteger();
  Serial.print(F("The SetValues command is handled: "));
  Serial.print(s1);
  Serial.print('\t');
  Serial.print(s2);
  Serial.print('\t');
  Serial.print(s3);
  Serial.print('\t');
  Serial.print(v1);
  Serial.print('\t');
  Serial.print(v2);
  Serial.print('\t');
  Serial.println(v3);
}

and test it? If all goes well you should see the actual values of s1 to v3 printed on Serial

[Edit:] I got the Megunolink CommandHandler to work! It required to add the following section to diagram.json and change "newline" from 'lf" to 'crlf"

  "serialMonitor": {
  "collapse": false,
  "convertEol": false,
  "display": "auto",
  "newline": "crlf"
  },

An exclamation mark has to be put in fron of the commands and here we go ;-)

Serial monitor returns "The SetValues command is handled: 1 1 1 4 3 2" but after !Start the order and timing is still the previous one. Still no luck. I added CommandParameter &Parameters to functions.

Oh, a code acts like it has been programmed, it does not necessarily do what we want it to do :slight_smile:

It definitely changes the values of the variables but the variables are not (no longer) related to the values in the arrays!

When you start the board the arrays are initiated with the values of s1 to s3 and v1 to v3. After that there is no connection anymore ...

Instead of changing s1 to v3 you have to change the content of the arrays!

Wait a second and I'll provide a working code on Wokwi ...

Here the code

/*
  Forum: https://forum.arduino.cc/t/replace-delay-with-milllis/1421640
  Wokwi: https://wokwi.com/projects/453610040979319809

  2025/12/28
  ec2021

  Example using arrays and boolean variables to control the function handleLeds()

  Here using the Megunolink Command Handler...

  Now it is possible to change the multiplier and pin array values as requested by the TO

  Be aware that changing the pin numbers by Serial may create serious problems as there is no check
  whether the pin data are valid or not!

  To change the sequence it would be better to add an array that keeps the indexes for the led pins
  and just changes this; the command that sets the entries of that array can easily restrict the data
  to valid values.


*/

#include "MegunoLink.h"
#include "CommandHandler.h"

CommandHandler<5, 92, 34> SerialCommandHandler;

InterfacePanel MyPanel;

constexpr int noOfItems = 3;
unsigned long multiplier[noOfItems] {3, 1, 4};
int pin[noOfItems] {2, 4, 3};

boolean startCmdReceived = false;
boolean stopCmdReceived = false;



// Commands Start and Stop
// set boolean variables to true
// that enable the required functionality

void Cmd_Start(CommandParameter &Parameters)
{
  Serial.println(F("The Start command is handled"));
  startCmdReceived = true;
}

void Cmd_Stop(CommandParameter &Parameters)
{
  Serial.println(F("The Stop command is handled"));
  stopCmdReceived = true;
}

void Cmd_SetValues(CommandParameter &Parameters)
{
  for (int i = 0; i < noOfItems; i++) {
    multiplier[i] = Parameters.NextParameterAsUnsignedLong();
  }
  for (int i = 0; i < noOfItems; i++) {
    int p = Parameters.NextParameterAsInteger();
    if (p >= 2 and p <=4) {
      pin[i] = p;
    } else {
      Serial.print("Wrong pin number ");
      Serial.println(p);
    }
  }
  Serial.println(F("The SetValues command is handled"));
}

// The function handleLeds()
// handles Start and Stop command
// On Stop command
//    * all leds are switched off
//    * the local static variables are set to their start values
//    * the booleans that control handleLeds() are set to false
// On Start command
//   * the items are handled according to the index itemNo
//   * in the first loop the item is switched on and the recent time is stored
//   * in further loops, when the delay time has expired, everything is prepared
//     for the next item
//   * if all items have been handled, itemNo is set to 0 and startCmdReceived
//     is cleared (set to false)


void handleLeds() {
  static byte itemNo = 0;
  static unsigned long switchOnTime = 0;
  static boolean isSwitchedOff = true;
  if (stopCmdReceived) {
    for (int i = 0; i < noOfItems; i++) {
      digitalWrite(pin[i], LOW);
    }
    itemNo = 0;
    startCmdReceived = false;
    isSwitchedOff = true;
    stopCmdReceived = false;
    return;  // Not really necessary in this case as the rest of the function depends on startCmdReceived = true
  }
  if (startCmdReceived) {
    if (isSwitchedOff) {
      digitalWrite(pin[itemNo], HIGH);
      isSwitchedOff = false;
      switchOnTime = millis();
    }
    if (millis() - switchOnTime > multiplier[itemNo] * 1000) {
      digitalWrite(pin[itemNo], LOW);
      isSwitchedOff = true;
      itemNo++;
    }
    if (itemNo >= noOfItems) {
      itemNo = 0;
      startCmdReceived = false;
    }
  }
}

// The following function blinks the blue led
// to verify that handleLeds() is not blocking
// loop()

void doSomethingElseAllTheTime(unsigned long interval) {
  constexpr byte bluePin {5};
  static unsigned long lastChange = 0;
  if (lastChange == 0) pinMode(bluePin, OUTPUT);
  if (millis() - lastChange > interval) {
    lastChange = millis();
    byte state = digitalRead(bluePin);
    digitalWrite(bluePin, !state);
  }
}
void setup()
{
  Serial.begin(9600);
  SerialCommandHandler.AddCommand(F("Start"), Cmd_Start);
  SerialCommandHandler.AddCommand(F("SetValues"), Cmd_SetValues);
  SerialCommandHandler.AddCommand(F("Stop"), Cmd_Stop);
  Serial.println("Begin");
  for (int i = 0; i < noOfItems; i++) {
    pinMode(pin[i], OUTPUT);
  }
}

void loop()
{
  SerialCommandHandler.Process();
  handleLeds();
  doSomethingElseAllTheTime(500);
}

See on Wokwi: https://wokwi.com/projects/453610040979319809

But warning: It is not a good idea to set and change pin numbers via Serial command without checking if they are valid!

Therefore I have implemented a very simple check in Cmd_SetValues based on the known range of pin number 2 to 4 ...

WoW! everything works PERFECT! Thank You so much!

To avoid crashes with wrong pin numbers you can use a "sequence" array:

/*
  Forum: https://forum.arduino.cc/t/replace-delay-with-milllis/1421640
  Wokwi: https://wokwi.com/projects/453615211146169345

  2026/01/19
  ec2021

  Example using arrays and boolean variables to control the function handleLeds()

  Here using the Megunolink Command Handler...

  Now it is possible to change the multiplier and pin array values as requested by the TO

  Be aware that changing the pin numbers by Serial may create serious problems as there is no check
  whether the pin data are valid or not!

  To change the sequence I have addes an array that keeps the indexes for the led pins
  and just changes this; the command that sets the entries of that array can easily restrict the data
  to valid values. It is programmed so that you can use 1 to 3 (instead of the array indexes 0 .. 2):

  !SetValues 1 1 1 1 2 3
  will set the time for each led to 1000 ms and the sequence from right to left (here pin 2, pin 3, pin 4)

  !SetValues 3 2 1 3 2 1
  will set the time for led pin[0] to 3000 ms led pin[1] to 2000 and led pin[2] to 1000 ms
  and the sequence from left to right (here pin 4, pin 3, pin 2)


*/

#include "MegunoLink.h"
#include "CommandHandler.h"

CommandHandler<5, 92, 34> SerialCommandHandler;

InterfacePanel MyPanel;

constexpr int noOfItems = 3;
constexpr int pin[noOfItems] {2, 3, 4};
unsigned long multiplier[noOfItems] {3, 1, 4};
int sequence[noOfItems] = {0, 2, 1};

boolean startCmdReceived = false;
boolean stopCmdReceived = false;



// Commands Start and Stop
// set boolean variables to true
// that enable the required functionality

void Cmd_Start(CommandParameter &Parameters)
{
  Serial.println(F("The Start command is handled"));
  startCmdReceived = true;
}

void Cmd_Stop(CommandParameter &Parameters)
{
  Serial.println(F("The Stop command is handled"));
  stopCmdReceived = true;
}

void Cmd_SetValues(CommandParameter &Parameters)
{
  for (int i = 0; i < noOfItems; i++) {
    multiplier[i] = Parameters.NextParameterAsUnsignedLong();
  }
  for (int i = 0; i < noOfItems; i++) {
    int p = Parameters.NextParameterAsInteger();
    if (p > 0 && p <= noOfItems) {
      sequence[i] = p - 1;
    } else {
      Serial.print("Wrong pin number ");
      Serial.println(p);
    }
  }
  Serial.println(F("The SetValues command is handled"));
}

// The function handleLeds()
// handles Start and Stop command
// On Stop command
//    * all leds are switched off
//    * the local static variables are set to their start values
//    * the booleans that control handleLeds() are set to false
// On Start command
//   * the items are handled according to the index itemNo
//   * in the first loop the item is switched on and the recent time is stored
//   * in further loops, when the delay time has expired, everything is prepared
//     for the next item
//   * if all items have been handled, itemNo is set to 0 and startCmdReceived
//     is cleared (set to false)


void handleLeds() {
  static byte itemNo = 0;
  static unsigned long switchOnTime = 0;
  static boolean isSwitchedOff = true;
  if (stopCmdReceived) {
    for (int i = 0; i < noOfItems; i++) {
      digitalWrite(pin[i], LOW);
    }
    itemNo = 0;
    startCmdReceived = false;
    isSwitchedOff = true;
    stopCmdReceived = false;
    return;  // Not really necessary in this case as the rest of the function depends on startCmdReceived = true
  }
  if (startCmdReceived) {
    if (isSwitchedOff) {
      digitalWrite(pin[sequence[itemNo]], HIGH);
      isSwitchedOff = false;
      switchOnTime = millis();
    }
    if (millis() - switchOnTime > multiplier[sequence[itemNo]] * 1000) {
      digitalWrite(pin[sequence[itemNo]], LOW);
      isSwitchedOff = true;
      itemNo++;
    }
    if (itemNo >= noOfItems) {
      itemNo = 0;
      startCmdReceived = false;
    }
  }
}

// The following function blinks the blue led
// to verify that handleLeds() is not blocking
// loop()

void doSomethingElseAllTheTime(unsigned long interval) {
  constexpr byte bluePin {5};
  static unsigned long lastChange = 0;
  if (lastChange == 0) pinMode(bluePin, OUTPUT);
  if (millis() - lastChange > interval) {
    lastChange = millis();
    byte state = digitalRead(bluePin);
    digitalWrite(bluePin, !state);
  }
}
void setup()
{
  Serial.begin(9600);
  SerialCommandHandler.AddCommand(F("Start"), Cmd_Start);
  SerialCommandHandler.AddCommand(F("SetValues"), Cmd_SetValues);
  SerialCommandHandler.AddCommand(F("Stop"), Cmd_Stop);
  Serial.println("Begin");
  for (int i = 0; i < noOfItems; i++) {
    pinMode(pin[i], OUTPUT);
  }
}

void loop()
{
  SerialCommandHandler.Process();
  handleLeds();
  doSomethingElseAllTheTime(500);
}

On Wokwi: https://wokwi.com/projects/453615211146169345

As coded pin index and multiplier index of a certain led are identical so that the multiplier data always affect the pins in the order they are placed in pin[] array.

The sequence data control which pin is switched on/off first, second etc.

!SetValues 3 2 1 3 2 1
  will set the time for led pin[0] to 3000 ms led pin[1] to 2000 and led pin[2] to 1000 ms
  and the sequence from left to right (here pin 4, pin 3, pin 2)

If you’re going to time with millis, be aware that Arduino millis is always +/- 1. To time closely, always use micros()!

+/- ?

Reference your topic title.
Delay() halts the program from running for the set period.
Sometimes it matters, sometimes it doesn't.
millis(0 is a time stamp. It's dynamic.
To use it for a delay, you read millis() at the start, keep reading it until the current value minus the start value equals the delay you want.
Using delay() is useful when there's nothing going on while the delay is running.
Delay() can really mess up critical things like interrupts.
Millis() doesn't mess things up.

And as long as you don’t want to combine that code with non-blocking code.

Delay codes run sequential (one after the other) only.

Timed codes run parallel (at the same time) but can force sequences.

Can timed codes run parallel in a single core machine?

Arduino micros() has a grain of 4 microseconds which is NOT THE SAME as Arduino millis() +/- 1 error which is due to the low byte of millis() only ever returning 250 out of 256 possible values. That is because millis() advances every 1024 microsecs instead of every 1000 microsecs… 250 x 1024 = 256000 = 256 x 1000… time for common sense that AI does not have. Be smart, figure out why for yourself and don’t play clever as it won’t be at all.

The timed intervals run parallel on an Uno. I have posted MANY EXAMPLES OF THAT. I have posted an example that lets users switch between delay mode and timed mode in the last year.

Don’t get hung up on Superficialities, just because 2 leds blinking at different rates don’t get switched ON and OFF in the same instruction does not mean that BOTH are waiting and changing IN THE SAME TIME PERIOD. They’re certainly not sequential!

What is with you and this absolute need to find any little detail to hang an objection on?

I have not got the answer to my question!

It's squabbling, and of questionable worth pedagogically speaking.

You obviously know the answer. Rhetorical questions have their place, as does nit-picking.

Of course the UNO can do nothing in parallel without using peripherals.

But take a pill: several many processes can be advanced by timed cooperative operations and so appear to be functioning at the same time. In parallel.

Roll with the rocks here. Sometimes things are expressed in a less than totally accurate manner. It is helpful to disambiguate casual statements, but you will never succeed if the goal is that everyone who posts is one hundred percent technically accurate on every detail. We human after all.

Everyone is different WRT knowledge, experience and ability to write. Code or English. Rather than jump in and drag it off to an arguable sidetrack, why not just make positive contributions? If you get mileage from doing, such posts can implicitly and gently improve the material that OPs sift through.

a7

You got a detailed not just YES but HOW.

More simply, the timed codes use the same clock and each task runs one short step before the next. When the goal is to be on the millisecond close, a loop() averaging 10’s of cycles per ms is going to be on-time for every task in it.

Delay code runs sequential, Timed code runs parallel. Demo:

[code]
// 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();
}
[/code]

Quod Erat Demonstradum

If you want to focus not on What is done but just How then you won’t see that an ON-time-OFF-time- blink cycle is a process regardless of just when ON and OFF happen. If you can’t see that then you can’t see multiple processes actually occurring at the same time…. yeah they appear to because they in fact are, however it’s done. See the woods from the trees, don’t fixate.

This is also my understanding.

In a single-core machine, tasks are executed one after another so rapidly (Fig. 1, 2: two tasks example) that users perceive them as occurring in pseudo-parallel. In reality, these tasks are executed concurrently through time slicing; whereas, true parallelism requires multiple processors or cores, one for each task.


Figure-1:


Figure-2:

#include <Arduino_FreeRTOS.h>

TaskHandle_t myTaskA;
TaskHandle_t myTaskB;
#define 	LED1	5
#define	    LED2	6

void setup()
{
  pinMode(LED1, OUTPUT); 		//LED1
  pinMode(LED2, OUTPUT);  		//LED3

  xTaskCreate(myTaskFunctionA, "MyTaskA", 128, NULL, 1, &myTaskA);
  xTaskCreate(myTaskFunctionB, "MyTaskB", 128, NULL, 1, &myTaskB);
}

void loop()   
{
  //leave it empty for Arduino UNO
}

void myTaskFunctionA(void *pvParameters)//LED1
{
  for (;;)
  {
    digitalWrite(LED1, HIGH);
    vTaskDelay(pdMS_TO_TICKS(500)); // Delay for 1/2 second
    digitalWrite(LED1, LOW);
    vTaskDelay(pdMS_TO_TICKS(500)); 
  }
}

void myTaskFunctionB(void *pvParameters)//LED2
{
  for (;;)
  {
    digitalWrite(LED2, HIGH);
    vTaskDelay(pdMS_TO_TICKS(1000)); // Delay for 1 second
    digitalWrite(LED2, LOW);
    vTaskDelay(pdMS_TO_TICKS(1000)); 
  }
}


:thinking:

  • When I walk while eating and listening to music, is this parallel action ?

:roll_eyes: