Error re-using a non-blocking interval timer?!

Hi Community,

I'm getting inconsistent results with a small non-blocking interval timer function when using multiple instances of it within a sketch. Rather than rewrite the same piece of code at the start of every function that requires an interval between actions, I wrote a small function intervalTimer() which returns true or false. I've seen this general method documented many times, but I can't find a specific example that answers why I'm getting inconsistent results. I've tried it on various Uno and Nano boards with the same type of error.

The desired output is all three messages printed every second, but the output shows only one message being printed each second. Which message is printed seems random, causing some functions to have an actual interval multiple times the desired interval.

I wonder if this is a memory issue as the interval timer function works perfectly when only called by one function. My understanding is that each time this function is incorporated within another function, a new memory space is allocated for the local static variables, but I'm starting to think I've misunderstood this, given the output.

I would really appreciate if someone could explain why I'm seeing this behaviour and offer a recommendation for an alternative that isn't just repeating code. I had thought about creating the interval function within a class to allow for multiple timer objects, but I wanted to try and understand what's happening here first so I can apply this learning to other projects.

Full code:

// call timer function at the begining of a function with an 'if not, return' statement.
bool intervalTimer(uint16_t interval /*milliseconds*/)
{
  // last trigger marker.
  static uint32_t lastTriggerMk;
  // read current time.
  uint32_t currentTime = millis();
  // has interval elapsed?
  if (currentTime - lastTriggerMk < interval)
  {
    // no, exit false.
    return false;
  }
  // yes, reset last trigger mark to current time.
  lastTriggerMk = currentTime;
  // exit true.
  return true;
}

// prints a message when function is triggered, along with how long sinse it was last triggered.
void functionOne()
{
  if (!intervalTimer(1000))
  {
    return;
  }
  static uint32_t lastMillis;
  uint32_t currentMillis = millis();
  uint16_t actualInterval = currentMillis - lastMillis;
  lastMillis = currentMillis;
  Serial.print(F("Function 1 triggered. Interval: "));
  Serial.println(actualInterval);
  Serial.println(F("--------"));
}

// prints a message when function is triggered, along with how long sinse it was last triggered.
void functionTwo()
{
  if (!intervalTimer(1000))
  {
    return;
  }
  static uint32_t lastMillis;
  uint32_t currentMillis = millis();
  uint16_t actualInterval = currentMillis - lastMillis;
  lastMillis = currentMillis;
  Serial.print(F("Function 2 triggered. Interval: "));
  Serial.println(actualInterval);
  Serial.println(F("--------"));
}

// prints a message when function is triggered, along with how long sinse it was last triggered.
void functionThree()
{
  if (!intervalTimer(1000))
  {
    return;
  }
  static uint32_t lastMillis;
  uint32_t currentMillis = millis();
  uint16_t actualInterval = currentMillis - lastMillis;
  lastMillis = currentMillis;
  Serial.print(F("Function 3 triggered. Interval: "));
  Serial.println(actualInterval);
  Serial.println(F("--------"));
}

// runs all 3 test functions in a loop.
void testOne()
{
  functionOne();
  functionTwo();
  functionThree();
}

//////// //////// //////// //////// //////// //////// //////// ////////
void setup()
{
  // put your setup code here, to run once:
  Serial.begin(115200);
}

void loop()
{
  // put your main code here, to run repeatedly:
  testOne();
}
//////// //////// //////// //////// //////// //////// //////// ////////

Serial Output:

[Starting] Opening the serial port - COM4
Function 3 triggered. Interval: 1000
--------
Function 3 triggered. Interval: 1000
--------
Function 3 triggered. Interval: 1000
--------
Function 3 triggered. Interval: 1000
--------
Function 3 triggered. Interval: 1000
--------
Function 3 triggered. Interval: 1000
--------
Function 3 triggered. Interval: 1000
--------
Function 3 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 9000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 2 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 25000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 1 triggered. Interval: 1000
--------
Function 3 triggered. Interval: 37000
--------
Function 1 triggered. Interval: 2000
--------
Function 3 triggered. Interval: 2000
--------
Function 1 triggered. Interval: 2000
--------
[Done] Closed the serial port 

Thanks in advance for any help! Tom

Each time you call the intervalTimer function you use the value of lastTriggerMk to determine whether the period has elapsed. If you call the intervalTimer function more than once before concurrent periods have ended then will that work ?

A class would have the lastTriggerMk variable declared as private so that each instance of the class would have its one lastTriggerMk variable

Thanks @UKHeliBob.

From what you're saying I think I've misunderstood that local variables are also private / discrete variables. So in the example there aren't 3 discrete versions of lastTriggerMk, 1 for each of the test functions, but a single lastTriggerMk that is called by all 3 test functions?

I think it's time to learn how to write classes properly and try that way.

The scope of a local variable declared at the start of a function is the function. However, if you call the same function several times the variable is not private to the occasion on which it was called.

The static keyword also goes against what you want because it causes the value of the variable to be defined just once and on every other occasion that the definition occurs it is ignored and the variable retains its current value

Go for it !

1 Like

Another way to do it is to have each separate interval timer have its own start time variable.

// call timer function at the beginning of a function with an 'if not, return' statement.
bool intervalTimer(uint32_t &intervalStart, uint16_t interval /*milliseconds*/)
{

  // read current time.
  uint32_t currentTime = millis();
  // has interval elapsed?
  if (currentTime - intervalStart < interval)
  {
    // no, exit false.
    return false;
  }
  // yes, reset last trigger mark to current time.
  intervalStart = currentTime;
  // exit true.
  return true;
}

// prints a message when function is triggered, along with how long since it was last triggered.
void functionOne()
{
  // last trigger marker.
  static uint32_t intervalStart = 0;
  if (!intervalTimer(intervalStart, 1000))
  {
    return;
  }
1 Like

Thanks @johnwasser.

Can I ask why intervalStart has an address pointer at the start of the function but not at any of the other references? I understand the basics of pointers, but I'm not sure why it would work in this example.

Thanks again for the alternative solution! Seeing differing methods solve the same issue really help tie different concepts together.

It's not a pointer, it's a reference. Similar, but very different. Google "c++ references".

1 Like

The '&' in the function argument tells the compiler to pass the argument 'by reference' instead of 'by value'. Integer types are normally 'passed by value' where a copy of the value is put on the stack and nothing the function can do will change the variable being passed. With 'pass by reference' the function gets the address of the variable and can write to the original variable. It's like a pointer but the dereference (*) is automatic.

To get the same effect but with a pointer:

  static uint32_t intervalStart = 0;
  if (!intervalTimer(&intervalStart, 1000))

bool intervalTimer(uint32_t *intervalStart, uint16_t interval /*milliseconds*/)
{
  // yes, reset last trigger mark to current time.
  *intervalStart = currentTime;
  static uint32_t intervalStart = 0;
  if (!intervalTimer(intervalStart, 1000))

bool intervalTimer(uint32_t &intervalStart, uint16_t interval /*milliseconds*/)
{
  // yes, reset last trigger mark to current time.
  intervalStart = currentTime;
1 Like

@johnwasser @RayLivingston It looks like I understand a lot less about pointers and references than I thought, but between a bit of reading and your explanations I think I know why a reference is used here.

To make sure I can use this functionally and to leave this topic with a working example I have tried both the reference method and a home-brew library.

Following @johnwasser's example, this is the code rewritten fully and working as expected:

// test of non-blocking interval timer function
// function passes in reference to 'last trigger mark' variable created in the function
// where the timer is required.

// interval timer function
bool intervalTimer(uint32_t &intervalStart, uint16_t interval)
{
    // read current time.
    uint32_t currentTime = millis();
    // has interval elapsed?
    if (currentTime - intervalStart < interval)
    {
        // no, exit false.
        return false;
    }
    // yes, reset last trigger mark to current time.
    intervalStart = currentTime;
    // exit true.
    return true;
}

// prints a message when function is triggered, along with how long since it was last triggered.
void functionOne()
{
    static uint32_t intervalTimerMk = 0;
    if (!intervalTimer(intervalTimerMk, 1000))
    {
        return;
    }
    static uint32_t lastMillis;
    uint32_t currentMillis = millis();
    uint16_t actualInterval = currentMillis - lastMillis;
    lastMillis = currentMillis;
    Serial.print(F("Function 1 triggered. Interval: "));
    Serial.println(actualInterval);
    Serial.println(F("--------"));
}

// prints a message when function is triggered, along with how long since it was last triggered.
void functionTwo()
{
    static uint32_t intervalTimerMk = 0;
    if (!intervalTimer(intervalTimerMk, 1000))
    {
        return;
    }
    static uint32_t lastMillis;
    uint32_t currentMillis = millis();
    uint16_t actualInterval = currentMillis - lastMillis;
    lastMillis = currentMillis;
    Serial.print(F("Function 2 triggered. Interval: "));
    Serial.println(actualInterval);
    Serial.println(F("--------"));
}

// prints a message when function is triggered, along with how long since it was last triggered.
void functionThree()
{
    static uint32_t intervalTimerMk = 0;
    if (!intervalTimer(intervalTimerMk, 1000))
    {
        return;
    }
    static uint32_t lastMillis;
    uint32_t currentMillis = millis();
    uint16_t actualInterval = currentMillis - lastMillis;
    lastMillis = currentMillis;
    Serial.print(F("Function 3 triggered. Interval: "));
    Serial.println(actualInterval);
    Serial.println(F("--------"));
}

void testSingle()
{
    functionOne();
}

void testMultiple()
{
    functionOne();
    functionTwo();
    functionThree();
}

/////// //////// //////// //////// //////// //////// //////// ////////
void setup()
{
    // put your setup code here, to run once:
    Serial.begin(115200);
}

void loop()
{
    // put your main code here, to run repeatedly:
    //   testSingle();
    testMultiple();
}
//////// //////// //////// //////// //////// //////// //////// ////////

I'm a new user so I can't upload a zip file with the library files yet, so I've added the code here.

Example file:

#include "Interval_Timer.h"

Interval_Timer timerOne(1000);
Interval_Timer timerTwo(1000);
Interval_Timer timerThree(1000);

// prints a message when function is triggered, along with how long since it was last triggered.
void functionOne()
{
    if (!timerOne.intervalTimer())
    {
        return;
    }
    static uint32_t lastMillis;
    uint32_t currentMillis = millis();
    uint16_t actualInterval = currentMillis - lastMillis;
    lastMillis = currentMillis;
    Serial.print(F("Function 1 triggered. Interval: "));
    Serial.println(actualInterval);
    Serial.println(F("--------"));
}

// prints a message when function is triggered, along with how long since it was last triggered.
void functionTwo()
{
    if (!timerTwo.intervalTimer())
    {
        return;
    }
    static uint32_t lastMillis;
    uint32_t currentMillis = millis();
    uint16_t actualInterval = currentMillis - lastMillis;
    lastMillis = currentMillis;
    Serial.print(F("Function 2 triggered. Interval: "));
    Serial.println(actualInterval);
    Serial.println(F("--------"));
}

// prints a message when function is triggered, along with how long since it was last triggered.
void functionThree()
{
    if (!timerThree.intervalTimer())
    {
        return;
    }
    static uint32_t lastMillis;
    uint32_t currentMillis = millis();
    uint16_t actualInterval = currentMillis - lastMillis;
    lastMillis = currentMillis;
    Serial.print(F("Function 3 triggered. Interval: "));
    Serial.println(actualInterval);
    Serial.println(F("--------"));
}

// runs all 3 test functions in a loop.
void testOne()
{
    functionOne();
    functionTwo();
    functionThree();
}

void testTwo()
{
    functionOne();
}

//////// //////// //////// //////// //////// //////// //////// ////////
void setup()
{
    // put your setup code here, to run once:
    Serial.begin(115200);
}

void loop()
{
    // put your main code here, to run repeatedly:
    testOne();
    // testTwo();
}
//////// //////// //////// //////// //////// //////// //////// ////////

Header File:

/*
Interval_Timer.h
Use this library to create a timer object for any functions which require individual timers.
Main function produces a true or false flag to indicate when a function should continue.

Created by Tom Griffiths, 02 July, 2022.
Released into the public domain with no liability or limitation.
For anyone with interest in doing so, this is a very novice first attempt at a library,
reformatting it into the preferred arduino standard would be an excellent learning tool
and greatly appreciated.
I can be found on the arduino forum @tomwrgriff. Thanks and happy coding.
*/

#ifndef INTERVAL_TIMER_H
#define INTERVAL_TIMER_H

#include <Arduino.h>

class Interval_Timer
{
private:
    uint16_t _interval; // length of interval in milliseconds.
    uint32_t _mk;       // last trigger marker.
    uint32_t _time;     // current time at start of intervalTimer function.
public:
    Interval_Timer(uint32_t interval);
    bool intervalTimer();
};

#endif

CPP File:

/*
Interval_Timer.cpp
Use this library to create a timer object for any functions which require individual timers
Main function produces a true or false flag to indicate when a function should continue.

Created by Tom Griffiths, 02 July, 2022.
Released into the public domain with no liability or limitation.
For anyone with interest in doing so, this is a very novice first attempt at a library,
reformatting it into the preferred arduino standard would be an excellent learning tool
and greatly appreciated.
I can be found on the arduino forum @tomwrgriff. Thanks and happy coding.
*/

#include "Interval_Timer.h"

// construct interval timer object and define interval duration.
Interval_Timer::Interval_Timer(uint32_t interval)
{
    _interval = interval;
    _mk = millis();
    _time = millis();
}

// call function at the begining of a function to act as action gate.
// if (! intervalTimer()) { return; }
// returns false if interval has not passed.
// returns true if interval has passed.
bool Interval_Timer::intervalTimer()
{
    // read current time.
    uint32_t _time = millis();
    // has interval elapsed?
    if (_time - _mk < _interval)
    {
        // no, exit false.
        return false;
    }
    // yes, reset last trigger mark to current time.
    _mk = _time;
    // exit true.
    return true;
}

To everyone who contributed to this topic, thank-you for sharing your knowledge!

Tom

Even a class variable declared 'public' will have a separate value for each instance.

Note: You are not using the member variable '_time' because you are creating a local variable of the same name. I would keep the local variable and remove the unused member variable to save space.

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.