Polled timers

Polled timers can be used to measure elapsed time intervals or to check for timeouts within code loops. Traditionally one might do this as follows:

const unsigned TIMEOUT_MS = 100;

unsigned start = millis();
while(millis() - start < TIMEOUT_MS) {
   // do some stuff
}
unsigned elapsed = millis() - start;
Serial.print("Elapsed time: ");
Serial.print(elapsed);
Serial.println("ms");

Note

A common source of bugs when hand-coding such loops can be illustrated by this alternative:

unsigned timeout = millis() + TIMEOUT_MS;
while(millis() < timeout) {
   // do some stuff
}

At first glance this looks better than the first approach as we don’t have a subtraction within the loop. However, when millis() exceeds MAXINT - TIMEOUT_MS the timeout calculation will wrap and the loop will never get executed. This takes a little under 25 days, but with microseconds it’ll happen in less than an hour. This may not be the desired behaviour.

It’s generally safer and easier to use a PolledTimer:

OneShotFastMs timer(TIMEOUT_MS);
while(!timer.expired()) {
   // do some stuff
}
auto elapsed = timer.elapsedTime(); // Returns a `NanoTime::Time` object, value with time units
Serial.print("Elapsed time: ");
Serial.println(elapsed.toString()); // Includes units in the elapsed time interval
// Show time rounded to nearest whole seconds
Serial.println(elapsed.as<NanoTime::Seconds>().toString());

If you prefer to use microseconds, use OneShotFastUs instead or specify the units directly:

OneShotElapseTimer<NanoTime::Microseconds> timer;

Another advantage of polled timers is speed. Every call to millis (or micros) requires a calculation from clock ticks into milliseconds (or microseconds). It’s also a function call.

Polled timers measure time using hardware clock ticks, and query the hardware timer register directly without any function calls or calculations. This makes them a much better choice for tight timing loops.

Here’s the output from the BenchmarkPolledTimer module:

How many loop iterations can we achieve in 100 ms ?
Using millis(), managed 55984 iterations, average loop time = 1786ns (143 CPU cycles)
Using micros(), managed 145441 iterations, average loop time = 688ns (55 CPU cycles)
Using PolledTimer, managed 266653 iterations, average loop time = 375ns (30 CPU cycles)

API Documentation

group polled_timer

Polled interval timers.

Unnamed Group

using OneShotElapseTimer = PolledTimer::OneShot<Timer2Clock, unit>
using PeriodicElapseTimer = PolledTimer::Periodic<Timer2Clock, unit>
using OneShotFastMs = OneShotElapseTimer<NanoTime::Milliseconds>
using OneShotFastUs = OneShotElapseTimer<NanoTime::Microseconds>
using ElapseTimer = OneShotFastUs
using OneShotCpuCycleTimer = PolledTimer::OneShot<CpuCycleClockNormal, units>
using PeriodicCpuCycleTimer = PolledTimer::Periodic<CpuCycleClockNormal, units>
using OneShotCpuCycleTimerFast = PolledTimer::OneShot<CpuCycleClockFast, units>
using PeriodicCpuCycleTimerFast = PolledTimer::Periodic<CpuCycleClockFast, units>
using CpuCycleTimer = OneShotCpuCycleTimer<NanoTime::Nanoseconds>
using CpuCycleTimerFast = OneShotCpuCycleTimerFast<NanoTime::Nanoseconds>
typedef OneShotFastMs oneShotFastMs
typedef OneShotFastUs oneShotFastUs

Defines

POLLED_TIMER_MARGIN_US

Timer intervals are limited to the maximum clock time, minus this safety margin.

Note
Specified in microseconds, this is the minimum timer poll interval to ensure no missed polls across the full timer range. Larger margin means smaller time range.

namespace PolledTimer

Typedefs

using PolledTimer::OneShot = typedef Timer<Clock, unit, false, uint32_t>
using PolledTimer::Periodic = typedef Timer<Clock, unit, true, uint32_t>
template <class Clock, NanoTime::Unit unit_, bool IsPeriodic, typename TimeType>
class Timer : public NanoTime::TimeSource<Clock, unit_, TimeType>
#include <PolledTimer.h>

Template class to implement a polled timer.

If the interval is set to 0, the timer will expire immediately, and remain so:

canWait() will return false. Call cancel() to prevent the timer from ever expiring: canExpire() will return false. Call start() to set the timer going with the previously set interval. Call reset() to set the timer with a new interval.
Note
Intervals and expiry use ‘tick’ values which are very efficient, whereas ‘time’ values must be computed so are very slow (~100+ cycles for uint32_t, considerably more for uint64_t). The class keeps a note of a ‘start’ time (in ticks) used to determine the elapsed() return value. An ‘interval’ value may be given is specified, then the expired() method returns true.
Template Parameters
  • Clock: Clock source
  • unit: Time unit for tick/time conversions
  • IsPeriodic:
  • TimeType: Variable type to use (uint32_t or uint64_t)

A periodic timer will automatically restart when expired. A one-shot timer will remain expired until reset.

Public Types

template<>
using TickType = typename Clock::TickType
template<>
using Margin = NanoTime::TimeConst<Clock, NanoTime::Microseconds, POLLED_TIMER_MARGIN_US>
template<>
using Clock = Clock
template<>
using TimeType = TimeType
template<>
using TicksPerUnit = typename Clock::template TicksPerUnit<unit_>
template<>
using TimeConst = TimeConst<Clock, unit_, time>
template<>
using TicksConst = TicksConst<Clock, ticks>
template<>
using MaxClockTime = typename Clock::template MaxTime<unit_>

Get the time corresponding to the maximum clock tick value.

template<>
using TimeSource = TimeSource<Clock, unit, TimeType>
template<>
using Ticks = Ticks<Clock, T>
template<>
using MaxTicks = TicksConst<maxTicks_>
template<>
using MaxTime = typename MaxTicks::template TimeConst<unit>

Public Functions

Timer(const TimeType &timeInterval = 0)

Create a Timer with optional expiry time.

Parameters
  • timeInterval: Relative time until expiry

void start()

Start the timer.

template <uint64_t timeInterval>
void reset()

Start the timer with a new expiry interval.

Template Parameters
  • timeInterval: Time to expire after last call to start()

template <uint64_t timeInterval>
constexpr uint32_t checkTime()

Check the given time interval is valid and return the corresponding tick count.

Note
If time interval is invalid fails compilation
Template Parameters
  • timeInterval:
Return Value
  • uint32_t:

bool reset(const TimeType &timeInterval)

Start the timer with a new expiry interval.

See
See resetTicks()
Parameters
  • timeInterval: Time to expire after last call to start()
Return Value
  • bool: true on success, false on failure

bool resetTicks(const TimeType &interval)

Start the timer with a new expiry interval.

Note
If time interval is 0, timer will expire immediately, and if it exceeds the maximum interval the timer will never expire.
Parameters
  • interval: Clock ticks to expire after last call to start()
Return Value
  • bool: true on success, false if interval is out of range

void cancel()

Cancelling a timer means it will never expire.

TickType elapsedTicks() const

Get elapsed ticks since start() was last called.

NanoTime::Time<TimeType> elapsedTime() const

Get elapsed time since start() was last called.

bool canExpire() const
bool canWait() const
bool expired()

Determine if timer has expired.

Return Value
  • bool:

Public Static Functions

static constexpr NanoTime::Unit unit()
static constexpr TickType maxInterval()
static constexpr Margin margin()
static constexpr BasicRatio32 ticksPerUnit()

Number of clock ticks per unit of time.

Return Value
  • BasicRatio32: Result as a rational fraction

static Ratio32 ticksPerUnit(Unit unit)

Get ticks per unit as a Ratio object.

Return Value
  • BasicRatio32:

static constexpr MaxClockTime maxClockTime()
static constexpr TimeConst<time> timeConst()

Obtain a TimeConst type representing the given time quantity.

Note
Use methods of TimeConst to obtain corresponding tick count, etc.
Template Parameters
  • time:
Return Value
  • TimeConst:

static constexpr TicksConst<ticks> ticksConst()

Class template defining a fixed tick quantity.

Note
Use methods of TickConst to obtain corresponding time values, etc.
Template Parameters
  • ticks:
Return Value
  • TicksConst:

static constexpr Time<TimeType> maxCalcTime()

The maximum time value supported by timeToTicks without overflowing.

Return Value
  • TimeType: Passing values larger than this to timeToTicks() will truncate at maximum value

static constexpr Ticks<Clock, TimeType> maxCalcTicks()

The maximum tick value supported by ticksToTime without overflowing.

Return Value
  • TimeType: Passing values larger than this to ticksToTime() will truncate at maximum value

static Ticks<Clock, TimeType> timeToTicks(TimeType time)

Get the number of ticks for a given time.

Parameters
  • time:
Return Value
  • TimeType: Tick count, rounded to the nearest tick

static constexpr uint64_t timeToTicks()

Get the number of ticks for a given time.

Template Parameters
  • time:
Return Value
  • uint64_t: Tick count, rounded to the nearest tick

static constexpr uint64_t ticksToTime()

Get the time for a given number of clock ticks.

Template Parameters
  • ticks:
Return Value
  • TimeType: Time count, rounded to the nearest unit

static Time<TimeType> ticksToTime(TimeType ticks)

Get the time for a given number of clock ticks.

Parameters
  • ticks:
Return Value
  • TimeType: Time count, rounded to the nearest unit

static String toString()
static constexpr const char *typeName()
static constexpr uint32_t frequency()
static constexpr MaxTicks maxTicks()
template <Unit unit>
static constexpr MaxTime<unit> maxTime()
template <Unit unit, typename TimeType>
static constexpr TimeSource<unit, TimeType> timeSource()

Create a Time Source for this Clock.

Template Parameters
  • unit:
  • TimeType: