Every task or process we have in our programs should have been designed to run with a predefined periodicity, this is a must for a real-life program, and timing is an important asset to keep in mind when we design our programs. It is desired to have an algorithm to set each task periodicity and ensure each run according to the time established. The Scheduler is the part of the program in charge to accomplish such objective, the most common version of this scheduler is the round robin, which means each task will run after the other with no preemption.

The starting point for our scheduler will be what we did in previous post, the scheduler it is kind of similar, now think in some kind of reusable algorithm that allows you to registers the tasks to run at their respective periodicity. The easiest way of doing this is to define a structure with a pointer to the function use as a task and at least an element to indicate the timing for periodicity, then we create an array of this kind of structure where each element is considered a task. A for sentence to loop trough the array is used comparing the each task period against a base time reference, if the period is elapsed then run the function.

The following code shows how the previous explanation can be translated in to code

/*Task control block defintion*/
typedef struct _TaskType 
{
    uint32_t period;        // Rate at which the task should tick
    uint32_t elapsedTime;   // Time since task's last tick
    void (*TickFct)(void);  // Function to call for task's tick
} TaskType;

/*constant definitions */
#define TICK_BASE_TIME      10  /*10ms base time*/
#define NUMBER_OF_TASKS     2
#define PERIOD_TOGGLE_LED   1000
#define PERIOD_POLL_BTN     50

/*Funtion to be called as tasks*/
void Task1_ToggleLed( void );
void Task2_PollBtn( void );

TaskType tasks[ NUMBER_OF_TASKS ];
uint8_t GlobalTick = 0;

/*This is a interrupt function for an fictional HW timer*/
void TickIsr( void ) 
{
    GlobalTick = TRUE;
}

int main( void ) 
{
    // Register firt task with a 1000ms period
    tasks[0].period      = PERIOD_TOGGLE_LED;
    tasks[0].elapsedTime = PERIOD_TOGGLE_LED;
    tasks[0].TickFct     = &Task1_ToggleLed;
   
    // Register second task with a 50ms period
    tasks[1].period      = PERIOD_POLL_BTN;
    tasks[1].elapsedTime = PERIOD_POLL_BTN;
    tasks[1].TickFct     = &Task2_PollBtn;

    /*Init a HW timer to generate and interrupt each 10ms */
    TimerTick( TICK_BASE_TIME );
    
    while(1) 
    {
        /*this is the actual scheduler, notice it will only run after one tick
        is reported by a base time mechanism*/
        if( GlobalTick == TRUE )
        {
            /* Heart of the scheduler code */
            for( i=0 ; i < NUMBER_OF_TASKS ; i++ ) 
            {
                if( tasks[i].elapsedTime >= tasks[i].period ) 
                { /* run the task only if the period match */
                    tasks[i].TickFct(); //execute task tick
                    tasks[i].elapsedTime = 0;
                }
                /*increae the task period by a Tick*/
                tasks[i].elapsedTime += TICK_BASE_TIME;
            }    
        }
    }
}

// Task: Toggle an output
void Task1_ToggleLed( void ) 
{
    toggle_pin( LED0 );   
}

 // Task: read an input
void Task2_PollBtn( void ) 
{
    if( read_pin( SW0 ) == LOW )
    {
        ...
    }
}

The code above shows an array of structures where we store the period time and the function we will call when the period time expired. A simple for loop examine the array with the task and calls the function using a pointer but only when its corresponding periodicity time elapsed. It is necessary to use a mechanism that can provide a base time, in the example we use a pseudo timer to generate an interrupt each TICK_BASE_TIME.

Base time in you PC

On the PC we can use the header time.h from here we take the function clock() to calculate periods of time in millisecond. We can create our own milliseconds function and the same function can be used in the following way to established periods of time to run certain process. In this example we print a simple message every 500ms. Use the same approach to make the scheduler dispatch the registered tasks for the example.

#include <time.h>

long milliseconds( void )
{
    return clock() / ( CLOCKS_PER_SEC / 1000 );
}

...

/*get the milliseconds for the first time*/
long tickstart = milliseconds();

...

/* We ask if 500ms has been elapsed since the last time */
if( ( milliseconds() - tickstart ) >= 500 )
{
    tickstart = milliseconds();/*get the seed time again*/
    printf( "Hola mundo" );     
}

Base time in microcontroller

If you are using stm32g0 microcontroller we can call the HAL_GetTick function to read the current count on milliseconds an used them to calculate a suitable period of time to run our scheduler. the function uses the Systick timer that comes with Cortex microcontroller and is already calibrated by the HAL library to generate a interrupt every millisecond.

/*get the current tick for the first time*/
tickstart = HAL_GetTick();   

... 

/* We ask if 500ms has been elapsed since the last time */
if( (HAL_GetTick() - tickstart) >= 500 )
{
    /*at this point 500ms has been elapsed and we need to take the current tick count again*/
    tickstart = HAL_GetTick();
    /*run you scheduler from this line*/
}
We take as a reference the scheduler made by Professor Fran Vahid, The code present in here was heavily base on his previous work at under the following license
/*
   Copyright (c) 2013 Frank Vahid, Tony Givargis, and
   Bailey Miller. Univ. of California, Riverside and Irvine.
   RIOS version 1.2
*/

Time to build your own scheduler

The previous code was just an example on how easily implement a simple scheduler, but now is time create a reusable piece of code we can configure and use in any application we want, to dispatch all your task, But this time it will up to you, I’m only give you the instruction for the recipe. As an extra we are going to include software timer as a light way means to execute some code periodically or to create simple non-blocking delays.

You can find here our own scheduler implementation, the purpose of the code it is only to be taken as reference to clarify your potential doubts, but we encourage you to write your own implementation

Code Snippets

Exercises:

  • Write one task to toggle one led every 500ms, then a second one to toggle a second led every 100ms
  • Code one task that poll a single button every 50ms, then another task to toggle a led every 100ms, but the led will start to toggle only when the button is pressed. communicate both task with a global variable
  • Repeat some of the exercises you did in STM32G0 training, but this time using the scheduler