Multiple Timer Library  

Page 1 / 3
  RSS

Pugwash
(@pugwash)
Prominent Member
Joined: 10 months ago
Posts: 969
2020-02-19 8:53 am  

Do you need many timers in one sketch? With the help of my OOP mentor and muse @frogandtoad, I have come up with this simple library for millisecond and microsecond timers that only really need one line of code.

OK two lines.

In the header the timers are invoked like this:

#include <multiTimer.h>

Timer timer1, timer2; //as many timers as you want

And in the main loop():

void loop() {

  if(timer1.pause(0, 5000)){
    Serial.println("5000 milliseconds period"); // or do something else every 5 seconds
  }
  /*
  Alternatively, use MILLIS instead of 0
  if(timer1.pause(MILLIS, 5000))
  */
  
  if(timer2.pause(MICROS, 3300000)){
    Serial.println("3300000 microsecond period"); // or do something else
  }
  /*
  Alternatively, use 1 instead of MICROS
  if(timer2.pause(1, 3300000))
  */
}

Install the contents of the attached .zip file in your Arduino/libraries directory and off you go!!

If you run into any problems, let me know in this thread, more functionality is yet to come.

SteveC - Topple Rudd Poltman


Quote
Topic Tags
frogandtoad
(@frogandtoad)
Reputable Member
Joined: 10 months ago
Posts: 384
2020-02-19 10:30 am  

@pugwash

I don't see your setup()... did you test it? 🙂

Ensure there are namespace qualifiers in the INO file - Try this if you haven't already:

#include <multiTimer.h>

//using namespace tim; // Either this here, or as per the below tim:: labels shown

tim::Timer timer1, timer2; //as many timers as you want

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

void loop() {

  if(timer1.pause(tim::MILLIS, 5000)){
    Serial.println("5000 milliseconds period"); // or do something else every 5 seconds
  }
  /*
  Alternatively, use MILLIS instead of 0
  if(timer1.pause(MILLIS, 5000))
  */
  
  if(timer2.pause(tim::MICROS, 3300000)){
    Serial.println("3300000 microsecond period"); // or do something else
  }
  /*
  Alternatively, use 1 instead of MICROS
  if(timer2.pause(1, 3300000))
  */
}

multitimer.h file:

A few issues here too - Note the inline comments:

//#include "multiTimer.h"
//If you're going to put it in the library directory, better to be consistent
#include <multiTimer.h>

// You forgot to add the namespace in the most important file!
namespace tim {

  bool Timer::pause(DELAY_MODE delay_mode, unsigned long delay_time){

    if(delay_mode == MILLIS){

      _current_time = millis();

      if(_current_time >= _previous_time + delay_time) {
        _previous_time = _current_time;
        return true;
       }
      //return false; // Redundant
    }

    if(delay_mode == MICROS){

      _current_time = micros();

      if(_current_time >= _previous_time + delay_time) {
        _previous_time = _current_time;
        return true;
       }
      //return false; // Redundant
    }

    return false; // Better, and will remove any compiler warnings
  }
  
 }

I have tested it, and worked OK for me.

Cheers!

This post was modified 1 month ago 2 times by frogandtoad

ReplyQuote
Pugwash
(@pugwash)
Prominent Member
Joined: 10 months ago
Posts: 969
2020-02-19 10:48 am  

@frogandtoad

Points taken!!

I am getting a compiler error <'tim' is not a namespace-name>

but

namespace tim {

  my code

}

is in the .cpp file

SteveC - Topple Rudd Poltman


ReplyQuote
Pugwash
(@pugwash)
Prominent Member
Joined: 10 months ago
Posts: 969
2020-02-19 11:02 am  

@frogandtoad

I also wanted to run this by you, for a bit of added functionality:

#include <multiTimer2.h>

Timer timer1, timer2;

void setup() {
  
  Serial.begin(9600);
  Serial.println("Sketch starting...");
}

void loop() {

  timer1.isRunning = false;
  if(timer1.pause(0, 5000)){
    Serial.println("5000 milliseconds period");
  }
  
  timer2.isRunning = true;
  if(timer2.pause(MICROS, 3300000)){
    Serial.println("3300000 microsecond period");
  }
  
}

.h file

#ifndef TWO_TIMERS
#define TWO_TIMERS

#if (ARDUINO >=100)
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

enum DELAY_MODE{MILLIS, MICROS};

class Timer
{
public:
/* declaring member functions */
bool isRunning;
bool pause(DELAY_MODE delay_mode, unsigned long delay_time);

private:
/* hidden private variables */
unsigned long _previous_time;
unsigned long _current_time;

};

#endif

.cpp file

#include <multiTimer2.h>

namespace tim {

bool Timer::pause(DELAY_MODE delay_mode, unsigned long delay_time){

if(delay_mode == MILLIS && isRunning){

_current_time = millis();

if(_current_time >= _previous_time + delay_time) {
_previous_time = _current_time;
return true;
}
}

if(delay_mode == MICROS && isRunning){

_current_time = micros();

if(_current_time >= _previous_time + delay_time) {
_previous_time = _current_time;
return true;
}
}
return false;
}

}

SteveC - Topple Rudd Poltman


ReplyQuote
frogandtoad
(@frogandtoad)
Reputable Member
Joined: 10 months ago
Posts: 384
2020-02-19 11:29 am  
Posted by: @pugwash

@frogandtoad

Points taken!!

I am getting a compiler error <'tim' is not a namespace-name>

but

namespace tim {

  my code

}

is in the .cpp file

Sorry, had to attend to something.

Yes, but was missing from the header file!

Where is the function for: "isRunning", or is it supposed to be just a variable?
I'm not sure what it's doing 🙂


ReplyQuote
Pugwash
(@pugwash)
Prominent Member
Joined: 10 months ago
Posts: 969
2020-02-19 11:33 am  

@frogandtoad

Yes, but was missing from the header file!

Are you saying that I have to put the class inside a namespace? That makes no sense if it is to be available globally!

"isRunning", I have made as a class member, in the .h file!

SteveC - Topple Rudd Poltman


ReplyQuote
frogandtoad
(@frogandtoad)
Reputable Member
Joined: 10 months ago
Posts: 384
2020-02-19 11:49 am  

@pugwash

Posted by: @pugwash

Are you saying that I have to put the class inside a namespace? That makes no sense if it is to be available globally!

Yes, of course... that's how namespaces work!  The header file should be wrapped in a namespace as well as the implementation file, but for the implementation file you can also use the direct label with the scope resolution operator for maximum security.

I'll show you an example... gimme some time to code it up.

As for the member, according to the comment I thought you were going to make it a function.

Anyhow, how do you intend to check it?

Cheers!

This post was modified 1 month ago by frogandtoad

ReplyQuote
Pugwash
(@pugwash)
Prominent Member
Joined: 10 months ago
Posts: 969
2020-02-19 12:01 pm  

@frogandtoad

I see "isRunning" as just a class property not a class method. Just sets the instance property to true or false depending on whether I want a specific timer to run or not to run. I could have done this outside the class, of course.

 

 

SteveC - Topple Rudd Poltman


ReplyQuote
frogandtoad
(@frogandtoad)
Reputable Member
Joined: 10 months ago
Posts: 384
2020-02-19 12:17 pm  

multiTimer.cpp file: - Option 1:

#include <multiTimer.h>

namespace tim { // namespace option 1... wrap everything

  bool Timer::pause(DELAY_MODE delay_mode, unsigned long delay_time){

    if(delay_mode == MILLIS){

      _current_time = millis();

      if(_current_time >= _previous_time + delay_time) {
        _previous_time = _current_time;
        return true;
       }
    }

    if(delay_mode == MICROS){

      _current_time = micros();

      if(_current_time >= _previous_time + delay_time) {
        _previous_time = _current_time;
        return true;
       }
    }

    return false; // Only needed here...
  }

 } // namespace...

multiTimer.cpp file: - Option 2:

#include <multiTimer.h>

 // namespace option 2... Better, safer use of direct namespace label
  bool tim::Timer::pause(tim::DELAY_MODE delay_mode, unsigned long delay_time) {

    if(delay_mode == tim::MILLIS){

      _current_time = tim::millis();

      if(_current_time >= _previous_time + delay_time) {
        _previous_time = _current_time;
        return true;
       }
    }

    if(delay_mode == MICROS){

      _current_time = micros();

      if(_current_time >= _previous_time + delay_time) {
        _previous_time = _current_time;
        return true;
       }
    }

    return false; // Only needed here...
  }

Header MUST be enclosed in the namespace!

Also, you should not use zero(0) for the function call, because the enum could be written as:

 enum DELAY_MODE{MILLIS=1000, MICROS=1000000};

...which of course would not work, but using the enums will always work - I believe the new C++ standard being released will tighten up security on this, and won't allow you to use zero(0) anymore in future - Weather it gets implemented under the Arduino compiler in future is another thing 🙂

By the way, I've been using the new Arduino IDE a few times, and it's coming along nicely, and the dark theme is great!

Ok, as for "isRunning"... what is the need to check if the pause is running?

For completeness, here is the .INO code:

#include <multiTimer.h>

// This using namespace directive brings all objects from the namespace into global scope
// Safest is to use tim:: labels at the point of first use to specify which object to bring into scope
//using namespace tim;

tim::Timer timer1, timer2; //as many timers as you want

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

void loop() {

  if(timer1.pause(tim::MILLIS, 5000)){
    Serial.println("5000 milliseconds period"); // or do something else every 5 seconds
  }

  if(timer2.pause(tim::MICROS, 3300000)){
    Serial.println("3300000 microsecond period"); // or do something else
  }
 }

Cheers!

This post was modified 1 month ago 3 times by frogandtoad

ReplyQuote
Pugwash
(@pugwash)
Prominent Member
Joined: 10 months ago
Posts: 969
2020-02-19 12:42 pm  

@frogandtoad

Ok, as for "isRunning"... what is the need to check if the pause is running?

Because I may want to start or stop the timers with code, button or interrupt, rather than just leaving the timers to run constantly, with no control over them!

**********************************

Also, you should not use zero(0) for the function call, because the enum could be written as:

 enum DELAY_MODE{MILLIS=1000, MICROS=1000000};

I have seen this done elsewhere and as far as I am concerned an enumerator attaches a numeric value to the list of descriptors. If this is hardcoded, I don't see the problem.

An example from the rf24 library:

 radio.setPALevel(2); // Options are RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX
radio.setDataRate(0); // Options are RF24_1MBPS, RF24_2MBPS, RF24_250KBPS
radio.setCRCLength(2); // Options are RF24_CRC_DISABLED, RF24_CRC_8, RF24_CRC_16

*********************************

Finally, if the declaration of a library object instance should always be

tim::Timer t1, t2;

why have I never seen this convention used anywhere before?

This post was modified 1 month ago 2 times by Pugwash

SteveC - Topple Rudd Poltman


ReplyQuote
frogandtoad
(@frogandtoad)
Reputable Member
Joined: 10 months ago
Posts: 384
2020-02-19 1:26 pm  

@pugwash

Posted by: @pugwash

Ok, as for "isRunning"... what is the need to check if the pause is running?

Because I may want to start or stop the timers with code, button or interrupt, rather than just leaving the timers to run constantly, with no control over them!

OK, fair enough.

Posted by: @pugwash

Also, you should not use zero(0) for the function call, because the enum could be written as:

 enum DELAY_MODE{MILLIS=1000, MICROS=1000000};

I have seen this done elsewhere and as far as I am concerned an enumerator attaches a numeric value to the list of descriptors. If this is hardcoded, I don't see the problem.

The whole idea behind this approach is to have meaningful descriptors, and if you always use the descriptor, then you can't enter the wrong number by accident.  As I said, the C++ standards committee think it's enough of a problem to make it illegal in their upcoming release, so there must have been a lot of submissions for safety reasons.

Posted by: @pugwash

Finally, if the declaration of a library object instance should always be

tim::Timer t1, t2;

why have I never seen this convention used anywhere before?

I didn't say it should always be, I said it is the safest and most preferred way, well amongst the professionals at least.  I'm not sure why you haven't seen it before?  But I have seen it and used it a lot in usenet groups for 20 years.

A lot of people will generally just use:

using namespace X;

... and then there is no need to do: X:: in front of every identifier, but by doing this, you bring all objects in that namespace into global scope, which increases the risk of name clashes.  Many people also do this out of pure laziness, just to avoid the labels 🙂

Cheers!


ReplyQuote
Pugwash
(@pugwash)
Prominent Member
Joined: 10 months ago
Posts: 969
2020-02-19 1:43 pm  

@frogandtoad

As I understood from the scope code in the other thread where we were discussing this, using namespace is to throw a fence around code to protect it, but in the main sketch or anywhere else, you always have to climb over the fence to get at that particular protected function/method by using an extra identifier.

If that is the purpose of using namespace, then I think I have got it and can see reasons for using it in large programs, but I think it is a bit "OTT" for the small stuff that fits in an Arduino! 

SteveC - Topple Rudd Poltman


ReplyQuote
frogandtoad
(@frogandtoad)
Reputable Member
Joined: 10 months ago
Posts: 384
2020-02-19 1:59 pm  

@pugwash

Posted by: @pugwash

If that is the purpose of using namespace, then I think I have got it and can see reasons for using it in large programs, but I think it is a bit "OTT" for the small stuff that fits in an Arduino! 

Yes, I think you've pretty much got it.  Of course, you do not have to incorporate namespace(s) if you do not wish to for personal small programs, but if you want to create libraries for others to use, then I'd say it's a mandatory requirement, so not a bad habit to get into anyway, unless you're lazy! 😛  

For example, what would you think (and what would you do?), if you downloaded or purchased someone else's math library, but as soon as you #include<> the library, it has a pause and a delay function that clashes with not only your library function and the Arduino function too?  Will you rename the functions in the math library? your library? etc... what can you do? You'll most likely throw that library out, look for a new one, and curse the person who wrote the library giving him a bad reputation, when no one will ever purchase their library ever again! - You get the gist!

Now put your print2Digits thingamabob in there, and rename it to, oh I dunno... pw_utilities.h? 🙂

Cheers!

This post was modified 1 month ago 2 times by frogandtoad

ReplyQuote
frogandtoad
(@frogandtoad)
Reputable Member
Joined: 10 months ago
Posts: 384
2020-02-19 2:06 pm  

Correction:

A few posts earlier (in option 2), I rushed and wrongly labeled:

tim::millis();

... the tim:: label should have been applied to tim::MICROS instead.

Cheers!
This post was modified 1 month ago by frogandtoad

ReplyQuote
Pugwash
(@pugwash)
Prominent Member
Joined: 10 months ago
Posts: 969
2020-02-21 1:40 pm  

A new version of the multiple timers library!

Features:

  1. Create as many timers that you need.
  2. Choose between the options millis() and micros().

Improvements in this version:

  1. Start and stop the timers at will, either hardcoded or using an interrupt.
  2. No rollover problems after 41 days (when using millis) or 1 hour and 11 minutes (when using micros).

Here is the demo sketch:

#include <multiTimer.h>

Timer timer1, timer2;

void setup() {
  
  Serial.begin(9600);
  Serial.println("Sketch starting...");
  Serial.println("To stop or start a timer enter 'timer underscore \ntimer number' e.g. 'timer_2' in the serial monitor");
  delay(5000);

}

void loop() {

  if(Serial.available() != 0){
    String response = Serial.readStringUntil("\n");
    if(response == "timer_1"){      
        timer1.isRunning = !timer1.isRunning;        
      }
    if(response == "timer_2"){
        timer2.isRunning = !timer2.isRunning;
        }   
  }
  
  if(timer1.interval(MILLIS, 5000)){
    Serial.println(millis()); // or do something else every 5 seconds
  }
 
  if(timer2.interval(MICROS, 3300000)){
    Serial.println(micros()); // or do something else
  }
  
}

NOTE: the timers are switched off by default, enter timer_1 or timer_2 in the serial monitor to start or stop the timers.

And here are the library files:

SteveC - Topple Rudd Poltman


ZeFerby liked
ReplyQuote
Page 1 / 3

Please Login or Register