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

Hardware Timer Interrupt

I just posted a library I think it can be useful for projects having mission-critical functions you don’t like to be blocked.

There are other similar libraries, such as TimerOne, TimerThree, etc., but not very simple to use as they are designed for other purpose, such as PWM.

Edited: Nov 24th 2019

This library has just been added to Arduino Library Manager

1 Like

New release v1.0.1 with

1. Longer Interval for all timers.
2. Reduce code size if use less timers. Eliminate compiler warnings.
3. Now supporting complex object pointer-type argument.
4. Fix some bugs in v1.0.0
5. Add more examples.

Features:

Why do we need this Hardware Timer Interrupt?

Imagine you have a system with a mission-critical function, measuring water level and control the sump pump or doing something much more important. You normally use a software timer to poll, or even place the function in loop(). But what if another function is blocking the loop() or setup().

So your function might not be executed, and the result would be disastrous.

You'd prefer to have your function called, no matter what happening with other functions (busy loop, bug, etc.).

The correct choice is to use a Hardware Timer with Interrupt to call your function.

These hardware timers, using interrupt, still work even if other functions are blocking. Moreover, they are much more precise (certainly depending on clock frequency accuracy) than other software timers using millis() or micros(). That's necessary if you need to measure some data requiring better accuracy.

Functions using normal software timers, relying on loop() and calling millis(), won't work if the loop() or setup() is blocked by certain operation. For example, certain function is blocking while it's connecting to WiFi or some services.

Hope it will be somehow useful for Blynkers.

1 Like

Just added new Hardware Timer Interrupt library for ESP32.

It’s simpler to use compared to original ESP32 Hardware Timer library.

2 Likes

Brand new Hardware Timer Interrupt library for ESP8266

From README.md

## More useful Information
The ESP8266 timers are badly designed, using only 23-bit counter along with maximum 256 prescaler. They're only better than UNO / Mega. 
The ESP8266 has two hardware timers, but timer0 has been used for WiFi and it's not advisable to use. Only timer1 is available. The timer1's 23-bit counter terribly can count only up to 8,388,607. So the timer1 maximum interval is very short. Using 256 prescaler, maximum timer1 interval is only 26.843542 seconds !!!

The timer1 counters can be configured to support automatic reload.

As I’ve seen many people struggling with

  1. using blocking calls or not
  2. where and how to use them

I plan to implement next project to add more examples to show how to integrate there hardware timer interrupts into so-called mission-critical projects using Blynk as only GUI.

I’m also thinking about using some kind of preemptive RTOS (possibly FreeRTOS) to integrate with Blynk.

Any suggestion is welcome.

Regards,

Edited: Nov 25th 2019

New release v1.0.1

  1. Fix compatibility issue causing compiler error while using pre-1.8.10-Arduino-IDEs and/or pre-2.6.0-ESP8266-cores.
  2. Add examples to integrate with Blynk, where ISR + Hardware Timer Interrupt are used to process input / output. This way important tasks can avoid being blocked by connecting tasks.
1 Like

FYI:
The TimerInterrupt library has just been added to Arduino Library Manager.

It’s easier for you to use now.

2 Likes

New important release v1.0.2 of this ESP8266TimerInterrupt library

New features in version v1.0.2:

  1. Allow maximum 16 ISR-based timers ,
  2. The maximum interval is practically unlimited (limited only by unsigned long miliseconds)
  3. The accuracy is nearly perfect compared to software timers. The most important feature is they’re ISR-based timer. Therefore, their executions are not blocked by bad-behaving functions / tasks.
  4. These important features are absolutely necessary for mission-critical tasks.

The ISR_Timer_Complex example will demonstrate the nearly perfect accuracy compared to software timers by printing the actual elapsed millisecs of each type of timers. Being ISR-based timers, their executions are not blocked by bad-behaving functions / tasks, such as connecting to WiFi, Internet and Blynk services. You can also have many (up to 16) timers to use. This non-being-blocked important feature is absolutely necessary for mission-critical tasks.
You’ll see Software Timer is blocked while system is connecting to WiFi / Internet / Blynk, as well as by blocking task in loop(), using delay() function as an example. The elapsed time then is very un-accurate.

The following ISR_based FireSmokeAlarm is one example using ISR-based Time from this library for so-called mission-critical projects.

2 Likes

Dear @khoih,
first of all, many thanks for this library. Please tell me/us is it possible to use this library instead of BlynkTimer (SimpleTimer)? Could you have a real case example to see how this can be managed?
Thanks and Best Regards,
Mike Kranidis

1 Like

Dear @mikekgr,

Have a look at example, ISR_Timer_Complex, written for specially for Blynkers, where you can use the same syntax as BlynkTimer and the library is specially written to replace software timers. You can use it if absolutely necessary for mission-critical tasks. In this example, an LED is toggled every 5000ms by an ISR-based timer.

[https://github.com/khoih-prog/ESP8266TimerInterrupt/tree/master/examples/ISR_Timer_Complex]

You can see the terminal output like this:

Starting
ESP8266TimerInterrupt: _fre = 312500.00, _count = 15625
Starting  ITimer OK, millis() = 64
doingSomething2s: Delta ms = 2000   // ISR_Timer, 2000ms interval, still working even if blocked by WiFi/Blynk connecting tasks
doingSomething2s: Delta ms = 2000
doingSomething5s: Delta ms = 5000
Delta ms = 5000                     //ISR_Timer, 5000ms interval, blynking an LED
doingSomething2s: Delta ms = 2000
[6159] 
    ___  __          __
   / _ )/ /_ _____  / /__
  / _  / / // / _ \/  '_/
 /____/_/\_, /_//_/_/\_\
        /___/ v0.6.1 on NodeMCU

[6161] Protocol connect: timeout =9000
[6164] Connecting to ****.duckdns.org:8080
[6255] Ready (ping: 5ms).
Blynk connected
doingSomething2s: Delta ms = 2000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 4258  //Software Timer, 2000ms interval, blocked by WiFi/Blynk connecting tasks
doingSomething2s: Delta ms = 2000
doingSomething5s: Delta ms = 5000
doingSomething10s: Delta ms = 5000
Delta ms = 5000
doingSomething2s: Delta ms = 2000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3000  //Software Timer, 2000ms interval, blocked by delay() in loop() to demonstrate bad task.
doingSomething2s: Delta ms = 2000
doingSomething5s: Delta ms = 5000
Delta ms = 5000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3001
doingSomething2s: Delta ms = 2000
doingSomething2s: Delta ms = 2000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3000
doingSomething2s: Delta ms = 2000
doingSomething5s: Delta ms = 5000
doingSomething10s: Delta ms = 10000


2 Likes

This is the code FYI

/****************************************************************************************************************************
 * examples/ISR_Timer_Complex.ino
 * For ESP8266 boards
 * Written by Khoi Hoang
 * 
 * Built by Khoi Hoang https://github.com/khoih-prog/ESP32TimerInterrupt
 * Licensed under MIT license
 * Version: v1.0.2
 * 
 * The ESP8266 timers are badly designed, using only 23-bit counter along with maximum 256 prescaler. They're only better than UNO / Mega.
 * The ESP8266 has two hardware timers, but timer0 has been used for WiFi and it's not advisable to use. Only timer1 is available.
 * The timer1's 23-bit counter terribly can count only up to 8,388,607. So the timer1 maximum interval is very short.
 * Using 256 prescaler, maximum timer1 interval is only 26.843542 seconds !!!
 * 
 * Now with these new 16 ISR-based timers, the maximum interval is practically unlimited (limited only by unsigned long miliseconds)
 * The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers
 * Therefore, their executions are not blocked by bad-behaving functions / tasks.
 * This important feature is absolutely necessary for mission-critical tasks. 
 * 
 * Notes:
 * Special design is necessary to share data between interrupt code and the rest of your program.
 * Variables usually need to be "volatile" types. Volatile tells the compiler to avoid optimizations that assume 
 * variable can not spontaneously change. Because your function may change variables while your program is using them, 
 * the compiler needs this hint. But volatile alone is often not enough.
 * When accessing shared variables, usually interrupts must be disabled. Even with volatile, 
 * if the interrupt changes a multi-byte variable between a sequence of instructions, it can be read incorrectly. 
 * If your data is multiple variables, such as an array and a count, usually interrupts need to be disabled 
 * or the entire sequence of your code which accesses the data.
 *
*****************************************************************************************************************************/

/****************************************************************************************************************************
 * This example will demonstrate the nearly perfect accuracy compared to software timers by printing the actual elapsed millisecs.
 * Being ISR-based timers, their executions are not blocked by bad-behaving functions / tasks, such as connecting to WiFi, Internet
 * and Blynk services. You can also have many (up to 16) timers to use.
 * This non-being-blocked important feature is absolutely necessary for mission-critical tasks. 
 * You'll see blynkTimer is blocked while connecting to WiFi / Internet / Blynk, and elapsed time is very unaccurate
 * In this super simple example, you don't see much different after Blynk is connected, because of no competing task is
 * written
*****************************************************************************************************************************/
 
//These define's must be placed at the beginning before #include "ESP8266TimerInterrupt.h"
#define TIMER_INTERRUPT_DEBUG      1

#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG true

#include <ESP8266WiFi.h>

//#define USE_BLYNK_WM   true
#define USE_BLYNK_WM   false

#define USE_SSL     false

#if USE_BLYNK_WM
  #if USE_SSL
    #include <BlynkSimpleEsp8266_SSL_WM.h>        //https://github.com/khoih-prog/Blynk_WM
  #else
    #include <BlynkSimpleEsp8266_WM.h>            //https://github.com/khoih-prog/Blynk_WM
  #endif
#else
  #if USE_SSL
    #include <BlynkSimpleEsp8266_SSL.h>
    #define BLYNK_HARDWARE_PORT     9443
  #else
    #include <BlynkSimpleEsp8266.h>
    #define BLYNK_HARDWARE_PORT     8080   
  #endif
#endif

#if !USE_BLYNK_WM
  #define USE_LOCAL_SERVER    true
  
  // If local server
  #if USE_LOCAL_SERVER
    char blynk_server[]   = "yourname.duckdns.org";
    //char blynk_server[]   = "192.168.2.110";
  #else
    char blynk_server[]   = "";
  #endif

char auth[]     = "***";
char ssid[]     = "***";
char pass[]     = "***";

#endif

#include "ESP8266TimerInterrupt.h"
#include "ESP8266_ISR_Timer.h"

#ifndef LED_BUILTIN
#define LED_BUILTIN       2         // Pin D4 mapped to pin GPIO2/TXD1 of ESP8266, NodeMCU and WeMoS, control on-board LED
#endif

#define HW_TIMER_INTERVAL_MS        50

#define WIFI_TIMEOUT      20000L

volatile uint32_t lastMillis = 0;

// Init ESP32 timer 0
ESP8266Timer ITimer;

// Init BlynkTimer
ESP8266_ISR_Timer ISR_Timer;

BlynkTimer blynkTimer;

void ICACHE_RAM_ATTR TimerHandler(void)
{
  static bool toggle = false;
  static bool started = false;

  static int timeRun      = 0;

  ISR_Timer.run();

  // Toggle LED every 50 x 100 = 5000ms = 5s
  if (++timeRun == 100)
  { 
    timeRun = 0;

    if (!started)
    {
      started = true;
      pinMode(LED_BUILTIN, OUTPUT);
    }
  
    #if (TIMER_INTERRUPT_DEBUG > 0)
    Serial.println("Delta ms = " + String(millis() - lastMillis));
    lastMillis = millis();
    #endif
    
    //timer interrupt toggles pin LED_BUILTIN
    digitalWrite(LED_BUILTIN, toggle);
    toggle = !toggle;
  }
}

void ICACHE_RAM_ATTR doingSomething2s()
{
  static unsigned long previousMillis = lastMillis;
  Serial.println("doingSomething2s: Delta ms = " + String(millis() - previousMillis));
  previousMillis = millis();
}

void ICACHE_RAM_ATTR doingSomething5s()
{
  static unsigned long previousMillis = lastMillis;
  Serial.println("doingSomething5s: Delta ms = " + String(millis() - previousMillis));
  previousMillis = millis();
}

void ICACHE_RAM_ATTR doingSomething10s()
{
  static unsigned long previousMillis = lastMillis;
  Serial.println("doingSomething10s: Delta ms = " + String(millis() - previousMillis));
  previousMillis = millis();
}

void ICACHE_RAM_ATTR doingSomething50s()
{
  static unsigned long previousMillis = lastMillis;
  Serial.println("doingSomething50s: Delta ms = " + String(millis() - previousMillis));
  previousMillis = millis();
}

#define BLYNK_TIMER_MS        2000L
  
void blynkDoingSomething2s()
{
  static unsigned long previousMillis = lastMillis;
  Serial.println("blynkDoingSomething2s: Delta programmed ms = " + String(BLYNK_TIMER_MS) + ", actual = " + String(millis() - previousMillis));
  previousMillis = millis();
}

void setup()
{
  Serial.begin(115200);
  Serial.println("\nStarting");
  
  // Interval in microsecs
  if (ITimer.attachInterruptInterval(HW_TIMER_INTERVAL_MS * 1000, TimerHandler))
  {
    lastMillis = millis();
    Serial.println("Starting  ITimer OK, millis() = " + String(lastMillis));
  }
  else
    Serial.println("Can't set ITimer correctly. Select another freq. or interval");

  // Just to demonstrate, don't use too many ISR Timers if not absolutely necessary
  ISR_Timer.setInterval(2000L, doingSomething2s);  
  ISR_Timer.setInterval(5000L, doingSomething5s);  
  ISR_Timer.setInterval(10000L, doingSomething10s);  
  ISR_Timer.setInterval(50000L, doingSomething50s);

  // You need this timer for non-critical tasks. Avoid abusing ISR if not absolutely necessary.
  blynkTimer.setInterval(BLYNK_TIMER_MS, blynkDoingSomething2s);  


  #if USE_BLYNK_WM
    Blynk.begin();
  #else
    unsigned long startWiFi = millis();
    
    WiFi.begin(ssid, pass);
    
    do
    {
      delay(200);
      if ( (WiFi.status() == WL_CONNECTED) || (millis() > startWiFi + WIFI_TIMEOUT) )
        break;
    } while (WiFi.status() != WL_CONNECTED);
    
    Blynk.config(auth, blynk_server, BLYNK_HARDWARE_PORT);
    Blynk.connect();

    if (Blynk.connected())
      Serial.println("Blynk connected");
    else
      Serial.println("Blynk not connected yet");  
  #endif

}

#define BLOCKING_TIME_MS      3000L

void loop()
{
  static unsigned long previousMillis = lastMillis;
  
  Blynk.run();

  // This unadvised blocking task is used to demonstrate the blocking effects onto the execution and accuracy to Software timer
  // You see the time elapse of ISR_Timer still accurate, whereas very unaccurate for Software Timer
  // The time elapse for 2000ms software timer now becomes 3000ms (BLOCKING_TIME_MS)
  // While that of ISR_Timer is still prefect.
  delay(BLOCKING_TIME_MS);
  
  // You need this Software timer for non-critical tasks. Avoid abusing ISR if not absolutely necessary
  // You don't need to and never call ISR_Timer.run() here in the loop(). It's already handled by ISR timer.
  blynkTimer.run();
}

1 Like

I’m still trying to finish similar libraries for ESP32 and Arduino UNO/Mega.
Many more sleepless nights.

Updated: Nov 29th 2019

Done for Arduino UNO/Mega.

The ESP32 seems to create a lot of problems by the way the core was written. Hidden code, half FreeRTOS pseudo-multi-tasking based code, etc. create a lots of uncontrolled crashes, sometimes in the hidden code. Especially ESP32 WiFi library.

Take much more time than expected. More nights and coffee :smile:

2 Likes

Dear @khoih,
I appreciate your fast and helpful reply as well as your contribution to the community…

Thanks and Best Regards,
Mike Kranidis

2 Likes

Dear @mikekgr,

Thanks a lot for your really nice and encouraging words.
I’ve been learning a lot from many Blynkers, including you. Now I’m trying to pay back somehow as I have some free time.
If you have any mission-critical project and would like to try applying these ISR Timers, I’m willing to work with you to make it an good real-life example for other Blynkers.

Best Regards,

2 Likes

Hi @Madhukesh
I think it’s better to move the discussion here. Below is your original post

I’m sorry I’ve tried many ways, yet couldn’t duplicate that problem.
I’ve tried that exact sketch (including pins D1 for Relay and D3 for Button) with these following conditions while pressing the button continuously without any crash:

  1. Router powered OFF before board boots up. Board tries to connect without success.
  2. Then turn router ON, waiting for board connecting to WiFi, then board connected to Blynk successfully.
  3. While board connected to Blynk, the Ethernet cable to router removed to simulate Internet loss
  4. Put the Ethernet cable back in for reconnection
  5. Turn OFF the router, then back ON, and the board reconnects successfully.

Could you please try these following, one by one:

  1. Use brand new v1.0.2 of ESP8266TimerInterrupt
  2. Remove all other devices, but Relay + Button, connected to other pins.
  3. Use the same pin as in original sketch (pins D1 for Relay and D3 for Button)
  4. Try with another board.

I tested with both Arduino IDE 1.8.9 and 1.8.10 with ESP8266 core 2.5.2 and 2.6.1.

If you still have crash problem, you can post the exact copy of your sketch, as well as the stack dump so that I can debug and find out what the real issue is. Currently, I have no idea what’s going on.

Update Nov 28th 2019:

The crash was caused by the buggy WiFiManager. Removing it solved the crash issue.
One more example dealing with 2 SWs has been added to examples. Thanks to @Madhukesh.

1 Like

New important release v1.0.2 of this Arduino TimerInterrupt library (now included and installed using Arduino Library Manager)

New features in version v1.0.2:

  1. Allow maximum 16 ISR-based timers , while using just 1 rare-and-precious Hardware Timer.
  2. The maximum interval is practically unlimited (limited only by unsigned long miliseconds)
  3. The accuracy is nearly perfect compared to software timers. The most important feature is they’re ISR-based timer. Therefore, their executions are not blocked by bad-behaving functions / tasks.
  4. These important features are absolutely necessary for mission-critical tasks.

The ISR_Timer_Complex example will demonstrate the nearly perfect accuracy compared to software timers by printing the actual elapsed millisecs of each type of timers. Being ISR-based timers , their executions are not blocked by bad-behaving functions / tasks, such as connecting to WiFi, Internet and Blynk services. You can also have many (up to 16) timers to use. This non-being-blocked important feature is absolutely necessary for mission-critical tasks .

You’ll see Software Timer is blocked while system is connecting to WiFi / Internet / Blynk, as well as by blocking task in loop() , using delay() function as an example. The elapsed time then is very un-accurate.

Updated: Nov 30th 2019
Got updated on Arduino Library Manager

3 Likes

Updated December 2nd 2019

Release v1.0.2 of ESP8266TimerInterrupt library is tested OK with new 2.6.2 ESP8266 core and Arduino IDE 1.8.10.

The transition from core 2.6.1.to 2.6.2 is very smooth. No issue so far, even small.

It’s advisable to update to core 2.6.1 / 2.6.2 as flash code size is around 2KB smaller, more stable and resilient to weak WiFi environment thanks to core SDK22x_190703

Quote

After a massive update of all my devices to core 2.6.1 and several days of testings, I found the following related to SDKs:

SDK22x_190703
Uses 2k flash less than SDK22x_191105
No wifi/mqtt disconnections on devices with RSSI >35 (the same as core 2.3.0)

SDK22x_191024 and SDK22x_191105
Uses 2k flash more than SDK22x_190703
No wifi/mqtt disconnections on RSSI >55
On RSSI=(35-55) it performs really bad

I tested taking out as many interfering factors as possible. Tested and replicated with different wifi routers (MikroTik, TP-Link and Linksys), with different weather conditions (dry, wet, wind, hot, cold, nice), different ESP8266 boards (itead sonoff basic, sonoff pow R2, sonoff dual, NodeMCU -with proper power source-, MagicHome, Esp01, between others), correct tool-chain and same firmware file (Tasmota) in all devices (to rule out compilation issues - old toolchain produces wifi issues on SDK22x_191105)

So, as a result, Core 2.6.1 SDK22x_190703 performs better than any other core/SDK and should be the default.

Updated December 3rd 2019

Add example of dealing with 4 SWs using Struct Array (with ISR function pointer member of struct) . Thanks to @Madhukesh. Full code @:

3 Likes

Hello ! @khoih
Sure everyone gonna like this ISR based Switch… Thank you for the contribution !!! :clap::clap:

Finally the v1.0.2 library of ESP32TimerInterrupt, which provides similar yet powerful features as those libraries of ESP8266 and Arduino:

New features in version v1.0.2:

  1. Allow maximum 16 ISR-based timers while using just 1 hardware timer.
  2. The maximum interval is practically unlimited (limited only by unsigned long miliseconds)
  3. The accuracy is nearly perfect compared to software timers. The most important feature is they’re ISR-based timer. Therefore, their executions are not blocked by bad-behaving functions / tasks.
  4. These important features are absolutely necessary for mission-critical tasks.

The ISR_Timer_Complex example will demonstrate the nearly perfect accuracy compared to software timers by printing the actual elapsed millisecs of each type of timers. Being ISR-based timers , their executions are not blocked by bad-behaving functions / tasks, such as connecting to WiFi, Internet and Blynk services. You can also have many (up to 16) timers to use. This non-being-blocked important feature is absolutely necessary for mission-critical tasks .
You’ll see Software Timer is blocked while system is connecting to WiFi / Internet / Blynk, as well as by blocking task in loop() , using delay() function as an example. The elapsed time then is very un-accurate.

This is partial terminal output when running the example

[https://github.com/khoih-prog/ESP32TimerInterrupt/tree/master/examples/ISR_Timer_Complex]


Starting
ESP32TimerInterrupt: _timerNo = 1, _fre = 1000000.00, _count = 0 - 50000
Starting  ITimer OK, millis() = 2140
[2341] 
    ___  __          __
   / _ )/ /_ _____  / /__
  / _  / / // / _ \/  '_/
 /____/_/\_, /_//_/_/\_\
        /___/ v0.6.1 on ESP32

[2342] Protocol connect: timeout =9000
[2346] BlynkArduinoClient.connect: Connecting to ****.duckdns.org:8080
[2506] Ready (ping: 35ms).
Blynk connected
2s: D ms = 2000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3435
2s: D ms = 2000
5s: D ms = 5000
2s: D ms = 2000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3000
2s: D ms = 2000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3000
2s: D ms = 2000
5s: D ms = 5000
11s: D ms = 11000
2s: D ms = 2000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3000
2s: D ms = 2000
5s: D ms = 5000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3002
2s: D ms = 2000
2s: D ms = 2000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3000
2s: D ms = 2000
5s: D ms = 5000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3000
2s: D ms = 2000
11s: D ms = 11000
2s: D ms = 2000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3000
5s: D ms = 5000
2s: D ms = 2000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3002
2s: D ms = 2000
2s: D ms = 2000
5s: D ms = 5000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3000
2s: D ms = 2000
11s: D ms = 11000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3000
2s: D ms = 2000
5s: D ms = 5000
2s: D ms = 2000
blynkDoingSomething2s: Delta programmed ms = 2000, actual = 3000
....
1 Like