You may delay, but time will not. Benjamin Franklin
The only reason for time is so that everything doesn't happen at once. Albert Einstein
All we have to decide is what to do with the time that is given us. J.R.R. Tolkien
I could not have put it any better than these great people!
Managing time in our sketches is something that we need to do frequently and by using timers wisely we are able to design, manage and process time driven events efficiently and effectively.
This article offers a technique (method) for using any number of timers in your sketches each of which is independent, non-blocking and such that they can all run concurrently. The technique is easy to understand, easily implemented, does not use the delay() function nor require the end user implementation of a timer interrupt service routine (ISR).
Why are Timers Useful?There are often occasions when we need to do something at regular times, for example read a sensor every 1/100 seconds, track and process timeouts, process a heart beat every 1/2 second, and so on. In such circumstances we need to use one or more timers to ensure we get around to do all we need to do. This is where the technique offered by this article (ez_timers) can be very helpful.
In this article a sketch is presented that defines all of the data and functions you will need to implement the technique offered by ez_timers into your sketches and on which you can base your sketch designs, decision control and flow.
So that the simplicity and power of the technique can be demonstrated, an example is provided based on a number of LEDs (eight) which are each controlled by their own timers to turn them on and off at different intervals. As will be seen, the technique is implemented in a straight forward and succinct way. It is easily extendable to many, many uses.
ez_timers DesignBefore we crack on and look at the example sketch, let's look at what the ez_timers technique and code comprises.
There are two parts to ez_timers:
1. its data structures and data requirements, and
2. a number of end user accessible simple functions.
Let's look at each of these:
1. Data Structures and Data Requirements
#define max_timers 8
#define elapsed true
#define not_elapsed !elapsed
#define active true
#define not_active !active // double defined for user preference
#define inactive not_active // double defined for user preference
uint8_t timer; // general variable for use dealing with timers
// timer control struct(ure)
struct timer_control {
bool timer_status; // records status of a timer - active or not active
uint32_t start_time;// records the millis time when a timer is started
} timers[max_timers];// declare an entry for each timer - 0:(max_timers-1)
The first thing we notice is the macro 'max_timers
'. In the 'out of the box' (OOTB) sketch this is set to 8, so the sketch will configure itself for 8 timers at compile time. These will then be referenceable from 0 to 7. Use this macro to define how many timers you wish to configure in your sketches.
We next see a number of macros that are used by the ez_timer functions and which can/should also be used by end user code to test the status of specific conditions. At this point, though, I only observe that two of these macros may be used identically, depending on end user preference. These are 'not_active
' and 'inactive
' - they are the same thing. More about the context of use of these macros below when we discus the ez_functions.
The next declaration is simply an unsigned byte, timer
, which can be used throughout your sketch as a working variable to reference any of the timers (up to 255), for example, in a for-loop, etc.
At the heart of ez_timers is the data struct(ure) 'timer_control
'. This defines two data items/types, these being 'timer_status
' (bool
) and 'start_time
' (uint32_t
- unsigned 32 bit integer). Each configured timer has its own entry in this structure from 0 to ('max_timers
' -1). The 'timer_control
' data type struct(ure) is then declared as 'timers
'. This data struct(ure) is referenced using 'timers
' for example 'timers[2].timer_status
', 'timers[timer].start_time
', etc.
2. ez_timerFunctions
There are just three ez_timer functions:
//
// set the given timer active and note start time in milliseconds
//
void start_timer(uint8_t timer) {
if (timer < max_timers) {
// valid timer
timers[timer].timer_status = active; // mark this timer as active
timers[timer].start_time = millis(); // record time this timer is started
}
}
//
// cancel (stop) the given timer
//
void stop_timer(uint8_t timer) {
if (timer < max_timers) {
// valid timer
timers[timer].timer_status = inactive; // mark this timer as inactive
}
}
//
// Function determines if the time has elapsed for given timer, if active.
// The elapsed_time parameter is in milliseconds.
//
bool timer_elapsed(uint8_t timer, uint32_t elapsed_time) {
if (timer < max_timers) {
// valid timer
if (timers[timer].timer_status == active) {
// timer is active so check elapsed time
if (millis() - timers[timer].start_time >= elapsed_time) {
// this timer has elapsed
timers[timer].timer_status = inactive; // mark this timer no longer active
return elapsed;
}
}
}
return not_elapsed;
}
Let's look a these functions in a little more detail:
1. 'start_timer
' - sets the given timer as active and records the start time in milliseconds. The only parameter is the timer number to be started: 0 to ('max_timers
'-1).
2. 'stop_timer
' - marks the given timer as inactive/not active. The only parameter is the timer number to be stopped: 0 to ('max_timers
'-1).
3. 'timer_elapsed
' - this function should be called as regularly as possible to check if a given timer has elapsed and, if so, what actions should then be followed. The function returns one of two values - 'elasped
' if the timer has completed or 'not_elapsed
' otherwise. Note that this function will also return a value of 'not_elapsed
' (which is the same value as 'not_active
') if the timer is not active.
The function has two parameters, the first is the timer number to be checked: 0 to ('max_timers
'-1), and the second the elapsed time in milliseconds to be checked against. For example:
if (timer_elasped(5, 10000) == elapsed){...} else{...}
checks timer 5 to see if 10 seconds has elapsed since it was started.
The function is best used in a for-loop that runs through all of the configured timers (0 to ('max_timers
' -1)). Also to note is that once a given timer has elapsed it would need to be restarted immediately if it is a continuously occurring requirement.
An ExampleThe ez_timers sketch has been provided OOTB with an example of how it can be used. The example configures eight LEDs and uses a timer for each, to flash them each at different intervals. To define and control the LEDs, a struct(ure) data type is also used which is easily modifiable to end user needs (reduce or increase the number of LEDs or vary flash rates).
The example provided is a simple way in which LEDs can be processed to give timed light effects. However, for a more general approach to processing the outcome of elapsing timers, the switch-case construct would work especially well, for example:
...
for (timer = 0;time < max_timers; timer++){
if (timer_elasped(timer, 10000) == elapsed){
switch (timer){
case 0: // timer 0
// restart this timer and process the event
start_timer(timer);
...
break;
case 1: // timer 1
// do not restart this timer, just process the event
...
break;
case 2: // timer 2
...etc
break;
...
}
}
}
...
In the above example code, a number of timers have been created which are referenced from 0, 1, 2,..., (max_timers
-1), with each elapsing timer event being processed by a respective case segment.
The ez_timers technique can provide an easy to implement method for running multiple timers in your sketches. However, if it is that you require a more comprehensive and sophisticated solution for timers, one that not only accommodates elapsed time timers but also real-time timers, then have a look at REM_SYS by the same author (see below), which gives a more industrial and fully asynchronous solution.
REM_SYS supports both elapsed and real-time timers (reminders). It is possible to define all timer types as elapsing events that are one-off, recurring without end or recurring until a given time has been reached/elapsed. More than this, for each type, it is possible to define sub-types to aid decision control and also to give each defined timer (reminder) a 'pay-load' of user defined parameters which are returned when timer events are reached (elapsed or real-time). REM_SYS even runs on an UNO microcontroller and can be configured with or without a real-time clock.
Further ReadingYou might also find these contributions interesting and useful, by the same author:
- Add a heart beat to your sketches - include a visible means to see that your code is running on your microcontroller without any additional components or wiring!
- A Music & Lights Workbench - designed to introduce those new to computer programming to the subject, using easy commands with cool effects. The approach is one of tutor and student
- A flexible, scalable library (ez_SIPO8_lib) - supporting the implementation of multiple serial-in/serial-out ICs, 74HC595, either individually or in cascaded banks, up to 255 ICs (2040 output pins)
- A general switch library (ez_switch_lib), suitable for most switch types and wiring schemes, incorporating novel features
- UnderstandIng and UsIng Button SwItches, the basIcs - button switches, a simple but often tricky piece of kit. The tutorial provides in ins and outs of implementing a simple button switch, with flexibility to explore differences in circuit design, different reading methods and debouncing.
- Interrupts Driven Button Switches - an approach and example of tying a button switch to an external interrupt.
- Toggle Switches - how to reliably read a toggle style switch.
- Buttons & Lights Game - a bit of fun using button switches and LEDs.
- External Interrupts, a generic framework - a framework supporting concurrent asynchronous multiple interrupts. Configure multiple external interrupts with different characteristics and add code to provide post-interrupt asynchronous processing.
- REM_SYS, A Programmatic Timed Reminder Alerting, a programmatic framework for both elapsed and real-time asynchronous timed alerting. Define any number of timer (reminder) alerts (sub second to hours) and process asynchronously.
Comments