Tuesday, January 31, 2012

Arduino Timer Library

I have developed a simple to use library that gets around a load of problems that arise when you start trying to do much inside 'loop'. It can change pin values or run a callback function. It seems like such an obvious thing that I doubt its original, so I would like to hear of similar projects.

DOWNLOAD Thanks to Jack Christensen for hosting it and the improvements and version management he has added to it.

 


The library does not interfere with the built-in timers, it just uses 'millis' in a crude type of scheduler to decide when something needs doing.

Examples

The Arduino 'delay' function is both a blessing and a curse. Its great for showing beginners how to make an LED flash. But as soon as you get more complex and start slowing down your 'loop' function  you will run into problems.

A classic example is turning a relay on for 10 minutes. The 'delay'-way looks like this:

int pin = 13;


void setup()
{
  pinMode(13, OUTPUT);
  digitalWrite(pin, HIGH);
  delay(10 * 60 * 60 * 1000);
  digitalWrite(pin, LOW);
}


void loop()
{
}

The disadvantage of the delay approach is that nothing else can go on while the 'delay' is happening. You cannot update a display, or check for key presses for example.

My 'Timer' library version looks like this:


#include "Timer.h"


Timer t;
int pin = 13;


void setup()
{
  pinMode(pin, OUTPUT);
  t.pulse(pin, 10 * 60 * 1000, HIGH); // 10 minutes  
}


void loop()
{
  t.update();
}

The 'pulse' method takes arguments of a pin to change, the period to change it for and its initial state.

The call to t.update() will take a matter of microseconds to run, unless the appropriate period of time has passed.

Lets look at another example that uses two timer events. One to flash an LED and another that reads A0 and displays the result in the Serial Monitor.


#include "Timer.h"


Timer t;
int pin = 13;


void setup()
{
  Serial.begin(9600);
  pinMode(pin, OUTPUT);
  t.oscillate(pin, 100, LOW);
  t.every(1000, takeReading);
}


void loop()
{
  t.update();
}


void takeReading()
{
  Serial.println(analogRead(0));
}

The first thing to notice is that we are using a callback function called 'takeReading'. We connect it to the Timer using the 'every' command, which in this case, will call the function every second.

We have also attached another event to the timer using the method 'oscillate'. This will cause the LED to toggle state every 100 milliseconds.

Each of the events has an integer ID associated with it, so that you can stop an event, as we do in this example below, which will write to the serial monitor every 2 seconds, flash the LED and after 5 seconds, stop the LED flashing fast, and flash it 5 times slowly.


#include "Timer.h"


Timer t;


int ledEvent;


void setup()
{
  Serial.begin(9600);
  int tickEvent = t.every(2000, doSomething);
  Serial.print("2 second tick started id=");
  Serial.println(tickEvent);
  
  pinMode(13, OUTPUT);
  ledEvent = t.oscillate(13, 50, HIGH);
  Serial.print("LED event started id=");
  Serial.println(ledEvent);
  
  int afterEvent = t.after(10000, doAfter);
  Serial.print("After event started id=");
  Serial.println(afterEvent); 
  
}


void loop()
{
  t.update();
}


void doSomething()
{
  Serial.print("2 second tick: millis()=");
  Serial.println(millis());
}




void doAfter()
{
  Serial.println("stop the led event");
  t.stop(ledEvent);
  t.oscillate(13, 500, HIGH, 5);
}


You can attach up to 10 events to a timer.

Installation

As with all libraries, unzip the file into the 'libraries' folder in your Arduino directory, which will be in something like 'My Documents\Arduino' on Windows, 'Documents/Arduino' on Mac etc. If this is the first library you have installed, you will need to create a directory there called 'libraries'.

The library is compatible with both Arduino 1.0 and earlier versions.

Reference


int every(long period, callback)
 Run the 'callback' every 'period' milliseconds.
 Returns the ID of the timer event.

int every(long period, callback, int repeatCount)
 Run the 'callback' every 'period' milliseconds for a total of 'repeatCount' times.
 Returns the ID of the timer event.

int after(long duration, callback)
 Run the 'callback' once after 'period' milliseconds.
 Returns the ID of the timer event.

int oscillate(int pin, long period, int startingValue)
 Toggle the state of the digital output 'pin' every 'period' milliseconds. The pin's starting value is specified in 'startingValue', which should be HIGH or LOW.
 Returns the ID of the timer event.

int oscillate(int pin, long period, int startingValue, int repeatCount)
 Toggle the state of the digital output 'pin' every 'period' milliseconds 'repeatCount' times. The pin's starting value is specified in 'startingValue', which should be HIGH or LOW.
 Returns the ID of the timer event.

int pulse(int pin, long period, int startingValue)
 Toggle the state of the digital output 'pin' just once after 'period' milliseconds. The pin's starting value is specified in 'startingValue', which should be HIGH or LOW.
 Returns the ID of the timer event.

int stop(int id)
 Stop the timer event running.
 Returns the ID of the timer event.

int update()
 Must be called from 'loop'. This will service all the events associated with the timer.

Conclusion

Have a go with the library, please let me know what you think.



                                                                                                                           

22 comments:

ViennaMike said...

Dr. Monk: How does this differ from the Metro library ?

Coincidentally, I just posted a similar article Move now, don't delay(); on my new blog.

Simon Monk said...

Hi Mike,

Well, I was right to think this ida has not occurred to anyone else.

I would like to think that my library is easier to use - but then it is for me, as I know it ;)

Also, I use callbacks rather than having lots of tests in the loop function, which IMHO is a cooler way of doing it.

BTW - I like your FSM description on your blog

Andrew said...

Hi Simon,

(10 * 60 * 60 * 1000) is 10 hours not 10 minutes.

Florian St. said...

Is it possible to use a callback function with paramters like this?

Timer t;

void callback(int x)
{do_something_with(x);}

t.after(1000,callback(27));

I get some errors like: invalid use of void expression

Thanks
Florian

Per :o) said...

Very fine library. But I having some problem with the after-function. It won't work with a value greater than 30.000.
I hoped I could use it for a delay of 5 min.
BR
Per :o)

Simon Monk said...

Per, sounds like I used an int where I should have used a long.

Have a look in the .cpp and .h files.

Simon Monk said...

@florian, no callbacks have to be parameterless - although you can set globals of course.

Unknown said...

I've been playing with your lib a bit and I have a few questions.

Can I set up timers outside the setup()? the after event for example is started in the void setup() so it will only be used once and the timer starts at start up. Can it be started in the loop()? Can it be restarted?

Will using delay in addition to the Timer.h lib cause problems?
If I need to create a short delay for debouching an input for example, will a 300ms delay cause issues with your lib?

More examples / explanations would be great for us noobs ;)

sudopeople said...

I really like this. I can think of one feature that I might need at some point.

As an example, say we want a 25% duty cycle on a flashing LED.

Maybe like this:
t.oscillate(13, 250, HIGH, 5, 750);
//(PIN, period1, startingVal, count, period2)

"otherPeriod" is the LOW state in this example, don't know what else to call it ;(

This would set the pin HIGH for 250ms, LOW for 750ms 5 times.
Count could be set to 0 (zero) for an infinite loop.

Lusankya23 said...

The maximum delay problem isn't with the Timer library. The compiler treats integer literals as 16-bit signed integers unless otherwise specified, making the largest number 32767. For a delay of 5 minutes, try using 5L*60L*1000L to force a long. Source: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1200706295/all

Neil Kenyon said...

Simon - great library, but I am having a similar problem to Per, except it's the "every" function, which wont work with a value greater than 30,000 (32,767, probably!).

I looked in the .cpp files and the .h files, but didn't understand them. Can you help, please.

colinng said...

Hi Simon,

Your library works well.

I had trouble with the timer values. I tried to get 45 seconds with

long myTimerVal = 45 * 1000; // WRONG

Because it overflows. I fixed that by doing:

long myTimerVal = 45L * 1000L; // CORRECT

Would you kindly mention that in your postings so people don't look for a bug that isn't there?

Thanks!

Colin

Zwerch said...

Hey,

I think I'm only too stupid for this, but in all of your examples you used the events in the "setup". But what, if I want to start them first later on, triggered by an event happening in the loop?
I also looked at the "Metro"-lib, and the examples there, and there the way I want the Timer to be triggered is much easier.
So, maybe I just did not understand something, but maybe you could update your examples with the case I explained.

Anyway, nice library!

Thanks :)

LoftyAnvil said...

Re: Per's comment
I encountered the same problem.
It looks like we're being a little too cute for the complier eg:

t.oscillate(pin, (1 * 60 * 1000), LOW); // 1 min - no go

t.oscillate(pin, 60000, LOW); // 1 min - ok

t.oscillate(pin, (long(1) * long(60) * long(1000)), LOW); // 1 min - ok

-john

Newton said...

Dear Dr. Monk,

I suspect that this is a trivial question, but I'm unable to use your timer library in Arduino 1.0 because I get:

'Timer' does not name a type

when I attempt to compile in Arduino 1.0.

Your example sketches run fine in my older version of Arduino.

Do you have an idea why this may be occurring?

Thank you,

Grant

Newton said...

Dear Dr. Monk,

Disregard my question. My problem was from not installing my timer library correctly. The timer now works great.

Thank you,

Grant

Claude Stocklin said...

thanks for the lib

is it possible to use timers outside the setup()?
I would like to make a "precision" delay

A few example would be nice

rgs

Upendrasan said...

will this library support for bug 1 algorithm????

Ryan Ace said...

c++ used in this router
Buy CNC Machine

Jaren Cartrite said...

I'm new to arduino programming so please bear with me. I would like to know how to reset the counter to zero without resetting the arduino.

What I am doing is when I push a latching button, I want it to pulse an LED for 4 minutes then stay on. Then when the button is unlatched I want the timer to reset to zero and turn off the LED. If the button is latched again I want it to go through this process again.

Is there a counter reset that I have over looked?

Jaren Cartrite said...

I'm new to arduino programming so please bear with me. I would like to know how to reset the counter to zero without resetting the arduino.

What I am doing is when I push a latching button, I want it to pulse an LED for 4 minutes then stay on. Then when the button is unlatched I want the timer to reset to zero and turn off the LED. If the button is latched again I want it to go through this process again.

Is there a counter reset that I have over looked?

Unknown said...

Hi,
In http://playground.arduino.cc/Code/Timer what mean "You can attach up to 10 events to a timer." ?

It is no possible to do
void f(){
t.pulse(led, 20, LOW);
}

void setup() {
pinMode(led, OUTPUT);
t.every(500, f);
}

void loop() {
t.update();
}

because t.pulse will be called more than 10 times (it's working) ? What mean "attach" in thie case?