Simple and Efficient Task Scheduler

In embedded applications, punctuality is very critical. You always need to put the tasks on schedules and run them on specific moments. This is why some applications use a real-time operating system such as FreeRTOS or at least a task scheduler. If you are looking for a small-footprint task scheduler, you hit the jackpot because Punctual covers all of your needs!

What is a task scheduler

Let’s consider we have 2 different tasks and they need to run on different periods such that 5s for the first and 2s for the second task. Imagine the following.

This doesn’t work of course. You can’t put them into the same infinite loop and use blocking delays. You have 2 options here: putting one/both of them into interrupt routine or building a non-blocking scheduler system. Check the following structure where the loop can continue running regardless of the tasks.

But what if task1() takes 3s to complete? In this scenario task2() can never do its job on the scheduled time and it is obvious that we need a better solution. Putting task2() in an interrupt does the trick. When task1() spends more time then the running period of the other task, elevation in an ISR solves the problem and they don’t interfere each other in anyway. This concept proves the necessity of a scheduler.

What is Punctual

Punctual is a system that helps you implement both of the solutions provided above for multi-tasking problems. It has an ISR routine where you can attach your tasks and it also provides interface to communicate with these elevated tasks in a thread-safe way. In addition, Punctual has timeout functionality that come in handy for building non-blocking delays or sleeps.

Project implementation

Punctual is written in C language and aimed to be extremely simple but functional. There are only 2 source files that you need to care about; punctual.c  and punctual.h.

Task scheduler

Punctual uses an array of the following struct to keep the scheduled tasks. As the first step, you need to call  PunctualInit() while interrupts are disabled in order to initialize the task list to NULL.

When you create a task, PunctualCreate()  puts the handle of the task function into first open slot of that array and then calls the handles of all created tasks in PunctualISR() one by one. When you PunctualDestroy() a task, its slot is freed and becomes available again.

To handle the communication between different tasks or between tasks and non-tasks, 2 functions are available. You can send any type of data to a task using PunctualSend() or you can read the latest return value of a task using PunctualReceive() from any thread.

There are only 3 settings to go.

Punctual is flexible and you can change the maximum number of tasks by changing PUNCTUAL_MAX_TASKS. You should define the calling period of the ISR with PUNCTUAL_ISR_PERIOD_US. The other option is defining a post-scaler to reduce the calling frequency of PunctualISR() when you need to slow it down.

How to create scheduled tasks

The simplest use case is a simple task that blinks an eye every second. Imagine that you want to add a blinking LED to your multi-thousand-line software without any impact on the other stuff. As I mentioned before, you cant just put a blocking delay in the center of main()  because that ruins everything without exception. You must either set a timeout and poll if the timer expired in a corner of main loop or put your code in one of the interrupts that is driven by a timer. I’m sure that both of the options are not practical, let’s say, if you want to blink 10 LEDs. A more proper way is creating a periodically-called function, so called “task” and leave the rest to your scheduler.

The prototype for Punctual tasks is uint32_t (*handle)(void *);  and an example is given below.

When we set this as a scheduled-task (it is called once a millisecond), it will blink the LED_Green at 2Hz and it will put extra 1s delay when pause = true is received. To define the task do the following.

And when you want to send pause signal to the ongoing task (preferable not in an interrupt) do it like this.

Here, the parameter type is (void *) by default but the task used is as (bool). When the default parameter is not used (void)param;  must be declared in task body to avoid unused-parameter warning.

Last but not least, it is possible to read the last return value of the scheduled-task with the following call.

Now you know how to add more tasks to run with these easy steps. The only thing to avoid it keeping the tasks as small as possible because they run in interrupts. When possible, use PunctualTimeout to run your functions in main without elevation because this is the most proper way.

Timeouts

Punctual provides timeout methods to build up time delays in your project. A timeout is basically an object that you need to create with PunctualTimeoutSet() and poll with PunctualTimeoutCheck() for expiration, let’s say in the main().

All timeouts are periodic and they continues by default without any delay or overhead that means you can use a timeout to build periodic tasks. When a timeout expires, it sets up a new due time immediately by starting from the previous due. In other words, if you set a 1000ms timeout once, it will expire at 1000th, 2000th, 3000th ms and so on; which means checking it at 1100th millisecond doesn’t shift the next due. You can also change the period of a previously created timeout using PunctualTimeoutEdit().

If you want to implement your own timer, you must modify the following parameter only.

An example will be given below.

How to create non-blocking delay

This simple example uses Punctual methods to create a 1Hz blinking LED.

Note that PunctualTimeoutCheck() returns true only once in a period then updates the due with respect to the period. You can do other things in the same loop with the blinker since they don’t affect each other.

Project Files

You can find an example project below that uses Punctual and runs on STM32 VL Discovery board.

Porting

This project uses Porty Ticker by default but it can easily be modified to be used with any project. The only thing to do is calling the PunctualISR() in a (preferably 1ms) timer interrupt and defining PunctualGetTime() such that it return current time in milliseconds.

Download

A complete project that runs on STM32F100 microcontroller: Punctual-0.1.0. For compilation check here.

Leave a Reply

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