using delay() can be such a DELAY when writing code non-blocking timers

Hi everybody,

recently I wrote some testcode to test wireless communication with the ESP-NOW-protocol for ESP8266/ESP32 boards. The testcode used delay() in the mainloop. The datatransmission was very unreliable. I found out it was caused by the command delay(). Looking back it is clear sure a delay delays = blocking the whole processor from doing anything else than delaying. Though delay() is the second command a newbee learns through the blink-the-LED-example. My Opioning throw out the command delay. It can always be replaced with something non-blocking.

So I wrote a tiny library that has some functions to use non-blocking timers and to encapsulate some of the details that were used. My Opinion is this “library” is a little bit too small and not well tested enough to open up another git-repositery. Additional I’m not very familiar with how to use github.

So I decided to post my code here:

here is the header-file

#ifndef nonBlocking_Timer_H
#define nonBlocking_Timer_H

#include <Arduino.h>

class nonBlocking_Timer
{
  private:
    byte dummy;
		static const uint8_t nBTc_MaxNumOfTimers = 20;
		
		uint8_t  nBTv_NoOfRegisteredTimers;
		uint8_t  nBTv_TimerIDNr[nBTc_MaxNumOfTimers + 1];
		uint32_t nBTv_TimeStampMillis[nBTc_MaxNumOfTimers + 1];
        
  public:
    nonBlocking_Timer(byte dummy);
    void init(); 
    int OccupyTimer();
    int StartTimer(int8_t p_TimerID_Nr);
    int UpDateTimer(int8_t p_TimerID_Nr, uint32_t p_IntervalMillis);
    uint32_t elapsedTime(int8_t p_TimerID_Nr);
    
};

#endif

here the *.CPP-file

#include <nonBlocking_Timer_class.h>

nonBlocking_Timer::nonBlocking_Timer(byte dummy)
{ this->dummy = dummy;
  init();
}

void nonBlocking_Timer::init()
  {     dummy = dummy; }

int nonBlocking_Timer::OccupyTimer()
{
  int l_Result = -1; // initialise value to return with value meaning "All timers in use"
  // if a free timer is available 
  if (nBTv_NoOfRegisteredTimers < nBTc_MaxNumOfTimers)
    {nBTv_NoOfRegisteredTimers = nBTv_NoOfRegisteredTimers + 1;
     l_Result = nBTv_NoOfRegisteredTimers;} // return TimerID-Number
  else  
    {l_Result = -1;} // return -1 for indicating "All timers in use"
  return l_Result;   
}

int nonBlocking_Timer::StartTimer(int8_t p_TimerID_Nr)
{ int l_result = -1; // initialise result with -1 indicating something went wrong
  
  // if TimerID_Nr is in the allowed range
  if (   (p_TimerID_Nr  >0) && (p_TimerID_Nr  <= nBTc_MaxNumOfTimers)  )
    {nBTv_TimeStampMillis[p_TimerID_Nr] = millis();
    l_result = 0;} // 
   else
    {l_result = -1;}

   return l_result;
}

int nonBlocking_Timer::UpDateTimer(int8_t p_TimerID_Nr, uint32_t p_IntervalMillis)
{
  int l_result = -1;
  // updating the timer means add amount of milliseconds to timestamp given with p_IntervalMillis
  // this means the milliseconds passed while other code was executeddoes NOT cause an extra-delay
  // the difference to TFm_StartTimer is that TFm_StartTimer stores the current millis()
  // if you call TFm_elapsedTime the REAL interval is the value of TFm_elapsedTime PLUS the milliseconds passed through exexution of additional code 
  // Timer-ID-Nr is in the allowed range
  if (   (p_TimerID_Nr  > 0) && (p_TimerID_Nr  <= nBTc_MaxNumOfTimers)   )
    {nBTv_TimeStampMillis[p_TimerID_Nr] = nBTv_TimeStampMillis[p_TimerID_Nr] + p_IntervalMillis;
    l_result = 0;}
  return l_result;  
}

uint32_t nonBlocking_Timer::elapsedTime(int8_t p_TimerID_Nr)
{
  uint32_t l_result = -1; // initialise return-value with "something went wrong" 
  // check if Timer-ID-Nr is in the allowed range
  if (    (p_TimerID_Nr > 0 ) && (p_TimerID_Nr  <= nBTc_MaxNumOfTimers)   )
  { l_result = nBTv_TimeStampMillis[p_TimerID_Nr] - millis();}
  return l_result; 
}

and here some testcode that demonstrates how to use it

#include <nonBlocking_Timer_class.h>
const byte Dummy = 1;
nonBlocking_Timer nbTimer_object(Dummy); // create object of class

int DemoTimerA_IDNr; // variables that are used to access the right variables inside 
int DemoTimerB_IDNr; // the nonBlocking_Timer-object through their assigend ID-Nr.
int DemoTimerC_IDNr; 

void setup() 
{
  Serial.begin(115200);
  Serial.println("Program started");

  // occupy and start timers 
  DemoTimerA_IDNr = nbTimer_object.OccupyTimer();
  if (DemoTimerA_IDNr > 0) // Check if assigning a Timer-IDNr was successful
    {nbTimer_object.StartTimer(DemoTimerA_IDNr);}
  else   
    {Serial.println("registering Timer A was NOT successful!");}

  DemoTimerB_IDNr = nbTimer_object.OccupyTimer();
  if (DemoTimerB_IDNr > 0) // Check if assigning a Timer-IDNr was successful
    {nbTimer_object.StartTimer(DemoTimerB_IDNr);}
  else   
    {Serial.println("registering Timer B was NOT successful!");}

  DemoTimerC_IDNr = nbTimer_object.OccupyTimer();
  if (DemoTimerC_IDNr > 0) // Check if assigning a Timer-IDNr was successful
    {nbTimer_object.StartTimer(DemoTimerC_IDNr);}
  else   
    {Serial.println("registering Timer C was NOT successful!");}
}


void loop() 
{
  if (nbTimer_object.elapsedTime(DemoTimerA_IDNr) > 1000)
    {Serial.println(" TimerA overDue");
     nbTimer_object.UpDateTimer(DemoTimerA_IDNr,1000);
    }
       
  if (nbTimer_object.elapsedTime(DemoTimerB_IDNr) > 2000)
    {Serial.println("                 TimerB overDue");     
     nbTimer_object.UpDateTimer(DemoTimerB_IDNr, 2000);
    }
  
  if (nbTimer_object.elapsedTime(DemoTimerC_IDNr) > 3000)
    {Serial.println("                                TimerC overDue");
     nbTimer_object.UpDateTimer(DemoTimerC_IDNr, 3000);
    }
}

I tested this on a ESP8266 Wemos D1 mini board. The code has nothing ESP8266-specific so it should run on any board that can be chosen inside the Arduino-IDE.

For making use of the include-files you have to create a folder with the exact same name as the *.h and the *.cpp-file
Foldername:

nonBlocking_Timer_class

This folder must be s subfolder of the “libraries”-folder.
If you let search windows for folders named libraries you will find multiple ones
The exact location of the right libraries-folder can differ depending on the location where you have installed the Arduino-IDE. For me it is working in this folder

C:\Users\Stefan\Documents\Arduino\libraries

So the nonBlocking_Timer_class-folder is located here

C:\Users\Stefan\Documents\Arduino\libraries\nonBlocking_Timer_class

hope this information will help newbees to get the library installed at the right place.

especially at newbees: I have written some explanations inside the code and the demo shows the use of multiple timers with different intervals. If you have any questions post them. I want to expand the explanations based on the newbee-questions. So feel free to ask whatever you like.

best regards

Stefan

nonBlocking-Timer-Demo-code.ino (1.76 KB)

nonBlocking_Timer_class.zip (1.61 KB)

Using millis() for non blocking timing is probably easier for newbies. It is is illustrated in Several Things at a Time.

And see Using millis() for timing. A beginners guide if more explanation is needed

...R

Hi Robin2,

the SeveralThingsAtOneTime is HARDER to use than other examples because it requires additional hardware LEDs and servos.

The tutorial you mentioned is HARDER to use because the user has to deal with ALL details. My library encapsulates a part of these details.

In my library you define a variable that is used as an ID-number and the rest always stays the same.
Now I'm very curious about your ARGUMENTS that show why this "using millis() for timing tutorial"
is easier to USE than my library.

If you can deliver arguments I might get convinced that this turial is better. Until now I haven't read any arguments.

best regards

Stefan

StefanL38:
The tutorial you mentioned is HARDER to use because the user has to deal with ALL details. My library encapsulates a part of these details.

I guess this is a question of philosophy.

I like to provide code that helps people to learn how to write programs.

I do realise that some people prefer to acquire a library and don't care what goes on inside it provided it works.

...R