BLYNK
BLYNK.IO       📲 GETTING STARTED       📗 DOCS       ❓HELP CENTER       👉 SKETCH BUILDER

Using BlynkTimer or SimpleTimer

Many users seem to struggle with the basic concepts of using BlynkTimer in their projects, despite the excellent discussions and tutorials that exist on this forum, so I thought I’d write a guide that will hopefully help clarify things. It’s rather long, as it attempts to explain the reasoning behind why and how we use timers, and covers some more advanced subjects, but I’ve tried to organise it in a way that helps people to find answers to specific questions…

Why use a timer at all?

Let’s say that we have a project where we want to take readings from a sensor every 5 seconds. There are a number of ways to do this, and the ‘lazy’ way is to do this:

void loop()
{
  take_sensor_reading(); // call the function which reads the sensor
  delay(5000); // do nothing for 5000ms (5 seconds)
  Blynk.run(); // perform a handshake with the Blynk server
}

The problem with this approach is that the delay() command is “blocking” – it blocks all code execution until the delay period has completed. While this isn’t a problem in some cases, it is a problem when working with Blynk.

For Blynk to work effectively, the device needs to perform a handshake with the Blynk server as often as possible, preferably hundreds of times per second, so that it is aware of any user input in the app. This only happens when Blynk.run(); is executed and adding the delay in the code prevents this happening. If there is no handshake from the device for some time (the default is 10 seconds) the Blynk server will regard the device as “Missing In Action” and will show it as disconnected and close the communication channel with the device.

To overcome this, the delay() needs to be replaced with a non-blocking solution. That brings us on to the second approach, which we’ll call the “millis() comparison” option.

When the device boots-up, it starts a timer which keeps track of how many milliseconds have elapsed since boot-up. This number increments automatically, once every millisecond, and can be used as a way of measuring how long it is since we last took a sensor reading, and if another sensor reading is due.

The code to do this might look like this:

long last_sensor_reading = 0; // Long integer variable to track when we last took a reading

void loop()
{
  if(milis() - last_sensor_reading >= 5000) // if it’s 5000ms or more since we last took a reading
  {
    // if it’s 5000ms or more since we last took a reading then we do these things…
    take_sensor_reading(); // call the function which reads the sensor
    last_sensor_reading = millis(); // update when we last took a reading
  }
  Blynk.run(); // perform a handshake with the Blynk server
}

This approach will work perfectly well, and will execute the take_sensor_reading() function once every 5 seconds without blocking the execution of the other code in the void loop().

However, it can get quite messy (and inefficient) when you want to call multiple functions at different time intervals. It also only solves one problem – how to call a function from within the void loop – and doesn’t provide a simple non-blocking solution that can be used elsewhere in your code.

The better solution is to use a library to perform all of the hard work in the background, and for this we use SimpleTimer or BlynkTimer.

SimpleTimer versus BlynkTimer

SimpleTimer is a 3rd party library which is widely used. To incorporate it into your sketch you would need to ensure that the library is installed within your IDE then tell the compiler to include the library in your sketch using #include <SimpleTimer.h>

An improved version of SimpleTimer (referred to as BlynkTimer) is included within the Blynk library, so doesn’t need to be included separately.

The improvements incorporated in BlynkTimer are minor – you can have more timers for each timer object (16 versus 12), and the timer will attempt to prevent you from accidentally flooding the Blynk server with data. The main reason for using BlynkTimer over SimpleTimer is that it is already included in the Blynk library.

Using BlynkTimer

Setting-up your sketch to use BlynkTimer is very easy…

First of all, you need to define a BlynkTimer object. This is done near the top of your code…

BlynkTimer timer;

This tells the compiler that we want to create a BynkTimer object called timer. This is that name that will be used to refer to the timer object within the rest of your code.

We could use a different object name if you wanted to, like this:

BlynkTimer mytimerobject;

But, because the Blynk standard examples use an object name of timer then we’ll use that for our examples.

For the BlynkTimer object to work, it needs to be called from the void loop() like this:

void loop()
{
  timer.run(); // call the BlynkTimer object
  Blynk.run(); // perform a handshake with the Blynk server
}

Note , if you’d given the BlynkTimer object a different name, such as mytimerobject then you’d need to use that name in the void loop() instead…

  mytimerobject.run(); // call the BlynkTimer object

When timer.run() executes, the library is actually doing a series of millis() comparisons like the one in the earlier example, but in a highly efficient way and with lots of additional functionality (which will be covered later). However, for the BlynkTimer library to work effectively, the timer.run() command needs to be executed as often as possible – preferably at least once every millisecond – which is why we put it in the void loop()

So, we’ve now declared the timer object, and ensured that the BlynkTimer library is doing it’s thing, but we don’t yet have any timers that do anything.

Creating Interval Timers

The most frequently used type of timer is the Interval timer, which calls a function at a specified interval. This type of timer is defined in void setup()

Going back to our original example of taking a sensor reading once every 5 seconds, we would define a an interval timer like this:

void setup()
{
  timer.setInterval(5000L, take_sensor_reading); // call the take_sensor_reading() function every 5 seconds
}

This tells the BlynkTimer library that we want to call the take_sensor_reading() function once every 5000 milliseconds (5 seconds). The L after the number 5000 forces the compiler to treat this as a Long Integer variable type. This is important when you want to use larger numbers with 8-bit boards like the Arduino Uno, as the maximum size of a regular Integer with an 8-bit board is 32,767 (or 32.767 seconds when used as a timer interval frequency).

Once again, if you gave the timer object a different name, such as mytimerobject then this line would look like this:

mytimerobject.setInterval(5000L, take_sensor_reading); // call the take_sensor_reading() function every 5 seconds

One thing that confuses many new users is that the same timer object can support up to 16 timers (or 12 if you’re using SimpleTimer) this means that you can do this:

void setup()
{
  timer.setInterval(5000L, take_sensor_reading); // call the take_sensor_reading() function every 5 seconds
  timer.setInterval(30000L, control_heater); // call the control_heater () function every 30 seconds
  timer.setInterval(7200000L, once_every_2_hours); // call the once_every_2_hours () function every 7200 seconds
}

You can see that the same timer object is being used for each of these timers. There is no need to declare multiple timer objects with a different names unless you have more than 16 timers (or 12 with SimpleTimer).

:bulb:Top tip – if you struggle to visualise 2 hours as being 7,200,000 microseconds then you can do this type of thing instead:

  timer.setInterval(2L*60L*60L*1000L, once_every_2_hours); // call the once_every_2_hours () function every 2 hours

Advanced Interval Timer Operations

In this section I’ll try to cover some of the issues that I’ve seen users struggle with when using interval timers…

Overriding a Timer (auto/manual mode)

It is possible to enable, disable and delete timers, and I’ll cover that in the next section, but in most situations it’s not necessary to do this.

Consider a situation where we are using one timer to take a reading from a temperature sensor every 5 seconds, then another timer which runs every 30 seconds and checks whether the temperature is below the target temperature and turns on a heater if it is, and off if it isn’t.

In addition, there is a manual override, which can be used to turn the heater on regardless of the temperature reading.

One approach to tackling this is to disable the 30 second timer when we are in manual mode, but the other is to leave the timer running all the time - but only execute the code in the temperature comparison function if we are in automatic mode.

That 30 second timed function to turn the heater on/off might look like this:

void control_heater()
{
  If(auto_mode == true)
  {
    // compare temperature and switch relay accordingly
  }
}

This code structure allows us to keep calling the control_heater() function with a timer, but only process the code within the function if our auto_mode variable is set to true. This variable would be switched between true and false elsewhere within our sketch.

Disabling/Enabling a timer to achieve the same result

To enable or disable a timer we first of all need to know the ID number of the timer. That is done by capturing the timer ID that is allocated when the timer is created.

Our earlier example looked like this:

void setup()
{
  timer.setInterval(30000L, control_heater);
}

To capture the timer ID we first declare a global variable to hold that ID, then use that to store the ID number that is allocated, like this:

int control_heater_timer_ID;

void setup()

{

  control_heater_timer_ID = timer.setInterval(30000L, control_heater);

}

We can then disable that timer from elsewhere within the sketch like this:


timer.disable(control_heater_timer_ID);

and enable it again later using:


timer.enable(control_heater_timer_ID);

However, there is a bug in the SimpleTimer (and therefore BlynkTimer) library, which means that timer IDs become corrupted if the timer with ID = 0 is disabled/enabled.

The solution to this is to create a dummy sacrificial timer, which will be allocated the ID 0 slot. This can be done using a Lambda timer function like this:

timer.setTimeout(3600000L, [] () {} ); // dummy/sacrificial Function

This needs to be the first timer created, like this:

int control_heater_timer_ID;

void setup()
{
  timer.setTimeout(3600000L, [] () {} ); // dummy/sacrificial Function
  timer.setInterval(5000L, take_sensor_reading);
  control_heater_timer_ID = timer.setInterval(30000L, control heater);
}

You’ll see that we haven’t bothered capturing a timer ID for the first two timers, as we don’t need to disable/enable these within the code, so they aren’t needed.

In this situation, the ID allocated to our control_heater timer will almost certainly be ID 2 (the ID numbers are zero-based, so the three timers will get ID’s of 0, 1 and 2). However, its not safe to assume that this will always be the case, as deleting timers, creating timeout timers and using fixed repetition timers all result in timer IDs being deleted and recycled, so it’s always safest to capture the timer ID for any timer that you’ll want to later reference by it’s ID.

Toggling Timers

Rather than enabling or disabling timers, they can be toggled to their opposite state (enabled if currently disabled, or disabled if currently enabled) using the toggle command:

timer.toggle(timer_ID);

This might be useful in some situations, but has the scope to get quite confusing if you’re not very careful.

the current status (enabled or disabled) of a timer can be queried uisng the isEnabled command:

bool enabled_result = timer.isEnabled(timer_ID);

This returns true if the timer is enabled and false if disabled.

Deleting Timers

If you wish to delete a timer entirely then you can use this command:

timer.deleteTimer(timer_ID);

Restarting a Timer

Lets say that we have an interval timer which runs every 2 hours, but part way through that two hour period we want to set the timer back to zero again, giving ourselves another two hours before the timer executes again. This can be done with this command:

timer.restartTimer(int timer_ID);

Fixed Repetition Timers

These are interval timers that run a fixed number of times, then delete themselves.
This might be useful if you want to do something like sound an alarm once every second for 10 repetitions then stop;
The syntax is very similar to a timeout timer, but it has an extra parameter that specifies the number of repetitions:

timer.setTimer(1000L, sound_alarm, 10);

As with interval timers, the timer ID of a fixed repetition timer can be captured when the timer is initialised, and the timer can be enabled, disabled, deleted etc

Timeout Timers

Timeout timers are an extremely useful alternative to delays, and can be used in several different ways.
The standard way is this:

timer.setTimeout(5000L, timer_completed);

This will call the timer_completed() function 5 seconds after the timer is initialised.

However, having to call a secondary function when the timer ends is sometimes quite a messy approach, so its often much neater to use the timeout timer as a Lambda function, like this:

  timer.setTimeout(5000L, []() 
  {  
    // When the timer completes, any code here will be executed
  }); 

Note that with timeout timers, the code execution is not paused until the timer ends. If you place a Lambda timer in a piece of code then the code execution will ‘step over’ the Lambda timer and continue to execute, until the timer expires.

For example, This code:

  Serial.printlin("Location A");

  timer.setTimeout(5000L, []() 
  {  
    // When the timer completes, any code here will be executed
    Serial.printlin("The timer has completed");
  }); 

  Serial.printlin("Location B");
  Serial.printlin("Location C");

would produce this output in the serial monitor:

Location A
Location B
Location C
The timer has completed

As with timeout and fixed repetition timers, the timer ID can be captured and used to enable/disable/delete etc.

Using variables instead of hard-coded numbers

In all of the examples above, hard-coded values have been used to specify the milliseconds value when the timer is initialised.
Timers, especially the fixed repetition an timeout variety, can be made much more flexible by replacing these numeric values with variables instead…


  long time_to_wait = 30000; // 30 seconds

  timer.setTimeout(time_to_wait , []() 
  {  
    // When the timer completes, any code here will be executed
  }); 

With the fixed repetition timer, the number of repetitions can also be a variable

long time_between_repititions = 5000; // 5 seconds
int number_of_repititions = 100;      // do it 100 times

timer.setTimer(time_between_repititions , sound_alarm, number_of_repititions );

Staggering Timers

Consider this code:

void setup()
{
  timer.setTimeout(1000L, call_function_1);
  timer.setTimeout(1000L, call_function_2);
  timer.setTimeout(1000L, call_function_3);
  timer.setTimeout(1000L, call_function_4);
  timer.setTimeout(2000L, call_function_5);
  timer.setTimeout(10000L, call_function_6);
}

void loop()
{
  timer.run();
  Blynk.run();
}

We have 5 timers, 4 of which are meant to run once every second, and the 5th is meant to run every two seconds.

We are running this sketch on a single-threaded MCU, so only one of these functions can be executed at any one time…

When the void loop() is executed, the timer.run command causes the timer library to check its schedules and it discovers that the first timer is due to be run, because it’s at least 1000ms since it last ran. Function 1 is called, and takes maybe 100ms to complete (obviously this depends on the code we have in Function 1).

The code then returns to the void loop and Blynk.run is executed, which maybe takes a few milliseconds as well.

Then the void loop is processed again, and timer.run causes the timer library to check it’s schedule once again and it discovers that Function 2 is due to be called. However as Function 1 took 100ms to execute, plus the time it’s taken to do the other things, Function 2 is now over 100ms late starting.
When we call Function 2, it does some complex calculations which take 200ms to complete.

Next time around, Function 3 is called, and that will start over 300ms late, and so on…
As you can see, it’s easy to reach a point where the CPU simply can’t keep up with the code it’s meant to be processing, and at some point it will probably give-up and reboot.

So, what is the solution to this problem?

First of all, we need to give some thought to how often our functions need to be executed. If we’re taking temperature readings, do we really need to do this 5 timers per second (and can our sensor actually handle this?), or would once every 5 or 10 or even 30 seconds be sufficient?

Secondly, we need to give some thought to how long each function takes to execute, especially when we are using a slow MCU like an Arduino Uno. Adding serial print statements at the beginning and end of a function can provide some data about how long it takes the MCU to process the commands within the function.

Thirdly, look-out for situations when multiple timers will coincide. In the example above, all 6 timers will be due at precisely the same moment, once every 10 seconds.

As the heading of this section suggests, we should aim to stagger the start times and/or frequency of the various timers to ensure that they coincide as infrequently as possible. Different strategies for doing this are discussed in this excellent post from @Gunner:

Bear in mind that if the “start each timer with a small delay at their initialisation” approach described by @Gunner is used then the duration of that delay should be be larger than the amount of time it takes for the function to execute.

Official Documentation

If you want to learn more, the official documentation for SimpleTimer is here:
https://playground.arduino.cc/Code/SimpleTimer/

and for information about the differences between SimpleTimer and BlynkTimer you can read this:

9 Likes