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.


About the Author
These are my books. Click on the image below to find out more about them.

About the Author
These are my books. Click on the image below to find out more about them.


                                                                                                                           

34 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?

Andrey Smirnov said...

Is there a way to catch when t.oscillate stopped working?

mactsk said...

The first timer id that is created with t.after is 0 so i can't check if (!t) because it reports i have to create it when it really exists..

this is a sample code that i can't fix
the first time that alarm=0 the loop is executed 2 times, .after internally creates the timer 0 so it runs twice

if (!active)
{
alarm=1;
Serial.println("on");
if (timeralarm)
{
timer.stop(timeralarm);
Serial.println("stop timer");
}
}
else
{
alarm=0;
if(!timeralarm)
{
Serial.println("start timer");
Serial.println("timeralarm");
Serial.println(timeralarm);
timeralarm=timer.after(timedelay,function);
Serial.println(timeralarm);
delay(200);
}
}

judas knox said...

Jaren, I'm a newbie also, and am trying to do similar but using relays to blink and turn two lasers and a motor off and on. You can change the code above to this to get it to shut off.

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

Changed from "HIGH" to "LOW". I don't know if this is the correct proceedure, for turning it off, but it works, even though the timer continues counting... I too would like to know how to use a momentary button to control the on off switching, and also be able to alternate between two outputs in a repetative sequence for a givin time.

judas knox said...

Jaren, I'm a newbie also, and am trying to do similar but using relays to blink and turn two lasers and a motor off and on. You can change the code above to this to get it to shut off.

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

Changed from "HIGH" to "LOW". I don't know if this is the correct proceedure, for turning it off, but it works, even though the timer continues counting... I too would like to know how to use a momentary button to control the on off switching, and also be able to alternate between two outputs in a repetative sequence for a givin time.

Fabio Giacomelli said...

Hi. A very simple library that does well it's work.

BUT

I think it'a a bad thing search for "first unused int timer" and reuse it .
Stop function doesn't work well in this way.
Sometimes I need to stop a timer, in a stupid manner ( it could be active or not ... ) . Whith "dynamic" timers numeration I risk to stop a wrong timer ( first time I used this library it happened, and I spent a lot of time before understand that reading the source ).

I think ( I did ) it's better to bind with the function " t.oscillate( Timer_n .... ) " the identifier of the timer.

Also I think a "delayed start" could be a good addition, without using a lot of "after" and functions. For example if I need to blink a led like a morse code to show an even I could do t.oscillate(delayed,stuff,x,100,4);
t.oscillate(delayed + 500,x,50,10);

Hi

Fred Gibson said...

Hi Dr Monk. Thanks for the library. I have been trying to switch on a relay for 15 sec and then switch it off for 15 min. I have tried various combinations of your "calls" without success. Do you have any suggestions?

Paul Huffman said...

Thank you for a great bit of code here. I am using it to turn off an LCD backlight after a timeout period. I am running to to trouble however with the .stop command. It doesn't seem to stop the timer. Are there any known issue with .stop?

Kevin said...

Dr Monk. I'm having an issue in getting a "no matching function for call to 'Timer::every(long int, void (&)())'. i can't figure out why I'm getting that error

My code is:

#include "Timer.h"
#define PIN 0
Timer t;

int mode = 0; // set the mode of what program to run
int pin = 0;

void setup() {
t.every(10000,turnoff); //how long to wait till you kill the light.
}

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

///Interrupts/////
void turnoff()
{mode=3;}

Unknown said...

You wrote that you wanted to hear from similar projects.
If have written a statemachine library that is (in my opinion) very useful.
It is found here:
http://playground.arduino.cc/Code/SMlib

It is NOT primarily a timer library but it can be used for running multiple timers as well by putting the timer action in the head function and having a timeout do a restart in the body.

SpaceInvader said...

Answer to Kevin :

you should add NULL as last parameter when calling every or after.
In your case:
t.every(10000,turnoff,NULL); //how long to wait till you kill the light.

And what is more, you should modify your function declaration :
void turnoff(void *context) { ...

Don't know why, but it works ;-)

Enjoy

Jack said...

Hi, your library is'nt correctly settled

Have you seen "Installing Additional Arduino Libraries" ?

This method does'nt work with arduino ide 1.5.4.

My method :
Create a file :"library.properties" with text below

name=Timer Event
author=Simon Monk
email=
sentence=Libary for the Arduino Timer event shield.
paragraph=With this library you can instantiate callback function.
url=http://www.doctormonk.com/2012/01/arduino-timer-library.html
architectures=avr
version=1.0
dependencies=
core-dependencies=arduino (>=1.0.0)


To install the library, first quit the Arduino application
Copy the components of MyLib (.cpp, .h library.properties, keywords.txt, /examples/*.* files) into the folder : ./arduino-1.5.x/libraries/MyLib

NB : .cpp and .h into folder MyLib/src

eg : for the library Timer
arduino-1.5.4/libraries/Timer_Event/examples/Blink2/*.ino
arduino-1.5.4/libraries/Timer_Event/src/*.cpp;*.h
arduino-1.5.4/libraries/Timer_Event/keywords.txt
arduino-1.5.4/libraries/Timer_Event/libraries.properties

restart ARDUINO IDE

Gadjet said...

Hi,
Just discovered your libray and wanted it to switch a relay for 1 hour, triggered from an event in the main loop.

It looks like you examples are not configured for this and a few others have already asked similar questions but there doesn't seem to be any responses.
Anyway I thought it was worth asking again.
How can I trigger a output for a period of time from within the main loop based on a user input?

Cheers and thanks for your eforts.