Reputation: 397
When I create a new software project, I do it via a script which makes the folder, copies and moves some files and generate some source code.
One of the copied files is called roundRobinTasks.cpp
. Obviously it is ment for round robin tasks like reading the from the serial buffer, handle can bus communication, monitoring emergency switch etc.
This file contains one function which looks as following:
void processRoundRobinTasks(void)
{
static unsigned char taskCounter = 0;
// HIGH PRIORITY ROUND ROBIN TASKS
// LOW PRIORITY ROUND ROBIN TASKS
switch( ++ taskCounter )
{
default:
taskCounter = 0;
/* fill in a task */
break;
case 1:
/* fill in a task */
break;
case 2:
/* fill in a task */
break;
}
}
This function is called every program cycle in int main()
and I split tasks into a high priority or a low priority task. Every high prio tasks is run every cycle, only 1 low prio task is run every cycle.
The taskCounter is an ever incrementing static variable. The idea is that for every new task you can add a new case label with an incrementing number. If taskcounter becomes greater than the highest task, the default label will set it back to 0. This prevents the need to pre define the amount of tasks.
To make it look prittier, I was looking into a couple of macros and I quickly arrived at this situation. I already filled in some details of what one could fill in.
void foobar()
{
// code
}
void processRoundRobinTasks(void)
{
HIGH_PRIORITY_TASKS
foobar() ;
LOW_PRIORITY_TASKS
TASK(1)
REPEAT_MS( 500 )
TGL( PORTB, 5 ) ; // is run every 500ms
END_REPEAT
TASK(2)
REPEAT_MS( 50 )
handleSomething() ; // is run every 50ms
END_REPEAT
TASK(3)
doSomethingElse() ;
TASK(4)
/* fill in a task */
END_TASKS
}
It compiles fine with these macros and it works the exact same.
#define HIGH_PRIORITY_TASKS static unsigned char taskCounter = 0;
#define LOW_PRIORITY_TASKS switch( ++ taskCounter ) \
{ \
default : \
taskCounter = 0 ;
#define TASK(x) break ; case x:
#define END_TASKS break ; \
}
With or without the macros, there is one minor bug possibility. If a user adds or removes a task and forgets to update all the task numbers, there will be tasks which may never be called like in this example.
default:
taskCounter = 0;
/* fill in a task */
break;
case 1:
/* fill in a task */
break;
case 3: // because task 2 does not exist I will never run
/* fill in a task */
break;
I am interested in a construct, in which I can simply type TASK
or if( Task() )
without a number followed by an actual task to do like:
void processRoundRobinTasks(void)
{
HIGH_PRIORITY_TASKS
foobar() ;
LOW_PRIORITY_TASKS
TASK
REPEAT_MS( 50 )
handleSomething() ; // is run every 50ms
END_REPEAT
TASK
doSomethingElse() ;
TASK
/* fill in a task */
END_TASKS
}
I am not limited to calling a function. I essentially can do whatever I want in this code.
I believe that it cannot be done with a function. I for one could not think of anything.
Can something like this be done with a macro? Are there other constructs for this such as a list with function pointers?
EDIT: For the macro haters. The advantage of this system is that one is not forced to make a function for every single task. imho some things are just too simple to dedicate a function to it.
I have worked with several C++ timer and task scheduler libraries and I came back from them. Though they work well, I was not fond of any of them. I had to create a new timer object for every single timed event. Being an embedded programmer, I sometimes desire for more simplicity. How more stupid simple some things are, the better.
For instance the REPEAT_MS(interval)
macro, is my best macro ever. I honestly stand by this one, I use it everwhere (except within class methods). Everything what you put between REPEAT_MS()
and END_REPEAT
gets repeated every interval time. You do not have to declare new variables, you don't have to make objects for them or initialize and you are not forced to make a function for litterly every little thing. This macro cost four bytes of RAM for every time you use it.
#define REPEAT_MS(x) { \
static uint32_t previousTime ;\
uint32_t currentTime = millis() ;\
if( currentTime - previousTime >= x ) {\
previousTime = currentTime ;
// code to be repeated goes between these 2 macros
#define END_REPEAT } \
}
I succesfully tried the macros of the given answer and it works really well. Now it does not matter if one forgets to update the numbers.
If you honestly believe that this is "unreadable" or "messy" I honestly believe that you cannot code. But lets keep our opionions to our selves...
Upvotes: 0
Views: 233
Reputation: 17503
How about something like this:
#define HIGH_PRIORITY_TASKS static unsigned char taskCounter = 1;
#define LOW_PRIORITY_TASKS switch( taskCounter ) \
{ \
default : \
taskCounter = 1 ;
#define TASK(x) taskCounter = (x); \
break ; \
case (x):
#define END_TASKS taskCounter = 1; \
break ; \
}
The idea is to update taskCounter
to the next task just before breaking out of the switch. It shouldn't matter if there are gaps in the taskCounter
values as long as the "first" task TASK(1)
is present.
Upvotes: 1