A: These are Reference Documents relating to FreeRTOS:
FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf (4.4 MB)
FreeRTOS_Reference_Manual_V10.0.0.pdf (2.4 MB)
B: Example of a Multi-tasking Operation
Concurrent (independent) Blinking of Three LEDs
LED (onboard) connected at GPIO-2 will blink at 4-sec interval (2-sec On/2-sec Off).
LED10 connected at GPIO-21 will blink at 2-sec interval (1-sec On/1-sec Off).
LED11 connected at GPIO-22 will blink at 1-sec interval (0.5-sec On/0.5-sec Off).
Figure-1:
After the uploading of the following sketch, it is observed that the above three LEDs are blinking independently (concurrently) at their respective rates. There is only one MCU which is driving three LEDs one after another in a sequential manner. But, the switching from one LED to the next LED is so fast that they appear to be blinking simultaneously. This fast switching arrangement is carried by FreeRTOS (Real Time Operating System) which is automatically loaded into the ESP32’s memory along with the sketch during uploading. There is no timing synchronization among the LEDs.
//function declarations
TaskHandle_t Task10Handle;
TaskHandle_t Task11Handle;
//GPIO definitions
#define LED 2
#define LED10 21
#define LED11 22
//task creation using FreeRTOS.h Librray functions
void setup()
{
Serial.begin(9600);
pinMode(LED, OUTPUT);
pinMode(LED10, OUTPUT);
pinMode(LED11, OUTPUT);
xTaskCreatePinnedToCore(Task10, "Task-10", 2048, NULL, 1, &Task10Handle, 1);
xTaskCreatePinnedToCore(Task11, "Task-11", 2048, NULL, 1, &Task10Handle, 1);
}
//LED blinks at 4000 ms interval
void loop()
{
digitalWrite(LED, HIGH);
vTaskDelay(2000);
digitalWrite(LED, LOW);
vTaskDelay(2000);
}
//LED10 blinks at 2000 ms interval
void Task10(void *pvParameters)
{
while (true)
{
digitalWrite(LED10, HIGH);
vTaskDelay(1000);
digitalWrite(LED10, LOW);
vTaskDelay(1000);
}
}
//LED11 blinks at 1000 ms interval
void Task11(void *pvParameters)
{
while (true)
{
digitalWrite(LED11, HIGH);
vTaskDelay(500);
digitalWrite(LED11, LOW);
vTaskDelay(500);
}
}
Figure-2: Task state diagram for Fig-1
1. Execution of Task10()
At time t1 of Fig-2, the MPU1 executes the indicated code (digitalWrite(LED11, HIGH)) to turn On LED11. Why it is LED11 and NOT LED10? When looking into the sketch, it is seen that Task11() (it drives LED11) has been created with higher priority (3); while, Task10() (it drives LED10), loop() and IdleTask() have lower priorities. So, Task11() is executed first.
After that the vTaskDelay(250) code is executed; as a result, the Task11() enters into blocked state and marked as unready in the Ready List of the Scheduler (the Supervisory software that manages orderly execution of the Tasks as planned/scheduled).
A Timer (there are four Timers inside ESP32) is started to count the elapsed time. At time t5 (when 500 ms has elapsed), with the help of a sophisticated mechanism (vectored interrupt or polled interrupt), the Task11() is marked as Ready in the Scheduler's Ready List.
The MPU/Scheduler finds that Task11() is ready to run, and it executes the indicated code (digitalwrite(LED11, LOW) to turn Off LED11. The immediate execution of vTaskDelay(250) puts Task11() into blocked state. At time t7, the LED11 will be turned On again.
The turn On, blocked state, turn Off, and blocked state of Task10() (it drives LED10) can be described exactly in the similar way the Task11() has been described above.
2. Task Switching from Task11() to Task10() (Context Switching)
At the expiry of “Time Slice” period, Task11() has entered into Blocked State at time t2 meaning that Task11() is marked as "unready" in the Scheduler's "Ready List".
The Scheduler checks the “Ready List” and finds that Task10() (recall that all Tasks are in ready states when they are created in the setup() function) is ready for execution. The MPU executes the indicated codes to turn On LED10. However, the following events occur before executing the very first instruction at time t2:
(1) When Task11() was being executed at time t1, the MPU was wholly owned by Task11(). The phrase "wholly owned" refers to the fact that the MPU was using its internal "Register Set" and "Program Counter", etc. to fetch/execute the instructions of Task11().
(2) Because only a tiny part of the program (the Task11()) has been executed, the CPU has produced some intermediate results and statuses (which are now within the registers) must be preserved in order to use them when Task11() will come again in execution phase. Also, the (return) address of the next instruction (to be executed when Task11() comes into execution phase) must also be preserved.
(3) Before control is transferred to Task10(), the Sceduler/MPU saves the intermediate results, statuses, and return address onto stack memory.
(4) After switching, Task10() owns the MPU and its all resources. The registers and program counter are loaded with a different set of values that are relevant to bigin the execution of Tasl10().
3. Execution of loop()
4. Execution of IdleTask()
In Fig-2, it is observed that Task11() has entered into blocked state at time t2; Task110() has entered into blocked state at time t3; loop() task has entered into blocked state at time t4.
The Scheduler checks the Ready List and finds no ready task to execute. What will it do now?
The MPU must do something which is, in the language of FreeRTOS, the creation of an “Idle Task” (IdleTask()) with priority 0 (the most lowest) for execution.
The CPU keeps executing this IdleTask() (spends CPU cycles doing nothing which is same as executing nop (no operation) instruction) until it finds a ready task at time t5.
C: The attached file may worth reading/practicing;
ch-18 ESP32Lec (6).pdf (1.2 MB)