Example-code for timing based on millis() easier to understand through the use of example-numbers / avoiding delay()

non-blocking timing. Execute code only from time to time.

There are a lot of different ways to learn programming. This thread wants to add another approach that is different to the yet existing ones.

UPDATE 06.01.2023

if you are mainly interested in applying non-blocking timing you can do a quick read of this
short tutorial / demonstration

If you are intersted in understanding the details how it works go on reading here.
I use an everyday analogon in this post to explain the basic principle
and in the posts
3 simple democode

4 three timers with different periods
etc there are an easy to use example-codes for non-blocking timing
for things that shall happen after a certain interval like

update 21.07.22

in post #7

there is a new visualisisation how non-blocking code works

  • non-blocking blinking an LED = switching on/off the LED at a certain frequency
  • non-blocking printing to the serial monitor once every x seconds

pizza baking example

as an everyday example with easy to follow numbers
.
.
delay() is blocking. As long as the delay is "delaying" nothing else of the code can be executed.
delay() makes the microcontroller "whirl" at maximum-speed occupying 100% calculation-power

The function delay() should have this name

freeze_microcontroller_completely_STOP_code_execution_until_freezingtime_is_over()

Now there is a technique of non-blocking (non-freezing) timing.
The basic principle of non-blocking timing is fundamental different from using delay()

You have to understand the difference first and then look into the code.

otherwise you might try to "see" a "delay-analog-thing" in the millis()-code which it really isn't
Trying to see a "delay-analog-thing" in millis() makes it hard to understand millis()
Having understood the basic principle of non-blocking timing based on millis() makes it easy to understand.

imagine baking a frosted pizza
the cover says for preparation heat up oven to 200°C
then put pizza in.
Baking time 10 minutes

You are estimating heating up needs 3 minutes
You take a look onto your watch it is 13:02 (snapshot of time)
You start reading the newspaper and from time to time looking onto your watch
watch shows 13:02. 13:02 - 13:02 = 0 minutes passed by not yet time
watch shows 13:03. 13:03 - 13:02 = 1 minute passed by not yet time
watch shows 13:04. 13:04 - 13:02 = 2 minutes passed by not yet time

watch shows 13:05 when did I start 13:02? OK 13:05 - 13:02 = 3 minutes time to put pizza into the oven

New basetime 13:05 (the snapshot of time)
watch 13:06 not yet time (13:06 - 13:05 = 1 minute is less than 10 minutes
watch 13:07 not yet time (13:07 - 13:05 = 2 minutes is less than 10 minutes
watch 13:08 not yet time (13:08 - 13:05 = 3 minutes is less than 10 minutes
watch 13:09 not yet time (13:09 - 13:05 = 4 minutes is less than 10 minutes
watch 13:10 not yet time...
watch 13:11 not yet time
watch 13:12 not yet time
watch 13:13 not yet time
watch 13:14 not yet time (13:14 - 13:05 = 9 minutes is less than 10 minutes
watch 13:15 when did I start 13:05 OK 13:15 - 13:05 = 10 minutes time to eat pizza (yum yum)

You did a repeated comparing how much time has passed by
This is what non-blocking timing does

In the code looking at "How much time has passed by" is done

currentTime - startTime >= bakingTime (by the way: this if-condition handles the rollover of millis() 100% correctly)

bakingTime is 10 minutes

13:06 - 13:05 = 1 minute >= bakingTime is false
13:07 - 13:05 = 2 minutes >= bakingTime is false
...
13:14 - 13:05 = 9 minutes >= bakingTime is false
13:15 - 13:05 = 10 minutes >= bakingTime is TRUE time for timed action!!

to stay in the pizza-example as pseudo-code

the non-blocking timing looks like this

void loop() {
  readingNewsPaper();       // doing 
  answerWhatsAppMsg();      // OTHER things 
  take_A_Sip_of_Coffee();   // in PARALLEL to

  // checking if TimePeriodIsOver 
  if ( TimePeriodIsOver(myPizzaTimer,10Minutes) ) {
    // IF timeperiod IS over then execute timed action
    take_pizza_out_of_the_oven();
  }  
}

the user-defined function TimePeriodIsOver() results in true if: self-explaining name: the defined period of time is over
the user-defined function TimePeriodIsOver() results in false if: self-explaining name: the defined period of time is NOT yet over

You could extend this pseudo-code. For example you want to add a special kind of cheese to the pizza
But this cheese shall only melt slightly for 3 minutes.

The complete baking-time is 10 minutes. If the added cheese shall only melt for 3 minutes
this means add the cheese after 7 minutes of baking

So in the pseudo-code you would write


void loop() {
  readingNewsPaper();       // doing 
  answerWhatsAppMsg();      // OTHER things 
  take_a_sip_of_coffee();   // in PARALLEL to

  // checking if TimePeriodIsOver 
  if ( TimePeriodIsOver(myPizzaTimer,10Minutes) ) {
    // IF timeperiod IS over then execute timed action
    take_pizza_out_of_the_oven();
  }  


  // checking if TimePeriodIsOver 
  if ( TimePeriodIsOver(myPizzaTimer,7Minutes) ) { // <=== see the difference in the period-of-time
    // IF timeperiod IS over then execute timed action
    add_cheese_to_pizza();
  }  

}

This explains the basic principle. There are a few more things to do for real coding.
But it is very important to understand the difference:

non-blocking timing does a very often and repeated checking of how much time has passed by.
And only if the right amount of time has passed by execute that part of the code that shall only
be executed from "time to time"

remove all delays()

To be able to check how much time has passed by very often

all function-calls delay(nnnn) must be removed.
If you still have delay(nnnn) in your code the delay is still blocking!

As long as there are still delays() in the code the fast repeated checking can't be done

1 Like
Non-blocking code help - timeline is attached
Controlling multiple timed relays off buttons
Reseting timer while code is running
Melody with delay
Servo isn't following my code, keeps jittering
Stepper motors and accelerometer
Make the oled display still working while in a WHILE-LOOP
Applying a PAUSE and RESUME feature in my Pneumatic Pick-and-Place Project
Help for traffic/crosswalk light code using mills()
Make led stay on after potentiometer value changes
Need help for my school project
One wire DS18B20 reading +85°C, then -127°C and Arduino hanging up completely
Hey there, currently doing a project that is literally driving me insane
Button Start Stop Loop Play Audio
Button commands are recognized just randomly
Delay() in main loop preventing ISR button detection
Code for an Arduino Automatic Transfer Switch
Using mllis() or micros() for a stepper motor
Blynk with NODEMCU and Timer
Plant watering system
Blink Without Delay pain in the a$$
Millis() help, turning a stepper motor
Timing for many different solenoid valves - fountain show
Arduino controled stairs lights with 2 PIR sensors not working
Arduino sketch not working (and i don't know why)
Time Delay when Scale reaches certain weight
How can I run two loops at the same time?
How to run Two codes at same time
2 Arduinos in Tinkercad verbinden ohne grossen Aufwand
Problems with the millis() function
Parallel schalten und AI
Water pump with selectable water levels
My School Project: Recreating Bop It! Smash :D
Running in background timer
Timer Problem AccelStepper Library
Need help understanding why code works only a certain way
Need help replacing delays with millis
Zwei Ultraschallsensoren auslesen lassen und mithilfe des gemessenen Abstands einen Motor nach vorne bzw. hinten laufen lassen
Hydroponic Automation Code with Arduino Uno + ESP8266 "need help"
Need help at coding
Beginner trying to learn
Relay Questions
Why are the loops not looping
When motors ON, push button not working
Using millis as a delay after an input
The millis seems can not reach the function?
Help needed: 2 buttons to turn passive buzzer on and off
Reading from SR04 during interrupt
Sequencing events in a loop-Blink once/Fade/repeat
Using millis as a delay after an input
Automatic watering system programming help
Proximity sensor serial print output
Autonomous mobile robot with obstacle avoidance
Auto Refilling Water Tank with Solenoid Valve and Ultrasonic Sensor
Multiple Statements to a Loop
Trying to run a cycler using leds
Running two line at differnet delay
My sketch doesn't log regulary but gains 1s delay with every second cycle
Possible to code the Arduino to shut off an LED after 20 mins?
How to measure the time my hand stays in front of an ultrasonic sensor?
Help with simple photoresistor chicken coop project
Adding in a time limit for completion of a circuit - Resetting the circuit if not met
If statement excution after sometime
Confused about statechange - execute once till change happens
Integrating 3 codes into one but i don't know how to do this
Arduino with 2 relay with time delay
How do I remove all delays from my code?
How to measure the time my hand stays in front of an ultrasonic sensor?
Having problems with code and programming buttons
Delay/Millis use
Is it possible to hop to the next line midway through a delay?
Delay turning LED off after button press
Is there a way of stopping a counter or delay in a statement when switch is still on
Using milli () delay instead of delay
Trying to Understand Milli() Better
Best practice, conditions in loops
Water fountain with button
Problems Using millis()
Led pattern using push button to turn on all leds problems
Problem on change the sampling rate of the sensor
Should I use Millis or stick with Delay for my project?
Synchronizing led with buzzer
Millis timer help
Condition clauses in regards to delays
Arduino newbie in need of help
Testing Millis with servos and experiencing odd behavior
When board powered up led turn on for 1min and meanwhile when button pressed turn off it immediately
Motor Controller Project
Program delay after pressing the touch button
Making timer with RTC clock
Is it possible to pauze arduino program
Assync web server button running servo
Bliniking a led with Millis()
How to get rid of this blocking delay
A Demo-Code explaining the switch-case state-machine and how to do things (almost) in parallel
Need help coding a button switch relay
10 second countdown
How make an led blink instead of just lighting up
How to make Timer Off without relay (Code)
Spa Temp Control
Help using RTC to do tasks at different times of day
Activate Finger Print Sensor
Getting out of a cycle in a for loop
How to use a colour sensor and obstacle sensor together
Moving a servo using 2 push buttons without using a delay
How to interrupt my robotic arm using ISR?
Arduino Uno: Touch Sensor and Motor
Help! - Beginner Servo Issue
Keypad user input timer switch without using delay()
EDIT:Why isn't my button responding in the code?
Detect state change when running other tasks
Reaktionstest Arduino
I want to add a Millis timer in place of a for loop
Multiple servos with buttons
Multiple servos with buttons
Running servo for loop delay with separate pushbutton if statements
I wanted to control my servo with my switch from Arduino Cloud
Switching System from One Shot to Oscillation
While loop refusues to stop
Arduino programm für eine fassmischer
Problem with butttons and i2c lcd
Steckdose mit Musik einschalten
Help with multitask
How to start a stepper motor with a push button, but stop it on its own
Need help understanding why code works only a certain way
Write code that is responsive to buttons ALL the time in parallel to multiple actions happening at the same time state-machine / non-blocking timing
Nextion Einbindung in Arduino Mega Programm
Nextion Einbindung in Arduino Mega Programm
7 leds blinking in loop
Delay or Millis() in a void function. Can't figure it out!
Hilfe beim ersten Projekt gesucht
Multitasking arduino code for servo operation
Multitasking arduino code for servo operation
Want to move a servo motor with three different speeds, and a cancel button
Delay in timer interrupt
Write non-blocking code
deltaTime and loop() in arduino
Very basic question about 16x2 LCD display scrolling
Timer0_mills error
Timer0_mills error
Help me merge this two codes together pls
Mulitple flashing LED with different sequences
Measure time that a condition is true to then trigger and action
Run 2 LED animations independently
ESP32 counter getting stuck
Guru Meditation Error: Core 0 panic'ed (IllegalInstruction). Exception was unhandled
How to delay using millis()
Pots do not control relay times
Make my DRO/dendrometer do more things
Need Help ! Using two IR sensors to control LED
Arduino sketch not working (and i don't know why)
Leds to be turned off immediately when I turn off the switch
How can I make the code stop when a specific button is pressed?
Cut off and loop a script at a specific time
Please help me rewrite delay with millis
Need help with esp32 microprocessor
Cut off and loop a script at a specific time
Using sevseg display with a delay
MobaTools Timerbase and Timer to Light LED
Probleme mit Temperaturregelung durch Tasterdruck
Help needed with debugging (Beginner)
Newbie question regarding millis()
freeRTOS hand issue
Newbie question regarding millis()
Delayed output without using the delay function
Load cell - Led blinking problem
Flow sensor and two relays!
Millis Timing Loop
Using millis with neopixel
Erklärung nicht-blockierendes Timing am Alltagsbeispiel Tiefkühl-Pizza backen ( millis() statt delay() ) Blink without delay BWD
Push Button Timer Help
IR remote motor control interrupt on new key press
36 h delay - trouble
Delay.... but not delay, so network aRest commands can come
HW-201 Sensor timing
Using interrupt (or another solution) to display sensor data
Code not working as expected,
Replace delay function with millisDelay

my opinion about the example BlinkWithoutDelay:

I want to comment on the basic blink_without_delay example-code.

The basic basic blink_without_delay example-code makes understanding non-blocking timing
harder than it must be for two reasons:

  1. a part of the variable-names is badly chosen.
  2. the demo-code distributes the variables to mutliple places

The variable-distribution makes it harder to understand the basic principle.

My suggestion is to use a function with a self-explaining name.

TimePeriodIsOver(myTimer,myIntervall)

bad chosen variable-name:
coding

 previousMillis = currentMillis;

is non-intuitive par excellence

What? something "previous" shall become current??

IMHO this can be named much better to say what is going on
see my function

boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - startOfPeriod >= TimePeriod ){
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;                   
  } 
  else return false;            // actual TimePeriod is NOT yet over 
}

I will add more postings over time with more explanations and more example-codes

1 Like

easier to use non-blocking timing for constantly repeat something after a certain amount of time

const int ledPin =  LED_BUILTIN; // the number of the LED pin

int ledState = LOW;             // ledState used to set the LED

// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

unsigned long MyTestTimer =  0; // Timer-variables MUST be of type unsigned long

unsigned long myInterval = 1000;


void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  pinMode(ledPin, OUTPUT);
}


// always put code that builds a senseful unit into its own function
void blinkLED() {
    if (ledState == LOW) {
      Serial.println("      ledState is LOW change it to HIGH");
      ledState = HIGH;
    } 
    else {
      Serial.println("ledState is HIGH change it to LOW");
      ledState = LOW;
    }
    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);  
}


void loop() {

  // once every x milliseconds (value stored in variable myInterval) 
  if ( TimePeriodIsOver(MyTestTimer, myInterval) ) {
    // execute code inside this if-condition
    blinkLED(); 
  }
}

This is a demo-code that shows the use of three different timers. Each with his own timer-variable where each timer has a different timePeriod

unsigned long DemoTimerA = 0; // variables that are used to store timeInformation
unsigned long DemoTimerB = 0; 
unsigned long DemoTimerC = 0; 
unsigned long DoDelayTimer = 0; 

// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

void setup() {
  Serial.begin(115200);
  Serial.println("Program started activate Show timestamp in serial monitor");
}

void loop() {
  if (  TimePeriodIsOver(DemoTimerA,1000)  ){
      Serial.println(" TimerA overDue");
    }
       
  if (  TimePeriodIsOver(DemoTimerB,2000)  ){
      Serial.println("                TimerB overDue");
    }

  if (  TimePeriodIsOver(DemoTimerC,3000)  ) {
      Serial.println("                               TimerC overDue");
    }

  if (  TimePeriodIsOver(DoDelayTimer,20000)  ){
      Serial.println("every 20 seconds execute delay(3500)... to make all other timers overdue");
      delay(3500);
    }    
}

There's absolutely nothing wrong with that- it's the whole point of the exercise. The naming's perfect.

if it works for you: perfect.

My experience is that a lot of people struggle with this poorly explained BWD-example
Sure once you have understood it everything is clear.

For me the question is: what is the most easiest way to understand the principle.

And of course this is different to different users.

This code-version is a new idea to visualise what the code is doing

If you open the serial monitor you see three lines:

left  Z/A changes every 0,5 seconds
right X/O changes every 2,0 seconds
Z:1455X

in the third line there is on the left letter 'A' or 'Z' which change once every 0,5 seconds
then there is a number that is counting up fast
on the right you see the letter 'X' or 'O' which change once every 2 seconds

This means there is happening a change 'A' / 'Z' / 'A' / 'Z' / 'A' / 'Z' .... every 0,5 seconds
The number is incremented once every 0,05 seconds
there is happening a change 'X' / 'O' / 'X' / 'O' / 'X' / 'O'..... every 2 seconds

this means three things change at different frequencies.

at first concentrate on the code written in void loop()

void loop() {
  blink_AZ(); // change character every  500 milliseconds between 'A' and 'Z'
  blink_XO(); // change character every  2000 milliseconds between 'X' and 'O'
  countUpPrintEach50ms(); // once every 50 milliseconds count up and print
}

these three lines are called 10000 times per second !
remark: there is no delay()

but

there are "delayed" actions delayed by non-blocking timing

only once every 500 milliseconds change 'A' / 'Z'
only once every 50 milliseconds increment number
only once every 2000 milliseconds change 'X' /'O'

to make it even more clear
function blink_AZ(); is executed 10000 times per second but only 2 times per second the character changes between A and Z

function blink_XO() is executed 10000 times per second but only 0,5 times per second the character changes between X and O

function countUpPrintEach50ms(); is executed 10000 times per second but only 20 times per second the counter is incremented by 1 and character left: number character right are printed to the serial monitor

the number counting up very fast represents the fast repeating loop (though the incrementing of the number is slowed down too, to make the increasing number readable)
the A / Z X / O represents doing things slowed down - doing a change just from time to time

After having understood this fact that all three functions are executed 10000 times per second look at the other details of the other functions below void loop()

Here is the complete code

unsigned long MyTestTimer = 0;                   // Timer-variables MUST be of type unsigned long
unsigned long myBlink_XO_Timer;
unsigned long myBlink_AZ_Timer;

char X_or_O = 'X';
char A_or_Z = 'A';

long myCounter = 0;

void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
}

void loop() {
  blink_AZ(); // change character every  500 milliseconds between 'A' and 'Z'
  blink_XO(); // change character every  2000 milliseconds between 'X' and 'O'
  countUpPrintEach50ms(); // once every 50 milliseconds count up and print
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

void blink_AZ() {
  if ( TimePeriodIsOver(myBlink_AZ_Timer, 500) ) { // check if 500 milliseconds have passed by
    // if 500 milliseconds HAVE passed by
    if (A_or_Z == 'A') { // check if character is an 'A'
      A_or_Z = 'Z';      // change to 'Z'
    }
    else {
      A_or_Z = 'A';      // change to 'A'
    }
  }
}

void blink_XO() {
  if ( TimePeriodIsOver(myBlink_XO_Timer, 2000) ) { // check if 2000 milliseconds have passed by
    // if 2000 milliseconds HAVE passed by
    if (X_or_O == 'X') { // check if character is an 'X'
      X_or_O = 'O';      // change to 'O'
    }
    else {
      X_or_O = 'X';      // change to 'X'
    }
  }
}

void clearSerialMonitor() {
  for (int i = 0; i < 50; i++) { // print 50 empty lines to clear screen
    Serial.println();
  }
}


void countUpPrintEach50ms() {
  if ( TimePeriodIsOver(MyTestTimer, 50) ) { // check if 50 milliseconds have passed by
    // if 50 milliseconds HAVE passed by
    myCounter++;
    clearSerialMonitor();
    Serial.println("left  Z/A changes every 0,5 seconds");
    Serial.println("right X/O changes every 2,0 seconds");
    Serial.print(A_or_Z);
    Serial.print(":");
    Serial.print(myCounter);
    Serial.println(X_or_O);
  }
}

best regards Stefan

1 Like

I appreciate all the responses. It looks like there has been some clarification for other members as well. I have used BlinkWithoutDelay (below, with my changes, and it works). There have been other suggestions which I am looking at as well.

#include <Servo.h>

const int led_A_Pin = 2;
const int led_B_Pin = 3;
const int led_C_Pin = 9;
const int led_D_Pin = 10;
const int led_E_Pin = 11;

const int led_A_Interval = 500;
const int led_B_Interval = 600;
const int led_C_Interval = 400;
const int led_D_Interval = 450;
const int led_E_Interval = 350;

const int blinkDuration = 1000;

byte led_A_State = LOW;
byte led_B_State = LOW;
byte led_C_State = LOW;
byte led_D_State = LOW;
byte led_E_State = LOW;

unsigned long currentMillis = 0;  
unsigned long previousLed_A_Millis = 0;
unsigned long previousLed_B_Millis = 0;
unsigned long previousLed_C_Millis = 0
unsigned long previousLed_D_Millis = 0
unsigned long previousLed_E_Millis = 0;

void setup() {

  Serial.begin(9600);
  Serial.println("Starting SeveralThingsAtTheSameTimeRev1.ino");  // so we know what sketch is running

  pinMode(led_A_Pin, OUTPUT);
  pinMode(led_B_Pin, OUTPUT);
  pinMode(led_C_Pin, OUTPUT);
  pinMode(led_D_Pin, OUTPUT);
  pinMode(led_E_Pin, OUTPUT);

}

void loop() {

  currentMillis = millis(); 
  updateLed_A_State();
  updateLed_B_State();
  updateLed_C_State();
  updateLed_D_State();
  updateLed_E_State();
  switchLeds();
}

void updateLed_A_State() {

  if (led_A_State == LOW) {
    if (currentMillis - previousLed_A_Millis >= led_A_Interval) {
       led_A_State = HIGH;
       previousLed_A_Millis += led_A_Interval;
    }
  }
  else {
    if (currentMillis - previousLed_A_Millis >= blinkDuration) {
       led_A_State = LOW;
       previousLed_A_Millis += blinkDuration;
    } 
  }    
}
void updateLed_B_State() {

  if (led_B_State == LOW) {
    if (currentMillis - previousLed_B_Millis >= led_B_Interval) {
       led_B_State = HIGH;
       previousLed_B_Millis += led_B_Interval;
    }
  }
  else {
    if (currentMillis - previousLed_B_Millis >= blinkDuration) {
       led_B_State = LOW;
       previousLed_B_Millis += blinkDuration;
    }
  }    
}
void updateLed_C_State() {

  if (led_C_State == LOW) {
    if (currentMillis - previousLed_C_Millis >= led_C_Interval) {
       led_C_State = HIGH;
       previousLed_C_Millis += led_C_Interval;
    }
  }
  else {
    if (currentMillis - previousLed_C_Millis >= blinkDuration) {
       led_C_State = LOW;
       previousLed_C_Millis += blinkDuration;
    }
  }    
}

void updateLed_D_State() {

  if (led_D_State == LOW) {
    if (currentMillis - previousLed_D_Millis >= led_D_Interval) {
       led_D_State = HIGH;
       previousLed_D_Millis += led_D_Interval;
    }
  }
  else {
    if (currentMillis - previousLed_D_Millis >= blinkDuration) {
       led_D_State = LOW;
       previousLed_D_Millis += blinkDuration;
    }
  }    
}

void updateLed_E_State() {

  if (led_E_State == LOW) {
    if (currentMillis - previousLed_E_Millis >= led_E_Interval) {
       led_E_State = HIGH;
       previousLed_E_Millis += led_E_Interval;
    }
  }
  else {
    if (currentMillis - previousLed_E_Millis >= blinkDuration) {
       led_E_State = LOW;
       previousLed_E_Millis += blinkDuration;
    }
  }    
}

void switchLeds() {
;
  digitalWrite(led_A_Pin, led_A_State);
  digitalWrite(led_B_Pin, led_B_State);
  digitalWrite(led_C_Pin, led_C_State);
  digitalWrite(led_D_Pin, led_D_State);
  digitalWrite(led_E_Pin, led_E_State);
}

Using C++ would be more comfortable.

Edit:

Take a here a view to gain the knowlede about coding in C++.

https://www.learncpp.com/learn-cpp-site-index/

This tutorial is very useful to get starting to prevent identical line of codes by using OOP.

You may start reading and learning here:

https://www.learncpp.com/cpp-tutorial/welcome-to-object-oriented-programming/

You have to prove the beginner-comfortability by

  1. posting the code
  2. starting a poll to receive feedback from real beginners if they find your code "comfortable"

I highly doubt it. Are you even still able to write code that is for beginners easy to understand?

I believe this only after you have posted the such a code.

I guess you personal will feel so badly uncomfortable with code that is for beginners easy to understand that you will refuse to post such an example.

Wonderful, simply brilliant tutorial. Thank you StefanL38.

I always struggled with those timers and ended up writing too much code for every timer. As a beginner I did not know about passing parameters by reference (&startOfPeriod). That makes it possible to use local variables with the same name with your helper-function. That really simplifies a code with multiple timers a lot.

Thank you and keep up the good teaching,
Fred

OK here is a shot at a C++ version. Beginners how far off the mark am I?

/*
 * Blink
 *
 * Blink using a c++ class to toggle pin 
 * Each time the toggle member function is called a 
 *  check is done to see if it is time to toggle yet
 */
 /* ==================================== */
// Interface description - this must be first and would
//  normally be in a header file as part of a library
class TogglePin {
  public:
    // constructors:
    TogglePin(byte inPinNumber);  // default constructor
    TogglePin(byte inPinNumber, unsigned long inDelayTime);  // constructor with delay time 
        
    // Note a simgle constructor could be used with default values like the following - but lets keep it simple
    //  TogglePin(byte inPinNumber, unsigned long inDelayTime=1000);  // constructor with default value

    // delay setter method:
    void setDelay(unsigned long inDelayTime);    // change delay time

    // doit method:
    void toggle(void);  // toggle pin if time it up

   private:
 
    // minimize chances for user to screw up 
     TogglePin (const  TogglePin& a);              // disallow copy constructor
     TogglePin & operator=(const  TogglePin& a);   // disallow assignment operator
     
    // object data 
    byte pinState;            // current pin state
    byte pinNumber;           // pin to use
    unsigned long delayTime;  // delay in ms
    unsigned long currentMs;  // current ms count
    unsigned long testMs;     // test ms 
};   // end TogglePin class definition


// Implementation - this would normally be in a .cpp
//   file as part of a library
TogglePin::TogglePin(byte inPinNumber)   // default constructor 
{
  pinMode(inPinNumber, OUTPUT);   // sets the digital pin as output
  pinState=HIGH;
  pinNumber=inPinNumber;
  delayTime=1000;    // default 1 sec delay
  testMs=millis();   // save current millis for later test
}
TogglePin::TogglePin(byte inPinNumber, unsigned long inDelayTime)  // constructor with delay time 
{
  pinMode(inPinNumber, OUTPUT);   // sets the digital pin as output
  pinState=HIGH;
  pinNumber=inPinNumber;
  delayTime=inDelayTime;  // save input delay time
  testMs=millis();  // save current millis for later test
}
void TogglePin::setDelay(unsigned long inDelayTime)
{
  delayTime=inDelayTime;
}
void TogglePin::toggle(void)
{
  currentMs=millis();
  if((currentMs-testMs)>=delayTime) {
    //delay is up
    pinState^=1;                      //toggle state
    digitalWrite(pinNumber,pinState); // write new pin value
    testMs=currentMs;
  } 
}
/* ==================================== */

/* ********************* Start Demo Code **/  

// include <TogglePin.h>   // if the above code was in a library

byte blinkPin1 = 12;     // LED connected to digital pin 12
byte blinkPin2 = 13;     // LED connected to digital pin 13 - onboard LED
TogglePin blink1(blinkPin1);            // default constructor 1 sec delay
TogglePin blink2(blinkPin2,300);        // constructor with 300 ms delay

 void setup()                   
{
    // Move along - nothing to see here
}

void loop()                     // run over and over again
{
  blink1.toggle();
  blink2.toggle();
  // other interesting code here
  // it should take much less than the blink time, otherwise the 
  // blink time will be dependent on the code execution time
}

/* ********************* End Demo Code **/

Hi @oldcurmudgeon ,

In case of writing classes I am a beginner myself.
Pretty good. Explaining most of it.
Me personal I'm obsessed with self-explaining names.
IMHO the name "toggle" does not describe spot-on what the function is doing.
The function toggle does not toggle the IO-pin each time it is called. Only after the delay-time is over. Therefore I would not choose the name "toggle" for this functionality.
Encapsulation has its advantages.

Dividing setting the delaytime, and switching the IO-pin IMHO makes it harder to understand how it works.

I did not understand this part:

// minimize chances for user to screw up 
     TogglePin (const  TogglePin& a);              // disallow copy constructor
     TogglePin & operator=(const  TogglePin& a);   // disallow assignment operator     

best regards Stefan

If you attempt to copy a class or assign it, the default of the compiler is to make a complete copy of the class and pass that. It makes a byte by byte copy which may not be the right thing to do if there are dynamically allocated resources. If these functions are required they should be explicitly written to make sure they "do the right thing" and that there operations are "reasonable". For example a function to set the delay

void someFunction(TogglePin blinkObj,unsigned long newTime) {
     blinkObj.setDelay(newTime);
}

and then call it in loop()

someFunction(blink1,500);

Would dynamically allocate storage for a TogglePin object, copy the blink1 object to the new storage and then pass that. The copied object would be updated and then be immediately deleted on exit from the function. After wasting all that time and space the original object would not be updated. By making the copy function private you get a compile error. After some thought you may (with experience) realize what you really wanted to do was pass a reference to blink1 to update it. Note the &

void someFunction2(TogglePin &blinkObj,unsigned long newTime) {
     blinkObj.setDelay(newTime);
}

The call would be the same

someFunction2(blink1,500);

Except now the blink1 object would be passed and updated, which is what we wanted. Since no copy was done there is no compile error.

Is this more than you really wanted to know? This is an old school thing I learned when starting in C++. For more information search on Marshal Cline and C++ rule of big 3

That's got the wrong acronym, it would be better named DelayUsingMillis(). And it is poorly used -- and...nevermind. It is bad in many ways.

Ah, I had the sense of the test backwards in my mental compiler and it turns into an infinite loop.

@oldcurmudgeon

you explained some details why you coded it the way you coded it. Through this explanation assumes that the reader is familiar with C++-concepts and familiar with the details of how these objects use memory.

My estimation is if a real beginner should understand this in detail:
two years of intensively learning c++-programming (300 to 500 hours of learning time maybe more)

you asked

my answer: 300 to 500 miles off the mark.

You would have to find a way to hide-away all the details to have an easy to apply solution similar to

pinMode(IO_Pin_Nr, INPUT_PULLUP);

where nobody is looking up what register-bit-banging must be done to put the IO-pin into this mode you just use pinMode(IO_Pin_Nr, INPUT_PULLUP);

or coming back to a very basic style of programming.

best regards Stefan

I´m not a fan of classes but I´m a great fan to use data structures.
The below coded data structure named BLINKME contains all neccessary infomation like pin address, timing data and the methods.
You can easily add or delete leds and flashing information without having to change the rest of the code.

Just give it a try.

In general - Arrays and structs are your friends.
Don't duplicate code in your sketch. Write code once - use it multiple times.

// make variables
constexpr uint8_t LedPins [] {9, 10, 11};
constexpr uint32_t BlinkTimes [] {333, 666, 999};
// make structures
struct BLINKME
{
  uint8_t ledPin;
  uint32_t intervalMillis;
  uint32_t previousMillis;
  void make(uint8_t ledPin_, uint32_t intervalMillis_)
  {
    ledPin = ledPin_;
    pinMode(ledPin, OUTPUT);
    intervalMillis = intervalMillis_;
  }
  void run(uint32_t currentMillis)
  {
    if (currentMillis - previousMillis >= intervalMillis)
    {
      previousMillis = currentMillis;
      digitalWrite(ledPin, digitalRead(ledPin) ? LOW : HIGH);
    }
  }
};
BLINKME blinkMes[sizeof(LedPins)]; 
void setup()
{
  Serial.begin(115200);
  uint8_t element = 0;
  for (auto &blinkme : blinkMes)
  {
    blinkme.make(LedPins[element],BlinkTimes [element]);
    element++;
  }
}
void loop()
{
  uint32_t currentMillis = millis();
  for (auto &blinkme : blinkMes) blinkme.run(currentMillis);
}
//------------------------------------------------------------

Have a nice day and enjoy coding in C++.

p.s. here comes a link to a very good text book to learn C++.

https://www.learncpp.com/

2 Likes

That newcomers can give it a try requires to add a demo-code that can be copy & pasted & compiled

I like structs and the data-based approach: "You can put the data in a spreadsheet with a couple macros....and this is how you do it in C++."

This could be more resistant to, for example, constexpr int LedPins [] {9, 10, 11};

BLINKME blinkMes[sizeof(LedPins)/sizeof(LedPins[0])];