1. Introduction

What does a generic operating system do?

What is different about a real-time operating system?

1.1. Terminology

  • task / thread / process

  • scheduler

  • queue

  • mutex

  • semaphore

  • signal

1.2. zyBook path to a task scheduler

1.2.1. Handling different tick() intervals

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
while (1) {
    // Only _tick() each state machine (task) if it's time is up.
    if (Blink_elapsedTime >= Blink_period) {
        Blink_tick();
        Blink_elapsedTime = 0;
    }

    // Multiple tasks have the same form,
    // but different variable names / values.
    if (ButtonInput_elapsedTime >= ButtonInput_period) {
        ButtonInput_tick();
        ButtonInput_elapsedTime = 0;
    }

    // Wait for the timer ISR to set the flag.
    while (!TimerFlag){}
    TimerFlag = 0;

    // Update each task's time-since-last-finished.
    Task0_elapsedTime += timerPeriod;
    Task1_elapsedTime += timerPeriod;
}

1.2.2. Using a struct to hold all of a task’s state

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
typedef struct task {
    int state;                  // note-1
    unsigned int period;        // note-2
    unsigned int elapsedTime;   // note-2
    int (*tick)(int);           // note-3
} task_t;


int Blink_tick(int state) {
    switch (state) {
        //...
        state = ...
        ...
    return state;
}


task_t Blink_task;
task_t ButtonInput_task;


// Configure the Blink state machine's struct
Blink_task.state = Blink_SMStart;
Blink_task.period = 1500;
Blink_task.elapsedTime = 1500;  // same as period -> triggered at startup
Blink_task.tick = &Blink_tick;  // note-3, getting the *address* of the function

// Configure the ButtonInput state machine's struct
ButtonInput_task.state = ButtonInput_SMStart;
ButtonInput_task.period = 500;
ButtonInput_task.elapsedTime = 500;  // same as period -> triggered at startup
ButtonInput_task.tick = &ButtonInput_tick;  // note-3, getting the *address* of the function




while (1) {
    /*                             /1 Call the tick() function
     *         /3 To get the       |   /2 with the current state
     *         |  next state       |   |
     *         v                   v   v                         */
    Blink_task.state = Blink_task.tick(Blink_task.state);
    ...
}
note-1

The state variable is really an enum that holds the possible state labels. The C standard specifies that enum values are of type int. We can’t quite typedef the enum because each state machine has its own encoding of integers to state labels.

note-2

The type should be unsigned since this notion of time is always increasing. However, be sure to use the appropriate bit width for the application. For example, Arduino returns unsigned long for both millis() and micros().

note-3

This is a pointer to a function which accepts an int (the current state) and returns an int (next state)..

The structure becomes even more useful when we make an array of them, each holding the information about its own task.

1
2
// TODO: this is nonsense in C
task_t tasks[] = {Blink_task, ButtonInput_task};

1.2.3. Simple cooperative task scheduler

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Get the length of the tasks[] array.
// This is computed by the compiler during compiling.
const unsigned int NUM_TASKS = sizeof tasks / sizeof tasks[0];

for (i=0; i < NUM_TASKS; i++) {
    if (tasks[i].elapsedTime >= tasks[i].period) {
        tasks[i].state = tasks[i].tick(tasks[i].state);
        tasks[i].elapsedTime = 0;
    }
    tasks[i].elapsedTime += timerPeriod;
}

The above works for any number of tasks!

2. Digi-Key / Shawn Hymel — Introduction to RTOS

3. FreeRTOS resources

Why FreeRTOS? → see page 59 of EETimes Embedded Markets Study - 2019

2016 book about v8.x.x (current is v10.x.x) Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf


3.1. Notes

3.1.1. How much stack space?

Wonder what value to set the stack depth to when creating a task?

Use uxTaskGetStackHighWaterMark(). It returns the number of words remaining in the stack:

highWaterMark = max(usStackDepth - max(stack usage), 0)

If the number is zero, then the stack has likely overflowed and needs to be made larger. A large number compared to the configured value means that you can safely reduce the stack size allocation.

3.1.2. Espressif ESP32 port differences

This document describes the API and behavioral differences between Vanilla FreeRTOS and ESP-IDF FreeRTOS that were made in order to support Symmetric Multiprocessing (SMP).
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/freertos-smp.html

4. Other links