0

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.

enter image description here

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

13
  • 3
    Please try to avoid the term "C/C++", unless you want to irritate us old geezers. There's no such single language as "C/C++", only the two separate, distinct and very different languages C and C++. Even in the rare cases where a question could apply to both C and C++, only tag the language you're actually program in. Commented Nov 24, 2021 at 11:58
  • You can build what is effectively your own language, but it will only be readable by you. Commented Nov 24, 2021 at 12:00
  • Usually default is the lat of switch statements. Commented Nov 24, 2021 at 12:08
  • 2
    "To make it look prittier, I was looking into a couple of macros " NO. It made your code an ugly and completely unreadable mess. You need to get rid of these macros immediately. C programmers can read C, they cannot read your secret macro language. Once you've unf*d the macro mess, consider using a table of function pointers instead of a switch. Commented Nov 24, 2021 at 12:14
  • 1
    "Macro haters" Yeah well, I only have 20 years of real-world embedded systems C programming experience - particularly experienced when it comes to taking over old rotten code bases where someone incompetent has made a mess but the project/product must live on. Trust me when I'm telling you that if you write macro goo like this in a professional setting, you will get fired from your job. If your job is to program real-time systems and you write some naive busy-delay scheduler using hobbyist libraries, you will get fired from your job. Commented Nov 24, 2021 at 15:07

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

3 Comments

I just replaced the macros by your's and they work well.
@bask185 Although you accepted this as an answer, I wouldn't personally use something like this. I'd just initialize a static array of function pointers - one function per low priority task with no gaps in the array, and have a static index to iterate through the elements of the array and call the function pointer.
I understand what you mean, and I will propably use that construction one day. Thing is, I program embedded stuff for 99%. Sometimes a task can be a simple oneliner like reading an input and setting a flag. I am simply not (yet) in the 'mood' to dedicate a function to such simple oneliners.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.