Have you ever had a need to design your sketches around timers, either one or many? Well, this article features the ESP 32's timer interrupts, readily accessible to designers, to describe a framework that may be used to create solutions requiring one or multiple timers, each with a specific purpose, either as one-off or recurring timers.
The approach is very straight forward - define your timer requirements, create your timers and provide suitable code specific to your needs to decide what needs to be done when your timers elapse.
The term used by the framework for user defined elapsing timers is Elapsed Timer Alerting or Elapsed Timer Alerts (ETAs). Every ETA you define specifies:
- its type (one-off or recurring),
- an ETA alert ID (a unique value that identifies the ETA so that it may be used for decision control once the ETA has elapsed) and
- an elapsed time countdown in milliseconds (the time to elapse from creation/restart to an alert being raised.
The framework is based on the generic approach for defining and processing timer interrupts on the ESP 32 microcontroller (see base sketch, Example 1 below), modified to allow timer interrupts to be processed depending on the ETAs we define and also asynchronously to the interrupt process.
We shall look at, sometimes briefly, each of three instances/examples:
1. the basic ESP 32 timer interrupt example, which can be readily found on the internet,
2. the basic ESP 32 ETA framework, stripped down to its out of the box (OOTB) configuration. This is the starting point for using the ETA framework for your projects, and
3. a fictitious, dummy example of how we can configure the OOTB ETA framework to produce something helpful. We shall look at a home environmental system for controlling our tender plants, keeping track of temperature, humidity, light levels and soil moisture. In addition, we shall also add a few other things - a sketch heart beat which shows the microcontroller is running our sketch and a statistics module to periodically give some basic stats on the serial monitor. The sketch is nothing more than an example of how the ETA framework may be configured for specific purposes.
The timer interrupt frequency for the framework is set at 1 millisecond. This was chosen so as to, perhaps, offer the framework to wide wide audience of developers who may not need any great frequency for their interests. However, there is nothing special about a 1 millisecond frequency - this may be changed as needed but, if so, be aware that the ETA elapsed time interval would have a different time value/meaning which would need to be taken into account when defining ETAs.
The ETA framework was developed using an ESP 32S 30 pin WROOM microcontroller and the Arduino IDE. The IDE board selection was "DOIT ESP32 DEVKIT V1". Connection of the ESP 32 was via a USB cable as standard and there are no associated schematics - it is a framework ready for crafting into a solution/project.
Finally, should the reader wish to implement the framework on Arduino (AVR) microcontrollers then this would require the replacement of the ESP 's timer interrupt ISR, definitions and set up for suitably selected Arduino ISR timer code for the target board. If this may be of interest then a useful resource for configuring Arduino timer interrupts, for a particular microcontroller (AVR board) and interrupt frequency, may be found at:
Arduino Slovakia - AVR Timer Interrupts Calculator
from which it is possible to simply cut and paste the correct interrupt timer code and set up into a sketch.
The Basic ESP 32 Timer Interrupt, Example 1Example 1 shows a basic sketch for defining one of the ESP 32's interrupt timers, timer 0 - there are four available (0-3). The sketch uses timer 0, by default, which is configured for 1 millisecond timer interrupts.
You will see that an interrupt function is used to simply increment an interrupt count each time the timer interrupt triggers. Because the interrupt timer is configured for 1 millisecond interrupts the interrupt count will be incremented every 1 millisecond, so the main loop reports updates to the serial monitor every 1000 timer interrupts, or every second.
That's it. This is the basis for the design and structure of the ETA framework.
If you wish to more fully understand the intricacies of the ESP 32's approach to timer interrupts then the internet provides many resources. This is outside of the scope of this article.
The Basic ETA Framework, Example 2The basic ETA framework takes as its starting point the sketch in Example 1, adding functions and definitions we can use to craft our design into a timer based application.
Before we look at the framework's principal functions and definitions we need to understand how the framework works:
- It is possible to define any number of individual elapsing timers each to which we can assign a unique identifier or 'ETA alert ID' ranging from 0 to 65, 535 (unsigned 16 bit word).
- We may create an ETA as being either a one-off timer or recurring timer. One-off ETA timers are automatically deleted once they have elapsed. Recurring ETA timers are automatically reset and continue to operate as they have been defined.
- When an ETA timer elapses it is added to a list of elapsed timers which is then processed within the main loop by user code. It is this method that allows us to separate the timer ISR functioning from main-line code, hence it is an asynchronous design.
- The main loop continually examines if we have outstanding timer interrupts to be processed and, for each unprocessed interrupt, will process our ETAs - decrementing their respective countdown intervals by 1 millisecond. When an ETA has elapsed it is then added to an elapsed ETA alert list. The elapsed ETA alert list is then continually processed until all elapsed ETAs have been processed.
- The user code will be designed to process elapsed ETAs according to their ETA alert ID that was assigned during the creation process. It is this mechanism that allows us to configure a sketch design with as many unique ETAs as we need to be configured and processed. You will see that the framework and Example 3 uses a switch/case approach to processing elapsed ETAs, but there is nothing special about this, craft any design you wish - it is the ETA alert ID that tells us which ETA has elapsed.
- I should note that the framework permits non-ETA code to also be included within the main loop. T a a 1 millisecond interrupt timer frequency, when not processing ETAs, the sketch will have quite a good deal of 'down-time' during which it can be doing other things, eg IOT processes, etc.
OOTB the framework includes one configured ETA, this being a heartbeat that runs at a frequency of 1 htz (ie every 500 milliseconds) which flashes the ESP's onboard LED (generally GPIO 2). The purpose of this is to show the sketch is operating, but it may be removed if required without any effects on the design.
The framework's main loop design follows this structure (pseudo code):
main loop{
read current timer interrupt count;
while (current interrupt timer > 0) {
update_ETAs();
while (elapsed ETAs in elapsed list) {
switch by ETA alert id {
case heart_beat:
toggle onboard LED;
case ETA_alert_id_1:
process this timer alert for this alert id;
case ETA_alert_id_2:
process this timer alert for this alert id;
...
case ETA_alert_id_n:
process this timer alert for this alert id;
default:
process default handling;
}
}
}
process any non-timer based needs here, e.g. IOT;
}
The above pseudo code shows a main loop with a switch/case design to process each of the defined ETAs (ETA_alert_id_1,..., ETA_alert_id_n).
Configuring the number of ETAs the sketch will handle
The framework will handle as many ETAs as we wish. However, we do need to define this number so that adequate resources may be allocated. This is done very simply - locate the macro 'max_ETAs
' and provide the number of ETAs that will be configured. For example, OOTB, this looks like:
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// % Elapsed Time Alert(s) declarations and functions %
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#define max_ETAs 1 // User defined - at least 1, the heartbeat
OOTB the framework is provided with one readymade ETA - the heartbeat monitor.
Now let's look at the framework's principal functions, ones that you will use:
Creating an ETA - Function: create_ETA
This function allows us to create ETAs of the required type, alert ID and elapse interval. The function has 3 parameters - ETA_type
, ETA_alert_id
and ETA_interval
(elapse interval time in milliseconds) as follows:
ETA_type
- 'one_off_ETA
' or 'recurring_ETA
'
ETA_alert_id
- user defined value in the range 0 to 65, 535
ETA_interval
- user defined elapsed time of alert in milliseconds
The success, or otherwise, of the function call may be tested - it will return one of several conditions which may be handled as required:
- Success - the ETA was successfully created/inserted into the ETA table - '
ETA_inserted'
- Failure 1 - attempt to exceed the defined maximum number of ETAs defined ('
max_ETAs
') - 'ETA_insert_failure
'
- Failure 2 - invalid ETA type, must be either '
one_off_ETA
' or 'recurring_ETA
'
- Failure 3 - the ETA is duplicated, it already exists in the ETA table. Note that an ETA is considered to be a duplicate if it possesses the same ETA type and ETA alert ID. - '
ETA_duplicate
'
To assist in coding self-documenting code each of the above conditions is defined as a macro as follows:
#define one_off_ETA 1
#define recurring_ETA 2
#define ETA_inserted 1 // positive result for success
#define ETA_insert_failure -1 // negative result for failure
#define ETA_invalid_type -2 // ditto
#define ETA_duplicate -3 // ditto
Example:
if(create_ETA(recurring_ETA, 102, 500) !=ETA_inserted){
}
Here we create a recurring ETA timer with an alert ID of 102 and an elapse interval of 500 milliseconds.
Where a design requires many ETAs to be defined then a better approach would be to preset a multidimensional array to hold ETA parameters, eg for each ETA its ETA type, ETA alert ID and ETA elapse time in milliseconds (see Example 3 sketch). For example:
...
define max_ETAs 5
unsigned int my_ETA_data[max_ETAs][3] = {
one_off_ETA, 99, 5000, // 5 second interval
recurring_ETA, 57, 50, // 1/20 second interval
recurring_ETA, 17, 250, // 1/4 second interval
one_off_ETA, 127, one_hour,// one hour interval
recurring_ETA, 1, one_day
};
...
The OOTB framework includes the above array and defines just one entry - that for the heartbeat monitor. Use this array to add your own ETA definitions as this will then ensure that the setup()
function will create them for you without further coding. To note is that the values 'one_hour
' and 'one_day
' are defined macros - see below.
Deleting an existing ETA - Function: delete_ETA
Just as we can create ETAs, we may also programmatically delete them. The process is straight forward and uses the function delete_ETA
. The function has just two parameters - 'ETA_type
' and 'ETA_Alert_id
'. These parameters have the same meaning and definition as above, see create_ETA
.
The success, or otherwise, of a delete process may be tested - the function returns one of two values - 'ETA_deleted
' or '!ETA_deleted
'. Again, 'ETA_deleted
' is a macro definition (defined as true
) and can be similarly used to aid self-documenting code.
...
if(delete_ETA(recurring_ETA, 17) == ETA_deleted){
// ETA deleted
...
}
Note that, although ETAs may be predefined and created at the start/initialisation of a sketch, there is no reason why they may not be dynamically created/deleted programmatically during a sketch's life, ensuring always that the total number of currently defined ETAs is <= the maximum number of ETAs the sketch is configured for.
Printing the ETA table - Function: print_ETAs
There is just one more user function that you may find helpful during debugging and testing - print_ETAs
.
This function has no parameters and will print to the serial monitor (set at 115200 baud by default) the current status of the ETA table, providing conformation that your ETAs are defined as you intend. It serves no purpose post development and can be then removed. For example:
...
print_ETAs();
...
Example output from the OOTB sketch (heartbeat ETA):
ETA entry = 0
ETA_type = recurring ETA
ETA_alert_id = 2
ETA_interval = 500
ETA_count_down = 500
Other data resourcesthat may be useful
A number of macro definitions have also been provided that may be helpful, these defining standard periods of time as milliseconds:
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
// % Data specific to this use of the ETA framework for this example %
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
//
// These macros can be used for creating ETAs and are provided
// for convenience of programming standard intervals, as required.
// They may be modified to achieve the desired count down frequencies,
// for example 2 * one_second, 5 * minute + 500, etc (all in milliseconds)
//
const uint32_t one_day = 86400000; // millisecs in 24 hours
const uint32_t one_hour = 3600000; // millisecs in 1 hour
const uint16_t one_minute = 60000; // millisecs in 1 minute
const uint16_t one_second = 1000; // millisecs in 1 second
For example:
...
create_ETA(one-off_ETA, 5, one_hour);
...
Again, the purpose here is to help produce self-documenting code.
Other functions of note
For completeness, the following functions are noteworthy but not intended for end user configuration or any use beyond that which is designed by the framework:
create_and_start_timer
- this function is called during thesetup()
process and only after all of your ETAs have been created. It configures and starts ESP timer 0 (default_timer
).
update_ETAs
- called in the main loop for every outstanding timer interrupt. The function processes each defined ETA decrementing countdowns until they reach 0. When 0 is reached an entry is added to the ETA alert list and the ETA is the either deleted (one_off_ETA
) or restarted (recurring_ETA
).
clear_ETAs
- clears down the ETA table prior to its set up.
An Example ETA Sketch for Home Environmental Control, Example 3get_ETA_alert
- called in the main loop. This function is used to process the ETA alert list (created by theupdate_ETAs
process) removing one ETA alert at a time and setting the ETA type and ETA alert ID in local variables that may then be used by the main loop code for decision control, ie what to do with the alert. Processing of the list continues until it is emptied after which the structure will loop to re-examine if further timer interrupts have occurred, and so on.
To provide an idea of how the framework may be used, Example 3 provides a sketch that mocks up a home environmental project to monitor the environmental conditions of, say, a green house - periodically testing temperature, light, humidity and soil moisture. It is offered purely for interest.
ConclusionI hope that you find this article of interest (and useful?). Timer interrupts are often a mystery to many and I have tried to ensure that the framework allows a developer to just concentrate on their needs for defining timers but without compromising configurability.
Comments
Please log in or sign up to comment.