Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 20 of 20

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

  1. #1

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

    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

  2. #2
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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:

    Click image for larger version. 

Name:	functions.png 
Views:	7 
Size:	31.5 KB 
ID:	19105

    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 by luni; 02-19-2020 at 03:14 PM.

  3. #3
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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 by luni; 02-19-2020 at 03:15 PM.

  4. #4
    Quote Originally Posted by luni View Post
    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.

  5. #5
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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.

  6. #6
    Senior Member JarkkoL's Avatar
    Join Date
    Jul 2013
    Posts
    112
    I implemented a fiber library that you can use: https://github.com/JarkkoPFC/fiber

    There's an example what does pretty much what you ask for.

  7. #7
    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

  8. #8
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    Can you post the declaration of one of your functions?

  9. #9
    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.

  10. #10
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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; 
    }

  11. #11
    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

  12. #12
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,473
    Quote Originally Posted by Rezo View Post
    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.

  13. #13
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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...

  14. #14
    Quote Originally Posted by PaulStoffregen View Post
    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.

  15. #15
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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

  16. #16
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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); 
    }

  17. #17
    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!

  18. #18
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    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.

  19. #19
    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.

  20. #20
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    799
    Sounds good, glad it works now.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •