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.
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
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);
}
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);
}
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)
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.
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?
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.
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.
[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.
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.