# [13] Mbed RTOS 實作 ## RTOS - 簡介 * 即時作業系統(Real-time operating system,RTOS),是指作業系統要在一個固定時間內,做出正確反應。 * RTOS相較一般的作業系統,最大的特色就在於「即時性」 * 即時作業系統可分類為「軟式(Soft)」與「硬式(Hard),此間差別主要在於完成 process 之間的時間差是否符合設定。 | | **硬性即時作業系統** | **軟性即時作業系統** | | -------- | -------- | -------- | | **時間要求** | <font color = "red">必須準時</font>,不能遲到 | 可以稍微遲到,但不能太頻繁 | |**後果**| 沒按時完成就可能出大問題(失敗) | 遲到會降低系統效能,但可接受 | |**應用範例** |安全氣囊、醫療設備、火箭控制| 視訊播放、遊戲、通訊軟體| |**任務的緊急程度** |<font color = "red">高度緊急</font>,沒有延遲空間| 相對不緊急,偶爾容忍延遲| | | **硬性即時作業系統** | **軟性即時作業系統** | | -------- | -------- | -------- | | **簡介** | 也可稱為安全臨界系統(Safety-Critical System),有最迫切的需求,必須在特定期限之前回應事件,保證在它們的期限內完成臨界即時任務。 | 跟硬性即時系統比較起來,限制的範圍比較少,簡單提供一件臨界的即時系統,將在其它的任務上接受優先權並保有優先權直到它完成。 | |**工作延遲**| 所有工作都不能夠延遲 | 允許少量的工作延遲,若沒有在回應時間內完成,只是造成系統的效能變差,不會影響系統的執行 | |**要求** | 非常嚴苛| 比較寬鬆| ## Mbed-OS RTOS Mbed OS RTOS 的物件導向功能包括執行緒管理( thread )、同步處理( synchronization objects )和定時器 ( timer )。它還提供用於附加於特定應用程序 idle hook function 的介面,用來讀取操作系統計數並提供 RTOS 報錯功能。 :::info Hook 是用來與作業系統掛勾進而攔截並處理某些訊息之用。idle hook function 即是由介面觸發後,使程式進入 Idle 狀態的函式。 ::: --- ### Thread **Program**:一群程式碼的集合。 **Process**:由Program所產生的執行個體(或稱做處理序、進程)。 **<font color = "red">**執行緒**(Thread)</font> (工作線)** 是作業系統能夠進行運算排程的最小單位,被包含在行程之中,式行程中的實際運作單位。在 Mbed OS RTOS 中以 thread 的函式庫來設定 thread的運作,而在 main 函式中,因為其為初始化程式本體的函式,所以他的初始排序優先權(os Priority)為`osPriorityNormal`。 **Firefox**:單獨只有firefox.exe這個Process,所有分頁是用Multi-Thread完成。 **Chrome**:每個分頁都相當於一個執行個體(Multi-Process)。 :::info RTOS函式庫 : https://os.mbed.com/handbook/RTOS ::: 以下為簡單的應用 : **<font color = "red">執行緒的工作(led2_thread 函式)</font>** LED1:每 0.5 秒翻轉一次亮/滅,由主程式控制。 LED2:每 1 秒翻轉一次亮/滅,由執行緒控制。 ``` C++= #include "mbed.h" // 引入 mbed OS 的核心函式庫 #include "rtos.h" // 引入 RTOS (Real-Time Operating System) 函式庫,支援執行緒功能 DigitalOut led1(LED1); // 定義數位輸出物件 led1,控制 LED1 燈(通常內建於開發板) DigitalOut led2(LED2); // 定義數位輸出物件 led2,控制 LED2 燈 Thread thread; // 建立一個執行緒物件,用來執行多執行緒中的另一個任務 // 定義執行緒的工作內容 void led2_thread() { while (true) { // 無限循環,確保執行緒不會停止 led2 = !led2; // 翻轉 led2 的狀態(亮變滅,滅變亮) Thread::wait(1000); // 等待 1000 毫秒(1 秒) } } int main() { thread.start(led2_thread); // 啟動執行緒,開始執行 led2_thread 函式中的內容 while (true) { // 主程式的無限循環 led1 = !led1; // 翻轉 led1 的狀態(亮變滅,滅變亮) Thread::wait(500); // 等待 500 毫秒(0.5 秒) } } ``` :::info The main function is already the first thread scheduled by the rtos ::: 執行緒之間會在以下階段間轉換 : * **RUNNING** : 執行緒在執行的階段,若為單執行緒晶片,則只會有一執行緒在執行。 * **READY** : 執行緒準備好執行的階段,一旦執行的執行緒(RUNNING thread)結束或等待下一個準備好的執行緒(READY thread)完成(補進的執行緒必須有最高的執行優先權),此執行緒才會進入執行的階段(RUNNING state)。 * **WAITING** : 此類執行緒會等待事件發生(event occur)才會觸發,因此歸類為等待階段 (WAITING state)。 * **INACTIVE** : 已經在程式碼內但未創造或者已經結束的執行緒會被歸類為此一階段,此階段基本上不會耗損任何系統資源。 ![](https://i.imgur.com/mqGLfg5.png) 在執行緒中執行優先權可以有以下幾種可以設定 : 有效範圍: -3 到 +3(共 7 個層級)。 ``` javascript= typedef enum { osPriorityIdle = -3, ///< Priority: idle (lowest)最低優先級 osPriorityLow = -2, ///< Priority: low osPriorityBelowNormal = -1, ///< Priority: below normal osPriorityNormal = 0, ///< Priority: normal (default) osPriorityAboveNormal = +1, ///< Priority: above normal osPriorityHigh = +2, ///< Priority: high osPriorityRealtime = +3, ///< 最高優先級,沒有+4範圍是-3~+3 osPriorityError = 0x84, ///< 系統無法確定優先順序或非法 osPriorityReserved = 0x7FFFFFFF ///< 保留值,特殊用途,不會用於正常的任務優先級設定 } osPriority; ``` 並以以下函式設定優先順序 : ``` set_priority(osPriority priority) ``` 當然也可以在宣告時便將所有執行緒要設定的參數設定好 : ``` Thread (osPriority priority=osPriorityNormal, uint32_t stack_size=DEFAULT_STACK_SIZE, unsigned char *stack_pointer=NULL) //Allocate a new thread without starting execution. ``` 宣告範例 ``` Thread t1(osPriorityHigh); ``` --- ### Signals 任何一個執行緒可以被標註為等待 signal 的執行緒。 以下為 `Signal` 範例程式 : ```javascript= #include "mbed.h" #include "rtos.h" DigitalOut led(LED1); // 定義一個DigitalOut對象來控制LED1 Thread thread; //創建了一個名為 thread 的 Thread 對象 void led_thread() { // 定義一個執行緒函式 while (true) { // 等待信號 0x1 thread.signal_wait(0x1); //收到0x1才會執行下面程式 led = !led; // 切換LED的狀態 } } int main (void) { thread.start(callback(led_thread)); // 啟動led_thread執行緒 while (true) { Thread::wait(1000); // 等待1秒 thread.signal_set(0x1); // 設置信號 0x1,0X1為訊號 } } ``` 程式原理: 主執行緒 每1秒發送信號 0x1 到 thread 執行緒。 led_thread 執行緒 在等待信號 0x1。當信號 0x1 被設置時,led_thread 會解除阻塞,接著切換 LED 的狀態。 --- ### Lab 1 請利用上述 RTOS 中的 thread 來完成跑馬燈功能:<font color = "red">**※必須執行3條以上執行緒**</font> 相關語法可以參考 : [Thread](https://os.mbed.com/docs/mbed-os/v6.15/apis/thread.html)。 共有4種狀態 1. 狀態1:每隔1秒亮一顆LED,總共8顆輪流。 2. 狀態2:當開發板上 Button 按下後,每隔1秒同時亮兩顆LED。 3. 狀態3:隨機亮起兩顆 LED。 4. 狀態4:LED燈全部閃爍(同時亮、同時暗)。 5. 最後再按一次Button返回(狀態1) <font color = "red">注意</font>:更換狀態LED不是從起始開始,是從上次中斷位置開始 {%youtube Q2vv-7ugcx4 %} ```c= #include "mbed.h" #include "rtos.h" // 宣告按鈕輸入 DigitalIn but(USER_BUTTON); // 宣告 8 顆 LED 燈的數位輸出 DigitalOut led[8] = {D6, D7, D8, D9, D10, D11, D12, D13}; // 全域變數 j 追蹤當前亮著的 LED 編號 int j = 0; // 全域變數 i 追蹤當前模式 int i = 0; // 創建執行緒物件 Thread a; Thread b; Thread c; Thread d; // 模式 1:每隔 1 秒亮一顆 LED void mode1() { /* Do something */ } // 模式 2:每隔 1 秒同時亮兩顆 LED void mode2() { /* Do something */ } // 模式 3:隨機亮起兩顆 LED void mode3() { /* Do something */ } // 模式 4:所有 LED 燈同時閃爍 void mode4() { /* Do something */ } int main() { // 啟動所有執行緒 a.start(mode1); b.start(mode2); c.start(mode3); d.start(mode4); while (1) { if (but.read() == 1) { // 如果按鈕被按下 i++; // 增加模式計數器 if (i == 4) { // 如果超過最大模式數,重置計數器 i = 0; } ThisThread::sleep_for(500); // 防止按鈕抖動,暫停 0.5 秒 printf("%d\r\n", i); // 輸出當前模式 } } } ``` --- ### Lab 2 請利用上述 RTOS 中的 Signals 來控制開發版的LED:<font color = "red">**※必須以Signals觸發Thread**</font> 1. 輸入"1",亮綠燈。 2. 輸入"2",亮藍燈。 3. 輸入"3",亮紅燈。 4. 輸入"4",全亮。 {%youtube LFdLqmkWYuQ %}} ```c= #include "mbed.h" // 引入 mbed 庫 #include "rtos.h" // 引入 RTOS 庫 char w; // 定義一個字符變量,用於存儲從串口接收到的數據 DigitalOut led[3] = {LED1, LED2, LED3}; // 定義一個 DigitalOut 數組,用於控制三個 LED。假設 LED1 為綠色,LED2 為藍色,LED3 為紅色 // 定義線程對象 Thread a; // 線程 a,用於控制綠色 LED Thread b; // 線程 b,用於控制藍色 LED Thread c; // 線程 c,用於控制紅色 LED Thread d; // 線程 d,用於控制所有 LED // 定義 mode1 函數,用於控制綠色 LED void mode1() { while (true) { // 無窮迴圈,線程會不斷運行 a.signal_wait(0x1); // 等待信號 0x1。當接收到信號 0x1 時,線程繼續執行 led[0] = 1; // 打開綠色 LED (假設 LED1 為綠色) led[1] = led[2] = 0; // 關閉藍色和紅色 LED } } // 定義 mode2 函數,用於控制藍色 LED void mode2() { while (true) { // 無窮迴圈,線程會不斷運行 b.signal_wait(0x1); // 等待信號 0x1。當接收到信號 0x1 時,線程繼續執行 led[參數] = 1; // 打開藍色 LED (假設 LED2 為藍色) led[參數] = led[參數] = 0; // 關閉綠色和紅色 LED } } // 定義 mode3 函數,用於控制紅色 LED void mode3() { while (true) { // 無窮迴圈,線程會不斷運行 c.signal_wait(0x1); // 等待信號 0x1。當接收到信號 0x1 時,線程繼續執行 led[參數] = 1; // 打開紅色 LED (假設 LED3 為紅色) led[參數] = led[參數] = 0; // 關閉綠色和藍色 LED } } // 定義 mode4 函數,用於控制所有 LED 燈亮 void mode4() { while (true) { // 無窮迴圈,線程會不斷運行 d.signal_wait(0x1); // 等待信號 0x1。當接收到信號 0x1 時,線程繼續執行 led[參數] = led[參數] = led[參數] = 1; // 打開所有 LED (綠色、藍色和紅色) } } int main() { // 初始化所有 LED 狀態為關閉 led[0] = led[1] = led[2] = 0; // 關閉綠色、藍色和紅色 LED Serial pc(SERIAL_TX, SERIAL_RX); // 創建一個 Serial 對象,用於串口通信 // 啟動所有線程,並將它們與各自的控制函數關聯 a.start(mode1); // 啟動線程 a,並執行 mode1 函數 b.start(mode2); // 啟動線程 b,並執行 mode2 函數 c.start(mode3); // 啟動線程 c,並執行 mode3 函數 d.start(mode4); // 啟動線程 d,並執行 mode4 函數 while (true) { // 無窮迴圈,主線程不斷執行 w = pc.getc(); // 從串口讀取一個字符,並將其存儲在變量 w 中 pc.putc(w); // 將讀取到的字符通過串口回顯給用戶 // 根據讀取到的字符設置相應的線程信號 if (w == '1') { // 設置信號 0x1,通知線程 a 執行 mode1 函數,亮綠色 LED /* Do something */ } else if (w == '2') { // 設置信號 0x1,通知線程 b 執行 mode2 函數,亮藍色 LED /* Do something */ } else if (w == '3') { // 設置信號 0x1,通知線程 c 執行 mode3 函數,亮紅色 LED /* Do something */ } else if (w == '4') { // 設置信號 0x1,通知線程 d 執行 mode4 函數,全亮所有 LED /* Do something */ } } } ``` --- ### Lab 3 請利用上述 RTOS 中的 Signals 來完成以下功能: 超音波測距:https://os.mbed.com/components/HC-SR04/ 1. 將超音波模組測量到的距離(cm)顯示於七段顯示器。 2. 根據距離的遠近分別亮開發版上的LED **10cm 以內不亮燈 10-15cm 之間亮紅燈 15-20cm 之間亮藍燈 20-25cm 之間亮綠燈 25cm 以上全亮** 七段接法顯示器參考(回想自己上次用的是共陰還是共陽):https://hackmd.io/@mUiVoVNXRYmajjpGYtP4dg/B1fl5gwiR {%youtube _C3ONyNaTMc %} ```c= #include "mbed.h" #include "ultrasonic.h" #include "rtos.h" // 設置顯示控制和數據線的端口 DigitalOut com[2] = {D0, D1}; // 兩位顯示器需要兩個端口 DigitalOut seg[8] = {D4, D5, D6, D7, D8, D9, D10, D11}; // A, B, C, D, E, F, G, DP DigitalOut led[3] = {LED1, LED2, LED3}; // LED 指示燈 int disp[2] = {0, 0}; // 兩位數顯示 int d; // 儲存距離 int table[11][8] = { /* Hint:去之前Arduino Lab找 {1, 1, 1, 1, 1, 1, 0, 0}, // 0 Do something, // 1 Do something, // 2 Do something,, // 3 Do something,, // 4 Do something,, // 5 Do something,, // 6 Do something,, // 7 Do something,, // 8 Do something,, // 9 */ {0, 0, 0, 0, 0, 0, 0, 0} // 空白 }; Thread a; Thread b; Thread c; Thread dd; // 超音波感測距離回調函數 void dist(int distance) { d = distance / 參數; // 將距離從毫米轉換為公分 printf("Distance changed to %dcm\r\n", d); } // 將距離數字分解為兩位數 void packing(int num) { // 根據數字是否小於10來設置顯示數字 if (num < 10) { disp[0] = -1; // 設置為 -1,表示不顯示十位數字的0 disp[1] = num; // 個位數顯示數字 } else { disp[0] = (num / 參數) % 參數; // 十位數 disp[1] = num % 參數; // 個位數 } } // 映射數字到顯示器段碼 void mapping(int disp) { for(int i = 0; i < 8; i++) { seg[i] = table[disp][i]; } } // 初始化超音波感測器 ultrasonic mu(D2, D3, .1, 1, &dist); // 設置觸發引腳和回波引腳,D2:Trig D3:Echo // LED 模式函數 void mode1() { a.signal_wait(參數); led[參數] = 1; // 亮紅燈 led[參數] = led[參數] = 0; } void mode2() { b.signal_wait(參數); led[參數] = 1; // 亮藍燈 led[參數] = led[參數] = 0; } void mode3() { c.signal_wait(參數); led[參數] = 1; // 亮綠燈 led[參數] = led[參數] = 0; } void mode4() { dd.signal_wait(參數); led[參數] = led[參數] = led[參數] = 1; // 全亮 } // 主函數 int main() { mu.startUpdates(); // 開始測量距離 while(1) { mu.checkDistance(); // 檢查距離更新 packing(d); // 將距離分解成數字 // 顯示兩位數字 for(int i = 0; i < 2; i++) { com[i] = 0; mapping(disp[i]); wait(0.001); // 每位顯示0.001秒 com[i] = 1; } // 根據距離設置 LED if(d <= 參數) { /* Do something */ } else if(d <= 參數) { /* Do something */ }else if(d <= 參數) { /* Do something */ } else if(d <= 參數) { /* Do something */ } else { /* Do something */ } } } ``` --- ### Mutex `Mutex` 是一種可以將執行緒進行同步處理的方式,使用 lock 和 unlock 的方式來共享資源使用。 :::warning `Mutex`的方法無法被 ISR (interrupt service routines)所使用。 ::: 以下為 `Mutex` 的示意圖 : ![](https://i.imgur.com/cKvXmC6.png) 以下為 `Mutex` 的範例程式 : ```javascript= #include "mbed.h" #include "rtos.h" // 創建一個 Mutex 物件來保護標準輸出,避免多執行緒間的輸出競爭。 Mutex stdio_mutex; // 通知函數,用來在標準輸出上打印線程名稱和狀態 void notify(const char* name, int state) { stdio_mutex.lock(); // 鎖定 mutex,防止其他線程同時寫入標準輸出 printf("%s: %d\n\r", name, state); // 打印線程名稱和狀態 stdio_mutex.unlock(); // 釋放 mutex } // 線程執行的函數 void test_thread(void const *args) { while (true) { // 無窮迴圈 notify((const char*)args, 0); // 輸出線程名稱和狀態 0 Thread::wait(1000); // 等待 1000 毫秒 (1 秒) notify((const char*)args, 1); // 輸出線程名稱和狀態 1 Thread::wait(1000); // 再次等待 1000 毫秒 (1 秒) } } int main() { // 創建兩個線程物件 Thread t2; Thread t3; //t2 和 t3 會與主線程 test_thread 同時進行,並且它們的執行是交錯的。 // 启动线线程 t2,執行 test_thread 函數,並傳遞參數 "Th 2" t2.start(callback(test_thread, (void *)"Th 2")); // 启动线线程 t3,執行 test_thread 函數,並傳遞參數 "Th 3" t3.start(callback(test_thread, (void *)"Th 3")); // 在主線程中執行 test_thread 函數,並傳遞參數 "Th 1" test_thread((void *)"Th 1"); } ``` 利用 lock() 來使 stdio 正常運作,並用 unlock()釋放資源。 :::info 在 ARM C 標準函式庫內已經存在 Mutex 。主要是可以讓 stdio 進行正常運作,因此以上的範例在實作時並不是必須,除非有些沒有以上保護機制的開發板才需要以上程式碼,如 ARM M0 系列。 ::: :::warning 正是因為 ARM C 標準函式庫在使用 stdio 時會使用到 Mutex,所以在 ISR 中變無法使用 printf, putc, getc, malloc 和 new 。 ::: ::: info Thread class 中的 wait() function 中間是以毫秒為單位,使用 Thread::wait(1000) 是該 funciotn 下 全部 thread 暫停一秒, t1.wait(500) 是 t1 該物件暫停 0.5 秒。 ::: 相關語法請參考 : [Mutex](https://os.mbed.com/docs/mbed-os/v6.15/apis/mutex.html)。 --- ### Semaphore `Semaphore` 可以用來管理多種特定線程使用共享資源 (a pool of shared resources) 的情況。 以下為 `Semaphore` 的示意圖 : ![](https://i.imgur.com/xeydSYc.png) 以下為 `Semaphore` 的範例程式 : ```javascript= #include "mbed.h" #include "rtos.h" // 宣告一個 Semaphore 物件,初始可用槽位為 2 //Semaphore three_slots(3); //改成3,會一次顯示3個,但下方所有two_slots要改成three_slots Semaphore two_slots(2); // 線程執行的函數 void test_thread(void const *name) { while (true) { // 無窮迴圈,線程會一直運行 two_slots.wait(); // 請求 Semaphore,若沒有可用槽位則等待 printf("%s\n\r", (const char*)name); // 打印線程名稱 Thread::wait(1000); // 等待 1000 毫秒 (1 秒) two_slots.release(); // 釋放 Semaphore,釋放一個槽位 } } int main(void) { // 創建兩個線程物件 Thread t2; Thread t3; // 啟動線程 t2,執行 test_thread 函數,並傳遞參數 "Th 2" t2.start(callback(test_thread, (void *)"Th 2")); // 啟動線程 t3,執行 test_thread 函數,並傳遞參數 "Th 3" t3.start(callback(test_thread, (void *)"Th 3")); // 在主線程中執行 test_thread 函數,並傳遞參數 "Th 1" test_thread((void *)"Th 1"); } ``` 可管理同時要釋放多少資源來處理執行緒。 相關語法請參考 : [Semaphore](https://os.mbed.com/docs/mbed-os/v5.15/apis/semaphore.html)。 --- ### Queue 在資料結構中我們學過 `Queue`,即是 FIFO (Frist In Frist Out,先進先出) 的結構,在實作執行緒排程時就會使用到此種結構方式。 FIFO 原則:最早進入系統的元素會最先被處理或移除,而最後進入的元素會最後處理。 以下為 `Queue` 的示意圖 : ![](https://i.imgur.com/gr3DlME.png) 常用指令 : ```javascript= Queue () //Create and initialise a message Queue . Queue< T, queue_sz > osStatus put (T *data, uint32_t millisec=0) //Put a message in a Queue ; T : templete osEvent get (uint32_t millisec=osWaitForever)//Get a message or Wait for a message from a Queue . ``` --- ### MemoryPool `MemoryPool` 是用來定意並管理記憶體空間的程式。你可以看到記憶體資訊 混合 `Queue` 的函式我們可以得到以下範例程式 : ```javascript= #include "mbed.h" #include "rtos.h" // 定義一個結構,表示消息的格式 typedef struct { float voltage; // 測量電壓的 AD 結果 float current; // 測量電流的 AD 結果 uint32_t counter; // 一個計數器值 } message_t; // 創建一個 MemoryPool 物件,管理 16 個 message_t 類型的緩衝區 MemoryPool<message_t, 16> mpool; // 創建一個 Queue 物件,能夠存儲最多 16 個 message_t 類型的消息 Queue<message_t, 16> queue; // 發送線程的函數 void send_thread(void) { uint32_t i = 0; // 用於生成假數據的計數器 while (true) { // 無窮迴圈,線程會一直運行 i++; // 假數據更新 // 從 MemoryPool 中分配一個 message_t 對象 message_t *message = mpool.alloc(); // 設置消息的內容 message->voltage = (i * 0.1) * 33; message->current = (i * 0.1) * 11; message->counter = i; // 將消息放入隊列中 queue.put(message); // 等待 1000 毫秒 (1 秒) Thread::wait(1000); } } int main(void) { // 創建一個線程物件 Thread thread; // 啟動線程,執行 send_thread 函數 thread.start(callback(send_thread)); while (true) { // 無窮迴圈,主線程會一直運行 // 從隊列中獲取一個消息 osEvent evt = queue.get(); if (evt.status == osEventMessage) { // 確保獲取到的是消息 // 將獲取的消息轉換為 message_t 指針 message_t *message = (message_t*)evt.value.p; // 打印消息內容 printf("\nVoltage: %.2f V\n\r" , message->voltage); printf("Current: %.2f A\n\r" , message->current); printf("Number of cycles: %u\n\r", message->counter); // 釋放消息所佔用的內存 mpool.free(message); } } } ``` 常用指令 : ```javascript= MemoryPool< T, pool_sz > //Create and Initialize a memory pool. MemoryPool () T* alloc (void) //Allocate a memory block of type T from a memory pool. T* calloc (void) //Allocate a memory block of type T from a memory pool and set memory block to zero. osStatus free (T *block) //Return an allocated memory block back to a specific memory pool. ``` --- ### Mail `Mail`的功能即是將上述的 `Queue` 和 `Memory pool` 功能整合起來,效果與上個類似。 以下為 `Mail` 的示意圖 : ![](https://i.imgur.com/EURq6iV.png) 以下為 `Mail` 的範例程式 : ```javascript= #include "mbed.h" #include "rtos.h" /* Mail */ typedef struct { float voltage; /* AD result of measured voltage */ float current; /* AD result of measured current */ uint32_t counter; /* A counter value */ } mail_t; Mail<mail_t, 16> mail_box; void send_thread (void) { uint32_t i = 0; while (true) { i++; // fake data update mail_t *mail = mail_box.alloc(); mail->voltage = (i * 0.1) * 33; mail->current = (i * 0.1) * 11; mail->counter = i; mail_box.put(mail); Thread::wait(1000); } } int main (void) { Thread thread; thread.start(callback(send_thread)); while (true) { osEvent evt = mail_box.get(); if (evt.status == osEventMail) { mail_t *mail = (mail_t*)evt.value.p; printf("\nVoltage: %.2f V\n\r" , mail->voltage); printf("Current: %.2f A\n\r" , mail->current); printf("Number of cycles: %u\n\r", mail->counter); mail_box.free(mail); } } } ``` 常用指令 : ```javascript= Mail< T, queue_sz > //Create and Initialise Mail queue. Mail () T *alloc (uint32_t millisec=0) //Allocate a memory block of type T. T *calloc (uint32_t millisec=0) //Allocate a memory block of type T and set memory block to zero. osStatus put (T *mptr) //Put a mail in the queue. osEvent get (uint32_t millisec=osWaitForever) //Get a mail from a queue. osStatus free (T *mptr) //Free a memory block from a mai ``` --- ### Status and Error Codes The Status and Error Codes section lists all the return values that the `CMSIS-RTOS functions` will return: :::info CMSIS-RTOS 是 ARM 公司為統一操作系統、降低嵌入式門檻而發佈的操作系統標準軟件接口。通俗講,CMSIS-RTOS 將操作系統(不管是 FREE-RTOS 還是 RTX 等)屏蔽起來,然後提供 CMSIS-RTOS 接口函數給最終用户調用。 ::: + `osOK` : function completed ; no event occurred. + `osEventSignal` : function completed; signal event occurred. + `osEventMessage` : function completed; message event occurred. + `osEventMail` : function completed; mail event occurred. + `osEventTimeout` : function completed; timeout occurred. + `osErrorParameter` : parameter error: a mandatory parameter was missing or specified an incorrect object. + `osErrorResource` : resource not available: a specified resource was not available. + `osErrorTimeoutResource` : resource not available within given time : a specified resource was not available within the timeout period. + `osErrorISR` : not allowed in ISR context: the function cannot be called from interrupt service routines. + `osErrorISRRecursive` : function called multiple times from ISR with same object. + `osErrorPriority` : system cannot determine priority or thread has illegal priority. + `osErrorNoMemory` : system is out of memory: it was impossible to allocate or reserve memory for the operation. + `osErrorValue` : value of a parameter is out of range. + `osErrorOS` : unspecified RTOS error: run-time error but no other error message fits. --- <!-- ### Bonus 請利用上述 RTOS 中的 <font color = "red">**Thread**</font> & <font color = "red">**Semaphore**</font> & <font color = "red">**Mutex**</font>來完成以下功能: 1. 每隔1秒亮一顆開發板上之LED,總共3顆輪流。 2. 當開發板上 Button 按下後,改變為 <font color = "red">**Semaphore**</font>的狀態,Semaphore 觸發內容為每隔1秒亮兩顆開發版上之LED。 3. 再按一次開發板上的 Button 後,改變為 <font color = "red">**Mutex**</font>的狀態,每隔1秒閃爍開發版上所有LED。 4. 最後再按一次Button回歸每隔1秒亮一顆LED的狀態 {%youtube h0r6Cq_d050 %} --> --- ## 課後問題 :::info * Q1.請詳述 Thread 在 Mbed 平台中是如何運作的。 * Q2.請解釋執行多執行緒之死結(DeadLock)的問題,以及如何避免。 * Q3.請解釋program、process、thread彼此之間的關係,並舉例說明。 * Q4.請嘗試解釋 Mbed 平台 API 中, wait() 與 Thread::wait() 之間的差別。 :::