Go Down

Topic: Simple software timer alternative to millis() (Read 4913 times) previous topic - next topic

femmeverbeek

I just proposed a millis() or micros() based timer here.  I think is a lot simpler than this one.

It does not suffer from the integer overflow occurring after 50 days (millis) or 7 hours (micros), so it can run indefinitely.

Usage
Just include the function chkTimer

In the sketch:

For each timer only one unsigned long must be declared, which contains the expiration time.

The function chkTimer does the rest. When true, the timer expired, which automatically sets a new expire time.

No problem to have hundreds of independent timers.


Code: [Select]
/* Function chkTimer resets and returns true when the timer expired.
 * It stores the end time in the unsigned long variabele passed to &expireTime. 
 * When expired the value of loopTime is added to the expireTime so continuously
 * polling this function checks true every loopTime.
 * The time scale can either be in milliseconds or in microseconds, depending
 * on which line containing currentTime is commented out.
 * All information the timer needs is stored in the variable, so to have more
 * independent timers, just declare more variabeles.
 * Written by Femme Verbeek 30-4-2020
 * Use for free
*/
boolean chkTimer (unsigned long &expireTime, unsigned long loopTime) {
unsigned long currentTime = millis();       // use this for milliseconds
//unsigned long currentTime = micros();       // use this for microseconds
  if (currentTime >= expireTime)            //check the timer
  { if ( (bitRead(currentTime,31) == 1) &&
         (bitRead(expireTime,31) == 0) )    //check overflow
      return false;                         //integer overfow but not expired
      else            {                     //expired         
        expireTime += loopTime;            //set new expireTime
        return true;  }                     //OK, chkTimer was triggered
  } else return false;                      //not expired
}

void setup() {
  Serial.begin(115200);
}

unsigned long mytimer1 = 0;       //start timing loop immediately
unsigned long mytimer2 = 5000;    //wait 5 seconds before starting timing loop   
unsigned long mytimer3 = 10000;   //wait 10 seconds before starting timing loop

void loop() {                     //timer intervals are Fibonacci numbers for maximum chaos
  if (chkTimer(mytimer1,377)) Serial.print("Hello ");
  if (chkTimer(mytimer2,610)) Serial.println("world ");
  if (chkTimer(mytimer3,1597)) Serial.println("and the universe ");
}





Power_Broker

You could also use a library such as FireTimer.h. It also handles timer overflow and can be installed via the Libraries Manager.
"The desire that guides me in all I do is the desire to harness the forces of nature to the service of mankind."
   - Nikola Tesla

Koepel

#2
May 30, 2020, 07:44 pm Last Edit: May 30, 2020, 07:45 pm by Koepel
You could also use a library such as FireTimer.h. It also handles timer overflow and can be installed via the Libraries Manager.
@Power_Broker, the rule to use millis() and micros() is to not try to "handle" a rollover.

This:
Code: [Select]
// Handle overflow
if (currentTime < timeBench)
 timeDiff = (UL_MAX - timeBench) + currentTime;
else
 timeDiff = currentTime - timeBench;


will do the same thing as this:
Code: [Select]
timeDiff = currentTime - timeBench;

I can not make a Issue on your Github at the moment.



I just proposed a millis() or micros() based timer here.  I think is a lot simpler than this one.
@femmeverbeek, you are trying to "handle" a rollover, that is not how to use millis() or micros() as the others have already explained in the other thread.


StefanL38

I'm aware of that this timer-solution posted below does NOT keep multiple timeperiods in snyc like the other code-constructions that use
Code: [Select]
currentMillis = millis();  //get the current time[color=#222222][/color]
  if (currentMillis - startMillis >= period) .....


In most cases it doesn't matter if the periods of multiple timers are slightly async to each other.
examples: It is completely irrelevant if a LCD that show some status-information or  code that polls for user-button-presses
execute every 1,0000000 seconds or after 1,02 seconds or even varying between 0,98 1,00 1,02 seconds

So the version below is easier to use and less to write because it reduces the code to write to a single line.

My question is: will the rollovers of millis() be handled properly like in the "standard"-use of millis()?

Code: [Select]
unsigned long DemoTimerA = 0; // variables that are used to store timeInformation
unsigned long DemoTimerB = 0;
unsigned long DemoTimerC = 0;
unsigned long DoDelayTimer = 0;

boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime >= TimePeriod )
  {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}

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

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);
    }   
}


best regards  Stefan 
any newbee can apply the most professional habit right from the start:
 add only ONE thing at a time. Debug that ONE thing until that ONE thing works reliable - repeat.
Newbee: become a professional by following this rule.

Koepel

My question is: will the rollovers of millis() be handled properly like in the "standard"-use of millis()?
Yes, depending on what you call "standard".

The currentMillis - previousMillis >= interval will be okay when both are unsigned long, even when currentMillis has rolled over and previousMillis not.
That is how the 32-bit unsigned long calculation works.
Everything else is wrong.

The millis() is corrected now and then to keep it accurate to the millisecond. If the Arduino has a crystal instead of a resonator, it is possible to make clock that is maximum a few minutes wrong in a year.

There are two options:
1. Normal, a delay in code delayes the millis() software timer as well, with: previousMillis = currentMillis
2. Stay in sync with the time: previousMillis += interval

The second one can go wrong, when for example the interval is 1 second and there is a delay in the loop of 1 second.

I think your code is okay, but is it really smaller ;)
Sometimes a led needs to blink, and the blinking needs to be turned on and off. Or a single-shot-timer is needed, or a millis() timer with a Finite State Machine. Using bare millis() timers isn't so bad.

StefanL38

#5
Jun 09, 2020, 06:51 pm Last Edit: Jul 06, 2020, 08:40 am by StefanL38
Hi Koepel,

thank you  for answering. Yes of course there are cases where bare millis() and other variants are good to use.
The most uses I have coded myself where of the type

Code: [Select]
currentMillis = millis();  
  if (currentMillis - startMillis >= period)
  {
    digitalWrite(ledPin, !digitalRead(ledPin));
    startMillis = currentMillis;  
  }

and for this type it is shorter.

Copy & paste the function once into your code.
And for each use just a single line with one variablename and period to change.
Again: anybody is free to code that variant she/he likes best
best regards  Stefan
any newbee can apply the most professional habit right from the start:
 add only ONE thing at a time. Debug that ONE thing until that ONE thing works reliable - repeat.
Newbee: become a professional by following this rule.

femmeverbeek

Quote
Sometimes a led needs to blink, and the blinking needs to be turned on and off. Or a single-shot-timer is needed, or a millis() timer with a Finite State Machine. Using bare millis() timers isn't so bad.
For blinking leds it is not important that the timer sequence starts immediately after the event of enabling, the code can be much simpler.
e.g. This will flash the onboard led @5Hz for a duration of 1 sec, followed by 1 sec off.
Code: [Select]
void setup()
{ pinMode(13,OUTPUT);
}
boolean enable=false;
void loop()
{ enable=(millis()/1000 %2);                       //1 sec on, 1 sec off
  digitalWrite(13, (millis()/100 %2) && enable);   // 0.1 sec on, 0.1 sec off
}

millis()/1000 is a number that counts seconds.
millis()/100 is a number that counts tenths of seconds.
The %2 is the remainder of 2, effectively the result = 0 or 1.

Using the same remainder method it is very simple to make any other pattern in flashing lights.
E.g. a running light of 6 leds connected to outputs 2..7

Code: [Select]
void setup()
{  for (int i=2; i<=7; i++) pinMode(i,OUTPUT);
}
void loop()
{  byte selector= 2+ (millis()/100) %6;
   for (int i=2; i<=7; i++) digitalWrite(i, selector==i);
}


the remainder %6 counts from 0 to 5. For each of the values of selector one of the leds is burning. 



TheMemberFormerlyKnownAsAWOL

For blinking leds it is not important that the timer sequence starts immediately after the event of enabling, the code can be much simpler.
Did you mean
Quote
If for blinking leds it is not important that the timer sequence starts immediately after the event of enabling, the code can be much simpler
?
Please don't PM technical questions - post them on the forum, then everyone benefits/suffers equally

j_molenaar

Code: [Select]
if ( currentMillis - expireTime >= TimePeriod )
{
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}


best regards  Stefan
[/quote]


I would do it a little bit different then stefan does, just for a more accurate timing.
Ive edited the function that overwrites the oldMillis value.
Just take a look at what ive mentioned, and you might get a bit more accurate timing.

Code: [Select]
if ( currentMillis - expireTime >= TimePeriod )
{
    expireTime = expireTime+TimePeriod; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}

j_molenaar

I also like to add a unsigned long that holds the seconds that was needed to switch once a cycle.
And the output this to the serial monitor, simple but useful.


Code: [Select]
unsigned long DemoTimerA = 0;
unsigned long DemoTimerB = 0;
unsigned long DemoTimerC = 0;
unsigned long inSec;
unsigned long currentMillis;



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

void loop()
{
  if(timer(DemoTimerA, 10000)){ Serial.print(inSec); Serial.println(" Sec"); }
  if(timer(DemoTimerB, 1000)){ Serial.print(inSec); Serial.println(" Sec"); }
}






boolean timer (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime >= TimePeriod )
  {
    inSec = (currentMillis - expireTime)/1000;
    expireTime = expireTime+TimePeriod; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}



Go Up