A C++ template library for embedded applications
MIT licensed
Designed and
maintained by
John Wellbelove
Like the ETL? Become a patron!
or
Join the ETL community

Callback Timer for C

A software timer framework for C that can manage up to 254 timers. Each one may be repeating or single shot.
When a timer triggers it will call the selected callback function.
The timers are driven from a call to ecl_timer_tick(uint32_t count). This call would normally be made from a high
priority interrupt routine. The destination function will receive the callback in the same context as the tick call.
The call to tick has a low overhead when a timer is not 'due'. Internally the timers are stored in 'first timeout' order so
only the head of the list needs to be checked.

Each timer may have a period of up to 232-2 ticks (4,294,967,294).
At 1ms per tick this would equate to just over 49 days.

Defines the following structure:-
ecl_timer_config

Uses definitions from ecl_timer.h

An example Keil project for the Nucleo 401RE may be found in examples\ArmTimerCallbacks - C

Requirements

The framework relies on the availability of either an atomic counter type or an interrupt disable/enable mechanism.
The user must supply a header named ecl_user.h in the header search path.

In this header, the user must define the following:-

ECL_TIMER_DISABLE_PROCESSING
ECL_TIMER_ENABLE_PROCESSING
ECL_TIMER_PROCESSING_ENABLED

The user may choose one of two methods.

Atomic lock

ECL_TIMER_DISABLE_PROCESSING Atomically increments the semaphore.
ECL_TIMER_ENABLE_PROCESSING  Atomically decrements semaphore.
ECL_TIMER_PROCESSING_ENABLED Atomically reads semaphore and compares to zero.

For the GCC compiler, the file could contain the following:-
extern uint32_t timer_semaphore;

#define ECL_TIMER_DISABLE_PROCESSING  __sync_fetch_and_add(&timer_semaphore, 1)
#define ECL_TIMER_ENABLE_PROCESSING   __sync_fetch_and_sub(&timer_semaphore, 1)
#define ECL_TIMER_PROCESSING_ENABLED  (__sync_fetch_and_add(&timer_semaphore, 0) == 0)

With this mechanism, calls to tick are never disabled. If the foreground thread is within a disable/enable section when
the timer interrupt/thread is activated then the tick update will be deferred until the next tick period. The timer
interrupt/thread may interrogate the return value of ecl_timer_tick() to check whether the update was deferred.

Important:
The atomic or interrupt enable/disable actions must invoke a full memory barrier.

Interrupt lock

ECL_TIMER_DISABLE_PROCESSING Disables the timer interrupts.
ECL_TIMER_ENABLE_PROCESSING  Enables the timer interrupts.
ECL_TIMER_PROCESSING_ENABLED Set to true.

The user should ensure that mechanisms, such as memory barriers are used to disable re-ordering of the
instructions.
If the foreground task is within a disable/enable section when the timer interrupt is triggered then the tick update will
be deferred until the interrupts are re-enabled. Depending on the resolution of the timers, the interrupt routine may
be able to compensate for the delay by passing a modified tick count to ecl_timer_tick().

Functions

void ecl_timer_init(struct ecl_timer_config* ptimers, uint_least8_t max_timers)
Initialises the timer framework.
ptimers    A pointer to the array of timer config structures.
max_timers The maximum number of timers that can be contained in the array.

ecl_timer_id_t ecl_timer_register(void             (*p_callback)(),
                                  ecl_timer_time_t period,                                       
                                  ecl_timer_mode_t repeating)
Registers a timer calling a free or static function.
p_callback A pointer to the callback free funtion that will be callled when the timer expires.
period     The timer period in ticks.
repeating  ECL_TIMER_SINGLE_SHOT if single shot, ECL_TIMER_REPEATING if repeating.

Returns the allocated timer id or ECL_TIMER_NO_TIMER if one was not available.

ecl_timer_result_t ecl_timer_unregister(ecl_timer_id_t id)
Unregisters a timer.
If the timer is active then it will be stopped.
Returns ECL_TIMER_PASS if a timer with the id was successfully unregistered, otherwise ECL_TIMER_FAIL.

void ecl_timer_enable(ecl_timer_enable_t state)
Enables or disables the timer manager according to the state.
ECL_TIMER_ENABLED to enable, ECL_TIMER_DISABLED to disable.

ecl_timer_result_t ecl_timer_is_running()
Returns ECL_TIMER_ENABLED if the timer manager is enabled, otherwise ECL_TIMER_DISABLED.

void clear()
Clears the callback timer back to the initial state. All timers will be stopped and unregistered.

ecl_timer_result_t ecl_timer_tick(uint32_t count)
This function updates the internal tick counter (if enabled) and must pass the number of ticks that have occurred since
the last call. If the count encompasses more than one period of a repeating timer then the timer will be triggered
multiple times in one call to tick.
Returns ECL_TIMER_PASS if the tick counter was updated, otherwise ECL_TIMER_FAIL. This may be used by the calling
routine to accumulate unprocessed tick counts.

ecl_timer_result_t ecl_timer_start(ecl_timer_id_t id, ecl_timer_start_t immediate)
Starts the timer with the specified id.
If the timer is already running then the timer Is restarted from the current tick count.
If immediate is ECL_TIMER_START_IMMEDIATE then the timer is triggered on the next call to ecl_timer_tick().
ECL_TIMER_START_DELAYED will cause the timer to be triggered after the configured delay.
Note: Single shot timers will only trigger once.
If the id does not correspond to a registered timer then returns ECL_TIMER_FAIL.

ecl_timer_result_t ecl_timer_stop(ecl_timer_id_t id)
Stops the timer with the specified id.
Does nothing if the timer is already stopped.
if the id does not correspond to a registered timer then returns ECL_TIMER_FAIL.

ecl_timer_result_t ecl_timer_set_period(ecl_timer_id_t id, ecl_timer_time_t period)
Sets a new timer period.
Restarts the timer.
Returns ECL_TIMER_PASS if successful, otherwise ECL_TIMER_FAIL.

ecl_timer_result_t ecl_timer_set_mode(ecl_timer_id_t id, ecl_timer_mode_t repeating)
Sets a new timer mode.
Restarts the timer.
Returns ECL_TIMER_PASS if successful, otherwise ECL_TIMER_FAIL.

Example


#include "ecl_timer.h"

int count1 = 0;

void callback1()
{
  ++count1;
}

int count2 = 0;

void callback2()
{
  ++count2;
}

int count3 = 0;

void callback3()
{
  ++count3;
}

#define NTIMERS 3
struct ecl_timer_config[NTIMERS];

//***************************************************************************
// The main loop.
//***************************************************************************
int main()
{
  ecl_timer_init(timers, NTIMERS);

  ecl_timer_id_t id1 = ecl_timer_register(callback1,
                                          1000,
                                          ECL_TIMER_SINGLE_SHOT);

  ecl_timer_id_t id2 = ecl_timer_register(callback2,
                                          100,
                                          ECL_TIMER_REPEATING);

  ecl_timer_id_t id3 = ecl_timer_register(callback3,
                                          10,
                                          ECL_TIMER_REPEATING);

  ecl_timer_start(id1, ECL_TIMER_START_DELAYED);
  ecl_timer_start(id2, ECL_TIMER_START_DELAYED);
  ecl_timer_start(id3, ECL_TIMER_START_DELAYED);

  ecl_timer_enable(ECL_TIMER_ENABLED);

  // Start timer interrupts here.

  while (1)
  {
    // Loop forever.
  }

  return 0;
}

//***************************************************************************
// The interrupt timer callback.
//***************************************************************************
void timer_interrupt(void)
{
  const uint32_t TICK = 1;
  static uint32_t nticks = TICK;

  if (ecl_timer_tick(nticks))
  {
    nticks = TICK;
  }
  else
  {
    nticks += TICK;
  }
}
ecl_timer.h