1. Introduction
What does a generic operating system do?
What is different about a real-time operating system?
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 |
note-2 |
The type should be |
note-3 |
This is a pointer to a function which accepts an |
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!
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