Simple timer library

I need a timer, that starts counting when told, and stops when told. The millis() function doesn't provide that, as it starts with the program, and can't be paused or stopped. I'm looking for something like:

timer.start();
...
...
...
timer.stop();
timer.getTime();

Whereas the getTime function returns the interval between the start and stop.

Can't you do that with millis()?

startTime=millis();

//time goes by

endTime=millis()
elapsedTime = endTime-startTime;

You don’t need the timer to start and stop to do that, you just need to pay a little attention in third grade math class. Use subtraction.

The demo Several Things at a Time illustrates the use of millis() to manage timing. It may help with understanding the technique.

Using millis() is much the same as using your kitchen wall clock to time the cooking of a chicken. You note the time when the chicken goes in the oven and periodically check the clock to see if the 90 minutes (or whatever) has elapsed. You don't need to stop and start your kitchen clock.

...R

Try this library....

https://playground.arduino.cc/Main/MsTimer2

JustGoFly:
If you have any interest in C++, this might be of interest to you. It’s a cute little c++ timer class, I just put together for you. Normally I’d load the class into separate cpp and header files. But I wanted to post this as one code snippet.

// This is the header file: MyTimer.h

class MyTimer
{
public:
  MyTimer(int secondsUntilExpire);
  bool Expired();

private:
  unsigned long secs;
};

// This is the cpp file: MyTimer.cpp
MyTimer::MyTimer(int secondsUntilExpire)
{
  secs = (unsigned long)secondsUntilExpire*1000;
}

bool MyTimer::Expired()
{
  return secs <= millis();
}

// Mainline - initializing three timers after which they can never be used again :slight_smile:
// You can re-use a declared timer, but that would require definition of MyTimer*, and new of that pointer.
// or as an exercise add a function SetTime(int secondsUntilExpire);  Which updates the internal secs variable. {
// My goal here is to post something very simple to hopefully get you interested in C++.
MyTimer timer1(10); // timeout after 10 seconds
MyTimer timer2(2);
MyTimer timer3(4);

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

void loop() {
  unsigned long secs=millis();
  delay(500);
  Serial.print  ("Current time: ");
  Serial.println(millis());
  if (timer1.Expired())
    Serial.println(“Timer 1 expired”);
  if (timer2.Expired())
    Serial.println(“Timer 2 expired”);
  if (timer3.Expired())
    Serial.println(“Timer 3 expired”);
}

The problem with this code is that it will only work once. For instance if you set a timer for 5 seconds then once millis() hits 5000 you get the expire. But now if you try to create another one or any way get another 5 seconds, then since millis has already passed 5000 it will be expired as soon you create it.

You need to be keeping track of where millis was when the timer started so you can compare with the current return from millis to see if it has been 5 seconds from that point, not 5 seconds from when millis was at 0.

secs = millis() + secondsUntilExpire*1000;

You got a bug there when you hit the millis rollover.

Instead of trying to predict what millis will be at the end, keep track of what millis was at the beginning and in your expired routine test against the current value of millis to see if that amount of time has passed or not. If you calculate the elapsed time with subtraction as in BWoD, you won't have any issues at rollover.

Delta - your recommendation is wrong. The code I wrote is correct - why not just run it?

There is no predicting. The class is written to enable someone to say expire in x seconds. x is not a prediction. NOW + X is the timeout.

If I set secs to millis() - that says immediately expire, and ignore the parameter requesting expire at some point in the future. millis() is NOW, and secondsUntilExpire is the number of seconds that the user is requesting from now that the timeout should occur.

Nope, that line that I cut out is a bug. It's a well known bug. I've been over it with many a poster, but I'll do it again for you.

Let's say that the current millis is 4,294,967,095. So it's going to roll over in 200ms. Now you set your timer for 5 seconds. So it calculates your expiration time sec at (32 bit unsigned math) 4,294,967,095 + 5000 == 4800.

Next it gets to check if it is time to expire, and millis is now 4,294,967,096 and the sec variable has 4800. Well sec is less than millis so your code says that the 5 seconds is up and it's only been 1 millisecond.

I wouldn't kid you and I'm not making this up. I'm quite adept at timing things with an Arduino. I know this code inside and out. You are making a rookie mistake by trying to calculate what millis will be at the end instead of keeping up with what millis is at the beginning.

You also still have the first bug I pointed out in the constructor. Unless the instance is constructed at the very beginning of time at the very beginning of the code, you're just setting the sec variable to have that number of milliseconds some of which may have already passed. A class like this should allow for construction and instantiation at any time in the code.

What you want is something that does the math this way:

class MyTimer{

  public:
  MyTimer(int);
  boolean isExpired();
  void setTimer(int);

  private:
  unsigned long startTime;
  unsigned long seconds;  
};

MyTimer::MyTimer(int aSeconds){
  setTimer(aSeconds);
}

void MyTimer::setTimer(int aSeconds){
  seconds = aSeconds;
  startTime = millis();
}

boolean MyTimer::isExpired(){
  if(millis() - startTime >= seconds * 1000){
    return true;
  }
  return false;
}

And now you can create the timer at any point in the program and you can set it at any point and however many seconds later you'll get isExpired returning true and it will survive rollover and time any interval less than 49 days across any time period.

Ya I like that - it moves the check of time to inside the call of isExpired.

JustGoFly:
Ya I like that - it moves the check of time to inside the call of isExpired.

Yeah, not only that but it will work too. Not just under certain conditions but all the time.

Delta_G: Yeah, not only that but it will work too. Not just under certain conditions but all the time.

So the code I posted doesn't work?

Maybe just on sunny days?

JustGoFly: So the code I posted doesn't work?

Maybe just on sunny days?

Your code has at least one well known bug that I pointed out to you already. If you try to run it long term it will fail at 49 days.

Delta_G: Your code has at least one well known bug that I pointed out to you already. If you try to run it long term it will fail at 49 days.

OK so now you're talking about the roll over every 49.7 days due to the unsigned long size? I guess I'm done posting updates. Anyone interested in pursuing this code can read more about the roll over issue here: http://www.gammon.com.au/millis

And hope they don't set a timeout for greater than 49.7 days. Or more code is needed to handle that issue.

Also the inaccuracy of the clock is an issue, so time to buy a RTC or deal with thread trolls that don't want programmers to post actual code.

JustGoFly: Also the inaccuracy of the clock is an issue, so time to buy a RTC or deal with thread trolls that don't want programmers to post actual code.

Are you calling me a troll for pointing out the bugs in your code? It's fine for you to post code, but please try to make it correct code.

Inaccuracies removed - I hope this helps the original poster.

JustGoFly: Inaccuracies removed

By deleting your post? That's bad forum form, since it makes it well nigh impossible for others to follow the rest of the thread. Or it would, except DeltaG quoted the now-deleted post in #5.

Although OP used the word "timer", to me his question reads more like the need for a stopwatch. On my mobile phone for example, the timer is where I set a value, hit go, and wait for the beep to take the pizza out. A stopwatch is where I hit go, later hit stop, and read the screen.

That's what OP wants, at least it's how I understood him and answered him in #1.

He asked:

timer.start();
...
...
...
timer.stop();
timer.getTime();

I suggested:

startTime=millis();

//time goes by

endTime=millis()
elapsedTime = endTime-startTime;

(I wonder if OP's absence since he asked the question was due to the bickering between D_G and JGF?)

kenwood120s: (I wonder if OP's absence since he asked the question was due to the bickering between D_G and JGF?)

Perhaps. But the answer is on the forum SO many times over and over. Pick any other of the thousand threads where this whole thing has been addressed if this one went off track. Sorry if I derailed it. I was initially only pointing out the bugs before the OP loaded code and hit problems later. Nothing is worse in a long term project that a bug that doesn't bite until day 49. Who (hobbyist) is going to test that long before they go live? Perhaps I should have left @JGF to deal with the aftermath himself. Lesson learned.