Firing multiple functions with a fixed interval between them - non blocking?

Status
Not open for further replies.

Rezo

Well-known member
Hi,

Lets say I have 10 functions I would like to run, we can call them myFunction1 through myFunction10.

I want to run each function with a fixed interval between them, lets say 100ms:

Code:
myFunction1 ()
  //wait 100ms
myFunction2()
  //wait 100ms
myFunction3()
  //wait 100ms
..........


But, I want to do this non blocking, as I have other functions that need to run as fast as possible in the main loop.

And suggestion on best method to do this? I've been playing with millis() and elapsedMillis for a few hours now and all I can do is scratch my head
 
One method is to call the functions from one shot timers. You'd start timer1 which will call func1 after some delay time. At the end of func1 you trigger timer2 which will call function 2 and so on.
Here a working example using the TeensyTimerTool (https://github.com/luni64/TeensyTimerTool) to provide the timers

Code:
#include "TeensyTimerTool.h"
using namespace TeensyTimerTool;

OneShotTimer t0(TCK), t1(TCK), t2(TCK); // define 3 one shot timers (20 available) 

void setup()
{
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);

  t0.begin(func0);  // set callback functions of the timers
  t1.begin(func1);
  t2.begin(func2);

  t0.trigger(100'000); // start sequence by calling func0 after 100ms
}

void loop()  
{
  static int cnt=0;  // simulate some load
  Serial.println(cnt++);
  delay(1); // don't overrun the sermon
}

void func0()  // the functions just blink a pin to check with the LA
{
  digitalWriteFast(0, HIGH);
  delay(1);
  digitalWriteFast(0, LOW);

  t1.trigger(150'000); // call func1 150ms after func0 is finished
}

void func1()
{
  digitalWriteFast(1, HIGH);
  delay(1);
  digitalWriteFast(1, LOW);

  t2.trigger(50'000); // call func2 50ms after func1 is finished
}

void func2()
{
  digitalWriteFast(2, HIGH);
  delay(1);
  digitalWriteFast(2, LOW);

  t0.trigger(250'000); // call func0 50ms after func2 is finished
}

Here the result:

functions.png

Of course there are other solutions like having the function pointers in an array and work through this array on a timer interrupt and....


But, I want to do this non blocking, as I have other functions that need to run as fast as possible in the main loop.
Of course there is no true 'non blocking' on a single core mcu. While your functions run they will necessarily block the main loop, so keep them short...
 
Last edited:
And here a quick "function pointer solution" which works if you only need a constant interval between functions
Code:
using func_t = void (*)();                                                // c++ version of a typedef
constexpr func_t functions[] = {func0, func1, func2};                     // array containing pointers to your functions 
constexpr unsigned nrOfFuncs = sizeof(functions) / sizeof(functions[0]);  // calculate at compile time to avoid error when adding functions

void callFunction()
{
  static unsigned curFunc = 0; 
  
  functions[curFunc]();   
  curFunc++;
  if(curFunc >= nrOfFuncs) curFunc = 0; 
}


IntervalTimer t; 

void setup()
{ 
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);

  t.begin(callFunction, 100'000);   
}

void loop()
{
  static int cnt=0;  // simulate some load
  Serial.println(cnt++);
  delay(1); // don't overrun the sermon
}

void func0()
{
  digitalWriteFast(0, HIGH);
  delay(1);
  digitalWriteFast(0, LOW);  
}

void func1()
{
  digitalWriteFast(1, HIGH);
  delay(1);
  digitalWriteFast(1, LOW);
}

void func2()
{
  digitalWriteFast(2, HIGH);
  delay(1);
  digitalWriteFast(2, LOW); 
}
 
Last edited:
And here a quick "function pointer solution" which works if you only need a constant interval between functions
Code:
using func_t = void (*)();                                                // c++ version of a typedef
constexpr func_t functions[] = {func0, func1, func2};                     // array containing pointers to your functions 
constexpr unsigned nrOfFuncs = sizeof(functions) / sizeof(functions[0]);  // calculate at compile time to avoid error when adding functions

void callFunction()
{
  static unsigned curFunc = 0; 
  
  functions[curFunc]();   
  curFunc++;
  if(curFunc >= nrOfFuncs) curFunc = 0; 
}


IntervalTimer t; 

void setup()
{ 
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);

  t.begin(callFunction, 100'000);   
}

void loop()
{
  static int cnt=0;  // simulate some load
  Serial.println(cnt++);
  delay(1); // don't overrun the sermon
}

void func0()
{
  digitalWriteFast(0, HIGH);
  delay(1);
  digitalWriteFast(0, LOW);  
}

void func1()
{
  digitalWriteFast(1, HIGH);
  delay(1);
  digitalWriteFast(1, LOW);
}

void func2()
{
  digitalWriteFast(2, HIGH);
  delay(1);
  digitalWriteFast(2, LOW); 
}

I like this approach, seems to be what I'm looking for.
But, tried to compile and test on a T3.2 and getting the following error:
Code:
sketch_feb19c:5: error: 'func0' was not declared in this scope
 constexpr func_t functions[] = {func0, func1, func2};                     // array containing pointers to your functions 
                                 ^
sketch_feb19c:5: error: 'func1' was not declared in this scope
 constexpr func_t functions[] = {func0, func1, func2};                     // array containing pointers to your functions 
                                        ^
sketch_feb19c:5: error: 'func2' was not declared in this scope
 constexpr func_t functions[] = {func0, func1, func2};                     // array containing pointers to your functions 
                                               ^
Multiple libraries were found for "TeensyTimerTool.h"
 Used: /Applications/Teensyduino.app/Contents/Java/hardware/teensy/avr/libraries/TeensyTimerTool
'func0' was not declared in this scope

I know this is not true non-blocking, but the functions I need to execute in the fixed interval take less than 1ms to process, each.
 
Sorry, I'm usually not using the Arduino builder and thought it doesn't need the forward declarations

So, either forward declare the functions
Code:
void func0();
void func1();
void func2();

using func_t = void (*)();
constexpr func_t functions[] = {func0, func1, func2};
constexpr unsigned nrOfFuncs = sizeof(functions) / sizeof(functions[0]);
....

or move the complete definitions above the array definition.
 
Thank you both.

Just implemented it into my main sketch and I'm at the last hurdle.
I'm passing a HEX value in each function (I store them with alphabetic names in an enum)

Getting this final error
Code:
error: void value not ignored as it ought to be

Sorry, this stuff is a little complex for me as I am not proficient in C++ as I want to be
 
Sure :)

Code:
enum {
  INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 0x0b
}

void canSend_01(uint8_t pid){
CAN_message_t msgTx, msgRx;
  msgTx.buf[0] = 0x02;  
  msgTx.buf[1] = 0x01;
  msgTx.buf[2] = pid;  
  msgTx.buf[3] = 0;
  msgTx.buf[4] = 0;  
  msgTx.buf[5] = 0;
  msgTx.buf[6] = 0;  
  msgTx.buf[7] = 0;
  msgTx.len = 8; 
  msgTx.flags.extended = 0; 
  msgTx.flags.remote = 0;
  msgTx.id = 0x7E0; 
  Can0.write(msgTx);  
}

constexpr func_t functions1[] = {canSend_01(INTAKE_MANIFOLD_ABSOLUTE_PRESSURE)};

Very slim snippet. but this is the basic form of it.
 
That's good, but I don't understand how you want to call this. Is canSend_01 always getting INTAKE_MANIFOLD_ABSOLUTE_PRESSURE as argument? And canSend_02 gets the next one from the enum? always or is this variable? But if it is fixed why pass it as parameter?

Here at least a compiling example. But I'm not sure if this is what you want. Might be helpful to explain what you want to achieve.

Code:
enum
{
  INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 0x0b
};

void canSend_01(uint8_t pid)
{
  // CAN_message_t msgTx, msgRx;
  // msgTx.buf[0] = 0x02;
  // msgTx.buf[1] = 0x01;
  // msgTx.buf[2] = pid;
  // msgTx.buf[3] = 0;
  // msgTx.buf[4] = 0;
  // msgTx.buf[5] = 0;
  // msgTx.buf[6] = 0;
  // msgTx.buf[7] = 0;
  // msgTx.len = 8;
  // msgTx.flags.extended = 0;
  // msgTx.flags.remote = 0;
  // msgTx.id = 0x7E0;
  //Can0.write(msgTx);
}

using func_t = void (*)(uint8_t);
constexpr func_t functions[] = {canSend_01};
constexpr unsigned nrOfFuncs = sizeof(functions) / sizeof(functions[0]);

void callFunction()
{
  static unsigned curFunc = 0;

  functions[curFunc](INTAKE_MANIFOLD_ABSOLUTE_PRESSURE);

  curFunc++;
  if(curFunc >= nrOfFuncs) curFunc = 0; 
}
 
My bad, I should have posted a more realistic example. I'll elaborate.

There are two functions, void canSend01(uint8_t pid) and void canSend22 (uint16_t pid)
Why do I have two? one takes 8 byte PIDs and the other takes 16 byte PID's that are all declared within the enum, but, they have different configurations for transmitting CAN frames.
I call these functions, which invokes an interrupt when a CAN response is received and updates an array that stores the data. Then I call an update to a display with the data from the array.

Each of these functions in called multiple times with different values from the enum.
Here is the actual declaration I have in the code at the moment
Code:
constexpr func_t functions1[] = {canSend_01(INTAKE_MANIFOLD_ABSOLUTE_PRESSURE), canSend_22(CHARGE_PRESSURE_ACTUAL), canSend_01(ABSOLULTE_BAROMETRIC_PRESSURE),canSend_01(OXYGEN_SENSOR_1_FUEL_AIR_EQUIVALENCE_RATIO)};


I can elaborate more if needed
 
Very slim snippet.

Please don't be shy. Many of us here can help you with these details, but only if you show us enough code. C++ is a complicated language, where fine details matter. In cases like this, where we can't see the definition of things "func_t", the help we can give you is really limited.
 
Ok, I think I got it.

So, you want to call different functions with different arguments automatically in the background. Until now the function pointer array only contains information about the sequence of the functions to be called but no information about the required arguments.

Therefore you want to do something like this:
Code:
constexpr func_t functions1[] = {canSend_01(INTAKE_MANIFOLD_ABSOLUTE_PRESSURE), canSend_22(CHARGE_PRESSURE_ACTUAL), canSend_01(ABSOLULTE_BAROMETRIC_PRESSURE),canSend_01(OXYGEN_SENSOR_1_FUEL_AIR_EQUIVALENCE_RATIO)};
which is not valid c++. I can show you how to fix this but I'll need a few minutes :) I know, this is difficult stuff for a beginner, but we'll get it going...
 
Please don't be shy. Many of us here can help you with these details, but only if you show us enough code. C++ is a complicated language, where fine details matter. In cases like this, where we can't see the definition of things "func_t", the help we can give you is really limited.

Thanks Paul. I really appreciate the help from all members so far on my various topics.
The only reason I haven't posted the full code is it took me months to obtain knowledge for multiple sources that (unfortunately) is not documented online and piece it all together - close to a year actually.

But I'm happy to share the main structure stripped on the config, which is related to FlexCan anyways and won't affect this thread.
 
I added another array which carries the arguments to the functions. Now you can combine functions and arguments as you wish. The dummy functions just print out the function name, the current time (ms) and the passed argument, so you can check if runs OK. This is not the most elegant way to do it but I hope it is understandable

Here the code:

Code:
enum
{
  INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 0x0b,
  CHARGE_PRESSURE_ACTUAL = 0xAA,
  ABSOLULTE_BAROMETRIC_PRESSURE = 0xBB,
  OXYGEN_SENSOR_1_FUEL_AIR_EQUIVALENCE_RATIO = 0xFF
};

void canSend_01(uint8_t pid)
{
    Serial.printf("canSend_01 %d: PID:%X\n", millis(), pid);    
}

void canSend_22(uint8_t pid)
{
  Serial.printf("canSend_22 %d: PID:%X\n", millis(), pid);
}


using func_t = void (*)(uint8_t);  // type of the functions

constexpr func_t  functions[] = {canSend_01,                        canSend_22,             canSend_01,                    canSend_01,};
constexpr uint8_t arguments[] = {INTAKE_MANIFOLD_ABSOLUTE_PRESSURE, CHARGE_PRESSURE_ACTUAL, ABSOLULTE_BAROMETRIC_PRESSURE, OXYGEN_SENSOR_1_FUEL_AIR_EQUIVALENCE_RATIO};

// calculate both array sizes and compare to avoid errors
constexpr unsigned nrOfFuncs = sizeof(functions) / sizeof(functions[0]);
constexpr unsigned nrOfArgs = sizeof(arguments) / sizeof(arguments[0]);
static_assert(nrOfFuncs == nrOfArgs, "Number of functions doesn match number of arguments"); // just to be sure...

void callFunction()
{
  static unsigned current = 0;

  functions[current](arguments[current]);  // this calls the next function from the functions array with the corresponding argument from the arguments array. 

  current++;
  if(current >= nrOfFuncs) current = 0; 
}


IntervalTimer t; 

void setup()
{ 
  pinMode(LED_BUILTIN, OUTPUT);
  
  t.begin(callFunction, 100'000);   
}

void loop()
{  
  digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
  delay(250); 
}

And here the output

Code:
canSend_01 173400: PID:BB
canSend_01 173500: PID:FF
canSend_01 173600: PID:B
canSend_22 173700: PID:AA
canSend_01 173800: PID:BB
canSend_01 173900: PID:FF
canSend_01 174000: PID:B
canSend_22 174100: PID:AA
canSend_01 174200: PID:BB
canSend_01 174300: PID:FF
canSend_01 174400: PID:B
canSend_22 174500: PID:AA
canSend_01 174600: PID:BB
canSend_01 174700: PID:FF
canSend_01 174800: PID:B
canSend_22 174900: PID:AA
canSend_01 175000: PID:BB
canSend_01 175100: PID:FF
canSend_01 175200: PID:B
canSend_22 175300: PID:AA
canSend_01 175400: PID:BB
canSend_01 175500: PID:FF
canSend_01 175600: PID:B
canSend_22 175700: PID:AA
canSend_01 175800: PID:BB
canSend_01 175900: PID:FF
 
Ups, just saw that you have 8 bit and 16bit arguments. I recommend to store both as 16bit and use the first 8bit only in the 8bit functions. Otherwise you need to define two different function pointer arrays which will get very messy...

Code:
enum
{
  INTAKE_MANIFOLD_ABSOLUTE_PRESSURE = 0x0b,
  CHARGE_PRESSURE_ACTUAL = 0xAA,
  ABSOLULTE_BAROMETRIC_PRESSURE = 0xBB,
  OXYGEN_SENSOR_1_FUEL_AIR_EQUIVALENCE_RATIO = 0xFF
};

void canSend_01(uint16_t pid)
{
    uint8_t pid8 = pid & 0x00FF;

    Serial.printf("canSend_01 %d: PID:%X\n", millis(), pid);    
}

void canSend_22(uint16_t pid)
{
  Serial.printf("canSend_22 %d: PID:%X\n", millis(), pid);
}


using func_t = void (*)(uint16_t);
constexpr func_t  functions[] = {canSend_01,                        canSend_22,             canSend_01,                    canSend_01,};
constexpr uint8_t arguments[] = {INTAKE_MANIFOLD_ABSOLUTE_PRESSURE, CHARGE_PRESSURE_ACTUAL, ABSOLULTE_BAROMETRIC_PRESSURE, OXYGEN_SENSOR_1_FUEL_AIR_EQUIVALENCE_RATIO};

constexpr unsigned nrOfFuncs = sizeof(functions) / sizeof(functions[0]);
constexpr unsigned nrOfArgs = sizeof(arguments) / sizeof(arguments[0]);
static_assert(nrOfFuncs == nrOfArgs, "Number of functions doesn match number of arguments"); // just to be sure...

void callFunction()
{
  static unsigned current = 0;

  functions[current](arguments[current]);

  current++;
  if(current >= nrOfFuncs) current = 0; 
}


IntervalTimer t; 

void setup()
{ 
  pinMode(LED_BUILTIN, OUTPUT);
  
  t.begin(callFunction, 100'000);   
}

void loop()
{  
  digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN));
  delay(250); 
}
 
Okay, looks like we're almost there!

It compiles but with two warnings:
Code:
CAN_T4_MB_interval:574: warning: narrowing conversion of '(._93)8234u' from 'short unsigned int' to 'uint8_t {aka unsigned char}' inside { } 
 constexpr uint8_t arguments1[] = {INTAKE_MANIFOLD_ABSOLUTE_PRESSURE, CHARGE_PRESSURE_ACTUAL, ABSOLULTE_BAROMETRIC_PRESSURE, OXYGEN_SENSOR_1_FUEL_AIR_EQUIVALENCE_RATIO};
                                                                                                                                                                       ^
CAN_T4_MB_interval:574: warning: large integer implicitly truncated to unsigned type

I changed constexpr from uint8_t to uint16_t which has fixed the warning.
I will test it tomorrow morning in the car with live data.

And with the above I do want to say thank you luni for the help and getting me this far!
 
I changed constexpr from uint8_t to uint16_t which has fixed the warning.

Sorry, overlooked that.

Just one additional remark: Looks like you are using some global resources (e.g. msgTxt, Can0 ...) in your background functions. If you use the same resources in your foreground tasks this is calling for trouble. E.g., if you are using msgTxt in a foreground task and a background function kicks in, it will change msgTxt and mess up your foreground function.
 
Thanks Luni!

Tested it this morning, WOW! the sample rate is so high, my display refresh rate jumped!
This seems to be doing exactly what I wanted it to do, and it's doing it well!

Regarding Can0, msgTx etc.. these are constructors that I declare in the code, but yes, they are the generic from the FlexCan library how-to. They can be whatever I want, just kept it simple for now.
 
Hi Luni,

Since the last post, I've been using the code you supplied successfully but now comes time for a change.

I have a few array that I am passing to a function that transmitts CAN frames.
I want to use the same concept to push out a frame with data from one of the arrays at a set interval.

Code looks something like this:
Code:
int pid0[8] = {0x03, 0x22, 0xf4, 0x0b, 0, 0, 0, 0};
int pid1[8] = {0x03, 0x22, 0xf4, 0x33, 0, 0, 0, 0}; 
int pid2[8] = {0x03, 0x22, 0xf4, 0x42, 0, 0, 0, 0}; 
int pid3[8] = {0x03, 0x22, 0xf4, 0x0f, 0, 0, 0, 0}; 
int pid4[8] = {0x03, 0x22, 0xf4, 0x3c, 0, 0, 0, 0}; 
int pid5[8] = {0x03, 0x22, 0xf4, 0x46, 0, 0, 0, 0};

canTX(int pidArray[]){

CAN_message_t msgTx;
    for(int i; i<8; i++){
        msgTx.buf[i] = pidArray[i]; // fill the CAN buffer with data from the array
    }
    msgTx.len = 8;            // number of bytes in request
    msgTx.flags.extended = 0; // 11 bit header, not 29 bit
    msgTx.flags.remote = 0;
    msgTx.id = 0x7E0; 
    Can0.write(msgTx); // transmit the CAN buffer
}

using func_t = void (*)(uint16_t);
constexpr uint16_t arguments[6] = {pid0,pid1,pid2,pid3,pid4,pid5};
void sendCAN(){
  static unsigned curFunc = 0;
  canTX(arguments[curFunc]);
  curFunc++;
  if (curFunc >= 6){
    curFunc = 0;
  }
}


I know the last function and declaring the constants won't work, because I just don't have the knowledge in c++ to understand how you got this to work.
Would it be possible to modify the last bit of code you wrote for me back then to use one function with multiple arguments?
 
Status
Not open for further replies.
Back
Top