The SoftTimers arduino library is a collection of software timers. It allows one to properly time multiple events and know when each "timer" expires meaning that an action is required. SoftTimers can also be used to compute the elapsed time since an event occured. The library aims at greatly simplifying multitask complexity.
Library features:
- Provides the non-blocking equivalent to
delay()
function. - Each timers encapsulate its own expiration (timeout) time.
- Provides elapsed time, remaining time and progress (in percentage) APIs.
- Supports milliseconds, microseconds or any other arbitrary time with external time counting function.
- Provides expiration loop count API (as if timer never expire and automatically
reset()
) to easily implement toggling, and time based state machines. - Automatically handles
micros()
andmillis()
overflows / wrap around special cases. - Provides multitasking abilities to sketches.
Build:
Service/Platform | Build | Tests |
---|---|---|
AppVeyor | ||
Windows Server 2019 | ||
Ubuntu 22.04 |
Statistics:
AppVeyor | GitHub |
---|---|
Consider the arduino Blick tutorial. It uses the delay()
function to know when to toggle a LED on and off. This approach is bad since it breaks the "realtime" property of the software to react to other event. If I want to make the LED instantly turn off when pressing a button, I had to wait for the delay to complete before processing the button.
Another issue is extensibility. Making 3 LEDs blink at different time interval is much harder with delays. How about 40 LEDs? Impossible?
The SoftTimers allows one to properly time multiple events and know when each "timer" expires meaning that an action is required. In this example above, a SoftTimer expires when it is time to toggle a LED.
SoftTimers also provide the elapsed time since an event occurred. In case of an interruption, the elapsed time can be used as debugging information. It can also be used as a countdown information displayed to the user.
The library regroups basic timer functionalities into a single class. The usual way to get the same functionality is to create multiple variables for each single timer. This method is hard to maintain when you need multiple timers to run at the same time.
SoftTimer classes are designed to be keep "simple and stupid". No software interrupts. Non-blocking. Each timer must be polled within the loop() to know their status.
The following instructions show how to use the library.
To use a SoftTimers, create a variable of type SoftTimer in the global scope of the program.
SoftTimer myRefreshTimer;
In setup()
, call setTimeOutTime()
to setup the non-blocking SoftTimer then call reset()
to initialize the internal counter.
void setup() {
myRefreshTimer.setTimeOutTime(30000); // every 30 seconds.
myRefreshTimer.reset();
}
Within the loop()
, call hasTimedOut()
to know if the timer has expired.
void loop() {
if (myRefreshTimer.hasTimedOut())
{
//TODO: refresh the input pins
myRefreshTimer.reset(); //next refresh in 30 seconds
}
}
At any moment, call getElapsedTime()
or getRemainingTime()
to get the absolute elapsed/remaining time since the last reset()
.
void loop() {
if (!myRefreshTimer.hasTimedOut())
{
static uint32_t count = 0;
count++;
if (count == 1000) //print remaining time 1/1000 of loops
{
//show user how much time left in milliseconds
uint32_t remaining = countdown.getRemainingTime();
Serial.print(remaining);
Serial.println(" ms...");
}
}
}
Fading a LED like in arduino's Fade Example is trivial using SoftTimers. The library helps in defining the constant speed at which the LED will fade by defining the total length of the process and by easily mapping the timer "progress" to the amount of fade (PWM) used with the output pin. All of this in a non-blocking manner.
The following example increases the intensity of a LED from OFF to ON in 1 second and then decreases the intensity of the LED back to OFF in 1 second.
#include <SoftTimers.h>
#define LED_PIN 9
/**************************************************
*Fade a LED as the Fade basic example:
*Increse the intensity of a LED from OFF to ON in 1 seconds
*then ON to OFF in 1 seconds
**************************************************/
SoftTimer fadeTimer; //millisecond timer
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
//update timers
fadeTimer.setTimeOutTime(1000); // every 1 second.
//start counting
fadeTimer.reset();
}
void loop() {
//determine the direction of brightness
int direction = fadeTimer.getLoopCount() % 2; //returns 0 or 1
bool increasing = (direction == 0); //assign increasing brightness with direction 0
//get progress
double progress = fadeTimer.getLoopProgress();
//correct progress based on direction
if (!increasing)
{
progress = 1.0 - progress;
}
//map progress to a 0-255 brightness intensity.
uint16_t brightness = progress*255;
//update the LED
analogWrite(LED_PIN, brightness);
}
Any program that need to display a countdown or compute the elapsed time between two events can also benefit from SoftTimers.
The following example runs a countdown of 5 seconds and then turns a LED on.
#include <SoftTimers.h>
#define LED_PIN 13
/**************************************************
* Run a countdown from 5 second to 0 and turn LED on
**************************************************/
SoftTimer countdown; //millisecond timer
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(115200);
//update timers
countdown.setTimeOutTime(5000); // 5 seconds
//start counting now
countdown.reset();
}
void loop() {
if (!countdown.hasTimedOut())
{
//show user how much time left in milliseconds
uint32_t remaining = countdown.getRemainingTime();
Serial.print(remaining);
Serial.println(" ms...");
}
else
{
//turn ON the LED
digitalWrite(LED_PIN, HIGH);
}
}
SoftTimer library also help reducing repetitive timed cycles to their simplest non-blocking form. SoftTimer library automatically computes current cycle index. Any toggling or cycle scenarios can be implemented with very few lines of code.
The following example implements a system where a single HIGH pin must be cycled every second within multiple pins as defined by the following loop:
- set pin 8, 9 and 13 to LOW, LOW and HIGH respectively and then wait 1 second.
- set pin 8, 9 and 13 to HIGH, LOW and LOW respectively and then wait 1 second.
- set pin 8, 9 and 13 to LOW, HIGH and LOW respectively and then wait 1 second.
- repeat the cycle forever...
#include <SoftTimers.h>
#define LED_PIN_0 13
#define LED_PIN_1 8
#define LED_PIN_2 9
/**************************************************
*Every second, cycle the HIGH pin within pins 13,8,9
**************************************************/
SoftTimer cyclePinTimer; //millisecond timer
void setup() {
pinMode(LED_PIN_0, OUTPUT);
pinMode(LED_PIN_1, OUTPUT);
pinMode(LED_PIN_2, OUTPUT);
Serial.begin(115200);
//update timers
cyclePinTimer.setTimeOutTime(1000); // every 1 second.
//start counting
cyclePinTimer.reset();
}
void loop() {
uint32_t loopCount = cyclePinTimer.getLoopCount();
uint32_t pinHighNumber = (loopCount % 3); //from 0 to 2
if (pinHighNumber == 0)
{
//turn ON the LED 0, turn OFF all other
digitalWrite(LED_PIN_0, HIGH);
digitalWrite(LED_PIN_1, LOW);
digitalWrite(LED_PIN_2, LOW);
}
else if (pinHighNumber == 1)
{
//turn ON the LED 1, turn OFF all other
digitalWrite(LED_PIN_0, LOW);
digitalWrite(LED_PIN_1, HIGH);
digitalWrite(LED_PIN_2, LOW);
}
else //if (pinHighNumber == 2)
{
//turn ON the LED 2, turn OFF all other
digitalWrite(LED_PIN_0, LOW);
digitalWrite(LED_PIN_1, LOW);
digitalWrite(LED_PIN_2, HIGH);
}
}
SoftTimer library allows one to make an easy abstraction of time when dealing with timed restricted state machines.
The following example implement an hypothetical state machine where each state has a maximum duration:
- State #1 – IDLE (1000 ms)
- State #2 – LISTENING (200 ms)
- State #3 – SYNCHRONIZING (500 ms)
- State #4 – UPDATING (300 ms)
- State #1 ...
#include <SoftTimers.h>
/**************************************************
* An hypothetical state machine needs to step from
* state to state. Each state has its own maximum
* duration:
* State #1 - IDLE (1000 ms)
* State #2 - LISTENING (200 ms)
* State #3 - SYNCHRONIZING (500 ms)
* State #4 - UPDATING (300 ms)
* State #1 ...
**************************************************/
SoftTimer nextStateTimer; //millisecond timer
const int STATE_IDLE = 0;
const int STATE_LISTENING = 1;
const int STATE_SYNCHRONIZING = 2;
const int STATE_UPDATING = 3;
int state = STATE_IDLE;
void setup() {
Serial.begin(115200);
//update timers
nextStateTimer.setTimeOutTime(1000); // IDLE, 1 second.
//start counting now
nextStateTimer.reset();
}
void loop() {
//show current state
switch(state)
{
case STATE_IDLE:
Serial.println("IDLE");
break;
case STATE_LISTENING:
Serial.println("LISTENING");
break;
case STATE_SYNCHRONIZING:
Serial.println("SYNCHRONIZING");
break;
case STATE_UPDATING:
Serial.println("UPDATING");
break;
default:
Serial.println("UNKNOWN STATE!");
break;
};
//look for next state...
if (nextStateTimer.hasTimedOut())
{
state++;
//limit state to known states
state = state%(STATE_UPDATING+1);
//reconfugure time based on new state
switch(state)
{
case STATE_IDLE:
nextStateTimer.setTimeOutTime(1000);
break;
case STATE_LISTENING:
nextStateTimer.setTimeOutTime(200);
break;
case STATE_SYNCHRONIZING:
nextStateTimer.setTimeOutTime(500);
break;
case STATE_UPDATING:
nextStateTimer.setTimeOutTime(300);
break;
default:
Serial.println("UNKNOWN STATE!");
break;
};
//start counting now
nextStateTimer.reset();
}
//hypothetical work...
//processState()
}
SoftTimer library allows one to easily compute the performance of a function or a section of code.
This feature is usefull for debugging. For example, it can be used to identify a section of code or an algorithm that needs to be optimized.
It is particuly usefull with microcontrollers that are equiped with a Watchdog Timer that resets the microcontroller if the loop()
function appears to be blocking. The profiling classes SoftTimerProfiler
, SoftTimerProfilerMillis
and SoftTimerProfilerMicros
provides an easy way to identify "blocking" sections of code that needs to be re-written in a non-blocking way.
The following example uses a SoftTimerProfiler
to compute the average time of the following:
- The function
analogRead()
with microseconds resolution. - An implementation of Bubble Sort algorithm with milliseconds resolution.
#include <SoftTimers.h>
#define ANALOG_PIN A0
/****************************************************************
* Compute the minimum, maximum and average time of a function or
* a section of code to execute.
****************************************************************/
int loop_count = 0;
static const int unsorted_values[] PROGMEM = {
179, 211, 219, 307, 495, 462, 485, 396, 286, 189, 361, 122, 312, 398, 218, 487, 287, 452, 86, 297, 147, 196, 298, 158, 265, 136, 229, 301, 412, 160, 300,
168, 489, 407, 34, 303, 443, 2, 149, 391, 131, 228, 360, 128, 343, 271, 294, 186, 69, 267, 164, 10, 340, 223, 63, 140, 132, 508, 258, 496, 457, 266, 44,
43, 115, 313, 447, 57, 247, 123, 296, 7, 499, 399, 112, 198, 126, 374, 213, 163, 220, 335, 474, 473, 451, 161, 420, 475, 18, 37, 212, 104, 144, 245, 28,
410, 143, 60, 446, 356, 127, 162, 224, 365, 510, 170, 109, 54, 137, 392, 155, 16, 330, 389, 302, 225, 467, 89, 153, 317, 275, 436, 184, 107, 388, 56, 230,
319, 270, 174, 488, 156, 280, 347, 30, 497, 148, 409, 38, 8, 454, 382, 427, 358, 461, 441, 274, 251, 135, 181, 440, 387, 263, 23, 311, 422, 426, 509, 468,
345, 173, 460, 90, 76, 39, 332, 385, 1, 484, 432, 419, 310, 64, 53, 390, 242, 222, 491, 324, 221, 283, 444, 237, 350, 134, 70, 45, 470, 395, 171, 29, 48,
209, 116, 429, 207, 438, 273, 328, 289, 423, 479, 459, 210, 430, 252, 259, 11, 167, 406, 185, 244, 142, 425, 35, 25, 450, 206, 78, 367, 12, 238, 309, 77,
264, 133, 415, 85, 208, 66, 493, 9, 180, 404, 351, 187, 483, 49, 91, 32, 166, 339, 394, 130, 94, 333, 243, 169, 58, 241, 165, 232, 22, 299, 276, 393, 82,
354, 103, 192, 88, 418, 84, 5, 119, 500, 6, 42, 190, 59, 341, 414, 110, 379, 463, 282, 151, 195, 480, 503, 150, 348, 377, 239, 346, 75, 19, 372, 3, 125,
417, 413, 323, 336, 506, 73, 261, 424, 95, 256, 15, 408, 201, 98, 233, 384, 327, 355, 260, 124, 14, 284, 326, 46, 17, 191, 72, 79, 363, 397, 51, 511, 437,
453, 386, 472, 102, 501, 357, 93, 215, 369, 183, 117, 177, 269, 381, 492, 308, 154, 456, 486, 305, 466, 316, 105, 272, 13, 67, 254, 157, 315, 121, 108,
375, 442, 203, 97, 338, 400, 376, 52, 36, 33, 490, 290, 27, 20, 113, 494, 26, 81, 4, 321, 401, 325, 47, 65, 458, 295, 481, 74, 465, 250, 193, 50, 279, 24,
380, 433, 235, 383, 285, 364, 306, 145, 505, 362, 507, 448, 268, 434, 111, 202, 138, 370, 322, 455, 236, 101, 471, 139, 129, 405, 334, 234, 291, 141, 371,
342, 159, 477, 99, 329, 314, 359, 21, 100, 257, 172, 352, 318, 320, 199, 176, 61, 246, 204, 240, 504, 502, 214, 416, 41, 449, 227, 178, 293, 92, 366, 182,
435, 253, 469, 331, 118, 96, 152, 55, 402, 498, 194, 445, 248, 353, 205, 421, 231, 226, 431, 344, 114, 277, 31, 464, 278, 216, 262, 40, 80, 106, 120, 68,
439, 476, 304, 87, 71, 175, 403, 349, 146, 83, 249, 373, 197, 288, 411, 428, 200, 281, 378, 292, 62, 368, 188, 337, 255, 217, 482, 478,};
static const size_t unsorted_values_count = sizeof(unsorted_values) / sizeof(unsorted_values[0]);
static const size_t sorted_values_count = unsorted_values_count;
static int sorted_values[sorted_values_count];
void setup() {
pinMode(ANALOG_PIN, INPUT);
Serial.begin(115200);
Serial.println("ready");
}
void profileAnalogRead() {
SoftTimerProfiler profiler(µs); // microseconds profiler
profiler.reset();
for(int i=0; i<1000; i++) {
profiler.start();
int value = analogRead(ANALOG_PIN);
profiler.stop();
}
profiler.end();
profiler.print("analogRead");
}
void printArray(int array[], size_t count) {
Serial.println("<values>");
for(size_t i=0; i<count; i++) {
Serial.print(array[i]);
bool is_last = !(i+1<count);
if (!is_last)
Serial.print(", ");
}
Serial.println();
Serial.println("</values>");
}
// perform the bubble sort
void bubbleSort(int array[], int size) {
// loop to access each array element
for (int step = 0; step < size - 1; ++step) {
// loop to compare array elements
for (int i = 0; i < size - step - 1; ++i) {
// compare two adjacent elements
// change > to < to sort in descending order
if (array[i] > array[i + 1]) {
// swapping occurs if elements
// are not in the intended order
int temp = array[i];
array[i] = array[i + 1];
array[i + 1] = temp;
}
}
}
}
void profileBubbleSort() {
SoftTimerProfilerMillis profiler; // milliseconds profiler
profiler.reset();
for(int i=0; i<50; i++) {
// Copy unsorted values into the array that we will sort.
// This operation is outside of the scope of the profiler.
memcpy_P(&sorted_values, unsorted_values, sizeof(sorted_values));
//printArray(sorted_values, sorted_values_count);
profiler.start();
bubbleSort(sorted_values, sorted_values_count);
profiler.stop();
}
profiler.end();
profiler.print("bubbleSort");
}
void loop() {
if (loop_count%2 == 0)
profileAnalogRead();
else
profileBubbleSort();
loop_count++;
}
The above sketch outputs the following to the serial port:
analogRead {
count:1000
total:97009
avg: 97.01
min: 94
max: 111
}
bubbleSort {
count:50
total:1063
avg: 21.26
min: 21
max: 22
On average, the function analogRead()
executes in 97.01 microseconds. The fastest call took 94 us and a worst call took 111 us.
The Bubble Sort algorithm executes on average in 21.26 milliseconds on this particular unsorted array of integers. Since the minimum and maximum times are almost identical, the algorithm does not seems to be affected by external factors.
More SoftTimer examples are also available:
- Countdown
- CycleHighPin
- FadeLed
- LimitPrintedMessages
- ProfileFunctions
- ProgressBar
- StateMachine
- ToggleLed
- ToggleLedAdvanced
Please refer to file INSTALL.md for details on how installing/building the application.
SoftTimers has been tested with the following platform:
- Linux x86/x64
- Windows x86/x64
We use Semantic Versioning 2.0.0 for versioning. For the versions available, see the tags on this repository.
- Antoine Beauchamp - Initial work - end2endzone
See also the list of contributors who participated in this project.
This project is licensed under the MIT License - see the LICENSE file for details