Calling function multiple times - will they affect each other?

Hello everyone,

I'm pretty new to all this Arduino stuff. In my everyday I work as an Automation engineer, programming PLCs for a living.

In my spare time I like programming Arduino, for the fun of it. But I miss some of the functions that by default are available in PLCs.

Lately I've been working on re-creating my own PLC-functions. One of the commonly used functions for me is Time On Delay (a timer that doesn't block program execution, much like the "Blink Without Delay" example sketch).

I came to wonder if it is even possible to use this function multiple times in the same program, counting separate delays?

// ************************************************************
// On-delay timer
// ************************************************************
bool TON(bool IN, unsigned long PT) {

  // DEFINE LOCALLY USED VARIABELS
  static unsigned long TimeCurrent;   // STORES TIME (MS) SINCE DEVICE START
  static unsigned long TimePrevious;  // STORES MOMENTARY SYSTEM TIME UPON ACTIVATING INPUT
  static bool StartFlag;              // RISING EDGE DETECTION FOR INPUT

  TimeCurrent = millis();

  // STORE SYSTEM TIME ON INPUT
  if(IN and !StartFlag) {
    TimePrevious      = TimeCurrent;
    StartFlag         = HIGH;
  }

  // RESET START AND DONE FLAGS, RETURN FALSE
  if(!IN) {
    StartFlag         = LOW;
    
    return LOW;
  }

  // CHECK IF ASSIGNED TIME HAS PASSED, ASSIGN RETURN VALUE TRUE
  if(TimeCurrent - TimePrevious >= PT and IN) {
    return HIGH;
  }
  
}

// ************************************************************
// ************************************************************

Say I want 2 individual on-delays with specifically 1000 ms delay and 3000 ms delay.

Will calling them like this work? (since the program will sweep across the call and call the same function twice?)

TON(HIGH, 1000);
TON(HIGH, 3000);

Or can I somehow instantiate individual instances of the function?

Also, bonus question:

If I want two return values, or the ability to access properties of the function (eg. a local variable accessible by namespace "TON.NAME_OF_VAR"), will this be possible without adding a global variable? I want to use this to return both "ET" (Elapsed Time) and "Q" (Output bool).

Thanks in advance!

Christian

How and Why to avoid delay()

How to use FreeRTOS with Arduino – Real time operating system

A class is what you need. Then it can have its own storage for the duration and other parameters.

If you don't feel like learning to write a class just yet, pick a library from here: Arduino playground - Timing

The important parameters for the timer are the start time and the duration; you can put those in a class (as suggested) or a struct; I'm far more a C programmer than a C++ programmer so I use a struct in below example.

// define a timer struct
struct MYTIMER
{
  uint32_t startTime;       // when timer is started
  uint32_t duration;        // how long
  bool isRunning;           // flag indicating if timer is running
};

// declare two timers
MYTIMER myTimers[] =
{
  {0, 500, false},
  {0, 1200, false},
};

Next you can add the below function; it basically checks if a timer is running or has lapsed and returns.

/*
   execute a 'timer'
   In:
     number of timer (index in myTimers array)
   Returns:
     true if timer is running, else false
*/
bool runTimer(uint8_t whichTimer)
{
  // if the timer is not running, start the timer
  if (myTimers[whichTimer].isRunning == false)
  {
    // set the start time
    myTimers[whichTimer].startTime = millis();
    // indicate that the timer is running
    myTimers[whichTimer].isRunning = true;
  }

  // if duration has lapsed
  if (millis() - myTimers[whichTimer].startTime >= myTimers[whichTimer].duration)
  {
    // reset isRunning flag
    myTimers[whichTimer].isRunning = false;
  }

  // indicate to caller if timer is running
  return myTimers[whichTimer].isRunning;
}

To prevent stupid mistakes (accessing a non-existing myTimers), add the below at the top of your code

// macro to determine number of elements in any type of array
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))

Next place the below in the beginning of runTimer()

bool runTimer(uint8_t whichTimer)
{
  if (whichTimer >= NUMELEMENTS(myTimers))
  {
    // inform user about mistake
    Serial.println("Invalid timer index");
    // hang forever
    for (;;);
  }

Lastly the setup() and loop() functions

void setup()
{
  Serial.begin(57600);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  // timer 0 as delay; when the duration has lapsed, toggle the built-in led
  if (runTimer(0) == false)
  {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  }
}

In the next step, you can use a pointer to a function that will be executed when the timer starts or when the timer lapses. First write a function that (for above example) toggles the led

/*
  toggle the built-in led
*/
void toggleLedBuiltin()
{
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}

Next modify the struct and add a function pointer; in the below, I added a pointer to a function that will be executed when the timer is started and a pointer to a function that will be executed when the timer has lapsed.

struct MYTIMER
{
  uint32_t startTime;       // when delay is started
  uint32_t duration;        // how long
  bool isRunning;           // flag indicating if timer is running

  void (*startFunction)();  // function to execute when timing starts
  void (*endFunction)();    // function to execute when timing ends
};

// function prototypes
void toggleLedBuiltin();

// declare two timers
MYTIMER myTimers[] =
{
  {0, 2000, false, NULL, toggleLedBuiltin},
  {0, 1200, false, NULL, NULL},
};

In above example, there is only a function that will be called when myTimer[0] lapses. myTimer[1] can be used as a millis() based 'delay'.

To be able to use toggleLedBuitin in the timer declaration, the compiler needs to know about it existence. I'm still using IDE 1.8.5 where the automatic prototyping by the IDE fails and hence I'v e added the prototype; you will need to add all prototypes for functions that are references in the struct.

Next you can modify the runTimer() function to execute the functions at start and end

/*
   execute a 'timer'
   In:
     number of timer (index in myTimers array)
   Returns:
     true if timer is running, else false
*/
bool runTimer(uint8_t whichTimer)
{
  if (whichTimer >= NUMELEMENTS(myTimers))
  {
    // inform user about mistake
    Serial.println("Invalid timer index");
    // hang forever
    for (;;);
  }
  // if the timer is not running, 'start' the timer
  if (myTimers[whichTimer].isRunning == false)
  {
    // set the start time
    myTimers[whichTimer].startTime = millis();
    // indicate that the timer is running
    myTimers[whichTimer].isRunning = true;

    // execute the start function if set
    if (myTimers[whichTimer].startFunction != NULL)
    {
      myTimers[whichTimer].startFunction();
    }
  }

  // if duration has lapsed
  if (millis() - myTimers[whichTimer].startTime >= myTimers[whichTimer].duration)
  {
    // reset isRunning flag
    myTimers[whichTimer].isRunning = false;
    // execute the end function if set
    if (myTimers[whichTimer].endFunction != NULL)
    {
      myTimers[whichTimer].endFunction();
    }
  }

  // indicate to caller if timer is running
  return myTimers[whichTimer].isRunning;
}

Your loop() can no be modified as shown below

void loop()
{
  runTimer(0);
}

I hope this gets you on the way; tested on a Leonardo.

For your convenience, the full code for this

// macro to determine number of elements in any type of array
#define NUMELEMENTS(x) (sizeof(x) / sizeof(x[0]))

// define a timer struct
struct MYTIMER
{
  uint32_t startTime;       // when delay is started
  uint32_t duration;        // how long
  bool isRunning;           // flag indicating if timer is running

  void (*startFunction)();  // function to execute when timing starts
  void (*endFunction)();    // function to execute when timing ends
};

// function prototypes
void toggleLedBuiltin();


// declare two timers
MYTIMER myTimers[] =
{
  {0, 2000, false, NULL, toggleLedBuiltin},
  {0, 1200, false, NULL, NULL},
};

void setup()
{
  Serial.begin(57600);
  while (!Serial);
  Serial.print("Number of timers = ");
  Serial.println(NUMELEMENTS(myTimers));
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
  runTimer(0);
}

/*
   execute a 'timer'
   In:
     number of timer (index in myTimers array)
   Returns:
     true if timer is running, else false
*/
bool runTimer(uint8_t whichTimer)
{
  if (whichTimer >= NUMELEMENTS(myTimers))
  {
    // inform user about mistake
    Serial.println("Invalid timer index");
    // hang forever
    for (;;);
  }
  // if the timer is not running, 'start' the timer
  if (myTimers[whichTimer].isRunning == false)
  {
    // set the start time
    myTimers[whichTimer].startTime = millis();
    // indicate that the timer is running
    myTimers[whichTimer].isRunning = true;

    // execute the start function if set
    if (myTimers[whichTimer].startFunction != NULL)
    {
      myTimers[whichTimer].startFunction();
    }
  }

  // if duration has lapsed
  if (millis() - myTimers[whichTimer].startTime >= myTimers[whichTimer].duration)
  {
    // reset isRunning flag
    myTimers[whichTimer].isRunning = false;
    // execute the end function if set
    if (myTimers[whichTimer].endFunction != NULL)
    {
      myTimers[whichTimer].endFunction();
    }
  }

  // indicate to caller if timer is running
  return myTimers[whichTimer].isRunning;
}

/*
  toggle the built-in led
*/
void toggleLedBuiltin()
{
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}