Timer library: clsTimer

I’m quite new to Arduino, having owned the Uno 3 for just a week. I’m writing an application for slot cars racing, timing, lights etc. In the progress of writing this application I have developed a library for managing timers. Please see below:

Prototype, clsTimer.h

/**
 * File:
 *  clsTimer.h
 *  
 * Notes:
 *  This file contains the prototype for the class clsTimer.
 *  
 * Methods:
 *  clsTimer      Class constructor
 *  ~clsTimer     Class destructor
 *  expire        Flags that a timer should be serviced 
 *  expired       Called when timer expires 
 *  interval      Returns the interval either m_intInitial or m_intRepeat 
 *  next          Returns pointer to next timer in linked list   
 * 
 * Members:
 *  m_blnCalled   Flag to indicate if timer has been called 
 *  m_intInitial  The initial time(ms) before the timer expires
 *  m_intRepeat   Optional, repeat time(ms) before the expiration, 0 = not used
 *  m_pCallback   Call-back routine to call when timer expires
 *  m_pData       Data to associate with this instance  
 *         
 * History:
 *  22/01/2012  Created by Simon Platten
 */            
#ifndef TIMER_MANAGER_H
  #define TIMER_MANAGER_H
  
  #include <Arduino.h>
  
  class clsTimer {
  private:  
// Flag to indicate if timer has been called
    bool m_blnCalled;
// true if timer ready to be serviced, false it not    
    bool m_blnExpire;
// The initial time(ms) before the timer expires  
    unsigned m_uintInitial;
// Optional, repeat time(ms) before the expiration, 0 = not used
    unsigned m_uintRepeat;    
// Call-back routine to call when timer expires
    void (*m_pCallback)(clsTimer* pTimer);
// Data to associate with this instance
    void* m_pData;
// Next timer in Linked-list
    clsTimer* m_pNext;
// Called to expire timer object    
    void      expire();
// Returns the interval either m_uintInitial or m_uintRepeat    
    unsigned  interval();
// Returns next timer in linked list
    clsTimer* next();    

  public:
    static void     checkTimers();
    static unsigned counter();
    static void     serviceTimers();
          
  public:
    clsTimer(unsigned uintInitial,               
             void (*pCallback)(clsTimer* pTimer), 
             void *pData = NULL,
             unsigned uintRepeat = 0);
    ~clsTimer();
// Returns any data configured for this timer    
    void* getData();    
  };
#endif

Implementation, clsTimer.cpp:

/**
 * File:
 *  clsTimer.cpp
 *  
 * Notes:
 *  This file contains the implementation of the class clsTimer.
 *
 * History:
 *  22/01/2012  Created by Simon Platten
 */  
#include "clsTimer.h"

// Pointer to all timers linked list
static volatile clsTimer* pAllTimers = NULL;
// Counter used by ISR, incremented every interrupt
static volatile unsigned uintTmrISRctr;
/**
 * Interupt Service Routine, called every 1ms to check all timers
 */ 
ISR(TIMER2_OVF_vect) {
  TCNT2 = 0x06;
// Increment the counter and check timer resources
  clsTimer::checkTimers();
};
/**
 * Timer constructor
 *  
 * Paramters:
 *  uintInitial,the initial expiration time in ms
 *  pCallback,  the routine to call when the timer expires
 *  pData,      pointer to data block accessible for this timer  
 *  uintRepeat, optional, repeat time in ms for subequent calls
 */         
clsTimer::clsTimer(unsigned uintInitial, 
                   void (*pCallback)(clsTimer* pTimer), 
                   void *pData,
                   unsigned uintRepeat) {
  if ( uintInitial > 0 && pCallback != NULL ) {
// Disable interrupts    
    cli();
    
    if ( pAllTimers == NULL ) {
// Reset counter      
      uintTmrISRctr = 0;
// Set-up interrupt      
      TCNT2 = 0x00;
      OCR2A = 250;		        // 1 ms @ Fosc = 16 MHz
      TCCR2A = 0x02;          // WGM: No wave generation
      TCCR2B = 0x04;          // START Timer, prescaler = 64
      TIMSK2 = (1 << TOIE2);  // Enable interrupt when Timer reaches OCRA
    }
// Configure this timer    
    m_uintInitial = uintInitial;    
    m_pCallback = pCallback;
    m_pData = pData;
    m_uintRepeat = ( uintRepeat > 0 ) ? uintRepeat : 0;
    m_blnCalled = m_blnExpire = false;
    
    if ( pAllTimers != NULL ) {
      m_pNext = (clsTimer*)pAllTimers;              
    } else {
// This is the first node in the list, ensure that there is no Next node    
      m_pNext = NULL; 
    }
// Insert this timer at the beginning of the linked list    
    pAllTimers = this;
// Re-enable interrupts
    sei();    
  }
}
/**
 * Destructor
 */ 
clsTimer::~clsTimer() {
}
/**
 * Static access method allows read-only access to ms counter
 */ 
unsigned clsTimer::counter() {
  return uintTmrISRctr;
} 
/**
 * Checks all timers to see if any have expired
 */ 
void clsTimer::checkTimers() {
// Check all timers
  unsigned uintCounter = ++uintTmrISRctr; 
  for( clsTimer* pTimer=(clsTimer*)pAllTimers; pTimer!=NULL; 
       pTimer=pTimer->next() ) {      
    if ( (uintCounter % pTimer->interval() ) == 0 ) {        
// Expire this timer, this will flag that its ready to be serviced
      pTimer->expire();
    }
  }
}
/**
 * Sets flag to indicate that timer is ready to be serviced
 */        
void clsTimer::expire() {
  m_blnExpire = true;
}
/**
 * Allows access to any data configured for this timer
 */ 
void* clsTimer::getData() {
  return m_pData;
}
/**
 * Gets the current timer interval
 */         
unsigned clsTimer::interval() {
  unsigned uintInterval;
  if ( m_blnCalled == false ) {
    uintInterval = m_uintInitial;
  } else {
    uintInterval = m_uintRepeat;
  }
  return uintInterval;
}
/**
 * Gets the next timer object in the linked list
 */        
clsTimer* clsTimer::next() {
  return m_pNext;
}
/**
 * Should be called in main loop to service any expired timers
 */  
void clsTimer::serviceTimers() {
  for( clsTimer* pTimer=(clsTimer*)pAllTimers; pTimer!=NULL; 
       pTimer=pTimer->next() ) {
    if ( pTimer->m_blnExpire == false ) {    
      continue;
    }    
// Timer has expired, clear flag
    pTimer->m_blnExpire = false;    
// Set initial call flag so we know the next call will be using the repeat time
    pTimer->m_blnCalled = true;
// Perform call-back  
    (*pTimer->m_pCallback)(pTimer);  
  }
}

To try this out, copy the files clsTimer.h and clsTimer.cpp to your Arduino IDE libraries folder, into a folder called ‘clsTimer’

Then try this sample:

/**
 * File:
 *  tmrTest.c
 */
#include <clsTimer.h> 

bool aryState[] = { false, false, false };
int aryPins[] = { 13, 12, 11, 0 };

void toggleLed(clsTimer* pTmr) {
  int* pintPin = (int*)pTmr->getData();
  int intIdx = pintPin - aryPins;

  if ( aryState[intIdx] == false ) {
    digitalWrite(*pintPin, HIGH);  
    aryState[intIdx] = true;
  } else {
    digitalWrite(*pintPin, LOW);      
    aryState[intIdx] = false;
  }
}

clsTimer* aryLEDs[] = {
  new clsTimer(  750, toggleLed, &aryPins[0], 750 ),
  new clsTimer( 1500, toggleLed, &aryPins[1], 1500 ),
  new clsTimer( 2250, toggleLed, &aryPins[2], 2250 ) 
                    };
                    
void setup() {
  for( int i=0; aryPins[i]!=0; i++ ) {
    pinMode(aryPins[i], OUTPUT);
  }
}

void loop() {
  clsTimer::serviceTimers();  
}

This example toggles 3 LED’s on pins 13, 12 and 11. LED 13 will flash every 750 ms, 12 every 1500ms and 11 every 2250ms.

Please let me know if you have any suggestions to improve, please feel free to download and use, please keep comment headers intact.

Thank you

Please let me know if you have any suggestions to improve

Class names and file names should match. clsTimer and Timer.cpp/Timer.h do not match.

I've tested it all with the names I published, it all verified without any warnings or problems and loaded to the unit without problem.

I'm using the latest version of the IDE.

I've tested it all with the names I published, it all verified without any warnings or problems and loaded to the unit without problem.

OK, but irrelevant. When one goes to look for the files that a class is defined in, one expects to find clsTimer in clsTimer.h and clsTimer.cpp, not in Timer.h and Timer.cpp.

If you want a library to be used, and easy to understand, you will follow the convention. While technically the names do not need to match, convention says that they should. Failing to follow convention makes using, debugging and troubleshooting more complicated than necessary AND increases the chance of name conflicts.

On another note, if there is really something novel with your library, Timer is not a very good name. The name should reflect the novelness. Otherwise, the class should be called JustAnotherTimer.

Ok, I take your point conventions are important which is why you will notice if you look at my code, I follow quite a strict coding style. I will rename my library files, clsTimer.h and clsTimer.cpp and put them into the a folder also called clsTimer.

In terms of there usage, I haven't seen any other timer libraries so cannot say how mine compares, the reason I created this was to allow for the easy creation of multiple timers. Tonight I will add 'suspend', 'resume' and 'remove' methods, also I will modify the destructor to remove one shot timers when they are no longer required.

Updates, adding setRepeat, suspend, remove and resume methods, also implemented destructor.
Prototype, clsTimer.h

/**
 * File:
 *  clsTimer.h
 *  
 * Notes:
 *  This file contains the prototype for the class clsTimer.
 *  
 * Methods:
 *  clsTimer      Class constructor
 *  ~clsTimer     Class destructor
 *  expire        Flags that a timer should be serviced 
 *  expired       Called when timer expires
 *  getCountDown  Returns the count down counter value 
 *  getData       Acccess method allows access to timer data block  
 *  interval      Returns the interval either m_intInitial or m_intRepeat 
 *  next          Returns pointer to next timer in linked list
 *  setRepeat     Sets the repeat timer interval 
 *  suspend       Sets suspend flag, preventing timer from expiring whilst set
 *  remove        Removes timer object from linked list      
 *  resume        Clears suspend flag, allowing timer to expire
 * 
 * Members:
 *  m_blnCalled   Flag indicates if timer has been called
 *  m_blnExpire   Flag indicates that timer has expired 
 *  m_blnSuspend  Flag indicates if activity has been suspended  
 *  m_uintCntDown Count down counter 
 *  m_uintInitial The initial time(ms) before the timer expires
 *  m_uintRepeat  Optional, repeat time(ms) before the expiration, 0 = not used
 *  m_pCallback   Call-back routine to call when timer expires
 *  m_pData       Data to associate with this instance
 *  m_pNext       Pointer to next node in linked list   
 *         
 * History:
 *  22/01/2012  Created by Simon Platten
 *  25/01/2012  Added suspend, resume and remove methods 
 */            
#ifndef TIMER_MANAGER_H
  #define TIMER_MANAGER_H
  
  #include <Arduino.h>
  
  class clsTimer {
  private:  
    bool m_blnCalled;
    bool m_blnExpire;
    bool m_blnSuspend;
    unsigned m_uintCntDown;      
    unsigned m_uintInitial;
    unsigned m_uintRepeat;    
    void (*m_pCallback)(clsTimer* pTimer);
    void* m_pData;
    clsTimer* m_pNext;
    void      expire();
    unsigned  interval();
    clsTimer* next();    
  public:
    static void     checkTimers();
    static void     serviceTimers();
          
    clsTimer(unsigned uintInitial,               
             void (*pCallback)(clsTimer* pTimer), 
             void *pData = NULL,
             unsigned uintRepeat = 0);
    ~clsTimer();
    unsigned  getCountDown();
    void*       getData();
    void        setRepeat(unsigned uintRepeat);
    void        suspend();
    void        remove();
    void        resume();                
  };
#endif

Implementation, clsTimer.cpp

/**
 * File:
 *  clsTimer.cpp
 *  
 * Notes:
 *  This file contains the implementation of the class clsTimer.
 *
 * History:
 *  22/01/2012  Created by Simon Platten
 *  25/01/2012  Added suspend, resume and remove methods   
 */  
#include "clsTimer.h"

// Pointer to all timers linked list
static volatile clsTimer* pAllTimers = NULL;
/**
 * Interupt Service Routine, called every 1ms to check all timers
 */ 
ISR(TIMER2_OVF_vect) {
  TCNT2 = 0x06;
// Increment the counter and check timer resources
  clsTimer::checkTimers();
};
/**
 * Timer constructor
 *  
 * Paramters:
 *  uintInitial,the initial expiration time in ms
 *  pCallback,  the routine to call when the timer expires
 *  pData,      pointer to data block accessible for this timer  
 *  uintRepeat, optional, repeat time in ms for subequent calls
 */         
clsTimer::clsTimer(unsigned uintInitial, 
                   void (*pCallback)(clsTimer* pTimer), 
                   void *pData,
                   unsigned uintRepeat) {
  if ( uintInitial > 0 && pCallback != NULL ) {
// Disable interrupts    
    cli();
    
    if ( pAllTimers == NULL ) {
// Set-up interrupt      
      TCNT2 = 0x00;
      OCR2A = 250;		        // 1 ms @ Fosc = 16 MHz
      TCCR2A = 0x02;          // WGM: No wave generation
      TCCR2B = 0x04;          // START Timer, prescaler = 64
      TIMSK2 = (1 << TOIE2);  // Enable interrupt when Timer reaches OCRA
    }
// Configure this timer    
    m_uintCntDown = m_uintInitial = uintInitial;    
    m_pCallback = pCallback;
    m_pData = pData;
    m_uintRepeat = ( uintRepeat > 0 ) ? uintRepeat : 0;    
    m_blnCalled = m_blnExpire = m_blnSuspend = false;
    
    if ( pAllTimers != NULL ) {
// Insert this timer into the list of all timers
      m_pNext = (clsTimer*)pAllTimers;              
    } else {
// This is the first node in the list, ensure that there is no Next node    
      m_pNext = NULL; 
    }
// Insert this timer at the beginning of the linked list    
    pAllTimers = this;
// Re-enable interrupts
    sei();    
  }
}
/**
 * Destructor
 */ 
clsTimer::~clsTimer() {
// Remove this node
  remove();
}
/**
 * Checks all timers to see if any have expired
 */ 
void clsTimer::checkTimers() {
// Check all timers
  for( clsTimer* pTimer=(clsTimer*)pAllTimers; pTimer!=NULL; 
       pTimer=pTimer->next() ) {      
    if ( pTimer->m_blnSuspend == true ) {
// This timer is currently suspended, skip over it!    
      continue;
    }
    if ( pTimer->m_uintCntDown > 0 ) {
// Counter still greater than 0, decrement    
      pTimer->m_uintCntDown--;
    } else {
// Expire this timer, this will flag that its ready to be serviced
      pTimer->expire();      
    }
  }
}
/**
 * Sets flag to indicate that timer is ready to be serviced
 */        
void clsTimer::expire() {
  m_blnExpire = true;
}
/**
 * Returns the count down counter value
 */ 
unsigned clsTimer::getCountDown() {
  return m_uintCntDown;
} 
/**
 * Allows access to any data configured for this timer
 */ 
void* clsTimer::getData() {
  return m_pData;
}
/**
 * Gets the current timer interval
 */         
unsigned clsTimer::interval() {
  unsigned uintInterval;
  if ( m_blnCalled == false ) {
    uintInterval = m_uintInitial;
  } else {
    uintInterval = m_uintRepeat;
  }
  return uintInterval;
}
/**
 * Gets the next timer object in the linked list
 */        
clsTimer* clsTimer::next() {
  return m_pNext;
}
/**
 * Should be called in main loop to service any expired timers
 */  
void clsTimer::serviceTimers() {
  for( clsTimer* pTimer=(clsTimer*)pAllTimers; pTimer!=NULL; 
       pTimer=pTimer->next() ) {
    if ( pTimer->m_blnExpire == false ) {    
      continue;
    }    
// Timer has expired, clear flag
    pTimer->m_blnExpire = false;    
// Set initial call flag so we know the next call will be using the repeat time
    pTimer->m_blnCalled = true;
// Perform call-back  
    (*pTimer->m_pCallback)(pTimer);
    
    if ( pTimer->m_uintRepeat > 0 ) {
      pTimer->m_uintCntDown = pTimer->m_uintRepeat;
    } else { 
// This timer is a one-shot timer, remove it!
      delete pTimer;
    }      
  }
}
/**
 * Sets the repeat timer interval
 */     
void clsTimer::setRepeat(unsigned uintRepeat) {
// Set repeat counter and reset count down counter
  m_uintCntDown = m_uintRepeat = uintRepeat;
}
/**
 * Sets suspend flag, preventing timer from expiring whilst set
 */
void clsTimer::suspend() {
  m_blnSuspend = true;
}
/**
 * Removes timer object from linked list
 */ 
void clsTimer::remove() {
// Disable interrupts whilst we play with the linked list
  cli();
  if ( (clsTimer*)pAllTimers == this ) {
// This node at the head of the linked list, skip over it!
    pAllTimers = this->next();
  } else {
    for( clsTimer* pTimer=(clsTimer*)pAllTimers; pTimer!=NULL; 
         pTimer=pTimer->next() ) {
      if ( pTimer->next() == this ) {
// The next node is the one to be removed, patch up skipping over this!
        pTimer->m_pNext = this->next();
        break;       
      }
    }       
  }  
// Enable interrupts
  sei();
}
/**
 * Clears suspend flag, allowing timer to expire
 */
void clsTimer::resume() {
  m_blnSuspend = false;
}

Test code.

/**
 * File:
 *  tmrTest.c
 */
#include <clsTimer.h> 

bool aryState[] = { false, false, false };
int aryPins[] = { 13, 12, 11, 0 };

void toggleLed(clsTimer* pTmr) {
  int* pintPin = (int*)pTmr->getData();
  int intIdx = pintPin - aryPins;

  if ( aryState[intIdx] == false ) {
    digitalWrite(*pintPin, HIGH);  
    aryState[intIdx] = true;
  } else {
    digitalWrite(*pintPin, LOW);      
    aryState[intIdx] = false;
  }
}
clsTimer* aryLEDs[] = {
  new clsTimer(  750, toggleLed, &aryPins[0], 750 ),
  new clsTimer( 1500, toggleLed, &aryPins[1], 1500 ),
  new clsTimer( 2250, toggleLed, &aryPins[2], 2250 ) };                   

void setup() {
  for( int i=0; aryPins[i]!=0; i++ ) {
    pinMode(aryPins[i], OUTPUT);
  }
}
void loop() {
  clsTimer::serviceTimers();  
}

Added reset method.

Prototype, clsTimer.h

/**
 * File:
 *  clsTimer.h
 *  
 * Notes:
 *  This file contains the prototype for the class clsTimer.
 *  
 * Methods:
 *  clsTimer      Class constructor
 *  ~clsTimer     Class destructor
 *  expire        Flags that a timer should be serviced 
 *  expired       Called when timer expires
 *  getCountDown  Returns the count down counter value 
 *  getData       Acccess method allows access to timer data block  
 *  interval      Returns the interval either m_intInitial or m_intRepeat 
 *  next          Returns pointer to next timer in linked list
 *  setRepeat     Sets the repeat timer interval 
 *  suspend       Sets suspend flag, preventing timer from expiring whilst set
 *  remove        Removes timer object from linked list
 *  reset         Resets the internal counter, starting the counter again       
 *  resume        Clears suspend flag, allowing timer to expire
 * 
 * Members:
 *  m_blnCalled   Flag indicates if timer has been called
 *  m_blnExpire   Flag indicates that timer has expired 
 *  m_blnSuspend  Flag indicates if activity has been suspended  
 *  m_uintCntDown Count down counter 
 *  m_uintInitial The initial time(ms) before the timer expires
 *  m_uintRepeat  Optional, repeat time(ms) before the expiration, 0 = not used
 *  m_pCallback   Call-back routine to call when timer expires
 *  m_pData       Data to associate with this instance
 *  m_pNext       Pointer to next node in linked list   
 *         
 * History:
 *  22/01/2012  Created by Simon Platten
 *  25/01/2012  Added suspend, resume and remove methods
 *  27/01/2012  Added reset method  
 */            
#ifndef TIMER_MANAGER_H
  #define TIMER_MANAGER_H
  
  #include <Arduino.h>
  
  class clsTimer {
  private:  
// Flag to indicate if timer has been called
    bool m_blnCalled;
// true if timer ready to be service, false it not    
    bool m_blnExpire;
// Flag to suspend timer    
    bool m_blnSuspend;
// Count down counter    
    unsigned m_uintCntDown;      
// The initial time(ms) before the timer expires  
    unsigned m_uintInitial;
// Optional, repeat time(ms) before the expiration, 0 = not used
    unsigned m_uintRepeat;    
// Call-back routine to call when timer expires
    void (*m_pCallback)(clsTimer* pTimer);
// Data to associate with this instance
    void* m_pData;
// Next timer in Linked-list
    clsTimer* m_pNext;
// Called to expire timer object    
    void      expire();
// Returns the interval either m_uintInitial or m_uintRepeat    
    unsigned  interval();
// Returns next timer in linked list
    clsTimer* next();    

  public:
    static void     checkTimers();
    static void     serviceTimers();
          
  public:
    clsTimer(unsigned uintInitial,               
             void (*pCallback)(clsTimer* pTimer), 
             void *pData = NULL,
             unsigned uintRepeat = 0);
    ~clsTimer();
// Returns count down value    
    unsigned  getCountDown();
// Returns any data configured for this timer    
    void*     getData();
// Sets the repeat timer interval    
    void      setRepeat(unsigned uintRepeat);
// Sets suspend flag, preventing timer from expiring whilst set
    void      suspend();
// Removes timer object from linked list
    void      remove();
// Resets the internal counter, starting the counter again
    void      reset();
// Clears suspend flag, allowing timer to expire            
    void      resume();                
  };
#endif

Implementation, clsTimer.cpp

/**
 * File:
 *  clsTimer.cpp
 *  
 * Notes:
 *  This file contains the implementation of the class clsTimer.
 *
 * History:
 *  22/01/2012  Created by Simon Platten
 *  25/01/2012  Added suspend, resume and remove methods
 *  27/01/2012  Added reset method  
 */  
#include "clsTimer.h"

// Pointer to all timers linked list
static volatile clsTimer* pAllTimers = NULL;
/**
 * Interupt Service Routine, called every 1ms to check all timers
 */ 
ISR(TIMER2_OVF_vect) {
  TCNT2 = 0x06;
// Increment the counter and check timer resources
  clsTimer::checkTimers();
};
/**
 * Timer constructor
 *  
 * Paramters:
 *  uintInitial,the initial expiration time in ms
 *  pCallback,  the routine to call when the timer expires
 *  pData,      pointer to data block accessible for this timer  
 *  uintRepeat, optional, repeat time in ms for subequent calls
 */         
clsTimer::clsTimer(unsigned uintInitial, 
                   void (*pCallback)(clsTimer* pTimer), 
                   void *pData,
                   unsigned uintRepeat) {
  if ( uintInitial > 0 && pCallback != NULL ) {
// Disable interrupts    
    cli();
    
    if ( pAllTimers == NULL ) {
// Set-up interrupt      
      TCNT2 = 0x00;
      OCR2A = 250;		        // 1 ms @ Fosc = 16 MHz
      TCCR2A = 0x02;          // WGM: No wave generation
      TCCR2B = 0x04;          // START Timer, prescaler = 64
      TIMSK2 = (1 << TOIE2);  // Enable interrupt when Timer reaches OCRA
    }
// Configure this timer    
    m_uintCntDown = m_uintInitial = uintInitial;    
    m_pCallback = pCallback;
    m_pData = pData;
    m_uintRepeat = ( uintRepeat > 0 ) ? uintRepeat : 0;    
    m_blnCalled = m_blnExpire = m_blnSuspend = false;
    
    if ( pAllTimers != NULL ) {
// Insert this timer into the list of all timers
      m_pNext = (clsTimer*)pAllTimers;              
    } else {
// This is the first node in the list, ensure that there is no Next node    
      m_pNext = NULL; 
    }
// Insert this timer at the beginning of the linked list    
    pAllTimers = this;
// Re-enable interrupts
    sei();    
  }
}
/**
 * Destructor
 */ 
clsTimer::~clsTimer() {
// Remove this node
  remove();
}
/**
 * Checks all timers to see if any have expired
 */ 
void clsTimer::checkTimers() {
// Check all timers
  for( clsTimer* pTimer=(clsTimer*)pAllTimers; pTimer!=NULL; 
       pTimer=pTimer->next() ) {      
    if ( pTimer->m_blnSuspend == true ) {
// This timer is currently suspended, skip over it!    
      continue;
    }
    if ( pTimer->m_uintCntDown > 0 ) {
// Counter still greater than 0, decrement    
      pTimer->m_uintCntDown--;
    } else {
// Expire this timer, this will flag that its ready to be serviced
      pTimer->expire();      
    }
  }
}
/**
 * Sets flag to indicate that timer is ready to be serviced
 */        
void clsTimer::expire() {
  m_blnExpire = true;
}
/**
 * Returns the count down counter value
 */ 
unsigned clsTimer::getCountDown() {
  return m_uintCntDown;
} 
/**
 * Allows access to any data configured for this timer
 */ 
void* clsTimer::getData() {
  return m_pData;
}
/**
 * Gets the current timer interval
 */         
unsigned clsTimer::interval() {
  unsigned uintInterval;
  if ( m_blnCalled == false ) {
    uintInterval = m_uintInitial;
  } else {
    uintInterval = m_uintRepeat;
  }
  return uintInterval;
}
/**
 * Gets the next timer object in the linked list
 */        
clsTimer* clsTimer::next() {
  return m_pNext;
}
/**
 * Should be called in main loop to service any expired timers
 */  
void clsTimer::serviceTimers() {
  for( clsTimer* pTimer=(clsTimer*)pAllTimers; pTimer!=NULL; 
       pTimer=pTimer->next() ) {
    if ( pTimer->m_blnExpire == false ) {    
      continue;
    }    
// Timer has expired, clear flag
    pTimer->m_blnExpire = false;    
// Set initial call flag so we know the next call will be using the repeat time
    pTimer->m_blnCalled = true;
// Perform call-back  
    (*pTimer->m_pCallback)(pTimer);
    
    if ( pTimer->m_uintRepeat > 0 ) {
      pTimer->m_uintCntDown = pTimer->m_uintRepeat;
    } else { 
// This timer is a one-shot timer, remove it!
      delete pTimer;
    }      
  }
}
/**
 * Sets the repeat timer interval
 */     
void clsTimer::setRepeat(unsigned uintRepeat) {
// Set repeat counter and reset count down counter
  m_uintCntDown = m_uintRepeat = uintRepeat;
}
/**
 * Sets suspend flag, preventing timer from expiring whilst set
 */
void clsTimer::suspend() {
  m_blnSuspend = true;
}
/**
 * Removes timer object from linked list
 */ 
void clsTimer::remove() {
// Disable interrupts whilst we play with the linked list
  cli();
  
  if ( (clsTimer*)pAllTimers == this ) {
// This node at the head of the linked list, skip over it!
    pAllTimers = this->next();
  } else {
    for( clsTimer* pTimer=(clsTimer*)pAllTimers; pTimer!=NULL; 
         pTimer=pTimer->next() ) {
      if ( pTimer->next() == this ) {
// The next node is the one to be removed, patch up skipping over this!
        pTimer->m_pNext = this->next();
        break;       
      }
    }       
  }  
// Enable interrupts
  sei();
}
/**
 * Resets the internal counter, starting the counter again 
 */
void clsTimer::reset() {
  m_uintCntDown = interval();
} 
/**
 * Clears suspend flag, allowing timer to expire
 */
void clsTimer::resume() {
  m_blnSuspend = false;
}

Download: http://avasig.com/clsTimer.zip

Very interesting. I'll throw in my 2 cents worth of comments/questions:

  • It seems to me this library interferes with millis() since it uses cli() sei(), which affect also timer0. Am I right ?
  • Perhaps I just need to re-read the comments, but I didn't find the resolution (1ms) mentioned.
  • Is there a risk of spending too much time inside the ISR, if one creates too many timers ? (TBH if one has many timers then probably one already has too many of them :) )
  • I don't know if it would make sense, but it seems not very easy to change the time resolution.

Finally, I think not keeping comments indented with code creates a bit of visual clutter, but that's just a matter of taste, of course.

:-)

The timer resolution is 1ms. Which means you cannot create a timer > 1ms, I don't believe the library will effect the ms timing at all, the interrupt is very tight and just sets a flag to indicate the timer has expired. cli() and sei() are called so that timers can be set-up and modified without the interrupt trying to access the linked list. If you hook up a scope you will see they the disable and enable takes microseconds. Call-backs are called outside of the interrupt by calling the service routine, so all the ISR does it adjust counters and set flags, nothing really.

I could increase the resolution of the timer, just waiting for feedback. 1ms is plenty for my purposes.

In terms of not indenting comments. I prefer to align them to the left, this makes reading them easier as they are always in the same position. I don't like inline comments at the end of the code and try to keep the code column width to 80 characters so the reader doesn't have to keep scrolling left and right to read the code.

Thank you for the feedback.

I re-read your code carefully. I didn't notice the ISR just sets the expire flag! Callbacks are called inside loop(), via a call to setviceTimers(), which is mandatory.

My comment about setting the resolution was about the reduction of cpu cycles spent in managing timers if one just needs e.g. 1 second or 100 ms resolution. But it seems the "weight" of the library is very low, so 1ms is probably the right choice. Higher resolutions probably mean the timing requirements are so strict one needs to operate right inside the ISR...

About comments: as I said, it's a matter of taste :-)

Thank you for sharing this code. I think it ought to be mentioned here http://arduino.cc/playground/Main/LibraryList#Timing

Thank you, all feedback welcome. I'm not really sure how to go about editing the Wiki pages.

Uploaded another modification, added static debug method to enable serial output message when timer expires.

I noticed a strange thing… I wrote a simple sketch where the timer callback just prints the current millis() value. The output is always greater than 1000, although the timer is set to repeat every 1000 ms… ?-|

Serial monitor output:

elapsed=1000
elapsed=2001
elapsed=3002
elapsed=4003
elapsed=5004
elapsed=6005
elapsed=7006
elapsed=8007
elapsed=9008

Sketch code:

#include <clsTimer.h>

void timerCallback(clsTimer* tmr) {
    unsigned long now = millis();
    Serial.print("elapsed=");
    Serial.println(now, DEC);
}

clsTimer timer1(1000, timerCallback, NULL, 1000);

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

void loop() {
    clsTimer::serviceTimers();
}

Thanks for any hints…

The class uses an interrupt to increment the individual timer counter which is an unsigned integer. Your timer call-back is called when the static 'serviceTimers' routine is called, this scans all the timer objects for any with the 'expire' flag set. The call-back is then called for that timer.

From your code it looks like 1ms has elapsed from the time the interrupt occurs to the time the call-back is actually called. This doesn't look like a problem to me, also bare in mind that you are calling:

millis() to get a reference, then making two calls to serial.print to send data to the serial port which will also take time.

Whenever you call a function the parameters are pushed onto the stack and then popped off the stack before returning.

If you want to be sure attach a scope and write to an I/O pin from your call-back. The 1ms drift could be due to calling millis().

Your timer interval is set to 1000ms with a repeat of 1000ms, so it looks like the time to get to the Serial.print routine is exactly 1001ms each time. As you aren't storing any reference to substract from millis() the value returned is increasing by exactly 1001ms.

On an 8bit micro such as the Arduino, this seems perfectly exceptable to me.

SPlatten: The class uses an interrupt to increment the individual timer counter which is an unsigned integer. Your timer call-back is called when the static 'serviceTimers' routine is called, this scans all the timer objects for any with the 'expire' flag set. The call-back is then called for that timer.

The code path is clear. Good.

SPlatten: From your code it looks like 1ms has elapsed from the time the interrupt occurs to the time the call-back is actually called. This doesn't look like a problem to me, also bare in mind that you are calling:

The fact that 1ms elapses from the time the interrupt occurs to the time millis() is called is definitely not a problem. What puzzles me is why this 1ms delay adds up with each callback call...

SPlatten: millis() to get a reference, then making two calls to serial.print to send data to the serial port which will also take time.

I tried to avoid Serial.print() delay by storing millis() value into a local variable as soon as I enter the callback routine and then passing that variable to Serial.print().

SPlatten: Whenever you call a function the parameters are pushed onto the stack and then popped off the stack before returning.

If you want to be sure attach a scope and write to an I/O pin from your call-back. The 1ms drift could be due to calling millis().

I thought about the scope but unfortunately I don't have one at hand (I'll have to wait 'til I get back to work -- being ill atm).

SPlatten: Your timer interval is set to 1000ms with a repeat of 1000ms, so it looks like the time to get to the Serial.print routine is exactly 1001ms each time. As you aren't storing any reference to substract from millis() the value returned is increasing by exactly 1001ms.

On an 8bit micro such as the Arduino, this seems perfectly exceptable to me.

Thanks for your suggestions. Will do some more tests and report back. I'm just trying to get my head around this issue to better understand how things work... :)

Modified class removing debug flag and replacing with DEBUG_TIMER definition and condition compilation check.

I think I got it. The "problem" is that each timer is paused, its callback called, then resumed, and its counter is set to the repeat value after the callback execution. The time that passes between the moments when the timer is paused and resumed (and its counter reset) has a fixed amount due to the library's mechanics, and a variable contribution given by the callback function. I modified the library so that repeating timers (i.e. timers that hava repeatCount > 0) are not suspended, and their counter is reset right after setting the expire flag (I can clean up the code and post a diff here, if you are interested). This causes the counter to decrement even during the callback execution. The call to the callback function happens therefore always after the same amount of time (provided that function doesn't last "too much", of course).

I think all this boils down to a design decision, and I don't think there's a "right" or "wrong" way of doing this sort of thing. Timing requirements of the project at hand also make a great difference in how one treats timed function calls.

Ok, glad your using it.