FreeRTOS Study
==
###### tags: `FreeRTOS` `RTOS`
# Task and Co-routines
## Task Introduction
> Note that an application can be designed using just tasks, just co-routines, or a mixture of both.
> Co-routines are really only intended for use on very small processors that have severe RAM constraints.
### Characteristics of a ‘Task’
- Scheduler 負責決定現在該由哪個 task 進行動作
- Task 不知道 scheduler 的運作方式,也就不知道自己什麼時候被點名
- 每個 Task 都有自己用來儲存狀態的 stack,被 scheduler 要求停止動作時,會把狀態儲存以備下次使用
### Characteristics of a ‘Coroutine’
- Coroutine 被用在非常小的裝置上,時至今日已很少被使用
- Coroutine 的概念跟 task 很像,但有以下幾點不同
1. **Stack usage**
所有 co-routines 共用一個 stack,這麼做可以大幅減少 RAM 的使用
2. **Scheduling and priorities**
Co-routines 採用了以優先權為依據的任務排程方式,但是它可以被包在採用搶占排程的 task 裡面
3. **Macro implementation**
co-routine 的實作是由一組 macro 組成
4. **Restrictions on use**
由於減少了 RAM 的使用,伴隨而來的缺點是對 co-routines 結構的嚴格限制
## Task States
### Running
正在執行的 task,如果處理器只有單核心,那就只會有一個 task 在 running state
### Ready
當有優先權相同或更高的 task 正在執行時,其他沒有被 blocked 或是 suspended 的 task 就會在 ready state 等待被 scheduler 點名出來執行
### Blocked
當 task 要等待某個指令才會繼續進行時會進入 block state,例如在 task 內呼叫 *vTaskDelay()*,除此之外,等待 queue、event、semaphore、notification 等事件也會使得 task 進入 block state。
所有在 block state 的 task 都會有一個計時器,計時器 timeout 時,即使 task 沒有等到要繼續執行的 event,也會被強制 unblock
### Suspended
只有明確呼叫 *vTaskSuspend()* 的 task 會進入 suspend state。而呼叫 *vTaskResume()* 可以使在 suspend state 的 task 回到 ready state

https://www.freertos.org/RTOS-task-states.html
## Task Priorities
- task 的優先權由低至高為 0 ~ (configMAX_PRIORITIES - 1)
- configMAX_PRIORITIES 被定義在 FreeRTOSConfig.h
- configMAX_PRIORITIES 可以為任意值,但為了 RAM 的使用效率著想,盡量不要設定為過高的數值
> If the port in use implements a port optimised task selection mechanism that uses a ‘count leading zeros’ type instruction (for task selection in a single instruction) and **configUSE_PORT_OPTIMISED_TASK_SELECTION** is set to **1** in FreeRTOSConfig.h, then **configMAX_PRIORITIES** cannot be higher than **32**.
> // TODO: study what is PORT_OPTIMISED_TASK_SELECTION
- [idle task](https://www.freertos.org/RTOS-idle-task.html) 優先權為 0 (tskIDLE_PRIORITY)
- Running state 的 task 必定為優先權最高或與 ready state 等高的 task
- configUSE_TIME_SLICING 被設為 1 的話,相同優先權的 task 可以共享處理器的時間(分時多工)
https://www.freertos.org/RTOS-task-priority.html
## Implementing a Task
### Basic
Task 長得像下列範例
```cpp=
void vATaskFunction(void *pvParameters) {
for (;;) {
// Task implementation
}
vTaskDelete(NULL);
}
```
- *pvParameters 是唯一傳入的參數
- 不要在 task 裡面使用 return
- 使用 **xTaskCreate()** 或 **xTaskCreateStatic()** 來創建 task,用 **vTaskDelete()** 來移除 task
### Task Creation Macro
**portTASK_FUNCTION** 和 **portTASK_FUNCTION_PROTO** 是用來給編譯器識別為特殊的句法,以轉換成相對應的程式碼。除非文件內有特別註明,否則不要隨便使用。(目前只有 PIC18 fedC 使用)
https://www.freertos.org/implementing-a-FreeRTOS-task.html
## The Idle Task and Idle Hook Function
### The Idle Task
- 當 scheduler 啟動時就會自動產生一個優先權為最低(0, a.k.a. tskIDLE_PRIORITY)的 task
- 這個優先權最低的 task 是用來回收呼叫 vTaskDelete() 的 task 所遺留的記憶體空間
- application task 也能使用這個最低的優先權(0, tskIDLE_PRIORITY),詳細請參考[configIDLE_SHOULD_YIELD configuration parameter](https://www.freertos.org/a00110.html)
### The Idle Task Hook
- Idle task hook 是在每個 idle task 的 cycle 中會執行的函式
- 通常用來使 MCU 進入省電模式
- 有兩種實現方式
1. 在 idle task hook 裡面實作你要的功能
- 把 FreeRTOSConfig.h 裡面的 **configUSE_IDLE_HOOK** 設為 **1**
- 定義 **void vApplicationIdleHook(void);** 函式
2. 創建一個優先權唯 tskIDLE_PRIORITY 的 task
- 請參考 [Embedded software application design](https://www.freertos.org/tutorial/index.html) 頁面來了解更多
- 不要去 block idle task
> 講簡單一點就是 idle task 進行時,idle task 會呼叫的 callback
https://www.freertos.org/RTOS-idle-task.html
# Queues, Mutexes, Semaphores
## Queues

- 用來做 tasks 之間或者是 interrupt 與 task 之間的溝通
- FIFO
- 資料小時用 pass by copy & 資料大時用 pass by copied pointer (類 reference)
- C 語言的基本型態變數或是小的 structure 可以直接複製到 queue 裡面
- 資料大小可能會改變的變數可透過自定義的 queue 來傳送,而負責傳送的 message 的 structure 會含有一個成員指標指向資料位置,另一個則負責記錄資料大小,may be something like following
```cpp=
struct message {
data_t *ptrToData,
double dataSize
}
```
- 單一 queue 可以接收不同來源的訊息
- tasks 之間透過 queue 傳遞資料的整個過程是 thread-safe 的
- task 和 interrupt 要收送 queue 內的資料時,呼叫的是不同的 API,不過都很簡單好使用:)
### Blocking on Queues
- 當一個 task 要讀取空的 queue 時,該 task 會被 block
- 當一個 task 要寫入滿的 queue 時,該 task 會被 block
- 當很多 task 因為同一個 queue 被 block 時,優先權高的 task 會先被 unblock
- interrupts 只能呼叫結尾是 "FromISR" 的 function
https://www.freertos.org/Embedded-RTOS-Queues.html
## Binary Semaphores
> TIP: ‘Task Notifications’ can provide a light weight alternative to binary semaphores in many situations
>
> 文件一開頭就寫了上面這句,因此想到要用 semaphores 時應該先去考慮使用 task notifications
- Binary semaphores 被應用在同步以及互斥鎖
- Binary semaphores 和 mutexes 很類似,但是 mutexes 有優先權繼承機制,binary semaphores 沒有
- Binary semaphores 可視為一個大小只有 1 個 message 的 queue,因此 queue 的狀態只會有 "滿的" 和 "空的" 兩種狀態(binary)
- 等待 semaphore 的 task 只能讀取 queue (解鎖),釋放 semaphore 的 task/interrupt 只能寫入 queue (上鎖)
- Binary semaphores 的使用者並不關心 queue 裡面存了什麼資料,只在乎它是什麼狀態

https://www.freertos.org/Embedded-RTOS-Binary-Semaphores.html
## Counting Semaphores
> TIP: ‘Task Notifications’ can provide a light weight alternative to counting semaphores in many situations
>
> 文件一開頭就寫了上面這句,因此想到要用 semaphores 時應該先去考慮使用 task notifications
- 和 Binary semaphores 一樣, Counting Semaphores 的使用者並不關心 queue 裡面存了什麼資料,只在乎它是什麼狀態
- Counting semaphores 通常用在下列兩種狀況
1. Counting events
每當有事件發生時,event handler 會給予一個 semaphore(增加 semaphore 的數量);而 handler task 每次處理事件時,會消耗一個 semaphore(減少semaphore的數量),因此在這種情境下,counting semaphores 的數量表示還沒有被處理的事件個數。
2. Resource managment
這種應用情境中,counting semaphores 表示可被使用的資源個數。某個 task 使用資源時,須取走一個 semaphore,當 semaphores 個數為 0 時表示已經沒有資源可被使用,需等待 task 使用完該資源並歸還 semaphores 時,才能讓下一個 task 使用。
https://www.freertos.org/Real-time-embedded-RTOS-Counting-Semaphores.html
## Mutexes
- Mutexes 和 binary semaphores 類似,差別在於 mutexes 有優先權繼承機制
- Binary semaphores 適合用在同步(tasks 之間或 task 與 interrupt 之間)
- Mutexes 適合用在簡易的互斥鎖
- Mutexes 可以想像成是資源的使用權,要使用資源的人需要去取得 mutex,使用完之後須歸還
- 在使用取得 mutex 的函式時,要傳入一個參數用來指定取得 mutex 前**可等待的最大 tick 數**,超過這個 tick 數之後,欲獲取 mutex 的 task 會被 block
- 當有一高優先權的 task 要領取 mutex,而一低優先權的 task 正在使用 mutex,則低優先權 task 會暫時被拉到與要領取 mutex 的 task 一樣高的優先權,這就是**優先權繼承機制**。這是被設計來解決優先權倒置的問題
- 優先權繼承機制並不能完全解決優先權倒置問題,但能最小化它
- Mutexes 不應該被使用在 interrupt 上,因為:
1. interrupt 沒有優先權
2. interrupt 不能為了等資源而被 block 住

https://www.freertos.org/Real-time-embedded-RTOS-mutexes.html
## Recursive Mutexes
- Mutex 可以透過 *xSemaphoreTakeRecursive()* 被同一個 task 取走多次
- 由於 mutex 有優先權繼承機制,因此只有取走 mutex 的 task 可以歸還 mutex
> 不然還有誰可以還?
- mutex 被取走幾次就要歸還幾次後才能再次被其他人使用
- 同上,Mutexes 不應該被使用在 interrupt 上,因為:
1. interrupt 沒有優先權
2. interrupt 不能為了等資源而被 block 住
https://www.freertos.org/RTOS-Recursive-Mutexes.html
# Source Code Organization
[Link](https://www.freertos.org/a00017.html)
```
FreeRTOS
|
+-Demo Contains the demo application projects.
| |
| +-Common The demo application files that are used by all the demos.
| +-Dir x The demo application build files for port x
| +-Dir y The demo application build files for port y
|
+-Source Contains the real time kernel source code.
|
+-include The core FreeRTOS kernel header files
|
+-Portable Processor specific code.
|
+-Compiler x All the ports supported for compiler x
+-Compiler y All the ports supported for compiler y
+-MemMang The sample heap implementations
```
## FreeRTOS Kernel Files
- 三個最基本的程式碼
- task.c (5000多行)
- queue.c (3000行)
- list.c (200多行)
- 其他 Kernel程式碼 (選用)
- timer.c (1000多行)
- coroutine.c (350多行)
## Memory Management Files
在選用 portable files (/Source/Portable/Compiler/Architecture)時,也要一併把 **/MemMang** 留著。
更多有關 MemMang 的介紹,請參考[連結](https://www.freertos.org/a00111.html)
# Static VS Dynamic Memory
## Introduction
- FreeRTOS 提供下列物件靜態 / 動態記憶體分配方式
- Tasks
- Software Timers
- Queues
- Event Groups
- Binary Semaphores
- Counting Semaphores
- Recursive Semaphores
- Mutexes
- 靜態 & 動態記憶體分配可同時使用在同一應用程式內
## Creating an RTOS Object Using Dynamically Allocated RAM
- 創建物件時所需要的函式參數少
- 記憶體分配由 RTOS 的 api 管理
- 物件被刪除時能自動會收記憶體空間,減少記憶體的使用率
- RTOS 的 api 會回傳記憶體堆疊的資訊,使得堆疊大小能夠做最佳化
- 記憶體分配策略可以自己選擇,例如
- heap_1.c 最簡單,且通常用於要求安全性的應用上
- heap_4.c 有做記憶體空間碎片的保護
- heap_5.c 可以將堆疊分散到記憶體的不同區域
- 將 **configSUPPORT_DYNAMIC_ALLOCATION** 設為 **1** 或**不定義**,可以使用下列 api 來建立動態記憶體物件
- xTaskCreate()
- xQueueCreate()
- xTimerCreate()
- xEventGroupCreate()
- xSemaphoreCreateBinary()
- xSemaphoreCreateCounting()
- xSemaphoreCreateMutex()
- xSemaphoreCreateRecursiveMutex()
## Creating an RTOS Object Using Statically Allocated RAM
- 物件可以被分配到指定的記憶體空間
- 所需最大記憶體空間在 link time 就知道,不需要等到 run time
- 開發者不用去擔心記憶體分配失敗的問題
- 簡化 RTOS 應用程式的開發
- 將 **configSUPPORT_STATIC_ALLOCATION** 設為 **1**,則可以使用靜態記憶體分配的相關 api,如下
- xTaskCreateStatic()
- xQueueCreateStatic()
- xTimerCreateStatic()
- xEventGroupCreateStatic()
- xSemaphoreCreateBinaryStatic()
- xSemaphoreCreateCountingStatic()
- xSemaphoreCreateMutexStatic()
- xSemaphoreCreateRecursiveMutexStatic()
https://www.freertos.org/Static_Vs_Dynamic_Memory_Allocation.html
# Heap Memory Managment
- FreeRTOS 有五種預設的記憶體堆疊的管理機制,被放在 /FreeRTOS/Source/portable/MemMang 裡面
- heap_1: 最簡易的一種,不允許記憶體被釋放
- heap_2: 允許記憶體被釋放,但不會去合併相鄰的閒置記憶體
- heap_3: 將標準的 malloc() 和 free() 重新包裹為 thread-safe 的函式
- heap_4: 會將相鄰的閒置記憶體合併。Includes absolute address placement option.
- heap_5: 除了可以做到 heap_4 能做到的事以外,還可以將一個堆疊分散到不同的記憶體碎片做存取
> heap_1 is less useful since FreeRTOS added [support for static allocation](https://www.freertos.org/Static_Vs_Dynamic_Memory_Allocation.html).
heap_2 is now considered legacy as the newer heap_4 implementation is preferred.
## heap_1.c
- 儘管無法釋放用不到的記憶體堆疊,但在不需要移除 task, queue, semaphore, mutex 的應用上,仍被大量使用
- 記憶體堆疊的總大小被定義在 **FreeRTOSConfig.h** 裡面的 **configTOTAL_HEAP_SIZE**
- **xPortGetFreeHeapSize()** 回傳尚未被使用的記憶體堆疊大小,可被用來最佳化 configTOTAL_HEAP_SIZE 的數值
### 有關 heap_1.c 的實現
- 如果應用程式不會去刪除 task, queue, semaphore, mutex 則可能適合使用 heap_1
- 記憶體的分配一開始就被確立,因此不會有記憶體碎片化的問題
- 把記憶體分配為靜態的陣列,因此適用於那些不是真的要使用動態記憶體分配的應用程式
## heap_2.c
- heap_4.c 出來以後 heap_2.c 就過時了,請以 heap_4 替代 heap_2
- 可以釋放記憶體,但不會去統合分散的記憶體堆疊
- 記憶體堆疊的總大小被定義在 **FreeRTOSConfig.h** 裡面的 **configTOTAL_HEAP_SIZE**
- **xPortGetFreeHeapSize()** 回傳尚未被使用的記憶體堆疊大小,可被用來最佳化 configTOTAL_HEAP_SIZE 的數值,但不會提供記憶體分散的資訊
### 有關 heap_2.c 的實現
- 不要在每次生成時大小都不一樣的 task, queue, semaphore, mutex 上使用 heap_2,這會造成記憶體碎片化,最終導致記憶體分配失敗
- 此方式為動態分配記憶體,比原生的 malloc() 等函式效能更好
## heap_3.c
- 僅僅是將原生的 malloc() 和 free() 函式重新包裹為 thread-safe 函式
### 有關 heap_3.c 的實現
- 需要一個 linker 來設置記憶體堆疊,以及有提供 malloc()、free() 函式的 library
- 記憶體空間和位置並非固定
- 會造成 RTOS kernel 的大小增加
- **configTOTAL_HEAP_SIZE** 不影響 heap_3
## heap_4.c
- 和 heap_2 類似,不過可以合併分散的記憶體空間
- 記憶體堆疊的總大小被定義在 **FreeRTOSConfig.h** 裡面的 **configTOTAL_HEAP_SIZE**
- **xPortGetFreeHeapSize()** 回傳尚未被使用的記憶體堆疊大小
- **xPortGetMinimumEverFreeHeapSize()** 回傳系統開機到當下最小的可用記憶體空間大小
- 上述兩個函式皆不會回傳分散的記憶體資訊
- vPortGetHeapStats() 能提供額外資訊,如下
```cpp=
/* Prototype of the vPortGetHeapStats() function. */
void vPortGetHeapStats( HeapStats_t *xHeapStats );
/* Definition of the Heap_stats_t structure. */
typedef struct xHeapStats
{
size_t xAvailableHeapSpaceInBytes; /* The total heap size currently available – this is the sum of all the free blocks, not the largest block that can be allocated. */
size_t xSizeOfLargestFreeBlockInBytes; /* The maximum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
size_t xSizeOfSmallestFreeBlockInBytes; /* The minimum size, in bytes, of all the free blocks within the heap at the time vPortGetHeapStats() is called. */
size_t xNumberOfFreeBlocks; /* The number of free memory blocks within the heap at the time vPortGetHeapStats() is called. */
size_t xMinimumEverFreeBytesRemaining; /* The minimum amount of total free memory (sum of all free blocks) there has been in the heap since the system booted. */
size_t xNumberOfSuccessfulAllocations; /* The number of calls to pvPortMalloc() that have returned a valid memory block. */
size_t xNumberOfSuccessfulFrees; /* The number of calls to vPortFree() that has successfully freed a block of memory. */
} HeapStats_t;
```
### 有關 heap_4.c 的實現
- 可以用在會反覆刪除 task, queue, semaphore, mutex 的應用程式上
- 記憶體的分配位置不固定,但效能比標準 c library 還要好
heap_4 對於想要植入記憶體動態分配的應用程式來說非常有用
## heap_5.c
- 與 heap_4 類似,可以整合相鄰且被釋放的記憶體空間,除此之外,還能**將記憶體堆疊擴展到不相鄰的記憶體片段上**
- 透過 **vPortDefineHeapRegions()** 初始化,且任何要使用 heap_5 管理方式的人都必須等待該函式被呼叫,其中建立任何 RTOS 物件都會時都會在背後呼叫 pvPortMalloc(),因此在建立 RTOS 物件前要確定有呼叫到 vPortDefineHeapRegions()
- vPortDefineHeapRegions() 帶了一個參數,這個參數是 HeapRegion_t 結構的矩陣,定義如下
```cpp
typedef struct HeapRegion
{
/* Start address of a block of memory that will be part of the heap.*/
uint8_t *pucStartAddress;
/* Size of the block of memory. */
size_t xSizeInBytes;
} HeapRegion_t;
```
- 該矩陣結尾時須加上 {NULL, 0} 的 HeapRegion_t,請參考下列例子
```cpp
/* Allocate two blocks of RAM for use by the heap. The first is a block of
0x10000 bytes starting from address 0x80000000, and the second a block of
0xa0000 bytes starting from address 0x90000000. The block starting at
0x80000000 has the lower start address so appears in the array fist. */
const HeapRegion_t xHeapRegions[] =
{
{ ( uint8_t * ) 0x80000000UL, 0x10000 },
{ ( uint8_t * ) 0x90000000UL, 0xa0000 },
{ NULL, 0 } /* Terminates the array. */
};
/* Pass the array into vPortDefineHeapRegions(). */
vPortDefineHeapRegions( xHeapRegions );
```
- **xPortGetFreeHeapSize()** 回傳尚未被使用的記憶體堆疊大小
- **xPortGetMinimumEverFreeHeapSize()** 回傳系統開機到當下最小的可用記憶體空間大小
- 上述兩個函式皆不會回傳分散的記憶體資訊
- vPortGetHeapStats() 能提供額外資訊
# RTOS Fundamentals
## Multitasking Basics
- Concurrency 是真正的多工,multitasking 是分時多工

https://www.freertos.org/implementation/a00004.html
## Scheduling Basics
- Scheduler 是 kernel 中負責管控哪個 task 可以運行的角色
- 排程策略 (Scheduling Policy) 是決定哪個 task 可以運作的演算法,通常會讓每個 task 公平的分到執行的權利
- 除了 scheduler 可以暫停某項 task 以外,task 也可以 suspend 自己
https://www.freertos.org/implementation/a00005.html
## Context Switching
- 每個 task 都會有自己的 context (哪個記憶體的變數為多少、執行到哪行...)
- OS 會在 task 進入 suspend 前把 context 記錄下來,task 回復並繼續執行前,會將 context 載入

https://www.freertos.org/implementation/a00006.html
## Real Time Applications
- RTOS 和 none-RTOS 的差異反映在他們的排程策略上
- RTOS 中任務完成的時間必須反應在現實中的時間
https://www.freertos.org/implementation/a00007.html
## Real Time Scheduling

- 一開始,vControlTask 在等待 timer event,vKeyHandlerTask 在等待 key pressed event,因此是由 idle task 取得處理器的工作時間
- 在 t1,key pressed event 發生了,且 vKeyHandlerTask 的優先權比 idle task 高,所以輪到 vKeyHandlerTask 工作
- 在 t2,vKeyHandlerTask 已經處理完自己的工作,且沒有其他優先權更高的 task 要運作,因此又輪到 idle task 工作
- 在 t3,timer event 發生了,這次輪到 vControlTask 工作
- t3 和 t4 之間,key pressed event 也發生了,但是 vKeyHandlerTask 的優先權比 vControlTask 低,因此沒辦法馬上開始工作
- 在 t4,vControlTask 完成了自己的工作,並將自己暫停以等待下一個 timer event,此時 vKeyHandlerTask 就可以出來處理自己的工作了
- 在 t5,key pressed event 被處理完了,因此 vKeyHandlerTask 將自己暫停以等待下一個 key pressed event,此時沒有其他 task 要執行,因此統再度把 idle task 叫起來工作
- t5 和 t6 中間處理了一個 timer event
- 在 t6,vKeyHandlerTask 被叫起來工作,但在工作中 timer event 也被觸發,且 vControlTask 的優先權比 vKeyHandlerTask 高,因此 vKeyHandlerTask 會被打斷,並開始執行 vControlTask 的工作
- 在 t8,vControlTask 的工作結束了,所以剛被打斷工作的 vKeyHandlerTask 可以回來繼續執行他的任務
https://www.freertos.org/implementation/a00008.html
#
v means return void
x means return signed number