# FreeRTOS on ESP32
contributed by<`RainbowEye0486`>
###### tags: `Side Project`
---
## 硬體規格
### 開發板
ESP32-WROOM-32
### 針腳圖

---
## Arduino IDE
### IDE 環境設定
==**安裝函式庫**==
首先必須先將 esp32 的 library 引入:
> Sketch -> include library -> Manage Libraries
找到 esp32 的 library 並且安裝
在
> file -> Preference -> Additional Board Manager URLs
新增 https://dl.espressif.com/dl/package_esp32_index.json
==**選擇板子**==
這時候選擇板子的選項應該多了一個全部都是 esp32 的選項,因為我使用的板子是 `ESP32-WROOM-32` ,所以選擇的開發板是 `DOIT ESP32 DIVKIT V1` ,其他的設定像是 `core debug level` ,可以幫助我們在 debug 的時候選擇要輸出到哪個層級,一般來說選擇較高層級的 debug level 較低的便不會顯示。
==**檔案位置**==
完整的 esp32 free RTOS library 檔案位置
```shell
$ cd ~/.arduino15/packages/esp32/hardware/esp32/1.0.6/tools/sdk/include/freertos/freertos
```
在 `FreeRTOSConfig.h` 當中,可以看到一些設定的相關參數,例如
```cpp=148
/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* Note that the default heap size is deliberately kept small so that
* the build is more likely to succeed for configurations with limited
* memory.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
*----------------------------------------------------------*/
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 1
#define configUSE_TICK_HOOK 1
#define configTICK_RATE_HZ ( CONFIG_FREERTOS_HZ )
/* Default clock rate for simulator */
//#define configCPU_CLOCK_HZ 80000000
/* This has impact on speed of search for highest priority */
#ifdef SMALL_TEST
#define configMAX_PRIORITIES ( 7 )
#else
#define configMAX_PRIORITIES ( 25 )
#endif
#ifndef CONFIG_ESP32_APPTRACE_ENABLE
#define configMINIMAL_STACK_SIZE 768
#else
/* apptrace module requires at least 2KB of stack per task */
#define configMINIMAL_STACK_SIZE 2048
#endif
```
比較重要的參數:
- `Priority level`: 25
- `最小 task 大小`: 768 bytes
對於多重定義的參數,不確定的話可以直接從 Serial 印出來檢查。
### 故障排除
如果上傳的時候遇到報錯訊息
```
A fatal error occurred: Failed to connect to ESP32: Invalid head of packet(0x0D)
```
代表無法寫入板子( upload on air ),這時候我們能做的事情有
1. 按住開機鍵上傳
2. 換一條好一點的傳輸線
3. 檢查 port 的讀取是否有問題
4. 重開機再上傳一次
---
## 初級實做
初級的實做範圍為單核心,並且沒有加上平行的處理,利用 arduino 本身的 setup 、 loop 當作主體去撰寫 freeRTOS
### Blink
:::spoiler 程式碼
```clike=
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
static const int led_pin = LED_BUILTIN;
void toggleLED(void *parameter) {
while(1) {
digitalWrite(led_pin, HIGH);
vTaskDelay(500 / portTICK_PERIOD_MS);
digitalWrite(led_pin, LOW);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
void setup() {
pinMode(led_pin, OUTPUT);
xTaskCreatePinnedToCore(toggleLED, "Toggle LED", 1024, NULL, 1, NULL, app_cpu);
}
void loop() {
}
```
:::
編譯完上傳之後就可以看到閃爍的板子了。
- `vTaskDelay(500 / portTICK_PERIOD_MS)` :
`INCLUDE_vTaskDelay` 必須先定義為1, resolusion 為一個單位的 tick ,其中傳入參數的變數型態是 `const TickType_t` 。 portTICK_PERIOD_MS 為一個 tick 所佔的時間,這邊代表 500ms 當作一個間隔,記得在這邊的 delay 跟普通 arduino 中的 delay 有所不同,這邊的 delay 是 non-blocking delay 。
- `xTaskCreatePinnedToCore`:
在 [freertos/include/freertos/task.h](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos.html) 中定義
> BaseType_t xTaskCreatePinnedToCore(
TaskFunction_t pvTaskCode,
const char \*constpcName,
const uint32_t usStackDepth,
void \*constpvParameters,
UBaseType_t uxPriority,
TaskHandle_t \*constpvCreatedTask,
const BaseType_t xCoreID
);
:::info
Create a new task with a specified affinity.
`pvTaskCode` : 相當於開一個新的執行緒的感覺,不會回傳,主要結構是一個 loop 。
`pcName`: 任務名字,為一個字串。
`ulStackDepth` : 任務 stack 的大小,與普通的 freeRTOS 不同。
`pvParameter` : Pointer that will be used as the parameter for the task being created.
`uxPriority` : The priority at which the task should run.
`pxStackBuffer` : pass back a handle by which the created task can be referenced.
`pxTaskBuffer` : Values 0 or 1 indicate the index number of the CPU which the task should be pinned to.
:::
### Task scheduling
這個實驗簡單的掩飾了優先級對我們的 task 產生的關係,我們可以看到,優先級最高的 task2 能夠搶佔 (preempt) 較低級的 task
:::spoiler 程式碼
```cpp
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
# else
static const BaseType_t app_cpu = 1;
# endif
// string to print
const char msg[] = "Barkadeer brig Arr booty rum.";
//Task handles
static TaskHandle_t task_1 = NULL;
static TaskHandle_t task_2 = NULL;
//*************************************************
void startTask1 (void *parameter) {
int len = strlen(msg);
while (1){
Serial.println();
for (int i=0; i < len; i++) {
Serial.print(msg[i]);
}
Serial.println();
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void startTask2 (void *parameter) {
while (1) {
Serial.print('*');
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(300);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Task Demo---");
Serial.print("Setup and loop task running on core ");
Serial.print(xPortGetCoreID());
Serial.print(" with priority ");
Serial.println(uxTaskPriorityGet(NULL));
xTaskCreatePinnedToCore(startTask1,"Task1", 1024, NULL, 1, &task_1, app_cpu);
xTaskCreatePinnedToCore(startTask2,"Task2", 1024, NULL, 2, &task_1, app_cpu);
}
void loop() {
for (int i = 0; i < 3; i++) {
vTaskSuspend(task_2);
vTaskDelay(2000 / portTICK_PERIOD_MS);
vTaskResume(task_2);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
if (task_1 != NULL) {
vTaskDelete(task_1);
task_1 = NULL;
}
}
```
:::
[Context Switching 概念](https://www.freertos.org/implementation/a00006.html)

- `vTaskSuspend()`: 注意到我們
==**挑戰實做**==
實做一個 Serial 讀寫界面能夠去控制 LED 閃爍的頻率
:::spoiler 程式碼
```cpp
#include <stdlib.h>
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
# else
static const BaseType_t app_cpu = 1;
# endif
static TaskHandle_t task_1 = NULL;
static TaskHandle_t task_2 = NULL;
//setting
static const uint8_t buf_len = 20;
static int blinking_rate = 500; //ms
static const int led_pin = LED_BUILTIN;
void startTask1 (void *parameter) {
char c;
char buf[buf_len];
uint8_t idx = 0;
memset(buf, 0, buf_len);
while (1) {
if (Serial.available() > 0) {
c = Serial.read();
if (c == '\n') {
blinking_rate = atoi(buf);
Serial.print("Updated LED delay to: ");
Serial.println(blinking_rate);
memset(buf, 0, buf_len);
idx = 0;
//vTaskDelay(2000 / portTICK_PERIOD_MS);
} else {
if (idx < buf_len - 1) {
buf[idx] = c;
idx++;
}
}
}
}
}
void startTask2 (void *parameter) {
while (1) {
vTaskDelay(blinking_rate / portTICK_PERIOD_MS);
digitalWrite(led_pin, HIGH);
vTaskDelay(blinking_rate / portTICK_PERIOD_MS);
digitalWrite(led_pin, LOW);
}
}
void setup() {
Serial.begin(9600);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("----LED Brinking Test with UI---");
Serial.println("Please Enter a number in miliseconds to change LED blinking rate");
pinMode(led_pin, OUTPUT);
xTaskCreatePinnedToCore(startTask1,"Read Serial", 1024, NULL, 1, &task_1, app_cpu);
xTaskCreatePinnedToCore(startTask2,"Toggle LED", 1024, NULL, 1, &task_1, app_cpu);
vTaskDelete(NULL);
}
void loop() {
}
```
:::
由於兩者的優先度是一樣的,所以任務是輪流做的,由於 task_2 在 delay 的時間佔大多數,所以讀取 Serial 的時候比較不會遇到搶佔的問題。
:::success
如果做其他的測試,我們可以發現如果更改優先度的話,例如
`xTaskCreatePinnedToCore(startTask1,"Read Serial", 1024, NULL, 2, &task_1, app_cpu);` ,會讓一直持續 busy looping 的 task_1 不允許 task_2 搶佔 cpu ,導致 task_2 所執行的 LED 燈完全不會有任何變化,但是由於我們之前說 `vTaskDelay` 這個函式是 non-blocking 的,所以如果在讀取完之後加上一個 `vTaskDelay(2000 / portTICK_PERIOD_MS);` 就會讓 LED 閃爍個兩秒,之後便會再次卡住。
:::
### Mamory Management
> Kernel -> Developer Docs -> Heap Memory Management
當中,我們可以看到在記憶體分配這部份,我們可以選擇 heap1~5 的分配方式,每一種分配方式都有相對應的分配限制。
:::spoiler 程式碼
```cpp
#include <stdlib.h>
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
# else
static const BaseType_t app_cpu = 1;
# endif
void testTask(void *parameter) {
while (1) {
int a = 1;
int b[100];
for (int i = 0; i < 100; i++) {
b[i] = a + 1;
}
Serial.println(b[0]);
}
}
void setup() {
Serial.begin(115200);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("freeRTOS Memory Demo");
xTaskCreatePinnedToCore(testTask,"Test Task", 1024, NULL, 1, NULL, app_cpu);
vTaskDelete(NULL);
}
void loop() {
}
```
:::
編譯完預計出現報錯訊息
```
Guru Meditation Error: Core 1 panic'ed (Unhandled debug exception)
Debug exception reason: Stack canary watchpoint triggered (Test Task)
Core 1 register dump:
PC : 0x40083774 PS : 0x00060c36 A0 : 0x3ffb8520 A1 : 0x3ffb8460
A2 : 0x3ffbdbb4 A3 : 0x3ffb85aa A4 : 0x3ffb85ab A5 : 0x00000000
A6 : 0x00000000 A7 : 0x00000000 A8 : 0x0000007f A9 : 0x3ff40000
A10 : 0x0000007f A11 : 0x00000000 A12 : 0x3ffb8218 A13 : 0x00000000
A14 : 0x00000000 A15 : 0x00000000 SAR : 0x00000000 EXCCAUSE: 0x00000001
EXCVADDR: 0x00000000 LBEG : 0x400d0bed LEND : 0x400d0bf5 LCOUNT : 0x00000000
ELF file SHA256: 0000000000000000
Backtrace: 0x40083774:0x3ffb8460 0x3ffb851d:0x3ffb8540 0x400d0ed2:0x3ffb8560 0x400d0f4e:0x3ffb8580 0x400d0f8d:0x3ffb85d0 0x400d0fa0:0x3ffb85f0 0x400d0bfd:0x3ffb8610 0x400860ed:0x3ffb87c0
Rebooting...
ets Jun 8 2016 00:22:57
rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1044
load:0x40078000,len:10124
load:0x40080400,len:5856
entry 0x400806a8
```
這邊我們可以發現發生 stack overflow 的現象,原因是我們分配給這個 task 的大小是 1024 ,但是整個 task 需要用到的空間大小大約 1200 bytes ,所以我們給它 1500 bytes 的空間大小
:::info
- 使用 `UBaseType_t uxTaskGetStackHighWaterMark(TaskHandle_t xTask)` 來查詢剩餘的 stack 空間,回傳為 word 當作單位,參數放 NULL 代表查詢自己。
- 使用 `xPortGetFreeHeapSize()` 來查詢剩餘的 heap 空間。
- 使用 `vPortFree(ptr)` 來釋放指標。
另外,如果想要同時檢查 task 跟 stack 狀態,也可以使用 [vTaskList()](https://www.freertos.org/a00021.html#vTaskList)
:::
==**挑戰實做**==
實做要求能夠創造兩個 task ,分別能夠紀錄在 Serial Monitor 的訊息,並且把這個訊息丟給令一個 task 去顯示。
:::spoiler 程式碼
```clike
include <stdlib.h>
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
# else
static const BaseType_t app_cpu = 1;
# endif
static const int buf_len = 225;
static char *msg_ptr = NULL;
static volatile uint8_t msg_flag = 0;
//***************************************************
void serialProducer(void *parameter) {
char c;
char buf[buf_len];
uint8_t idx = 0;
memset(buf, 0, buf_len);
while (1) {
if (Serial.available() > 0) {
c = Serial.read();
// Store received character to buffer if not over buffer limit
if (idx < buf_len - 1) {
buf[idx] = c;
idx++;
}
// Create a message buffer for print task
if (c == '\n') {
buf[idx - 1] = '\0';
if (msg_flag == 0) {
msg_ptr = (char *)pvPortMalloc(idx * sizeof(char));
// If malloc returns 0 (out of memory), throw an error and reset
configASSERT(msg_ptr);
memcpy(msg_ptr, buf, idx);
msg_flag = 1;
}
memset(buf, 0, buf_len);
idx = 0;
}
}
}
}
void serialConsumer(void *parameter) {
while (1) {
if (msg_flag == 1) {
Serial.println(msg_ptr);
// Give amount of free heap memory (uncomment if you'd like to see it)
Serial.print("Free heap (bytes): ");
Serial.println(xPortGetFreeHeapSize());
// Free buffer, set pointer to null, and clear flag
vPortFree(msg_ptr);
msg_ptr = NULL;
msg_flag = 0;
}
}
}
void setup() {
Serial.begin(115200);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Heap Demo---");
Serial.println("Enter a string");
xTaskCreatePinnedToCore(serialProducer,"Serial Producer",1024,NULL,1,NULL,app_cpu);
xTaskCreatePinnedToCore(serialConsumer,"Serial Consumer",1024,NULL,1,NULL,app_cpu);
vTaskDelete(NULL);
}
void loop() {
}
```
:::
實做方式為, `serialProducer` 會讀取一個小於 225 的字串,並且在收到換行符號的時候將 `buf` 上的字串轉移到 `msg_ptr` 上 `msg_flag` 打開,與此同時 `serialConsumer` 收到改變的旗標,印出 `msg_ptr` 的字串、檢查剩餘的 heap 空間,並在讀取完畢後將 heap 分配的空間清空。
:::info
- `configASSERT` : 跟 C 語言當中的 assert 用法一樣,裡面是 0 的時候會讓整個程式停下來。
:::
:::warning
這邊的 `msg_flag` 為 volatile ,原因是希望能夠將旗標能夠在改變的一瞬間寫回記憶體,能夠確保資料的一致性。
:::
## Queue
這個實驗想要解決寫入保護的問題,我們知道當記憶體被兩個不同的 thread(task) 修改(非同步)的時候,可能會因為修改的時間前後導致 [race condition](https://en.wikipedia.org/wiki/Race_condition) 。解決的方式有幾種,第一是讓讀寫以 atomic action 執行,或是用 mutex 、 semaphore 來確保 critical section ,這邊使用的是一個讀寫 queue。
:::warning
1. queue 的空間是存放在 heap 當中的。
2. 不要在 ISR 當中讀寫 queue ,因為 interrupt 不遵循 tick timer
:::
:::spoiler 程式碼
```clike
```
:::
:::info
- [xQueueReceive](https://www.freertos.org/a00118.html) : 傳入 `QueueHandle_t` 、 `void * pvItemToQueue` 以及 timeout tick 數,如果接收成功回傳 `pdTRUE` ,失敗則回傳 `pdFALSE`
- [xQueueSend](https://www.freertos.org/a00117.html) : 傳入 `QueueHandle_t` 、 `void * pvItemToQueue` 以及 timeout tick 數,回傳值同上。
:::
:::spoiler 程式碼
```clike
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
static const uint8_t msg_queue_len = 5;
static QueueHandle_t msg_queue;
//*****************************************************************************
// Tasks
void printMessages(void *parameters) {
int item;
while (1) {
if (xQueueReceive(msg_queue, (void *)&item, 0) == pdTRUE) {
//Serial.println(item);
}
Serial.println(item);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Queue Demo---");
msg_queue = xQueueCreate(msg_queue_len, sizeof(int));
xTaskCreatePinnedToCore(printMessages,
"Print Messages",
1024,
NULL,
1,
NULL,
app_cpu);
}
void loop() {
static int num = 0;
if (xQueueSend(msg_queue, (void *)&num, 10) != pdTRUE) {
Serial.println("Queue full");
}
num++;
vTaskDelay(500 / portTICK_PERIOD_MS);
}
```
:::
調整程式碼分別在 queueSend 跟 queueRead 的時候延遲的速率,就可以觀察到當讀寫速率不同的時候 queue 的變化。
==**挑戰實做**==
實做一個非同步訊息傳輸結構,包含兩個 task 跟 queue ,可以完成以下事情

其中, taskA 主要負責接收 Serial 的訊息並且傳遞到 q1 中, taskB 則從 q1 拿取相對應的指令,改變閃爍頻率並且將 blinked 資訊傳送給 q2 。
:::spoiler 程式碼
```clike
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
static const uint8_t buf_len = 255; // Size of buffer to look for command
static const char command[] = "delay "; // Note the space!
static const int delay_queue_len = 5; // Size of delay_queue
static const int msg_queue_len = 5; // Size of msg_queue
static const uint8_t blink_max = 100; // Num times to blink before message
// Pins (change this if your Arduino board does not have LED_BUILTIN defined)
static const int led_pin = LED_BUILTIN;
typedef struct Message {
char body[20];
int count;
} Message;
static QueueHandle_t delay_queue;
static QueueHandle_t msg_queue;
//*****************************************************************************
// Tasks
// Task: command line interface (CLI)
void doCLI(void *parameters) {
Message rcv_msg;
char c;
char buf[buf_len];
uint8_t idx = 0;
uint8_t cmd_len = strlen(command);
int led_delay;
memset(buf, 0, buf_len);
while (1) {
if (xQueueReceive(msg_queue, (void *)&rcv_msg, 0) == pdTRUE) {
Serial.print(rcv_msg.body);
Serial.println(rcv_msg.count);
}
if (Serial.available() > 0) {
c = Serial.read();
if (idx < buf_len - 1) {
buf[idx] = c;
idx++;
}
if ((c == '\n') || (c == '\r')) {
Serial.print("\r\n");
if (memcmp(buf, command, cmd_len) == 0) {
char* tail = buf + cmd_len;
led_delay = atoi(tail);
led_delay = abs(led_delay);
if (xQueueSend(delay_queue, (void *)&led_delay, 10) != pdTRUE) {
Serial.println("ERROR: Could not put item on delay queue.");
}
}
memset(buf, 0, buf_len);
idx = 0;
} else {
Serial.print(c);
}
}
}
}
// Task: flash LED based on delay provided, notify other task every 100 blinks
void blinkLED(void *parameters) {
Message msg;
int led_delay = 500;
uint8_t counter = 0;
pinMode(LED_BUILTIN, OUTPUT);
while (1) {
if (xQueueReceive(delay_queue, (void *)&led_delay, 0) == pdTRUE) {
strcpy(msg.body, "Message received ");
msg.count = 1;
xQueueSend(msg_queue, (void *)&msg, 10);
}
digitalWrite(led_pin, HIGH);
vTaskDelay(led_delay / portTICK_PERIOD_MS);
digitalWrite(led_pin, LOW);
vTaskDelay(led_delay / portTICK_PERIOD_MS);
counter++;
if (counter >= blink_max) {
strcpy(msg.body, "Blinked: ");
msg.count = counter;
xQueueSend(msg_queue, (void *)&msg, 10);
counter = 0;
}
}
}
void setup() {
Serial.begin(115200);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Queue Solution---");
Serial.println("Enter the command 'delay xxx' where xxx is your desired ");
Serial.println("LED blink delay time in milliseconds");
delay_queue = xQueueCreate(delay_queue_len, sizeof(int));
msg_queue = xQueueCreate(msg_queue_len, sizeof(Message));
xTaskCreatePinnedToCore(doCLI,
"CLI",
1024,
NULL,
1,
NULL,
app_cpu);
xTaskCreatePinnedToCore(blinkLED,
"Blink LED",
1024,
NULL,
1,
NULL,
app_cpu);
vTaskDelete(NULL);
}
void loop() {
}
```
:::
兩個 queue 分別紀錄 delay 以及 msg 。
定義一個傳輸用的 message 結構
```clike
typedef struct Message {
char body[20];
int count;
} Message;
```
中間的 body 當作紀錄的字串, count 紀錄 LED 閃爍幾次。
## Mutex
freeRTOS 的 Mutex 其實是跟 Semaphore 包在同一個 .h 檔底下的,基本上在 freeRTOS 當中的 mutex 跟 Semaphore 算是同一種東西。
:::spoiler 程式碼
```clike
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
// Globals
static int shared_var = 0;
static SemaphoreHandle_t mutex;
//*****************************************************************************
// Tasks
void incTask(void *parameters) {
int local_var;
while (1) {
if (xSemaphoreTake(mutex, 0) == pdTRUE) {
local_var = shared_var;
local_var++;
vTaskDelay(random(100, 500) / portTICK_PERIOD_MS);
shared_var = local_var;
Serial.println(shared_var);
xSemaphoreGive(mutex);
} else {
// Do something else
}
}
}
//*****************************************************************************
// Main (runs as its own task with priority 1 on core 1)
void setup() {
randomSeed(analogRead(0));
Serial.begin(115200);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Race Condition Demo---");
mutex = xSemaphoreCreateMutex();
xTaskCreatePinnedToCore(incTask,"Increment Task 1",1024,NULL,1,NULL,app_cpu);
xTaskCreatePinnedToCore(incTask,"Increment Task 2",1024,NULL,1,NULL,app_cpu);
// Delete "setup and loop" task
vTaskDelete(NULL);
}
void loop() {
// Execution should never get here
}
```
:::
:::info
> Kernel -> API Reference -> Semaphore / Mutexes
- `xSemaphoreCreateMutex()` : 初始化一個 mutex/semaphore
- [xSemaphoreTake](https://www.freertos.org/a00122.html) : 獲取 mutex/semaphore ,相當於 [mutex_lock](https://linux.die.net/man/3/pthread_mutex_lock)
- [xSemaphoreGive](https://www.freertos.org/a00123.html) : 釋放 mutex/semaphore ,相當於 [mutex_unlock](https://linux.die.net/man/3/pthread_mutex_lock)
:::
這邊我們簡單的使用了 random 的方式來決定兩個 `incTask` 每次增加完後的 delay 時間,模擬 race condition 的情形,但是在對 critical section 進行保護之後,最終的輸出結果可以看到速率不一的更新,但是 `shared_var` 的增加是依次的。
==**挑戰實做**==
處理 `void *parameter` 傳入時可能會產生 local 不同步的情形,導致寫入的時候雖然改變了 `delay_arg` ,但是傳入參數的時候送進去的值還是 0 。
:::spoiler 程式碼
```clike
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
static const int led_pin = LED_BUILTIN;
//*****************************************************************************
// Tasks
void blinkLED(void *parameters) {
// Copy the parameter into a local variable
int num = *(int *)parameters;
// Print the parameter
Serial.print("Received: ");
Serial.println(num);
// Configure the LED pin
pinMode(led_pin, OUTPUT);
while (1) {
digitalWrite(led_pin, HIGH);
vTaskDelay(num / portTICK_PERIOD_MS);
digitalWrite(led_pin, LOW);
vTaskDelay(num / portTICK_PERIOD_MS);
}
}
void setup() {
long int delay_arg;
// Configure Serial
Serial.begin(115200);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Mutex Challenge---");
Serial.println("Enter a number for delay (milliseconds)");
// Wait for input from Serial
while (Serial.available() <= 0);
delay_arg = Serial.parseInt();
Serial.print("Sending: ");
Serial.println(delay_arg);
// Start task 1
xTaskCreatePinnedToCore(blinkLED,
"Blink LED",
1024,
(void *)&delay_arg,
1,
NULL,
app_cpu);
// Show that we accomplished our task of passing the stack-based argument
Serial.println("Done!");
}
void loop() {
// Do nothing but allow yielding to lower-priority tasks
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
```
:::
預期輸出
```
---FreeRTOS Mutex Challenge---
Enter a number for delay (milliseconds)
Sending: 2323
Done!
Received: 0
```
更改的兩個步驟,第一是把 `delay_arg` 改成全域變數,再來將寫入時用 mutex 包起來。
```clike
while(1){
if (xSemaphoreTake(mutex, 0) == pdTRUE) {
delay_arg = Serial.parseInt();
Serial.print("Sending: ");
Serial.println(delay_arg);
xSemaphoreGive(mutex);
break;
}
}
```
預期輸出可以在輸入後將閃爍頻率更改。
## Semaphore
:::info
- `xSemaphoreCreateBinary` : 在初始化的時候我們可以宣告這是一個 buinary 的 Semaphore ,這也是同一個 .h 檔底下最大的不同之處。
- [xSemaphoreCreateCounting(uxMaxCount, uxInitialCount)](https://www.freertos.org/CreateCounting.html) : 創建一個最大量為 uxMaxCount ,初始值設定為 uxInitialCount 的 Semaphore 。
:::
:::spoiler
```clike
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
static const int num_tasks = 5;
typedef struct Message {
char body[20];
uint8_t len;
} Message;
static SemaphoreHandle_t sem_params;
void myTask(void *parameters) {
Message msg = *(Message *)parameters;
xSemaphoreGive(sem_params);
xSemaphoreTake(sem_params);
Serial.print("Received: ");
Serial.print(msg.body);
Serial.print(" | len: ");
Serial.println(msg.len);
xSemaphoreGive(sem_params);
vTaskDelay(1000 / portTICK_PERIOD_MS);
vTaskDelete(NULL);
}
void setup() {
char task_name[12];
Message msg;
char text[20] = "All your base";
Serial.begin(115200);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Counting Semaphore Demo---");
// Create semaphores (initialize at 0)
sem_params = xSemaphoreCreateCounting(num_tasks, 0);
// Create message to use as argument common to all tasks
strcpy(msg.body, text);
msg.len = strlen(text);
// Start tasks
for (int i = 0; i < num_tasks; i++) {
// Generate unique name string for task
sprintf(task_name, "Task %i", i);
// Start task and pass argument (common Message struct)
xTaskCreatePinnedToCore(myTask,
task_name,
1024,
(void *)&msg,
1,
NULL,
app_cpu);
}
for (int i = 0; i < num_tasks; i++) {
xSemaphoreTake(sem_params, portMAX_DELAY);
}
Serial.println("All tasks created");
}
void loop() {
// Do nothing but allow yielding to lower-priority tasks
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
```
:::
在原本的版本當中,我們加上沒有在 Task 的一系列 Serial.print 當中設立 semaphore 保護,導致在印 "Received: All your base | len: 13" 這串文字的時候可能會被其他的 task 打斷,這時候我們在印出的前後加上 Semephore 的獲取及釋放,可以成功的保護這些 print
```clike=\
xSemaphoreTake(sem_params);
Serial.print("Received: ");
Serial.print(msg.body);
Serial.print(" | len: ");
Serial.println(msg.len);
xSemaphoreGive(sem_params);
```
:::warning
但是這裡保留一個疑問,為什麼這樣 semaphore 就可以保證裡面的訊息會被按照順序印出來呢?印象中只保證進入 critical section 的 task 會被限制而已,但是應該不保證進入 critical section 的執行順序。
:::
==**挑戰實做**==
如圖,

我們會創造五個 producer 跟兩個 consumer , 每個 producer 會將自己代表的編號寫入 circuler buffer 三次,之後由 consumer 去讀取 buffer 中的資料。
:::spoiler 程式碼
```clike
//#include semphr.h
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
enum {BUF_SIZE = 5};
static const int num_prod_tasks = 5;
static const int num_cons_tasks = 2;
static const int num_writes = 3;
static int buf[BUF_SIZE];
static int head = 0; // Writing index to buffer
static int tail = 0; // Reading index to buffer
static SemaphoreHandle_t bin_sem; // Waits for parameter to be read
void producer(void *parameters) {
int num = *(int *)parameters;
xSemaphoreGive(bin_sem);
for (int i = 0; i < num_writes; i++) {
buf[head] = num;
head = (head + 1) % BUF_SIZE;
}
vTaskDelete(NULL);
}
void consumer(void *parameters) {
int val;
while (1) {
val = buf[tail];
tail = (tail + 1) % BUF_SIZE;
Serial.println(val);
}
}
void setup() {
char task_name[12];
// Configure Serial
Serial.begin(115200);
// Wait a moment to start (so we don't miss Serial output)
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Semaphore Alternate Solution---");
bin_sem = xSemaphoreCreateBinary();
for (int i = 0; i < num_prod_tasks; i++) {
sprintf(task_name, "Producer %i", i);
xTaskCreatePinnedToCore(producer,
task_name,
1024,
(void *)&i,
1,
NULL,
app_cpu);
xSemaphoreTake(bin_sem, portMAX_DELAY);
}
for (int i = 0; i < num_cons_tasks; i++) {
sprintf(task_name, "Consumer %i", i);
xTaskCreatePinnedToCore(consumer,
task_name,
1024,
NULL,
1,
NULL,
app_cpu);
}
Serial.println("All tasks created");
}
void loop() {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
```
:::
### Software Timer
如果我們想要創造一個週期性的任務,可以有以下作法:
1. 使用 `vTaskDelay` 每隔一段時間重新新增一個 task ,但是 overhead 太大,所以不建議。
2. `xTaskGetTickCount` ,隔一段時間檢查一次還剩下多少工作還沒做完。但是精確度只到一毫秒。
3. hardware timer:可移植性差,而且在 ESP 當中可以使用的種類不多。
4. software timer:
實做創建兩個任務,分別執行在啟動後兩秒執行一次以及每隔一秒循環啟動一次的任務。
:::spoiler 程式碼
```cpp
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
static TimerHandle_t one_shot_timer = NULL;
static TimerHandle_t auto_reload_timer = NULL;
//*****************************************************************************
// Callback function
// Called when one of the timers expires
void myTimerCallback(TimerHandle_t xTimer) {
// Print message if timer 0 expired
if ((uint32_t)pvTimerGetTimerID(xTimer) == 0) {
Serial.println("One-shot timer expired");
}
// Print message if timer 1 expired
if ((uint32_t)pvTimerGetTimerID(xTimer) == 1) {
Serial.println("Auto-reload timer expired");
}
}
//*****************************************************************************
void setup() {
Serial.begin(115200);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Timer Demo---");
// Create a one-shot timer
one_shot_timer = xTimerCreate(
"One-shot timer", // Name of timer
2000 / portTICK_PERIOD_MS, // Period of timer (in ticks)
pdFALSE, // Auto-reload
(void *)0, // Timer ID
myTimerCallback); // Callback function
// Create an auto-reload timer
auto_reload_timer = xTimerCreate(
"Auto-reload timer", // Name of timer
1000 / portTICK_PERIOD_MS, // Period of timer (in ticks)
pdTRUE, // Auto-reload
(void *)1, // Timer ID
myTimerCallback); // Callback function
if (one_shot_timer == NULL || auto_reload_timer == NULL) {
Serial.println("Could not create one of the timers");
} else {
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println("Starting timers...");
xTimerStart(one_shot_timer, portMAX_DELAY);
xTimerStart(auto_reload_timer, portMAX_DELAY);
}
vTaskDelete(NULL);
}
void loop() {
// Execution should never get here
}
```
:::
:::info
- [xTimerCreate](https://www.freertos.org/a00125.html) : 精準度仍然是 1ms
- `xTimerStart` :傳入兩個參數,第一個是控制的 timer ,第二個是 block 的時間。
:::
==**挑戰實做**==
實做包括一個能夠接收字元輸入的計時器,每當字元輸入,就會讓 LED 持續亮五秒之後熄滅。
:::spoiler
```cpp
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
static const TickType_t dim_delay = 5000 / portTICK_PERIOD_MS;
static const int led_pin = LED_BUILTIN;
static TimerHandle_t one_shot_timer = NULL;
// Turn Off LED after 5 seconds
void autoDimmerCallback(TimerHandle_t xTimer) {
digitalWrite(led_pin, LOW);
}
void serialin (void *parameter) {
char c;
pinMode(led_pin, OUTPUT);
while(1) {
if (Serial.available() > 0) {
c = Serial.read();
Serial.print(c);
// Turn on the LED
digitalWrite(led_pin, HIGH);
// Start timer (if timer is already running, this will act as
// xTimerReset() instead)
xTimerStart(one_shot_timer, portMAX_DELAY);
}
}
}
void setup() {
Serial.begin(115200);
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS LED Dimmer Demo---");
one_shot_timer = xTimerCreate(
"One-shot timer", // Name of timer
dim_delay, // Period of timer (in ticks)
pdFALSE, // Auto-reload
(void *)0, // Timer ID
autoDimmerCallback); // Callback function
xTaskCreatePinnedToCore(serialin,
"Do CLI",
1024,
NULL,
1,
NULL,
app_cpu);
vTaskDelete(NULL);
}
void loop() {
// Execution should never get here
}
```
:::
### Hardware Interrupt
在很多即時系統當中,尤其是 freeRTOS ,硬體的中斷擁有最高的優先級,比軟體控制的還要高。
based timer clock 為 80 MHz ,設定參數 `timer_divider` 為 80 的用意是想要讓閃爍頻率是一赫茲,也就是一秒切換一次。
:::spoiler 程式碼
```cpp
```
:::
:::info
由於硬體中斷的關係,所以處理時用的是 ESP 給的 API ,所以跟 freeRTOS 關係不大。
如果函式希望用來處理硬體中斷事件,必須加上屬性 `IRAM_ATTR`
文件中補充說明
> Interrupt handlers must be placed into IRAM if ESP_INTR_FLAG_IRAM is used when registering the interrupt handler. In this case, ISR may only call functions placed into IRAM or functions present in ROM.
ISR handler 必須直接放在 IRAM 裡面,除此之外,其他的 freeRTOS API 也會被放置在 IRAM 中。某些對時間敏感的程式碼需要放在 IRAM 中,這樣可以減少從 flash 載入程式碼的時間。ESP32 通過一個 32 kB 的 cache 讀去程式碼跟和資料。在某些情況下,將函數放到 IRAM 中可以減少 cache miss 所造成的延遲。
- `timerBegin` : 似乎只限於[ arduino esp ](https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-timer.h)所使用的函式,因為在 ESP-IDF 當中找不到相關紀錄,創建並開啟 timer
- `timerAttachInterrupt` : 向 timer 提供 ISR 。
- `timerAlarmWrite` : 何時觸發 timer ?會不會重複?
- `timerAlarmEnable` : ISR trigger
:::
:::spoiler 程式碼
```cpp
static const uint16_t timer_divider = 80;
static const uint64_t timer_max_count = 1000000;
// Pins (change this if your Arduino board does not have LED_BUILTIN defined)
static const int led_pin = LED_BUILTIN;
// Globals
static hw_timer_t *timer = NULL;
//*****************************************************************************
// Interrupt Service Routines (ISRs)
// This function executes when timer reaches max (and resets)
void IRAM_ATTR onTimer() {
// Toggle LED
int pin_state = digitalRead(led_pin);
digitalWrite(led_pin, !pin_state);
}
//*****************************************************************************
// Main (runs as its own task with priority 1 on core 1)
void setup() {
// Configure LED pin
pinMode(led_pin, OUTPUT);
// Create and start timer (num, divider, countUp)
timer = timerBegin(0, timer_divider, true);
// Provide ISR to timer (timer, function, edge)
timerAttachInterrupt(timer, &onTimer, true);
// At what count should ISR trigger (timer, count, autoreload)
timerAlarmWrite(timer, timer_max_count, true);
// Allow ISR to trigger
timerAlarmEnable(timer);
}
void loop() {
// Do nothing
}
```
:::
:::info
利用 semaphore 的方式來實現,過一秒觸發一次。 `xSemaphoreTake` 並不適用於 ISR 中,原因是 ISR 不能被視為是一個任務,因此也不能 blocked 。
- [xSemaphoreGiveFromISR](https://www.freertos.org/a00124.html):必須先用 `xSemaphoreCreateBinary()` 或是 `xSemaphoreCreateCounting()` 創建,不能跟 mutex 一起使用,原因是 ISR 系列的中斷不能 blocked ,所以理所當然也不會有 mutex 或是 semaphore。
- `portYIELD_FROM_ISR()` : 如果 `task_woken == pdTRUE` ,代表 task 成功收到 semaphore ,可以成功將 port 讓渡出去。
:::
這邊的作法比較像是用一個 while 迴圈來讓自己被自己鎖住(死鎖),並且讓 hardware interrupt 去強制解鎖,但是這樣做會有一個風險,就是如果根據互斥鎖的特性:
> If a thread attempts to relock a mutex that it has already locked, pthread_mutex_lock() shall behave as described in the Relock column of the following table.
- a mutex of type `PTHREAD_MUTEX_NORMAL` can deadlock
- a mutex of type `PTHREAD_MUTEX_ERRORCHECK` shall return an error
- a mutex of type `PTHREAD_MUTEX_RECURSIVE` will work as a recursive lock, that you must then unlock as many times as you locked it
- a mutex of type `PTHREAD_MUTEX_DEFAULT` will have undefined behaviour, which actually means that if on that platform the default lock is of any of the previous 3 types, it will behave characteristically as in the columns above, and if it is some other type then the behaviour will be undefined.
雖然這邊使用的是 pthread 來解釋,但是當專案大起來的時候很容易造成我們根本不曉得鎖卡在哪裡。加上不確定使用的互斥鎖是不是比較接近 normal 的那種,所以不建議這個寫法。
:::spoiler 程式碼
```cpp
//*****************************************************************************
// Interrupt Service Routines (ISRs)
// This function executes when timer reaches max (and resets)
void IRAM_ATTR onTimer() {
BaseType_t task_woken = pdFALSE;
// Perform action (read from ADC)
val = analogRead(adc_pin);
// Give semaphore to tell task that new value is ready
xSemaphoreGiveFromISR(bin_sem, &task_woken);
// Exit from ISR (Vanilla FreeRTOS)
//portYIELD_FROM_ISR(task_woken);
// Exit from ISR (ESP-IDF)
if (task_woken) {
portYIELD_FROM_ISR();
}
}
//*****************************************************************************
// Tasks
// Wait for semaphore and print out ADC value when received
void printValues(void *parameters) {
// Loop forever, wait for semaphore, and print value
while (1) {
xSemaphoreTake(bin_sem, portMAX_DELAY);
Serial.println(val);
}
}
```
:::
:::info
- `portENTER_CRITICAL_ISR(&spinlock)` : 雖然不能使用會 block 住的 mutex ,但是可以使用一直都在活動的自旋鎖。
- `portEXIT_CRITICAL_ISR(&spinlock)` : 返回自旋鎖。中間包住的是 critical section
- `portMUX_TYPE spinlock = portMUX_INITIALIZER_UNLOCKED` : 自旋鎖的初始化。
:::
我們看每次的輸出都是從19往下倒數到0,這段程式碼做的事情就是每隔兩秒就把計數器降到0,在這段期間內,每0.1秒便會中斷程式一次並且計數器向上加一。
:::spoiler 程式碼
```clike
void IRAM_ATTR onTimer() {
// ESP-IDF version of a critical section (in an ISR)
portENTER_CRITICAL_ISR(&spinlock);
isr_counter++;
portEXIT_CRITICAL_ISR(&spinlock);
}
void printValues(void *parameters) {
// Loop forever
while (1) {
// Count down and print out counter value
while (isr_counter > 0) {
// Print value of counter
Serial.println(isr_counter);
// ESP-IDF version of a critical section (in a task)
portENTER_CRITICAL(&spinlock);
isr_counter--;
portEXIT_CRITICAL(&spinlock);
}
// Wait 2 seconds while ISR increments counter a few times
vTaskDelay(task_delay);
}
}
```
:::
### Deadlock
優先度高的任務有可能會使優先度低的任務 starvation ,這時後有兩種方式可以解決,第一是讓高優先度的任務有比較多的 idle 時間,這樣就可以確保低優先度的任務有時間可以充分執行;另一種方式是設定低優先度的任務會有 aging 機制,其優先度也會隨著時間而上升,至於上升的程度則因設定而異。一但判斷執行夠了,其優先度會降到原本的等級,再慢慢上升。
與之相比,另一種情形是 livelock ,這種情形是當放棄資源嘗試獲取另一個資源時,又產生另一個 deadlock ,這時候這種動態的 deadlock 就是 livelock。
:::spoiler 程式碼
```clike
if (xSemaphoreTake(mutex_2, mutex_timeout) == pdTRUE) {
// Say we took mutex 2 and wait (to force deadlock)
Serial.println("Task B took mutex 2");
vTaskDelay(1 / portTICK_PERIOD_MS);
// Take mutex 1
if (xSemaphoreTake(mutex_1, mutex_timeout) == pdTRUE) {
// Say we took mutex 1
Serial.println("Task B took mutex 1");
// Critical section protected by 2 mutexes
Serial.println("Task B doing some work");
vTaskDelay(500 / portTICK_PERIOD_MS);
} else {
Serial.println("Task B timed out waiting for mutex 1");
}
} else {
Serial.println("Task B timed out waiting for mutex 2");
}
// Give back mutexes
xSemaphoreGive(mutex_1);
xSemaphoreGive(mutex_2);
// Wait to let the other task execute
Serial.println("Task B going to sleep");
vTaskDelay(500 / portTICK_PERIOD_MS);
```
:::
節錄 deadlock 的對應解法之一, timeout 。
一樣需要兩種不同的資源才能夠進入 critical section ,但是每個任務都指等待一秒的超時時間()
:::spoiler 程式碼
```clike
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
// Globals
static SemaphoreHandle_t mutex_1;
static SemaphoreHandle_t mutex_2;
//*****************************************************************************
// Tasks
// Task A (high priority)
void doTaskA(void *parameters) {
// Loop forever
while (1) {
// Take mutex 1 (introduce wait to force deadlock)
xSemaphoreTake(mutex_1, portMAX_DELAY);
Serial.println("Task A took mutex 1");
vTaskDelay(1 / portTICK_PERIOD_MS);
// Take mutex 2
xSemaphoreTake(mutex_2, portMAX_DELAY);
Serial.println("Task A took mutex 2");
// Critical section protected by 2 mutexes
Serial.println("Task A doing some work");
vTaskDelay(500 / portTICK_PERIOD_MS);
// Give back mutexes (in reverse order that we took them)
xSemaphoreGive(mutex_2);
xSemaphoreGive(mutex_1);
// Wait to let the other task execute
Serial.println("Task A going to sleep");
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
// Task B (low priority)
void doTaskB(void *parameters) {
// Loop forever
while (1) {
// Take mutex 1 (introduce wait to force deadlock)
xSemaphoreTake(mutex_1, portMAX_DELAY);
Serial.println("Task B took mutex 1");
vTaskDelay(1 / portTICK_PERIOD_MS);
// Take mutex 2
xSemaphoreTake(mutex_2, portMAX_DELAY);
Serial.println("Task B took mutex 2");
// Critical section protected by 2 mutexes
Serial.println("Task B doing some work");
vTaskDelay(500 / portTICK_PERIOD_MS);
// Give back mutexes (in reverse order that we took them)
xSemaphoreGive(mutex_2);
xSemaphoreGive(mutex_1);
// Wait to let the other task execute
Serial.println("Task A going to sleep");
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
//*****************************************************************************
// Main (runs as its own task with priority 1 on core 1)
void setup() {
// Configure Serial
Serial.begin(115200);
// Wait a moment to start (so we don't miss Serial output)
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---FreeRTOS Deadlock Demo---");
// Create mutexes before starting tasks
mutex_1 = xSemaphoreCreateMutex();
mutex_2 = xSemaphoreCreateMutex();
// Start Task A (high priority)
xTaskCreatePinnedToCore(doTaskA,
"Task A",
1024,
NULL,
2,
NULL,
app_cpu);
// Start Task B (low priority)
xTaskCreatePinnedToCore(doTaskB,
"Task B",
1024,
NULL,
1,
NULL,
app_cpu);
// Delete "setup and loop" task
vTaskDelete(NULL);
}
void loop() {
// Execution should never get here
}
```
:::
### Priority Inversion

優先度置換的概念是當低優先度的任務進入 critical section 的時候,高優先度的任務搶佔,但是某個時刻當高優先度的任務想要獲取 lock ,但是這個 lock 正被低優先度的任務擁有,這時候高優先度任務只能先阻塞並讓出 CPU ,反而是低優先度的任務可以獲得執行權,直到釋放 lock 之後再讓高優先度任務使用。硬體中斷在這邊是不適用的。
避免的方式是利用多核心,或是不要用 critical section 。

產生的問題之一,假設低優先度的任務搶佔成功後,在還沒歸還 lock 之前,可能會被中優先度的任務搶佔,如果剛好這個中優先度的任務執行的時間很長,就會變成是中優先度的執行反而讓高優先度的工作無法搶佔。
解決辦法之一是 Priority Inheritance ,這種方式能夠避免低優先度的任務長期佔用,又可以避免 critical section 執行上出入。作法是當高優先度的任務搶佔並試圖進入 critical section 的時候,先讓低優先度的任務把他的事情做完,然後再讓高優先度的任務進入 critical section 。

### Multicore Proceccer
**AMP** vs **SMP** : 最主要的差異就是非對稱式的核心會有一顆主核心負責調度以及發布任務等等,而對稱式則每個核心互相制衡。
[ESP-IDF SMP](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/freertos-smp.html)


## 使用 Debug 工具觀察內部任務運作情形
## Reference
- [成大資工 Wiki](http://wiki.csie.ncku.edu.tw/embedded/freertos)
- https://github.com/ShawnHymel/introduction-to-rtos/blob/main/11-priority-inversion/esp32-freertos-11-demo-priority-inversion/esp32-freertos-11-demo-priority-inversion.ino
- [segger](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/app_trace.html#app-trace-system-behaviour-analysis-with-segger-systemview)
- [debugger](https://www.visualmicro.com/page/ESP32-Debugging.aspx)