Go Down

Topic: SimpleTimer library (Read 5744 times) previous topic - next topic

mromani

Hi,
   I've developed a simple library to fire timed events.
Resolution is 1ms.

A more complete solution is to use Time + TimeAlarm libraries, which also have clock features, but are bigger than this lib (compiled code size).

Here's the code:

SimpleTimer.h:
Code: [Select]

/*
* SimpleTimer.h
*
*/

#ifndef SIMPLETIMER_H
#define SIMPLETIMER_H

#include <WProgram.h>

typedef void (*timer_callback)(void);

class SimpleTimer {

public:
   // maximum number of timers
   const static int MAX_TIMERS = 10;

   // setTimer() constants
   const static int RUN_FOREVER = 0;
   const static int RUN_ONCE = 1;

   // constructor
   SimpleTimer();

   // this function must be called inside loop()
   void run();

   // call function f every d milliseconds
   int setInterval(long d, timer_callback f);

   // call function f once after d milliseconds
   int setTimeout(long d, timer_callback f);

   // call function f every d milliseconds for n times
   int setTimer(long d, timer_callback f, int n);

   // destroy the specified timer
   void deleteTimer(int numTimer);

   // returns true if the specified timer is enabled
   boolean isEnabled(int numTimer);

   // enables the specified timer
   void enable(int numTimer);

   // disables the specified timer
   void disable(int numTimer);

   // enables or disables the specified timer
   // based on its current state
   void toggle(int numTimer);

   // returns the number of used timers
   int getNumTimers();

private:
   // value returned by the millis() function
   // in the previous run() call
   long prev_millis[MAX_TIMERS];

   // pointers to the callback functions
   timer_callback callbacks[MAX_TIMERS];

   // delay values
   long delays[MAX_TIMERS];

   // number of runs to be executed for each timer
   int maxNumRuns[MAX_TIMERS];

   // number of executed runs for each timer
   int numRuns[MAX_TIMERS];

   // which timers are enabled
   boolean enabled[MAX_TIMERS];

   // actual number of timers in use
   int numTimers;
};

#endif


SimpleTimer.cpp:

Code: [Select]

#include "SimpleTimer.h"


SimpleTimer::SimpleTimer() {
   long current_millis = millis();

   for (int i = 0; i < MAX_TIMERS; i++) {
       enabled[i] = false;
       callbacks[i] = 0;
       prev_millis[i] = current_millis;
   }

   numTimers = 0;
}


void SimpleTimer::run() {
   int i;
   long current_millis;

   // get current time
   current_millis = millis();

   for (i = 0; i < MAX_TIMERS; i++) {

       // only process active timers
       if (callbacks[i]) {
           
           // is it time to process this timer ?
           if (current_millis - prev_millis[i] >= delays[i]) {

               // update time
               prev_millis[i] = current_millis;

               // check if the timer callback has to be executed
               if (enabled[i]) {

                   // "run forever" timers must always be executed
                   if (maxNumRuns[i] == RUN_FOREVER) {
                       (*callbacks[i])();
                   }
                   // other timers get executed the specified number of times
                   else if (numRuns[i] < maxNumRuns[i]) {
                       (*callbacks[i])();
                       numRuns[i]++;

                       // after the last run, delete the timer
                       // to save some cycles
                       if (numRuns[i] >= maxNumRuns[i]) {
                           deleteTimer(i);
                       }
                   }
               }
           }
       }
   }
}


int SimpleTimer::setTimer(long d, timer_callback f, int n) {
   if (numTimers >= MAX_TIMERS) {
       return -1;
   }

   delays[numTimers] = d;
   callbacks[numTimers] = f;
   maxNumRuns[numTimers] = n;
   enabled[numTimers] = true;

   numTimers++;

   return (numTimers - 1);
}


int SimpleTimer::setInterval(long d, timer_callback f) {
   return setTimer(d, f, RUN_FOREVER);
}


int SimpleTimer::setTimeout(long d, timer_callback f) {
   return setTimer(d, f, RUN_ONCE);
}


void SimpleTimer::deleteTimer(int numTimer) {
   if (numTimer >= MAX_TIMERS) {
       return;
   }

   // nothing to disable if no timers are in use
   if (numTimers == 0) {
       return;
   }

   callbacks[numTimer] = 0;
   enabled[numTimer] = false;
   delays[numTimer] = 0;

   // update number of timers
   numTimers--;
}


boolean SimpleTimer::isEnabled(int numTimer) {
   if (numTimer >= MAX_TIMERS) {
       return false;
   }

   return enabled[numTimer];
}


void SimpleTimer::enable(int numTimer) {
   if (numTimer >= MAX_TIMERS) {
       return;
   }

   enabled[numTimer] = true;
}


void SimpleTimer::disable(int numTimer) {
   if (numTimer >= MAX_TIMERS) {
       return;
   }

   enabled[numTimer] = false;
}


void SimpleTimer::toggle(int numTimer) {
   if (numTimer >= MAX_TIMERS) {
       return;
   }

   enabled[numTimer] = !enabled[numTimer];
}


int SimpleTimer::getNumTimers() {
   return numTimers;
}



Example sketch (modified version of Time+TimeAlarm example):

Code: [Select]

/*
* SimpleTimerAlarmExample.pde
*
* A timer is called every 15 seconds
* Another timer is called once only after 10 seconds
* A third timer is called 10 times.
*
*/

#include <SimpleTimer.h>

SimpleTimer timer;
 
void setup()
{
 Serial.begin(9600);    
 Serial.println("SimpleTimer Example");
 Serial.println("One timer is triggered every 15 seconds");
 Serial.println("Another timer is set to trigger only once after 10 seconds");
 Serial.println("Another timer is set to trigger 10 times");
 Serial.println();
 
 timer.setInterval(15000, RepeatTask);            // timer for every 15 seconds    
 timer.setTimeout(10000, OnceOnlyTask);            // called once after 10 seconds
 timer.setInterval(1000, digitalClockDisplay);
 timer.setTimer(1200, tenTimesTask, 10);
}

void RepeatTask()
{
 Serial.println("15 second timer");        
}

void OnceOnlyTask()
{
 Serial.println("This timer only triggers once");  
}

void tenTimesTask() {
 static int k = 0;
 k++;
 Serial.print("called ");
 Serial.print(k);
 Serial.println(" / 10 times.");
}

void  loop()
{  
 timer.run();
}

void digitalClockDisplay()
{
 int h,m,s;
 s = millis() / 1000;
 m = s / 60;
 h = s / 3600;
 s = s - m * 60;
 m = m - h * 60;
 Serial.print(h);
 printDigits(m);
 printDigits(s);
 Serial.println();
}

void printDigits(int digits)
{
 // utility function for digital clock display: prints preceding colon and leading 0
 Serial.print(":");
 if(digits < 10)
   Serial.print('0');
 Serial.print(digits);
}

AlphaBeta

#1
Jun 10, 2010, 02:02 pm Last Edit: Jun 10, 2010, 02:03 pm by AlphaBeta Reason: 1
You can add it to the list of timing related libraries:
http://www.arduino.cc/playground/Main/GeneralCodeLibrary#Timing_code

:)

mromani

Moved to http://www.arduino.cc/playground/Code/SimpleTimer

Thanks :-)

LukeZ

Hi guys, this is a useful library, however, I have found a bug which is easily remedied.

The issue is that prev_millis is not reset for each timer on first creation. Let's say you create a timer, it will be assigned ID = 0. That timer ends, now you create a new timer. The code will just reuse the old ID which is now free, so this second timer will also get assigned ID = 0. However, since prev_millis isn't reset on creation, it will also be assigned prev_millis[0] from the previous timer.

The time that has passed since the first timer was "destroyed" and the new timer created is essentially now reflected in prev_milllis[0]. This time may very well exceed whatever timeout you have on your new timer, so you might be surprised to find that it expires immediately.

The fix is simple enough. In "SimpleTimer.cpp", in the SimpleTimer::SetTimer function, you need to add this line:
prev_millis[numTimers] = millis();

The new function should look like this:

Code: [Select]

int SimpleTimer::setTimer(long d, timer_callback f, int n) {
   if (numTimers >= MAX_TIMERS) {
       return -1;
   }

   delays[numTimers] = d;
   callbacks[numTimers] = f;
   maxNumRuns[numTimers] = n;
   enabled[numTimers] = true;
   prev_millis[numTimers] = millis();
   
   numTimers++;

   return (numTimers - 1);
}


This ensures that every timer you create, even if it is reusing some old TimerID, will have its "start time" set correctly.

Maybe someone could update the library source...


Luke

Go Up