Task Turner part 2, the Basic Code is Working

Originally published May 3, 2017

Republished June 10, 2024

Task Turner the basic code

Here is the source code, we will discuss it below. TaskTurner

Additions

Makefile

We have a Makefile! Yippie. Makefiles are awesome, but always difficult for me. Once the make is setup and working, you don’t really need to do more than add a file now and then. I don’t work on the Makefile everyday, so I when I have to make a new one, the syntax is foriegn and weird. I found some basic examples here: http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/ They will get you moving in the right direction.

Now the include directory is specified in the Makefile. I removed the “#include “../include/blah.h” and notice that it is now “#include bah.h”, much nicer and no worries when the project gets more complex. At times the ../../../../dir_of_all_includes/dir_of_this_include/blah.h can get out of control.

Also, you can setup the makefile to build different outut files. So “make test” will build all the tests, “make prod” will build a production version, “make local” will build a local version for debugging and so on. We can talk about the tests in a future post.

Separate main.c

The main.c file is now using the taskrunner code. The taskrunner is dead simple. And that is the whole idea.

Added Init functions to the taskrunner

Yes, the taskinit and taskrunner are almost identical. Could they be refactored into a single function? Yes I think they could. The other thing that works is use the Task structure in tasks.h and remove the init function altogether. If you do that, how do you initialize things neatly?

Make a separate table of init functions. They can all use a “runNow” set to 1. Then it is as easy as passing the initialization table into the taskrunner() function.

Hmm… maybe I will do that. It is also nice to have the init function and the task function together, in one structure. If the function prototype needs to be different, then a separate init is needed.

Why is taskrunner() so simple?

I will, in fact, claim that the difference between a bad programmer and a good one is whether he considers his code or his data structures more important. Bad programmers worry about the code. Good programmers worry about data structures and their relationships.

Linus Torvalds

This is not Linux. The whole thing we are calling the operating system is about 10 lines of C code. How can this possibly be used to build a complex system like you want to build?

Well, easily, that is how.

First, the tasks are all contained in one simple table. Here is something cool maybe you did not realize. We can have as many tables as we want!

So, to do a test of one task, build a test_task table with only one task in it. Or two tasks that seem to have a weird dependency? Run with just those two, and leave the rest out.

What if this is one product in a family. Well, make a completely separate table for each product. It keeps the code clean, uses a single set of ‘#ifdef” pragma and the tasks can be shared between products.

In one instance I used this setup for a product that used Zigbee. The Zigbee radio code in the device was not designed to use threads or multitasking. The assumption was it would run on a simple microcontroller. So, periodically, the code had to call the radio_tx() and radio_rx() functions. Throughout the spaghetti code people had sprinkled calls to service the radio.

We successfully refactored the code using the task table concept. There was a high priority table of tasks, and I added a flag, like runNow, for doRadio. If the doRadio flag was set, then the high priority radio tasks would get run. Not just get run, run in the correct order, with no code in between. Magically a couple of hard to reproduce bugs disappeared. If a task needed to do something radio related, it just set a flag. The execution time was better as well, and more deterministic. 

int taskrunner(TaskItem *TaskList, unsigned int listLen)
{
 int iter;
 int task_err;

 printf("Running Tasks, %d\n", listLen);
 for(iter = 0; iter < listLen; iter++)
 {
   if((NULL != *TaskList[iter].func) &&
     (0 != *TaskList[iter].runNow))
   {
      printf("%s, %d\n", TaskList[iter].taskName, *TaskList[iter].runNow);
      task_err = TaskList[iter].func(iter);
      if ( 0 != doRadio )
      {
          taskrunner(RadioTasks, NUM_RADIO_TASKS);
          
      }
   }
 }
 return(0);
}

By the way, that was inside a single process running in Embedded Linux. We tried and tried to refactor the code to use multiple processes or threads. in the end the simple solution was best.

What’s next

Next post, let’s get into constructing a task. After that, there is a cool trick to make this into a low power system. Stay tuned for more fun.

P.S.

I am glad I posted this to the blog. This is code I have recreated about 5 times for various projects. Now I have a gitHub repository with the code, so I can just clone and start modifying. As Martha Stewart would say, “It’s a good thing”.


Posted

in

,

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *