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!