Easy to use non-blocking timing for periodic-delayed code-execution

Hi Everybody,

I'm using a self-written function for non-blocking periodic-delayed code-execution.
The most simplest example is switching an LED ON / off. Which could be called toggle the LED

If LED is off => switch LED ON
.
If LED is ON => switch LED off

void toggleLED_OnOff(byte myLED_Pin) {
  if ( digitalRead(myLED_Pin) == LOW) { // if LED is off
    digitalWrite(myLED_Pin,HIGH);       // switch LED ON
  }
  else {                         // if LED is ON
    digitalWrite(myLED_Pin,LOW); // switch LED off    
  }
}

example to switch on / off a LED periodically and continiously once every 5 seconds (5000 milliseconds)

as a time-table

actual| 
time  | state of LED
00:00 | switch LED ON
00:05 | switch LED off
00:10 | switch LED ON
00:15 | switch LED off
00:20 | switch LED ON
00:25 | switch LED off
00:30 | switch LED ON
00:35 | switch LED off
...

most basic example:


const byte LED_Pin = 13;
unsigned long StartTime;

void toggleLED_OnOff(byte myLED_Pin) {
  if ( digitalRead(myLED_Pin) == LOW) { // if LED is off
    digitalWrite(myLED_Pin,HIGH);       // switch LED ON
  }
  else {                         // if LED is ON
    digitalWrite(myLED_Pin,LOW); // switch LED off    
  }
}


void setup() {
  pinMode(LED_Pin,OUTPUT);
  digitalWrite(LED_Pin,HIGH); // switch ON LED
  StartTime = millis(); // initialise variable StartTime with actual value of function millis()
}


void loop() {
  // code that shall be executed all the time

  
  // check if more or less then 5000 milliseconds have passed by
  if ( TimePeriodIsOver(StartTime,5000) ) {
    // if 5000 milliseconds HAVE passed by   automatically update variable StartTime
    toggleLED_OnOff(LED_Pin); // toggle 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
}

to demonstrate the non-blocking character of this timing-function the code-version below uses the serial monitor in a special way similar to a LC-Display
The effect will be that you see just one single line with text in the serial monitor that shows a fast upcounting number and the state of the LED beeing ON or off

you see 5 seconds long

counting fast 2524740 LED is switched ON

then 5 seconds long

counting fast 2558332 LED is switched off

and so on

the fast counting up number shows how many times per second loop is iterating the lines of code


const byte LED_Pin = 13;
unsigned long StartTime;
unsigned long myCounter;
unsigned long PrintTimer;

void toggleLED_OnOff(byte myLED_Pin) {
  if ( digitalRead(myLED_Pin) == LOW) { // if LED is off
    digitalWrite(myLED_Pin, HIGH);      // switch LED ON
  }
  else {                         // if LED is ON
    digitalWrite(myLED_Pin, LOW); // switch LED off
  }
}


void setup() {
  Serial.begin(115200);
  pinMode(LED_Pin, OUTPUT);
  digitalWrite(LED_Pin, HIGH); // switch ON LED
  StartTime = millis(); // initialise variable StartTime with actual value of function millis()
  myCounter = 0;
}


void loop() {
  // code that shall be executed all the time
  myCounter++;                // increment variable myCounter by 1 with each iteration of loop()
  PrintCounter_and_LEDState();

  // check if more or less then 5000 milliseconds have passed by
  if ( TimePeriodIsOver(StartTime, 5000) ) {
    // if 5000 milliseconds HAVE passed by   automatically update variable StartTime
    toggleLED_OnOff(LED_Pin); // toggle 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
}


void ClearSerialMonitor() {
  for (byte i = 0; i < 40; i++) {
    Serial.println();
  }
}


void PrintCounter_and_LEDState() {

  // to create a steady-looking text print 40 empty lines
  // that scroll the last printed text out of sight
  if ( TimePeriodIsOver(PrintTimer, 200) ) { // once every 200 milliseconds
    ClearSerialMonitor(); 

    Serial.print("counting fast ");
    Serial.print(myCounter);
    Serial.print(" LED is switched ");
    if ( digitalRead(LED_Pin) == HIGH) {
      Serial.println("ON");
    }
    else {
      Serial.println("off");
    }
  }
}
1 Like

This is a more graphical explanation how the function TimePeriodIsOver() works

In continioulsy periodic use it looks like this

Can much easier

void toggleLED_OnOff(byte myLED_Pin) {
  digitalWrite(myLED_Pin, !digitalRead(myLED_Pin));
}
1 Like

Yes I know this version. But presenting this version without explaining it throurougly leaves beginners with scratching their head: Huh what wizardness is this??!

1 Like

Programming without the knowledge of || == && != <= >= ... that isn't possible.

Write a turial about them include the single not-operator !

Why ?

https://www.learncpp.com/cpp-tutorial/logical-operators/

Read - Understand - Make !

this is poor coding. You use boolean operator and boolean promotion and the "undocumented" knowledge (unless you go check the source code) that HIGH is 1 and LOW is 0 and are of integer type and not a class type that does not promote to an integral type or back from an integral type...

if you want to do short and clean use the ternary operator. This way you respect the API and types

void toggleLED_OnOff(byte myLED_Pin) {
  digitalWrite(myLED_Pin, (digitalRead(myLED_Pin) == HIGH) ? LOW : HIGH);
}

see this to know what could happen if Arduino decided to change the way HIGH and LOW are defined... it's not unheard of...

2 Likes

The else is redundant here.

@StefanL38
please, before your write any more "content" ... please read and apply:

and

just an example: you were told regarding camelCase over and over again, so please don't post any more of your "content" as long as you don't get that right! If you need help on that, first post in programming and let others correct your code before you put it into tutorials!

1 Like

I see this as a totally minor problem. The Arduino-Team seems to see writing in camelCase as the almost only thing that is important about example-codes that the Arduino-Team is delivering with their examples.

As long as the Arduino-Team does not change to a much more descriptive style in the example-codes, as long as most of the users here don't apply the recommend tutorial-rules in their answers I won't care about that. I claim that my tutorials are easier to understand as most others.

If you can point to specific things and explain why the way I have written it is confusing beginners I will think about it.

I don't discuss with you why or why not camelCase. It's described, it's common practice. No more to add. Just use it.

Another thing which confuses

  StartTime = millis(); // initialise variable StartTime with actual value of function millis()

You could explain why you want to have this variable the value of millis() at the end of setup. at this point millis() will have lit. "no" progress (tests show it is 0). This obscure line can be omitted without any negative effect during runtime. StartTime is a global so it will be initialized with 0 anyway. if you want to make it clear that it is 0, do that together with the declaration

Very similar:

myCounter = 0;

why? And if myCounter = 0 would have been important, why have you left out PrintTimer?

People might have different styles to write code, but at least they should be consistent within their code. Your code isn't consistent and can confuse others.

If I can't accept feedback on my work I would not publish code.

Hello noiasca

Many thanks for the links.

Perfect documentations to make a good job to design useful tutorials for this forum. :+1:

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

In bigger code where some actions do start at an unpredictable time f.e. caused by a button-pressthe initialisation is required to make it work. So I introduce to this in the very first example.

But I will think about if I add some explanation about this or adding a follow-up example with a button-press

For what’s it’s worth I actually find your documentation style totally cumbersome and your variables naming is hurting my eyes

by adding tons of unrelated stuff (debug macros, async blinking etc) and relatively useless comments you loose sight of the primary goals of your tutorials and confuse beginners. That’s the main reason I would not recommend them.

But it’s a free world so do as you want until mods get unhappy. This is arduino space at the end of the day

1 Like

The style guide could use a few touch ups.

P.S. I could not find the source, otherwise I would have contacted the maintainers.

[edit]

Update: I found the repo, the issue is fixed now.

agreed, recommending == TRUE is really poor. The notion of a truth value is very well known as in natural language you would not say

if it is raining is true then
  open your umbrella 

you say

if it is raining then
  open your umbrella 

Moreover, TRUE (uppercase) is not defined.

Well if the variablename contains the word "true" I agree.
But in most cases it does not explicitly contain the word "true"
and then I would recommend to show both versions.

if (characterReceived == true) {
  // process character
}

as variable characterReceived is of type boolean itself this can be written shorter as

if (characterReceived) {
  // process character
}

And add a link to the explanation how in C++ the if-statement treats value zero and non-zero values

Of course this adds text. And some experienced users might be annoyed by reading additional text. Well my opinion is:
As an expert it is a peace of cake to jump over additional text or redundant comments
because an expert knows where to look at despite to beginners

why ? the concept of truth value is represented by the boolean type in the first place. So if the user was smart enough to make something a truth value, then he should stick with this.

bool itIsRaining = (digitalRead(rainSensorPin) == HIGH); // will be true when the sensor detects the rain.
...
if (itIsRaining) {                 // is it raining ?
  digitalWrite(umbrellaPin, HIGH); // yes, open the umbrella
}

otherwise stick to non boolean values

int  rainSensorStatus = digitalRead(rainSensorPin); // will be HIGH when the sensor detects the rain.
...
if (rainSensorStatus == HIGH) {    // is it raining?
  digitalWrite(umbrellaPin, HIGH); // yes, open the umbrella
}