Functions without parameters

Let begin from the easiest one: the queues for function pointers without parameters.

A definition:

//-8<--8<--8<--ANTIRTOS_C BEGIN--8<--8<--8<--8<---
#define fQ(q, Q_SIZE)                            \
    volatile int q##_last = 0;                   \
    int q##_first = 0;                           \
    void (*q##_Queue[Q_SIZE])(void);             \
    int q##_Push(void (*pointerQ)(void)) {       \
        if ((q##_last + 1) % Q_SIZE == q##_first)\
            return 1; /* Queue is full */        \
        q##_Queue[q##_last++] = pointerQ;        \
        q##_last %= Q_SIZE;                      \
        return 0; /* Success */                  \
    }                                            \
    int (*q##_Pull(void))(void) {                \
        if (q##_last == q##_first)               \
            return 1; /* Queue is empty */       \
        q##_Queue[q##_first++]();                \
        q##_first %= Q_SIZE;                     \
        return 0;                                \
    }
//-8<--8<--8<--ANTIRTOS_C END --8<--8<--8<--8<---

Usage:

Define your tasks:

void yourTaskOne(){
//put here what ever you want to execute
}

void yourTaskTwo(){
//put here what ever you want to execute
}

Initialize queues like:

  fQ(Q1,8); // define first queue (type fQ) with name Q1, 8 elements length
  fQ(Q2,8);   // define second queue (type fQ) with name Q2, 8 elements length
......

In main loop:

Q1_Pull();
Q2_Pull();
...........

In first of your interrupts routines:

void yourInterruptRoutineOne(void){
    Q1_Push(yourTaskOne);  // just push your task into queue!
}

In second one:

void yourInterruptRoutineTwo(void){
    Q2_Push(yourTaskTwo);  // just push your task into queue!
}

This is it! Every task is handled. Interrupts kept blazing fast.

The simplicity matter!

Functions with parameters

OK, now it is time to involve parameters if we need them to pass to the functions in queue, let define queue for functions with parameters:

#define fQP(q, Q_SIZE, param_type)                                              \
    void (*q##_funcs[Q_SIZE])(param_type);                                      \
    param_type q##_params[Q_SIZE];                                              \
    volatile int q##_last = 0;                                                  \
    int q##_first = 0;                                                          \
    int q##_Push(void (*func)(param_type), param_type params) {                 \
        if ((q##_last + 1) % Q_SIZE == q##_first)                               \
            return 1; /* Queue is full */                                       \
        q##_funcs[q##_last] = func;                                             \
        q##_params[q##_last++] = params;                                        \
        q##_last %= Q_SIZE;                                                     \
        return 0; /* Success */                                                 \
    }                                                                           \
    int q##_Pull(void) {                                                        \
        if (q##_last == q##_first)                                              \
            return 1; /* Queue is empty */                                      \
        q##_funcs[q##_first](q##_params[q##_first++]);                          \
        q##_first %= Q_SIZE;                                                    \
        return 0; /* Success */                                                 \
    }

Usage

Define all your tasks:

void blinkLED(int led){
  switch(led){
    case 0: 
      HAL_GPIO_TogglePin(LED_YELLOW_PORT, LED_YELLOW_PIN);
      break;
    case 1:
      HAL_GPIO_TogglePin(LED_GREEN_PORT, LED_GREEN_PIN);
      break;    
  }

void printSymbol(char ch){
 printf("Number: %c\n", ch);
}

Define all your queues:

  fQP(Q1,8,int);  //8 elements length, type fQP, functions receive int
  fQP(Q2,8,char); //8 elements length, type fQP, functions receive char

In main loop just add:

Q1_Pull();
Q2_Pull();

Where you want just push the tasks into queues and pass them parameters:

  Q1_Push(blinkLED, 0);
  Q2_Push(printSymbol, 'a');

More parameters

Assume you function require more parameters, just wrap them into your structure, for example:

typedef struct {
    int index;
    int logic;
} pinout;

 So task function will receive here type pinout:

void myTask(pinout p){
// put here what ever you want, your task
}


Now you may initialize your queues, like:

  fQP(Q3,8,pinout);  //8 elements length, type fQP, functions receive type 'pinout'

You may push parameters now like:

 Q3_Push(myTask, (pinout){0,1}); //passing arguments

 In main loop as usual:

Q3_Pull();

Delayed conveyers of function pointers without parameters

Ok, now adding delay functionality to make possible to delay our functions from execution to implement task scheduling:

#define del_fQ(q, Q_SIZE)                                           \
    int q##_time = 0;                                               \
    void (*q##_del_fQueue[Q_SIZE])(void);                           \
    int q##_execArr[Q_SIZE] = { 0 };                                \
    int q##_execTime[Q_SIZE];                                       \
    fQ(q, Q_SIZE)                                                   \
    int q##_Push_delayed(void (*pointerF)(void), int delayTime){    \
        int q##_fullQ = 1;                                          \
        for (int i = 0; i < Q_SIZE; i++) {                          \
            if (!q##_execArr[i]) {                                  \
                q##_del_fQueue[i] = pointerF;                       \
                q##_execArr[i] = 1;                                 \
                q##_execTime[i] = q##_time + delayTime;             \
                q##_fullQ = 0;                                      \
                break;                                              \
            }                                                       \
        }                                                           \
        return q##_fullQ;                                           \
    }                                                               \
    void q##_Tick(void){                                            \
        for (int i = 0; i < Q_SIZE; i++) {                          \
            if (q##_execTime[i] == q##_time) {                      \
                if (q##_execArr[i]) {                               \
                    q##_Push(q##_del_fQueue[i]);                    \
                    q##_execArr[i] = 0;                             \
                }                                                   \
            }                                                       \
        }                                                           \
        q##_time++; /* Increment time */                            \
    }                                                               \
    int q##_Revoke(void (*pointerF)(void)){                         \
        int result = 1;                                             \
        for (int i = 0; i < Q_SIZE; i++) {                          \
            if (q##_del_fQueue[i] == pointerF) {                    \
                q##_execArr[i] = 0;                                 \
                result = 0;                                         \
            }                                                       \
        }                                                           \
        return result;                                              \
    }

Usage:

 1.  Define delayed queue/queues, for example:

del_fQ(Q1,4); // maximum 4 function pointers in delayed queue Q1

 2.  Define you tasks, example:

void writePinOne(){
//  your task here
 digitalWrite(5,HIGH);
}

void writePinTwo(){
//  your task here
 digitalWrite(6,HIGH);
}

3. Just push you task from needed place like:

Q1_Push_delayed(writePinOne, 10);      // 10 ticks delay for first task
Q1_Push_delayed(writePinTwo, 100);      // 100 ticks delay for second task

4. In main loop just push the tasks and execute, like:

void loop() {
  // put your main code here, to run repeatedly:
  Q1_Pull(); // pull from the queue
}

5. Add into periodic function/interrupt routine tick function like follow:

// your periodic function for time measure, for example
// timer interrupt ticks one per 1 sec
void Interrupt(void) {
  Q1_Tick(); // execute tick function for make delayed functionality works
}

Enjoy!

If you need to revoke all the instances of your function from the queue, just do:

Q1_Revoke(writePinOne); // revoking function writePinOne() from the queue

 Try it on Wokwi!