Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

[嵌入式系統] 嵌入式系統進階

4,841 views

Published on

https://www.facebook.com/eeRhapsody
嵌入式系統進階

Published in: Engineering

[嵌入式系統] 嵌入式系統進階

  1. 1. Chien-Jung Li Dec 2013 嵌入式系統進階
  2. 2. 2 大綱  嵌入式系統進階觀念  關鍵字volatile、const、static與typedef  嵌入式系統平台  嵌入式作業系統  任務與排程  打造一個簡易型Embedded OS  Cooperative排程器實作  逾時控制  狀態機:以實作紅綠燈號誌系統為例  混合Cooperative與Pre-emptive任務之排程器實作  共用資源保護
  3. 3. 嵌入式系統進階觀念
  4. 4. 4 原廠評估板(Evaluation Board)  Evaluation Board (EVB)  Evaluation Module (EVM)  Evaluation Kits
  5. 5. 5 Target Board與Real Board
  6. 6. 6 In-Circuit Emulator (ICE) / Debugger  連接PC與target board,可以想像成ICE會取代target board上的CPU以提供目標程式完成的模擬環境,讓開 發者可以知道程式在target board上的工作狀態.  現在很多裝置都支援In-System Programming (ISP),有時 候就不需要ICE ICE操作架構圖 PC USB cable ICE JTAG cable IDE Target board
  7. 7. 7 整合式開發環境(IDE) - PC  Integrated Development Environment (IDE)  Simulator + Editor + Compiler + Assembler, etc.
  8. 8. 8 整合式開發環境(IDE) – 嵌入式
  9. 9. 9 Java on PC與嵌入式系統開發 在PC上的Java開發 嵌入式系統開發 硬體平台 PC + Windows + JVM 評估板 編譯工具 Java編譯器 Cross-compiler(通常CPU原廠會提供或指定) 函式庫(Library) Java SDK 編譯工具內附(ANSI C Lib)或CPU廠商提供的 Middleware 編輯器 Eclipse UltraEdit, SourceInsight, NotePad++ 程式項目管理 Eclipse Makefile/.bat/IDE Debug環境 JDB+Eclipse Cross-compiler或ICE廠商提供 Debug方法 Trace, breakpoint, watch, 列印輸出 通過ICE遠端debug, 列印 技術文件 線上文件/坊間書籍 線上文件 / 廠商提供的datasheet, user’s guide Sample Code 通常都是API的使用範例 通常是CPU各功能或周邊設備的驅動程式範例 程式執行 Myprogram.class 下載(ICE模擬/燒ROM/ROM模擬器/更新) 執行程式格式 .class elf, hex, bin, s-record
  10. 10. 10 讓程式在EVM上跑起來  步驟1:在PC上安裝IDE (含cross-compiler, debugging tool, editor, ICE驅動程式等等)  步驟2:寫好你的程式  步驟3:寫makefile (compile, link)  步驟4:Build (執行makefile的所有指令)  步驟5:將程式燒錄至裝置(或ICE測試)並測試結果 高階語言 (C語言) 編譯器 (Compiler) 目的程式 (Object File) 低階語言 (組合語言) 編譯器 (Compiler) 目的程式 (Object File) 函式庫 (Library) 連結器 (Linker) 執行檔 資料檔 位元檔 (Binary File) makefile
  11. 11. 11 計算機啟動流程  Step1: CPU到特定位址抓取第一行指令(特定Program Counter或reset中斷ISR為boot())  Step2: User程式運行後,先對CPU初始化  Step3: 將程式從唯讀記憶體(ROM或Flash)載入RAM  Step4: 初始化應用程式會用到的硬體設備  Step5: 初始化各子系統 (如RTOS、動態記憶體管理等)  Step6: 執行應用程式主程式
  12. 12. 12 CPU透過中斷的啟動流程 Other functions main() boot() … DMA key address error RESET
  13. 13. 13 中斷向量表  中斷向量表從某個位址開始,每4個Bytes(有些是8個Bytes)為一個 單位(entry),每一個entry記錄一個函式的位址  沒有用到的中斷不但不應該發生,當他發生時還得視為一種錯誤 ISR_n
  14. 14. 14 CPU初始化  只發生一次 (hopefully!)  將所有變數、記憶體等東西初始化至known state  初始化變數、旗標、I/O與IRQs等  設定CPU的中斷控制  初始化中斷優先權、開中斷  清除等待中的中斷請求  中斷致能
  15. 15. 15 Boot程式  Step01: 設定某些重要的CPU暫存器,特別是stack pointer與 狀態暫存器  Step02: CPU各部分功能初始化  Step03: 系統初始化  Step04: 呼叫應用系統的主程式  Step05: 結束(通常應用程式的主程式不會返回boot程式) /************************************************************* boot.c BOOT程式 - CPU啟動後的第一行程式 *************************************************************/ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Function: boot() CPU啟動後被執行的第一個function RESET中斷的ISR ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ __interrupt void boot(void) { // 設定SP暫存器 // asm("xld.w %r15, 0x2000"); ...
  16. 16. 16 關於const關鍵字 (I)  關鍵字const有甚麼含意?  業餘者會回答:「它意味著常數」  專業者會回答:「它意味著唯讀」  下面的宣告是甚麼意思?  const int a;  int const a;  const int *a;  int* const a;  int const * a const;
  17. 17. 17 關於const關鍵字 (II)  你可能會問,即使不用關鍵字const,也還是可以寫出 功能正確的程式,那為什麼還要如此看重關鍵字const 呢?這有幾個理由: 1) const能給讀你程式碼的人傳達非常有用的資訊。實際上宣告 一個變數為const是為了告訴了別人這個變數的應用目的。如 果你曾花很多時間清理別人留下的垃圾,你就會很快學會感 謝這點多餘的資訊。(當然,懂得用const的designer很少會留 下垃圾讓別人來清理的。) 2) 通過給optimizer一些附加的資訊,使用關鍵字const也許能產 生更緊湊的程式碼。 3) 合理地使用const可讓編譯器很自然地保護那些不希望被改變 的變數,防止它們被無意的程式碼修改到。如此可以減少bug 出現的機會。
  18. 18. 18 關於volatile關鍵字 (I)  volatile通常在寫驅動程式的時候才會用到 void func(void) { int a, b; a = 1; a = 1; a = 1; b = 1; b = 2; b = 3; } void func(void) { int a, b; a = 1; // a = 1; // a = 1; // b = 1; // b = 2; b = 3; }
  19. 19. 19 關於volatile關鍵字 (II)  被修飾為volatile的變數是說: 這變數可能會被意想不到地改變,使用volatile修飾之後編譯器就 不會去假設這個變數的值。精確地說,即optimizer用到這個變數 時必須每次都小心地重新讀取它的值,而不是使用保存在暫存器 裡的備份。  volatile變數的例子: 1) 設備的硬體暫存器(例如狀態暫存器) 2) 一個ISR會訪問到的非自動變數(non-automatic variable) 3) 多執行緒應用中被幾個tasks共用的變數
  20. 20. 20 關於volatile關鍵字 (III)  面試官會測試你是否真正懂得volatile的重要性: 1) 一個變數可以既是const又是volatile嗎?為什麼。 答:是的。例如唯讀的狀態暫存器,它是volatile因為它可能被意想不到 地改變,它是const因為程式不應該試圖去修改它。 2) 一個指標可以是volatile嗎?為什麼。 答:雖然不很常見,但是的。例如ISR修改指向一個buffer的指標時。 3) 下面的函數有什麼錯誤: int square(volatile int *ptr) { return *ptr * *ptr; } int square(volatile int *ptr) { int a, b; a = *ptr; b = *ptr; return a * b; } long square(volatile int *ptr) { int a; a = *ptr; return a*a; } 求(*ptr)2
  21. 21. 21 關於static關鍵字  關鍵字static的作用是什麼?這個簡單的問題很少有人 能回答完全。  關鍵字static有三個明顯的作用: 1) 在函式體內被宣告為static的變數,在這一函式被調用過程中其 值維持不變(也就是靜態的意思)。 2) 在模組內(但在函式體外)被宣告為static的變數可以被模組內所有 函式訪問,但不能被模組外其它函式訪問。它是一個本地的全 域變數。 3) 在模組內,被宣告為static的函式只可被這一模組內的其它函式 呼叫。即,這個函式被限制在宣告它的模組的本地範圍內使用。
  22. 22. 22 關於typedef  typedef用以宣告一個已經存在的資料類型的同義字。 這也可以用前置處理器做類似的事。例如: 以上兩種情況都是要定義dPS和tPS為一個指向結構s指標。哪種方 法更好呢?(如果有的話)為什麼?答案是:用typedef更好。思考 下面的例子: #define dPS struct s * typedef struct s * tPS; dPS p1, p2; tPS p3, p4; struct s * p1, p2; struct s * p1, * p2;
  23. 23. 23 關於中斷 (Interrupts)  中斷是嵌入式系統中重要的組成部分,很多編譯器開發商提供一 種語言擴充—讓標準C支援中斷(如Kiel C中的關鍵字__interrupt)。  下面是一個ISR,請評論一下這段程式碼。  這個函數有太多的錯誤了: 1) ISR不能返回值。如果你不懂這個,那麼你是不會被雇用的。 2) ISR不能傳遞參數。若你不知道,你被雇用的機會等同第一項。 3)在許多的處理器/編譯器中,浮點運算一般都是不可重入。有些處理器/ 編譯器就是不允許在ISR中做浮點運算。此外,ISR應該是短而有效率的, 在ISR中做浮點運算是不明智的。 4) 與第3點一脈相承,printf()經常有重入和性能上的問題。 __interrupt double compute_area (double radius) { double area = PI * radius * radius; printf("/nArea = %f", area); return area; }
  24. 24. 24 中斷類型  Single vs. Multiple (nested, 巢狀中斷)  一個ISR可以中斷另一個嗎?  Multiple Prioritized  高priority ISR可以中斷一個低priority的ISR嗎?  Maskable vs. Non-Maskable  中斷可以在程式中被關閉嗎?  Level vs. Edge Triggered  Sampled or Transition Induced
  25. 25. 25 單一中斷與多重中斷  有些MCUs只允許一個(single)中斷  有些MCUs允許巢狀中斷  為了很好地使用多重中斷,大多數處理器都支援中斷 優先權排序 (8051系列允許巢狀中斷: 預設是關閉的)  高優先權事件可取代低優先權者  固定或可變的優先權  8051的中斷:  中斷優先權分為高/低兩種等級,可使用軟體控制  在同一種等級中,優先權由硬體控制,不可更改 中斷優先權
  26. 26. 26 Mask與Non-maskable  可遮罩的中斷可以在程式碼中關閉  全域disable (例如使用EA總中斷位元)  開啟特定的中斷 (IE SFR加以遮罩,如開啟Timer1中斷)  不可遮罩的中斷無法在程式中關閉 (如Reset) Level與Edge觸發中斷  Level觸發:  CPU會在每個instruction結束時對IRQ輸入取樣,如果有IRQ發生 則執行相應的ISR。  Edge觸發:  邊緣觸發會先被存在latch直到CPU處理它  當中斷啟用時, CPU會在每個instruction結束時對IRQ輸入取樣
  27. 27. 27 使用邊緣或位準觸發的時機  當中斷訊號的時間太長:邊緣觸發  e.g.: 60 Hz square wave for clock  使用Level sensitive會反應多次  當中斷訊號的時間太短:邊緣觸發  e.g.: very short pulse from a sensor  Level Sensitive會遺失事件  普通中斷線路:位準觸發  多重事件:位準觸發
  28. 28. 28 可以使用標準C函式庫嗎?  以printf()為例,輸出要印到哪裡去?編譯器廠商根本不 知道你的硬體平台與應用是什麼?  螢幕?有螢幕嗎?寫到檔案嗎?  malloc()與free():編譯器廠商不知道你的硬體中有多少 記憶體(起始位址?大小?),但通常廠商會提供一些方法 讓你設定自己的硬體狀態,然後就可以使用編譯器廠 商提供的malloc()與free()。  像strcpy()與strcat()這種跟硬體無關的標準C函式就可以 直接使用沒有問題。  引用函式庫也必須考量嵌入式系統記憶體有限的問題。
  29. 29. 29 GNU C/C++及其Library  GNU C/C++與library:  open source  成功移植到許多平台  技術文件齊備  使用ANSI C、POSIX等函式庫還是得小心。  例如在PC上printf()是印到螢幕,但嵌入式系統有時甚至連明顯 的輸出設備都沒有。  每個平台或CPU都有其特性,要開發出在所有平台上都 具有優良性能的演算法是不切實際的,而且大多函式 庫的開發都是以PC作為平台,正確性會比性能重要(因 為PC的CPU執行速度很快)。
  30. 30. 30 嵌入式系統團隊必須為護自己的SDK  建立嵌入式系統平台,提供應用程式開發人員舒適的 開發環境與足夠的系統功能,SDK應包含  開發環境: tool-chain + IDE + debug tool  系統功能: 系統與硬體功能定義明確的API,並將其實作包裝為Library。若 有必要可提供某些模組的source code供上層開發人員作客製化  API手冊與SDK使用手冊  模擬器環境  硬體環境: Evaluation board及其手冊與schematic  其他工具: 系統更新工具、系統性能統計工具、資源轉換工具等  包裝SDK使模組間的耦合鬆散並有較佳軟硬體分離  通過量產標準的系統與工具,可藉由SDK組織研發資產 的累積  很容易可以完成prototype給客戶做評估  模組外包時,可提供廠商標準測試平台  一貫的SDK架構可使新人容易進入狀況  通過SDK可明確地將系統與應用程式區分開,更易維護
  31. 31. 嵌入式系統平台
  32. 32. 32 系統與平台(Platform)  平台其實是一種廣泛的說法  嵌入式系統軟體的層次  Boot-Loader與驅動程式  OS與API  子系統與library  應用程式  嵌入式系統平台  系統軟體與驅動程式  硬體平台  開發環境(編譯器等)  模擬器  程式編寫規範
  33. 33. 33 系統平台架構
  34. 34. 34 數據流 (Message Flow)  計算機系統 = 處理(輸入)事件並產生輸出結果  輸入來源: (1) 監督程式(monitor)監控周邊設備狀態的改變(此方法稱為polling或 busy waiting) (2) 由周邊設備主動產生中斷(interrupt)  當設備狀態變化時,相應的驅動程式會被執行。 (1) 若是polling,驅動程式由監督程式引發 (2) 若是中斷方式,則相應的ISR會被執行(因為中斷可能發生在任何時刻, 當時CPU可能正在執行任何程式,所以ISR會有許多API不能使用,否 則會有重進入的麻煩。)
  35. 35. 35 系統中的Message Loop  以中斷ISR來說明: Application Message Dispatcher Driver ISRsDriver ISRsDriver ISRs while(1) { os_MSG new_message; if(os_get_message(&new_message)) { os_process_message(&new_message); } else { drv_enter_idle_mode(); } }
  36. 36. 36 可重用性與移植性  Reusability與Portability  硬體相關的程式模組,其可重用性與移植性都很低  設計驅動程式時,還會將其分成兩層 AP_func() { ... drv_key_api_1(); ... } drv_key_api_1() { ... hw_key_api_1(); ... } hw_key_api_1() { // 硬體相關的程式 asm("..."); hw_key_internel_func(); ... } hw_key_internel_func() { // 硬體相關的程式 asm("..."); ... }
  37. 37. 37 可擴充性與可調整性  Scalability與Reconfigurability = 系統高度模組化,在不同 應用可選用不同的模組來組成系統  sys_config.h = constants/ macros  編譯系統前可修改sys_config.h來設定系統的功能與特性 /********************** sys_config.h ************************/ // Constants #define _HW_CONFIG_FALSE 0 #define _HW_CONFIG_TRUE 1 #define KME_MODEL_A 1 #define KME_MODEL_B 2 #define KME_MODEL_C 3 // Define product name #define PRODUCT_NAME KME_MODEL_B // Supported HW driver #define HW_MUSIC_MP3_SUPPORT _HW_CONFIG_FALSE #define HW_MR_SENSOR_SUPPORT _HW_CONFIG_FALSE #define HW_REMOTE_CONTROLLER_SUPPORT _HW_CONFIG_FALSE #define HW_SD_CARD_SUPPORT _HW_CONFIG_FALSE // LCD properties #if (PRODUCT_NAME == KME_MODEL_A) #define LCD_RESOLUTION_WIDTH 160 #define LCD_RESOLUTION_HEIGHT 160 #else #define LCD_RESOLUTION_WIDTH 160 #define LCD_RESOLUTION_HEIGHT 240 #endif #define LCD_COLOR_LEVEL 8 #define LCD_TOUCH_PANEL _HW_CONFIG_TRUE // Sub-systems #define SYS_TCPIP_SUPPORT _HW_CONFIG_FALSE #define SYS_FAT32_SUPPORT _HW_CONFIG_FALSE
  38. 38. 38 條件式編譯 #include <sys_config.h> void my_func(void) { #ifdef (PRODUCT_NAME == KME_MODEL_B) // 跟產品B有關的程式碼 product_B(); #else // 給除了B以外的產品使用 not_product_B(); #endif #if (HW_MUSIC_MP3_SUPPORT == _HW_CONFIG_TRUE) // 和播放mp3有關的程式 play_mp3(); #else // 系統不支援mp3時所使用的code do_nothing(); #endif } #include <sys_config.h> void my_func(void) { product_B(); do_nothing(); } #define HW_MUSIC_MP3_SUPPORT _HW_CONFIG_TRUE #include <sys_config.h> void my_func(void) { product_B(); play_mp3(); }
  39. 39. 39 系統程式風格 // sample code of message dispatcher - 程式風格範例 // - forever loop void os_message_dispatcher(void) { struct os_msg new_msg; struct os_event new_event; while(1) { if(os_get_msg(&new_msg) == TRUE) { // 取得新的message // driver層送來新的message new_event = os_preprocess_message(&new_msg); if(new_event == NULL) continue; // 事件已經處理, 無需再往上傳遞 if(new_event.owner != NULL) { os_send_sys_event(); // 將事件傳遞給指定的應用程式或物件 } else { // 處理新事件,方法視產品而定 // 1. 送給current/active AP, 2. 送給所有AP或物件, 使其自己決定 os_process_new_event(&new_event); } } else { os_enter_idle_mode(); // 暫時沒有硬體訊息, 系統進入idle mode } } }
  40. 40. 40 較差的訊息處理程式 /************************************************************* foo模組訊息處理主程式: foo_module_basic_message_processor *************************************************************/ int foo_module_basic_message_processor(struct message *new_msg) { int msg_type = new_msg->message_type; if(msg_type == MSG_TYPE_0001) { ... // 處理第1類訊息的程式碼 return } else if(msg_type == MSG_TYPE_0002) { ... // 處理第2類訊息的程式碼 return MSG_PROCESSED; // 已處理完畢, 系統無需再將此訊息送給其他模組 } else if(msg_type == MSG_TYPE_0003) { ... } ... { // 一堆else if ... // <== 這個程式可能很長, 要翻半天才知道這個模組到底會處理甚麼訊息 else { return MSG_PASS; // 繼續讓其他模組處理這個訊息 } }
  41. 41. 41 較好的訊息處理程式 // 重要原則: 宣告模組中處理各個訊息的靜態函數(類似物件的method) // 1. 這些函數只有這個模組會呼叫 2. 每個訊息有其專用的處理函數 static int xxx_msg_1_processor(struct message *new_msg); static int xxx_msg_2_processor(struct message *new_msg); static int xxx_msg_3_processor(struct message *new_msg); ... static int xxx_msg_default_processor(struct message *new_msg); /************************************************************* foo模組訊息處理主程式: foo_module_basic_message_processor *************************************************************/ int foo_module_basic_message_processor(struct message *new_msg) { int msg_type = new_msg->message_type; switch(msg_type) { case MSG_TYPE_0001: return xxx_msg_1_processor(new_msg); case MSG_TYPE_0002: return xxx_msg_2_processor(new_msg); case MSG_TYPE_0003: return xxx_msg_3_processor(new_msg); ... ... default: // foo模組預設的訊息處理函數 xxx_msg_1_processor(new_msg); } return MSG_PASS; // 繼續讓其他模組處理這個訊息 } /**************************************************** foo模組訊息1處理程式: foo_msg_1_processor *****************************************************/ static int foo_msg_1_processor(struct message *new_msg) { ... // 處理第1類訊息的程式碼 // 已經處理完畢, 系統無需再將此訊息傳送給其他模組 return MSG_PROCESSED; }
  42. 42. 42 API設計文件  API設計文件: 告訴使用者如何使用函式、說明函式功能、調用順序、限制或 注意事項等。  API文件應包含:  該模組的功能說明與使用範圍  資料結構與常數說明  各函式的功能、參數與 回傳值意義說明  使用範例  注意事項與限制  相關函式 • Prototype: void * skip_upper_frame(void) • Declaration: Skip upper frame as figure below • Parameters: none • Return Value: Return NULL if there’s no upper frame; else return the SP of current frame, since the SP is not changed, the returned value is useless for applications.
  43. 43. 43 嵌入式作業系統,在哪裡?  OS是一組管理計算機硬體與軟體資源的程序,同時也 是計算機系統的核心基礎。OS身負管理配置記憶體空 間(memory space)、系統資源優先序、I/O設備、網路與 文件系統等基本事務,較複雜的OS有可提供一個讓使 用者與系統互動的操作介面(UI)。
  44. 44. 44 市面上常見的Embedded OS (EOS) Open Source的EOS 需付費的EOS μC/OS μC/OS II Embedded Linux VxWorks ECos Nucleus μCLinux pSOS RTEMS QNX FreeRTOS Windows XP Embedded μITRON Windows CE (Win. Mobile) Andorid Symbian OS … OS-9 / iOS
  45. 45. 45 RTOS簡介 (I)  RTOS是一種嵌入式系統的即時OS,負責管理以下資源 分享與配置: CPU Time  Round Robin vs. Priority, Preemptive vs. Non-preemptive Memory I/O  事件驅動型的任務(event-driven tasks)  任務間通訊 (Inter-Task Communication, IPC)  任務調度與狀態(Tasks Scheduling, States)  隱藏硬體細節 (Hiding Hardware Details)
  46. 46. 46 RTOS簡介 (II)  Real-time又分為Hard Real-time與Soft Real-time  核電廠的控制系統,用Windows XP合適嗎?  不管應用為何,通常使用者不能忍受反應遲鈍的電子 產品(更別說跟醫療、交通還有安全有關的東西),因此 即時嵌入式OS有以下幾個基本要求:  任務的可中斷性(pre-emptive): 不管系統目前的狀態為何,當緊急事件發生時OS必須保證相應的程序 會馬上被執行。  可預測性: OS中所有函式與服務的執行時間必須是確定的。  穩定性與可靠度
  47. 47. 47 Soft- and Hard- Real-time System  Soft R-T Systems  錯過即時處理只會導致 系統性能下降  ATM提款機  交易處理  導航  存貨控制  Hard R-T Systems  錯過即時處理將會導致 系統崩潰或引發嚴重後果 或災難  生命維持器  引擎控制器  飛行控制器  機器人控制  核子反應爐控制
  48. 48. 48 RTOS的特性  可移植性高 (Portable)  系統必須可在ROM裡直接執行(ROMable)  可調整性(Scalable)與可重組性(Configurable)  多任務(Multi-tasking)與任務管理  可調整的任務排程 (Scheduling Algorithm)  Task(或稱thread)同步機制:如semaphore、mutex  Task間通訊機制 (IPC):如mail box、message queue  中斷管理  記憶體管理  資源管理
  49. 49. 49 事件驅動系統  事件發生時,系統的軟硬體一起合作對事件做出反應  對程序而言是非同步的 (不知道事件何時會發生)  系統必須對事件做出回應  多種事件可能同時發生  RTOS試圖配置CPU Time  當事件發生時:  其它處理被暫時擱置  處理該事件  結束後,之後繼續處理剛剛被擱置的東西  通常,這也稱作是“Real-Time”的系統  要如何知道有沒有事件發生?  輪詢 (其實我們已經接觸過用過輪詢方法來檢測事件了)  中斷
  50. 50. 50 輪詢事件  輪詢是檢測有無事件發生最簡單但最沒有效率的方式。  處理器平常甚麼事也不做,只是在一個迴圈裡面持續 等待事件  輪詢經常用在簡單的I/O handlers:  等待(Wait) : Input buffer滿了嗎?  還沒: 繼續等  滿了: 讀取input data buffer然後繼續後續處理
  51. 51. 51 中斷  想像有一個按鈕,按下它會馬上強制CPU去呼叫一段特 殊的子程式(subroutine),即中斷服務常式(ISR)。  ISR隨時可以被呼叫  硬體中斷(硬體呼叫程式):  允許對外部事件做出反應,需知道呼叫程式的位址  不與程式執行同步,隨時都可能發生,在程式的不定位置發生  可以最小化CPU所浪費的時間,最即時  不需要輪詢事件是否發生  軟體中斷(程式呼叫程式):  呼叫時不需要知道程式的位址  跟程式執行同步,在程式的特定位置發生  範例:系統呼叫。
  52. 52. 52 利用按鈕中斷來點燈的例子  事件:按下開關  中斷產生:選擇開燈ISR  中斷被處理的過程  CPU擱置main  CPU呼叫ISR  ISR儲存程式返回指標  除理完畢ISR返回main  
  53. 53. 53 軟體設計 – Polling與IRQ
  54. 54. 54 可重進入函式  一段Code可以在中斷時執行,並且在他還沒執行完之 前又被呼叫一次、兩次或以更多次,都沒問題。  必須只使用local resources (i.e. auto-variables)  不可去修改global resources  Compiler的libraries通常都是「不可重進入」  大部分的OS functions也是 「不可重進入」  使用特定硬體的Code也是「不可重進入」  範例: int y; void notReent(int *x) { y = y + *x; *x = y; } int y; void reentSafe(int *x) { DisableInterrupts(); y = y + *x; *x = y; RestoreInterrupts(); }
  55. 55. 55 臨界區段(Critical Code Segment)  一段不可被中斷的程序碼區(code sequence),否則可能 造成錯誤 :  如果中斷在這些區段中發生,將會造成錯誤  最簡單的避免方式是,進入臨界區段之前關閉中斷,要出區段 之前再打開中斷。  範例:兩個行程同時使用UART,但程式中有一個signal是以 UART中斷flag來做執行依據。中斷可能造成錯誤 (雖然實 際上可能機率很小,但還是不能忽略該問題)  當資源共享時會發生  需要互斥存取(Mutually Exclusive Access)  需要使用Semaphore (超級版的Mutex)
  56. 56. 56 嵌入式系統Task架構實例  不需並行處理,所以不需要多任務,即應用程式只需 要一個main task處理即可。  產品有省電需求,當main task沒事做時會自動sleep, 此時RTOS會將控制權交給優先權較低的idle task,由idle task負責讓CPU進入睡眠狀態。  有硬體事件發生時,ISR會將硬體事件傳入main task的 message queue中,此時CPU會從睡眠模式清醒過來, idle task繼續執行,接著喚醒main task。  因為main task的優先級較高,所以RTOS自然會將CPU執 行權交給main task,則main task可以處理新來的硬體事 件,處理完後又會去sleep,RTOS再將控制權交給idle task以進入睡眠。  執行時期系統就是這樣一直循環。
  57. 57. 57 系統中Task與ISR的關係 (I)
  58. 58. 58 系統中Task與ISR的關係 (II) /************************************************************* Boot程式 *************************************************************/ void boot(void) { ... // 做完硬體初始化後, 才開始建立系統中的tasks // 在RTOS中, 一個task基本上就是一個function (如同windows的thread) // 建立main task, stack size = 2048 Bytes, 優先權最高 rtos_create_task(main_task, STACK_SIZE_2048_BYTE, PRIORITY_HIGHEST); // 建立idle task, stack size = 1024 Bytes, 優先權最低 rtos_create_task(idle_task, STACK_SIZE_1024_BYTE, PRIORITY_LOWEST); // RTOS的Scheduler開始run, 最高優先權的task會得到執行權 rtos_start(); // never go back! }
  59. 59. 59 /************************************************************* Main Task *************************************************************/ void main_task(void) { ... // 做一些應用程式初始化 ... while(1) // forever message loop { if(get_message(...) == TRUE) { process_message(); // 處理訊息 continue; // 繼續檢查是否有待處理的訊息 } else { rtos_task_sleep(); } } } /************************************************************* Idle Task *************************************************************/ void idle_task(void) { while(1) { enter_power_saving_mode(); // 讓CPU進入睡眠模式 rtos_task_wakeup(MAIN_TASK_ID); // 被硬體事件喚醒, 喚醒main task // 使其可處理新來的硬體事件 } } /************************************************************* xxx_ISR: 硬體中斷可把CPU從睡眠模式中喚醒 *************************************************************/ void xxx_ISR(void) { ... // 處理硬體事件 put_message(); // 將硬體事件傳入main task的message queue中 }
  60. 60. 60 多任務應用容易造成資源衝突與破壞  實際上我們很少有機會去重寫一個嵌入式OS,最多就 是將現有的RTOS移植到其他平台(通常CPU廠商都會提 供或推薦合適的RTOS)。  多任務的程序在執行時比較容易發生不可預測的問題。 // "temp" is a global variable // while (1) { x = 1; y = 2; // swap x and y temp = x; x = y; y = temp; ... os_delay(1); } // "temp" is a global variable // while (1) { m = 3; n = 4; // swap m and n temp = m; m = n; n = temp; ... os_delay(1); }
  61. 61. 61 Multi-Tasking程式寫作注意事項 (I)  Windows:  Process可想成每個可執行的檔案,每個process都以為自己擁有整個系 統的控制權,也就是每個process擁有自己的地址空間,不會互相干擾。  若某個process想要多個模組並行處理,可以create多個thread,系統的 scheduler會根據其演算法在適當時機切換thread的執行權  Process是windows的基本執行單位,而thread是process的基本執行單位  嵌入式RTOS:  RTOS的基本執行單位是task  Task共享地址空間,原理和多個thread共享一個process的地址空間一樣  在windows上若有一個thread出問題,頂多就是把一個process搞死,但 在RTOS裡的某個task出亂子,則整個系統都會被影響
  62. 62. 62 Multi-Tasking程式寫作注意事項 (II) Boot() { ... main(); } main() { ... create_task(task1, ...); create_task(task2, ...); create_task(task3, ...); ... start_sys(); } rtos_scheduler() { 1. select new task to run 2. save context of current task 3. set SP 4. iret } Task1() { while(1) { ... } } clock_ISR() { rtos_scheduler(); } xxx_ISR() { ... } Task2() { while(1) { ... } } sys_call_sleep() { ... }
  63. 63. 63 Multi-Tasking程式寫作注意事項 (III) prev back … Context frame pointer … prev back … Context frame pointer … prev back … Context frame pointer … Task1 context ReadyQueuePtr Task2 context Taskn context prev back … Waiting TickNo … prev back … Waiting TickNo … prev back … Waiting TickNo … DelayedQueuePtr prev back … Waiting EventID … prev back … Waiting EventID … prev back … Waiting EventID … WaitingQueuePtr
  64. 64. 64 Multi-Tasking程式例說 //***************************************************************** // UI_task: 處理畫面, 使用者輸入, 並將影音數據送給decoder-task播放 //***************************************************************** void UI_task(void) { empty_buffer(); id = create_task(Decoder_task); while(1) { process_UI_event(); put_AV_data_to_buffer(); wakeup(id); } } //***************************************************************** // Decoder_task: 對buffer中的影音數據做解碼並播放 //***************************************************************** void Decoder_task(void) { while(1) { if(buffer == EMPTY) sleep(); else do_decoding(); } }
  65. 65. 65 Scheduler的調度演算法  RTOS不會使用太複雜的調度演算法  FIFO: 先排進ready-queue的task可一直執行到放棄執行權  Round-Robin: 在ready-queue的task均分CPU執行權,task在執行一段特 定時間(time-slice)後會被切換出去,有時候對time-slice微調可增加系統 效率。時間太長就等同於FIFO,時間太短則系統會過度忙於task-switch  Priority-based: 保證ready-queue中priority最高的task一定可獲執行權  Priority with Round-Robin: 混合上兩種,task的執行順序不可預測性高  EDF (Estimated deadline first scheduling): 執行期限越近者,優先權調高  RMS (Rate monolithic scheduling): 執行週期越短者,優先權較高  選擇演算法須考慮應用程式是否具有以下兩個特性:  Pre-emptive: 是否任意一個task都可能因為有更重要的事情發生而被 中斷其執行狀態  Determinative: 是否所有task的執行順序,以及task switch的時間都必須 是可預測的
  66. 66. 66 全域變數與臨界區域  Linux或Windows的應用程式與系統層分屬user mode與 kernel mode,每個應用程式有自己的地址空間,不會 互相干擾  RTOS系統中的所有執行單位(系統、應用程式、ISR)共享 地址空間,一不小心就可能互相影響,多個task與不定 時發生的硬體事件觸發ISR都會使程式執行順序無法正 確預測,更使共享的全域變數無安全性可言(必須提供 保護機制)  可分成兩種狀況討論  Task與ISR之間  Task與task之間的critical section保護
  67. 67. 67 臨界區域  Critical section的例子:  Inter-task溝通時會去存取到的變數,特別是global variables。這是 critical section最常見的形式。  銜接硬體的介面程式,像ports與ADCs。如果同樣的ADC同時被一個以 上的tasks個使用時會如何?  呼叫common functions的程式碼。如果一個function同時被一個以上的 tasks調用時會如何?(要保證該function為可重進入)  在co-operative系統中,這些問題都不會發生,因為每 次都只有一個task在執行。  在先佔式系統中處理臨界區域的兩種可能方法: 1. 進入臨界區前先關閉排程器中斷,要離開臨界區前再將之打開。 2. 使用lock (像semaphore)。Task A先檢查X埠的lock,如果該埠被鎖住, Task A等待。若沒有被locked,則Task A取得該鎖並lock住X埠,當Task A完成後,離開臨界區前解鎖。
  68. 68. 68 ISR與Task之間 uint8 A, B; //***************************************************************** // Function: 一般函式 //***************************************************************** void fun(void) { ... disable_interrupt(); // 禁止中斷產生 // 進入critical section, 保證全域變數A, B B = 10; // 不被ISR破壞 A = B + 5; enable_interrupt(); // 恢復中斷 ... } //***************************************************************** // Function: 中斷處理函式, 中斷可能在任何程序執行時發生 //***************************************************************** __interrupt__ void ISR(void) { // 通常進ISR後, 中斷會被禁用 ... B = 12; // 在ISR中操作全域變數 ... }
  69. 69. 69 Task與Task之間 void task1(void) // high priority { ... enter_critical_section(1); A = 1234; C = A; exit_critical_section(1); ... } void task2(void) // low priority { ... enter_critical_section(1); A = 5678; D = A; exit_critical_section(1); ... }
  70. 70. 70 任務同步與任務間通訊  任務同步 (Synchronizing Tasks):Semaphores、Wait Event或Time  任務間通訊 (Inter-Task Communication): Mailbox (Pointer)、Circular Queue(或Buffer)、Shared Memory  在PC上面的程序間通訊 (IPC, Inter-process communications), 在嵌入式系統,也等同Inter-Task Communications。  IPC是一種在tasks間安全傳遞資料、訊息、事件的方法  為什麼需要IPC?  IPC允許tasks被block在IPC method上來支援事件驅動模型  任務可以獨立地被產生 (相依性越低,系統越強健)  何時不用IPC? 當系統只有1個 task、架構非常簡單、或是對效能要求很高時
  71. 71. 71 IPC方法:Queues  Queue通常用ring buffer實現,訊息可以被複製進queue buffer或者是從中被取出來  缺點: 資料移動了兩次(被搬進去、被移出來)、可能消耗更多記憶體  優點: 資料受到保護  實作相關功能:  若queue已滿,寫入者(writer)會被block  若queue是空的,讀取者(reader)會被block  可以有multiple readers/writers  訊息可能是fixed或variable length  FIFO,也許能安排優先序 Queue的APIs範例: CreateQueue(QControlBlock, Name, StartAddress, Size, <Fixed/Var>, <MsgLen>); WriteQueue(QControlBlock, Msg, Size, Suspend); ReadQueue(QControlBlock, Msg, Size, ActualSize, Suspend);
  72. 72. 72 IPC方法: Mailboxes (Pointer)  本質是fixed size queue,每個元素size等於pointer size  缺點:資料不受保護、資料要維持「in scope」、當資料被消化後 會變的不明(not clear)  優點:比queue還快、比queue有效率、如果將pointer換成data它就 變成了queue  實作相關功能:  類似 queues 的功能 Sample APIs: CreateMBox( MBoxControl, Name ); WriteMBox( MBoxControl, Msg, Suspend ); ReadMBox( MBoxControl, Msg, Suspend );
  73. 73. 73 IPC方法: Signals/Events/Semaphores  當semaphores用來完成IPC機制時  允許tasks間的同步  允許ISR與task溝通 - “HEY! Data is ready!”  缺點:  資料量很小(只有1 bit)  Overhead (比使用shared variable慢)  優點:  Task可以被semaphore阻擋  是可控制的IPC機制 (相較於使用shared variable)  實作相關功能:  事件群組(event groups): semaphores的集合可被結合為一張truth table, 以實現類似「Resume task when (Event1) && (Event3) && (!Event4)」
  74. 74. 74 Semaphore與Mutex  互斥鎖 (Mutex):  Binary Semaphore  僅兩種狀態:available或busy  訊號鎖 (Semaphore):  Binary (Mutex)或 > 2種狀態 (counting):  counting semaphore - 遞增或遞減  範例:共享“n”個資源或裝置  semaphore = 可用資源的數量 bit getSemaphore( uint8 *semaphore ) { bit result = 0; // Assume failure. bit oldEA = EA; // Save current state. EA = 0; // Disable interrupts. if ( *semaphore > 0 ) { // Any resources // avail? *semaphore = *semaphore - 1; // Grab one. result = 1; // Good to go. } EA = oldEA; // Restore interrupts. // NOTE: Could give up processor // if we didn’t get the semaphore. return result; }
  75. 75. 75 共享記憶體 (Shared Memory)  前述的IPC機制總是可以用共享記憶體完成  缺點:  非執行緒安全(not thread-safe),除非有硬體鎖  無法控制以避免誤解  優點:  速度快  沒有RTOS overhead  Ring Buffer can be thread safe with one producer and one consumer.
  76. 76. 76 任務排程 (Task Scheduling)  Scheduler安排CPU Time  決定下一個執行的Task  內文切換 (context switch)  uC/OS uses:  Priority Based, number 0-63  High Priority (0) Supercedes Low Priority (63)  High Priority Tasks can Interrupt Low Ones  Overhead: Time Wasted, Not Spent on Task
  77. 77. 77 排程器 (Schedulers)  兩種常用的排程演算機制:  Priority  Round-robin  勿與先佔式(preemption, 也稱time-slicing)或非先佔式 (non-preemption, 或稱cooperative)的 multi-tasking搞混。  Scheduler屬於Kernel的一部分。  排程器可混合上述方法來實作
  78. 78. 78 Task Priorities與Blocking  Task優先權必須審慎決定  若你創造了一個永遠不會被block掉的高優先權task,那 麼低優先權的tasks將無法獲得執行  例如: Task A的優先序為50,它用來定期輪詢周邊狀態暫存器(peripheral status register)。Task B的優先序為51(較低),用於串列命列介面 (serial command interface)。此情況下,Task B永遠不會被執行。
  79. 79. 79 Round-Robin Scheduling  原則上,tasks會以預先定義的順序執行  例如:  你有3個tasks (T1, T2, T3)  執行順序為T1, T2, 然後 T3  在任何時候,當T1釋放CPU控制權後, 若T2已經就緒,那就允 許執行T2  T2執行完後,若T3此時已就緒,就執行T3  當T3執行完後,T1則會獲得執行權,如此反覆
  80. 80. 80 Priority Scheduling  每個task都有其優先序  高優先序的tasks會比低優先序者先執行  例如:  Task T1的優先序最高,而T3最低  T2正在執行  T1跟T3同時間都已經就緒  T1會先被允許執行
  81. 81. 81 Non-Preemptive Kernel  每個task都必須do something以釋出CPU控制權  也稱為cooperative-multitasking,比先佔式的好實作  ISR可以中斷掉tasks  臨界區比較容易處理
  82. 82. 82 Preemptive Kernel  常被Real-Time applications所使用  最高優先序者總是可以執行  任務切換(A task switch)發生於  A task makes a higher priority task ready to run.  An ISR completes  實作比較複雜  臨界區的問題比較顯著
  83. 83. 83 死結 (Deadlock)  Tasks與resources間有迴圈關係(circular dependency)  Task1取得MutexA,等待MutexB  Task2取得MutexB,等待MutexA  唯一解法:重開機
  84. 84. 84 優先序繼承 (Priority Inheritance)
  85. 85. 85 可預測性 (Determinism)  系統行為必須保證可被預測(預期)  優先序反轉會造成系統變成unpredictable  低優先序的task變得好像高優先序的task  無法保證即時性(最高優先序task的工作時間要等待低優先完成)
  86. 86. 86 優先序反轉 (Priority Inversion)
  87. 87. 打造一個簡易型Embedded OS
  88. 88. 88 Super Loop架構 (I) void main(void) { // Prepare run function X X_Init(); while(1) // 'for ever' (Super Loop) { X(); // Run function X } }
  89. 89. 89 Super Loop架構 (II) void main(void) { Init_System(); while(1) // 'for ever' (Super Loop) { X(); // Call the function (10 ms duration) Delay_50ms(); // Delay for 50 ms } }
  90. 90. 90 避免浪費CPU的執行週期  要完成週期性的函式執行,並且避免CPU time被浪費, 我們可以請「中斷」來幫忙!  我們都知道,中斷是「通知CPU有事情(event)發生」的 硬體機制,計時器溢位就是其中一種。  計時器是硬體計時,延遲迴圈是軟體計時  可以利用硬體計時器來定期又精準地產生「中斷」, 例如每1 ms發生一次溢位中斷;我們經常將這個精準的 時間週期作為「系統的時間參考」,又稱為tick (很像 時鐘每1秒鐘滴答1次)。  假使我們要打造一個作業系統,第一件事就是製造出 「tick」,這樣子OS要處理的工作才有共同的時間概念 (就好像掛在牆壁上的時鐘)。
  91. 91. 91 Task、Function與Scheduling  在嵌入式系統設計中,你常會聽到「task design」 、 「task execution times」與「multi-tasking」等字眼。通 常,「task」指的是「a function that is executed on a periodic basis」。  控制單一task的執行時間又稱為「scheduling the task (工作排程)」,是由系統中的排程器所控制。排程器會 用ISR來實現(或被ISR呼叫),而ISR則是在timer溢位中斷 發生時被調用。
  92. 92. 92 用3個Timer來使3個Task定時工作 /*------------------------------------------------------------------*- Multi-timer ISR program framework -*------------------------------------------------------------------*/ #include <AT89C52.h> #define INTERRUPT_Timer_0_Overflow 1 #define INTERRUPT_Timer_1_Overflow 3 #define INTERRUPT_Timer_2_Overflow 5 void Timer_0_Init(void); void Timer_1_Init(void); void Timer_2_Init(void); void main(void) { Timer_0_Init(); // Set up Timer 0 Timer_1_Init(); // Set up Timer 1 Timer_2_Init(); // Set up Timer 2 EA = 1; // Globally enable interrupts while(1); } void Timer_0_Init(void) { 略 } void Timer_1_Init(void) { 略 } void Timer_2_Init(void) { 略 } void X(void) interrupt INTERRUPT_Timer_0_Overflow { // This ISR is called every 1 ms // Place required code here... } void Y(void) interrupt INTERRUPT_Timer_1_Overflow { // This ISR is called every 2 ms // Place required code here... } void Z(void) interrupt INTERRUPT_Timer_2_Overflow { // This ISR is called every 5 ms // Place required code here... }
  93. 93. 93 排程器 (Scheduler)  排程器是的OS基礎,也有人把排程器視作一種最最最 簡單的OS。  從很底層的眼光來看,排程器就是一個定時器的ISR, 而這個ISR是由許多tasks共享。  使用排程器讓3個task工作(只使用1個timer) void main(void) { Scheduler_Init(); // Set up the scheduler // Add the tasks (1ms tick interval) Scheduler_Add_Task(Function_A, 0, 2); // Function_A will run every 2 ms Scheduler_Add_Task(Function_B, 1, 10); // Function_B will run every 10 ms Scheduler_Add_Task(Function_C, 3, 15); // Function_C will run every 15 ms Scheduler_Start(); while(1) { Scheduler_Dispatch_Tasks(); } }
  94. 94. 94 合作式排程器  Co-operative Scheduler: 合作式排程器  Single task架構  Tasks會定期(或one-shot)取得執行權  當CPU空閒,下一個等待中的task會被執行  Task執行完後會把CPU控制權還給scheduler  排程器一次只能安排記憶體給一個task  對外部事件反應迅速  合作式scheduler簡單、可預測、穩固與安全
  95. 95. 95 先佔式排程器  Pre-emptive Scheduler: 先佔式排程器  支援Multi-tasking架構  Tasks會定期(或one-shot)取得執行權  當一個task被排程,會被加入一個waiting list  當一個task執行完一段固定的時間後,若還未執行完,會先被暫 停,然後被scheduler退回waiting list。此時,下一個task會取得 執行權。  Scheduler的實作比較複雜,需要有semaphore的機制來避免不同 task同時存取共享資源  Scheduler要規劃一段記憶體來儲存task的中間狀態(context)  由於效率問題,先佔式scheduler通常會用組合語言實作  比起co-operative scheduler,可預測性稍差
  96. 96. 96 簡易型混合式排程器  Hybrid Scheduler: 混合式排程器  提供非常有限的Multi-tasking能力  支援任意數量的co-operatively-scheduled tasks  支援1個pre-emptive task (它可以中斷掉co-operative tasks)  Scheduler要同時為兩個tasks準備記憶體  可用C語言實現  對外部事件的反應迅速  只要小心設計,穩固性等同於co-operative scheduler
  97. 97. 97 節省功率  使用Super Loop其中一個缺點是CPU浪費了大部分的CPU 週期在delay loop (busy waiting, CPU執行空指令來等待): void main(void) { Init_System(); while(1) // 'forever' (Super Loop) { X(); // Call the function (10 ms duration) Delay_50ms(); // Delay for 50 ms } } void myOS_go_sleep(void) { PCON |= 0x01; // Enter idle mode } 2 99.9 11 0.1 2.009 mA 100 avgI      5 V 2.009 mA 2 99.9 11 0.1 =10.045 mW 2.009 mA 100 avgP      
  98. 98. 98 要使用Timer 0或Timer 1定時?  假使MCU沒有Timer 2,也可使用Timer 0或Timer 1。  通常Timer 2才會有16-bits的reload模式;Timer 0跟Timer 1的reload模式則是8-bits的。  在典型的8051應用裡,8-bit timer發生中斷的週期大約 為0.25 ms(或更短)。這對一些需要快速處理的應用是蠻 合適的,然而在大多數的應用裡,這麼短的tick interval 在有OS的應用中會增加CPU的負擔(睡眠的%降低)。  若需要使用到UART,也要為鮑率產生器預留計時器。
  99. 99. 99 簡單的OS架構 (I)  最簡單的OS有兩個features:  時間觸發架構(time-triggered architecture)  合作式排程(co-operative scheduling algorithm)  Starting tasks  系統的time-triggered性質意指functions是在先被預測的時間點 (pre-determined points)開始(或觸發)。相對於此架構的是「事件 觸發(event-triggered)架構」  在安全性的應用中最好是使用time-triggered架構,因為它具有 很好的可預測性。 例如:護士會定時照料病人。
  100. 100. 100 簡單的OS架構 (II)  Stopping tasks  合作式(co-operative)是指一個task只要開始就會run到結束,也 就是OS不會中斷一個active task,屬於「single task」approach。  先佔式(pre-emptive)系統中,tasks通常一次會run個1 ms,然後 OS會先暫停該task,接著run另一個task 1 ms,如此繼續。對使 用者來講,先佔式OS就好像是在同一時間run multiple tasks。  合作式排程較簡單,而且比先佔式還要容易預測。 例如我們想要在pre-emptive系統run兩個tasks,而這兩個tasks都要存取相同的埠。 如果一個task要讀取該埠,此時scheduler剛好執行context switch使另一個task來 存取該埠;在這情況,除非我們採取一些動作來避免,不然資料可能會遺失或 被毀損。這個問題在multi-tasking的系統中很常遇到,我們稱那一段程式碼為 critical section。  Critical section指的是一段程式碼,只要他開始run,就要run到 結束,中間不能被中斷 (區間內不被打斷的執行敘述為atomic)。
  101. 101. 101 先佔式排程器的鎖 #define UNLOCKED 0 #define LOCKED 1 bit Lock; // Global lock flag // . . . // Ready to enter critical section // – wait for lock to become clear // (FOR SIMPLICITY, NO TIMEOUT CAPABILITY IS SHOWN) while(Lock == LOCKED); // Lock is clear // Enter critical section // Set the lock Lock = LOCKED; // CRITICAL CODE HERE // // Ready to leave critical section // Release the lock Lock = UNLOCKED; // . . . #define UNLOCKED 0 #define LOCKED 1 bit Lock; // Global lock flag // . . . // Ready to enter critical section // – wait for lock to become clear // while(Lock == LOCKED); // Lock is clear // Enter critical section // Set the lock Lock = LOCKED; // CRITICAL CODE HERE // // Ready to leave critical section // Release the lock Lock = UNLOCKED; // . . .
  102. 102. 102 Scheduler之實作  系統時基 (System tick)  Scheduler的重要元件  Scheduler data structure  初始函式  使用單一個ISR在固定週期更新scheduler  添加tasks到scheduler的函式  任務分派函式(dispatcher function),定時被呼叫以執行task  從scheduler刪除tasks的函式
  103. 103. 103 Timer2 驅動程式 /************************************************************************** Filename: hal_timer2.h Revised: $Date: 2013-12-9 $ Revision: $Revision: $ Description: This file contains the interface to the Timer2 Service. Note: BaudRate Generator is not implemented ***************************************************************************/ #ifndef HAL_TIMER2_H #define HAL_TIMER2_H /****** INCLUDES *****/ #include "hal_board.h" /****** MACROS *******/ /****** CONSTANTS *****/ /* Operation Modes for timer */ #define HAL_TIMER2_MODE_16BITS_AUTO 0x00 #define HAL_TIMER2_MODE_16BITS_CAPTURE 0x01 // not support Baud Gen #define HAL_TIMER2_MODE_BAUD_GEN 0x02 /******** TYPEDEFS ***********/ typedef void (*halTimer2CBack_t) (void); /********* FUNCTIONS – API *********/ /* Initialize Timer2 Service */ extern void HalTimer2Init(void); /* Configure channel in different modes */ extern uint8 HalTimer2Config(uint8 opMode ,bool intEnable ,halTimer2CBack_t cback); /* Start a Timer */ extern uint8 HalTimer2Start(uint16 timePerTick); /* Stop a Timer */ extern uint8 HalTimer2Stop(void); /* Enable and disable particular timer */ extern uint8 HalTimer2InterruptEnable(bool enable); #endif
  104. 104. 104 /****************************************************** Filename: hal_timer2.c Revised: $Date: 2013-12-9 $ Revision: $Revision: $ Note: BaudRate Generator is not implemented ********************************************************/ #include "hal_mcu.h" #include "hal_defs.h" #include "hal_types.h" #include "hal_timer.h" #include "hal_timer2.h" // IE /* Timer2 Interrupt Enable Bit*/ #define IE_T2_IntEn_Bit BV(5) // IF@2TCON #define T2CON_T2_IntFlag_Bit BV(7) #define T2CON_T2EX_IntFlag_Bit BV(6) // Enable@TCON #define T2CON_T2_START_BV BV(2) /* Prescale settings and Clock settings */ // has defined in timer.h // #define HAL_TIMER_PRESCALE_VAL 12 // #define HAL_TIMER_12MHZ 12 /* ISR Vector names */ #define T2_VECTOR timer2 typedef struct { bool configured; bool intEnable; uint8 opMode; uint8 prescaleVal; uint8 clock; uint8 TH; uint8 TL; halTimer2CBack_t callBackFunc; } halTimer2Settings_t; /******* GLOBAL VARIABLES *********/ static halTimer2Settings_t halTimer2Record; /******* FUNCTIONS – Local ********/ uint8 halTimer2SetOpMode (uint8 opMode); uint8 halTimer2SetCount (uint16 timePerTick); void halTimer2SendCallBack (void); void halProcessTimer2 (void); /******* FUNCTIONS - API **********/ /********************************************* * @fn HalTimer2Init * @brief Initialize Timer2 Service * @param None * @return None */ void HalTimer2Init (void) { IE &= ~(IE_T2_IntEn_Bit); // disable timer2 interrupt /* Setup prescale value & clock for timer2 */ halTimer2Record.clock = HAL_TIMER_12MHZ; halTimer2Record.prescaleVal = HAL_TIMER_PRESCALE_VAL; }
  105. 105. 105 /* @fn HalTimer2Config * @brief Configure the Timer Serivce * @param opMode - Operation mode * intEnable - Interrupt Enable * cBack - Pointer to the callback function * @return Status of the configuration *****************************************************************************/ uint8 HalTimer2Config (uint8 opMode, bool intEnable, halTimer2CBack_t cBack) { halTimer2Record.configured = TRUE; halTimer2Record.opMode = opMode; halTimer2Record.intEnable = intEnable; halTimer2Record.TH = 0; halTimer2Record.TL = 0; halTimer2Record.callBackFunc = cBack; return HAL_TIMER_OK; } /* @fn HalTimer2Start * @brief Start the Timer2 Service * @param timerPerTick - number of micro sec per tick, (ticks x prescale) / clock = usec/tick * @return Status - OK or Not OK ***************************************************************************************************/ uint8 HalTimer2Start (uint16 timePerTick) { if (halTimer2Record.configured) { halTimer2SetOpMode(halTimer2Record.opMode); halTimer2SetCount(timePerTick); HalTimer2InterruptEnable(halTimer2Record.intEnable); T2CON |= T2CON_T2_START_BV; } else { return HAL_TIMER_NOT_CONFIGURED; } return HAL_TIMER_OK; }
  106. 106. 106 /****************************************************** * @fn HalTimer2Stop * @brief Stop the Timer2 Service * @param * @return Status - OK or Not OK *****************************************************/ uint8 HalTimer2Stop (void) { T2CON &= ~(T2CON_T2_START_BV); return HAL_TIMER_OK; } /***************************************************** * @fn halTimer2SetCount * @brief * @param timerPerTick - Number micro sec per ticks * @return Status - OK or Not OK *****************************************************/ uint8 halTimer2SetCount (uint16 timePerTick) { uint16 count; count = (65536 - timePerTick); halTimer2Record.TH = (uint8) (count >> 8); halTimer2Record.TL = (uint8) (count & 0x00FF); RCAP2H = halTimer2Record.TH; RCAP2L = halTimer2Record.TL; TH2 = halTimer2Record.TH; TL2 = halTimer2Record.TL; return HAL_TIMER_OK; } /*********************************************** * @fn halTimer2SetOpMode * @brief Setup operate modes * @param opMode - operation mode of the timer * @return Status - OK or Not OK **********************************************/ uint8 halTimer2SetOpMode (uint8 opMode) { T2MOD &= ~(0x03); // set DCEN = 0, T2OE = 0 T2CON &= ~(0xFF); // Set T2CON to 0; switch(opMode) { case HAL_TIMER2_MODE_16BITS_AUTO: break; case HAL_TIMER2_MODE_16BITS_CAPTURE: T2CON_bit.CP_RL2 = 1; T2CON_bit.EXEN2 = 1; break; default: break; } return HAL_TIMER_OK; }
  107. 107. 107 /******************************************************* * @fn HalTimer2InterruptEnable * @brief Setup operate modes * @param enable - TRUE or FALSE * @return Status - OK or Not OK ******************************************************/ uint8 HalTimer2InterruptEnable (bool enable) { if (halTimer2Record.intEnable) IE |= IE_T2_IntEn_Bit; else IE &= ~(IE_T2_IntEn_Bit); return HAL_TIMER_OK; } /****************************************************** * @fn halTimer2SendCallBack * @brief Send Callback back to the caller * @param channelMode - channel mode * @return None *****************************************************/ void halTimer2SendCallBack (void) { if (halTimer2Record.callBackFunc) (halTimer2Record.callBackFunc)( ); } /****************************************************** * @fn halProcessTimer2 * @brief Processes Timer 2 Events. * @param * @return *******************************************************/ void halProcessTimer2 (void) { halTimer2SendCallBack(); } /****** INTERRUPT SERVICE ROUTINE *********/ /****************************************** * @fn halTimer2Isr * @brief Timer 2 ISR * @param * @return ******************************************/ HAL_ISR_FUNCTION( halTimer2Isr, T2_VECTOR ) { T2CON &= ~(T2CON_T2_IntFlag_Bit|T2CON_T2EX_IntFlag_Bit); // T2CON &= ~(T2CON_T2EX_IntFlag_Bit); halProcessTimer2(); }
  108. 108. 108 main函式的樣子 #include "MyAPP1.h" void LED_flash_update(void); void main() { HalDriverInit(); myOS_init(); myOS_add_task(LED_flash_update, 0, 500); myOS_start(); while(1) { myOS_task_dispatcher(); } } void LED_flash_update(void) { HalLedSet(HAL_LED_4, HAL_LED_MODE_TOGGLE); } void myOS_init(void) { ... 略 ... // System tick使用Timer2產生 => 每1 ms溢位中斷1次 HalTimer2Config(HAL_TIMER2_MODE_16BITS_AUTO, HAL_INT_ENABLE , myOS_scheduler_update); } void myOS_start(void) { HAL_ENABLE_INTERRUPTS(); HalTimer2Start(SYSTEM_TICK); }
  109. 109. 109 系統操作邏輯 task_t gScheduler_tasks[SCHEDULER_MAX_TASKS];
  110. 110. 110 排程器的資料結構與Task陣列 typedef struct // This struct saves information of a task. . { // A task costs 7 Bytes. (AT89C52 has 256-Bytes RAM) void (*pTask)(void); // Pointer to the task (task is function, this pTask is a function pointer) uint16 delay_ticks; // Delay in ticks until the function will be run uint16 period_ticks; // Interval (ticks) between subsequent runs. uint8 execute_me; // Incremented (by scheduler) when task is due to execute, it’s a fire flag. } task_t; 7-bytes 7-bytes 7-bytes 7-bytes 7-bytes 7-bytes 7-bytes task_t gScheduler_tasks[SCHEDULER_MAX_TASKS];
  111. 111. 111 myOS.h /************************************************************************************************** Filename: myOS.h Revised: $Date: 2013-12-9 $ Description: This file contains the information of my simple embedded OS **************************************************************************************************/ #include "hal_types.h" #include "hal_drivers.h" #include "hal_board_cfg.h" /********** CONSTANTS *********/ #define SYSTEM_TICK 1000 // Maximum number of tasks, you can adjust this for your application #define SCHEDULER_MAX_TASKS 3 #define RETURN_NORMAL 0 #define RETURN_ERROR 1 // Error Code #define ERROR_SCH_TOO_MANY_TASKS 0x01 #define ERROR_SCH_CANNOT_DELETE_TASK 0x02 /********** TYPEDEFS *********/ typedef struct // This struct saves information of a task. . { // A task costs 7 Bytes. (AT89C52 has 256-Bytes RAM) void (*pTask)(void); // Pointer to the task (task is function, this pTask is a function pointer) uint16 delay_ticks; // Delay in ticks until the function will be run uint16 period_ticks; // Interval (ticks) between subsequent runs. uint8 execute_me; // Incremented (by scheduler) when task is due to execute } task_t;
  112. 112. 112 /**************************************************************************** * FUNCTIONS - API ***************************************************************************/ // Initialize the OS extern void myOS_init(void); // Start the OS extern void myOS_start(void); // Dispatch the tasks extern void myOS_task_dispatcher(void); // Add a task to the OS scheduler extern uint8 myOS_add_task(void (*pFunc)(), const uint16, const uint16); // Delete a task from the OS scheduler extern uint8 myOS_delete_task(const uint8); // Reports the OS status extern void myOS_status_reporter(void);
  113. 113. 113 系統初始化與啟動函式 /*********************************************************************************************** * @fn myOS_init * @brief Initialize myOS. Clear the tasks list * @param None * @return None *********************************************************************************************/ void myOS_init(void) { uint8 task_id; for (task_id = 0; task_id < SCHEDULER_MAX_TASKS; task_id++) { // Delete tasks and this generates an error code (since array is empty) myOS_delete_task(task_id); } gError_code = 0; // Reset error code // The system tick is generated by Timer2. The required Timer 2 overflow is 1 ms. // Call HalTimer2Config to put it in 16-bits auto-reload mode, // and the callback function = myOS_scheduler_update HalTimer2Config(HAL_TIMER2_MODE_16BITS_AUTO, HAL_INT_ENABLE, myOS_scheduler_update); } /********************************************************************************************** * @fn myOS_start * @brief Start the OS. Enable interrupts and Timer2. * @param None *********************************************************************************************/ void myOS_start(void) { HAL_ENABLE_INTERRUPTS(); // Enable interrupts HalTimer2Start(SYSTEM_TICK); // Start Timer2 with STSTEM_TICK (1000us = 1ms) } myOS.c
  114. 114. 114 添加task的函式 /*************************************************************************************************** * @fn myOS_add_task * @brief Add a task (function) to be executed at regular intervals or after a user-defined delay * @param *pFunc: function pointer to the task * DELAY_TICK: to execute a task after DELAY_TICKS (1 tick = 1 ms) * PERIOD_TICK: to execute a task in every PERIOD_TICK (set to 0 if execute it only once) * @return task_id - the index of the scheduled task in the list ***************************************************************************************************/ uint8 myOS_add_task(void (*pFunc)(), const uint16 DELAY_TICK, const uint16 PERIOD_TICK) { uint8 task_id = 0; // Find an empty space in the array (if there is one), and get the index of that position while ((gScheduler_tasks[task_id].pTask != 0) && (task_id < SCHEDULER_MAX_TASKS)) { task_id++; } if (task_id == SCHEDULER_MAX_TASKS) { // Check if end of the list is reached? // If it is, set gError_code to show "task list full" and return an error code gError_code = ERROR_SCH_TOO_MANY_TASKS; return gError_code; } // We're here since there is a space in the task array gScheduler_tasks[task_id].pTask = pFunc; gScheduler_tasks[task_id].delay_ticks = DELAY_TICK; gScheduler_tasks[task_id].period_ticks = PERIOD_TICK; gScheduler_tasks[task_id].execute_me = 0; return task_id; // return position of task (to allow later deletion) }
  115. 115. 115 刪除task的函式 /********************************************************************************************* * @fn myOS_delete_task * @brief This function is called to delete a task by the task_id. * @param TASK_ID - ID of the task * @return Return_code - OK or Not OK ********************************************************************************************/ uint8 myOS_delete_task(const uint8 TASK_ID) { uint8 Return_code; if (gScheduler_tasks[TASK_ID].pTask == 0) { // No task at here, set gError_code to show nothing to delete // Also return an error code gError_code = ERROR_SCH_CANNOT_DELETE_TASK; Return_code = RETURN_ERROR; } else { Return_code = RETURN_NORMAL; } gScheduler_tasks[TASK_ID].pTask = 0x0000; // 8051's Program Counter is 16-bits wide gScheduler_tasks[TASK_ID].delay_ticks = 0; gScheduler_tasks[TASK_ID].period_ticks = 0; gScheduler_tasks[TASK_ID].execute_me = 0; return Return_code; // return status }
  116. 116. 116 排程器更新函式 /********************************************************************************************** * @fn myOS_scheduler_update * @brief Scheduler's ISR. He will be called in every 1ms. It choose a task to run and * update the schedule time-ticks of each task. * @param None * @return None *********************************************************************************************/ void myOS_scheduler_update(void) { // Clear T2 Flag, this is done in hal_timer2.c. Just comment it out here // T2CON &= ~(T2CON_T2EX_IntFlag_Bit); uint8 task_id; for (task_id = 0; task_id < SCHEDULER_MAX_TASKS; task_id++) { if (gScheduler_tasks[task_id].pTask) { if (gScheduler_tasks[task_id].delay_ticks == 0) { gScheduler_tasks[task_id].execute_me += 1; // If the task is set to run periodically, then schedule the task to run again if (gScheduler_tasks[task_id].period_ticks) { gScheduler_tasks[task_id].delay_ticks = gScheduler_tasks[task_id].period_ticks; } } else { // If the task is not yet ready to run: just decrement the delay // (This implements the tick-time counter) gScheduler_tasks[task_id].delay_ticks -= 1; } } } }
  117. 117. 117 任務分派器 /********************************************************************************************* * @fn myOS_task_dispatcher * @brief When a task (function) is due to run, myOS_task_dispatcher() will run it. * This function must be called (repeatedly) from the main loop. * @param None * @return None *********************************************************************************************/ void myOS_task_dispatcher(void) { uint8 task_id; // Dispatches (runs) the next task (if one is ready) for (task_id = 0; task_id < SCHEDULER_MAX_TASKS; task_id++) { if (gScheduler_tasks[task_id].execute_me > 0) { (*gScheduler_tasks[task_id].pTask)(); gScheduler_tasks[task_id].execute_me -= 1; // Periodic tasks will automatically run again ( Re-scheduled in myOS_scheduler_update() ) // If this is a 'one shot' task, remove it from the array here if (gScheduler_tasks[task_id].period_ticks == 0) myOS_delete_task(task_id); } } myOS_status_reporter(); // Call the reporter to report system status myOS_go_sleep(); // The scheduler enters idle mode at this point }
  118. 118. 118 重要的設計issues (I)  Worst-case task execution time  設計者最好要確定task的execution time不會超過一個 tick interval。
  119. 119. 119 重要的設計issues (II):Task重疊  假設應用程式有兩個tasks: Task A (1秒執行1次)、Task B(每3秒執行1次)  若換個設定方式:
  120. 120. 120 重要的設計issues (III):Tick Interval  假設我們有三個tasks (X,Y,Z): Task X每10 ms執行1次、Task Y每30 ms執行1次、Task Z每25 ms執行1次 排程器的tick interval可由各task的最大公因數決定  Task X 週期 10 ms之公因數: 1 ms, 2 ms, 5 ms, 10 ms  Task Y 週期 30 ms之公因數: 1 ms, 2 ms, 3 ms, 5 ms, 6 ms, 10 ms, 15 ms, 30 ms.  Task Z 週期 25 ms之公因數: 1 ms, 5 ms, 25 ms  最大公因數為5 ms,因此tick interval可設為5 ms  可設定Task X立即執行, Task Y 延後1 ms執行,Task Z延後2 ms執行。 此時tick interval的決定要考慮延遲1 ms, 2 ms與各tasks週期10 ms, 30 ms與25 ms,很明顯最大公因數變為1 ms,因此tick interval要改 為1 ms。 .
  121. 121. 121 重要的設計issues (IV):中斷原則  ‘One Interrupt per Microcontroller’ rule  OS初始化函式啟用計時器後,為了確保OS正確運行, 除了這個 timer的中斷之外,其他所有interrupts都要關掉。  如果你沒這麼做,你就是試著將myOS當成是一種event-triggered 的系統來用。如果你真的允許了其他中斷,你的系統就沒辦法 保證完全正常,你可能會得到超乎預期的系統行為。
  122. 122. 122 合作式思考  Think “co-operatively”  Timing與task duration的限制  如果task不能在Tick Interval內完成,你要打斷他嗎?  當等待某個硬體操作時(例如等待ADC轉換完畢),你要 如何確保系統不會懸宕住(hang)? Duration of a Task < Tick Interval while ((ADCON & ADCI) == 0);
  123. 123. 123 逾時控制  為了使應用程式更可靠,你必須保證不會有任何函式 會hang住。Loop timeouts提供了一種簡單有效的方法- Timeout protection - 來處理這個問題: uint16 Timeout_loop = 1; // Take sample from ADC // Wait until conversion finishes (checking ADCI) // - simple loop timeout while (((ADCON & ADCI) == 0) && (Timeout_loop != 0)) { Timeout_loop++; // Disable in hardware simulator... } uint16 Timeout_loop = 0; // Take sample from ADC // Wait until conversion finishes (checking ADCI) // - simple loop timeout while (((ADCON & ADCI) == 0) && (++Timeout_loop != 0)); HalTimerConfig(HAL_TIMER_0, HAL_TIMER_MODE_16BITS, HAL_INT_ENABLE, NULL); uint8 HalTimerStart(HAL_TIMER_0, 60000); while ((ADCON & ADCI) == 0 && !TF0);
  124. 124. 124 任務導向設計(Task-oriented Design)  基於Co-operatively排程,有兩種有用的任務導向設計:  Multi-Stage Task 這是一種將long tasks (scheduled at infrequent intervals)拆解為很 多shorter tasks的技術(scheduled at frequent intervals)  Multi-State Task 一種能以single task取代multiple tasks的方法,single task到底會 執行甚麼內容會視系統當前的狀態(state)而定,這樣的系統將會 依靠一個有現狀態機(Finite State Machine, FSM)來運作。
  125. 125. 125 Multi-Stage Task  應用程式使用排程器的time-triggered架構  你如何確定一個long task不會影響到排程器的正常運作? 合作式scheduler有一個重點,你要保證下一個tick來之 前,task要工作完畢:  當然,我們可以使用「timeout逾時」的方式來保證  另一種方法:使用multi-stage tasks  系統設計範例: 溫度監控器,每隔5秒鐘回報鍋爐溫度給PC
  126. 126. 126 系統範例:鍋爐溫度監控器  溫度監控器每隔5秒鐘使用RS-232回報鍋爐溫度給PC, 回傳字串的格式如右 TIME: 13.10.00 TEMPERATURE (Celsius): 2310 TIME: 13.10.05 TEMPERATURE (Celsius): 2313 TIME: 13.10.10 TEMPERATURE (Celsius): 2317 TIME: 13.10.15 TEMPERATURE (Celsius): 2318
  127. 127. 127 Multi-Stage Task特點  硬體資源利用  使用multi-stage tasks會使CPU更有效率  穩定度與安全性  可使系統更responsive,因為tick interval可以維持較短  移植性  適用大多數的嵌入式系統
  128. 128. 128 範例:應用在LCD Library  如果我們希望每0.5ms更新LCD螢幕上的一個字元,對 一個40字元的LCD來講大概需要花費20 ms,這是屬於 long task。  假設我們schedule LCD_Update()函式每20 ms執行一次, 每一次只更新LCD上的一個位置,在最壞的情況,你需 要800 ms才能完成整個螢幕的更新。同時間,我們可以 完成其他的tasks。  在多數情況下,LCD上只有一些位置會刷新。所以假使 我們能追蹤這些需要被刷新的字元,我們就可以調為 每100 ms去調用一個花費0.5 ms的LCD刷新task。
  129. 129. 129 Multi-State Task  應用程式使用time-triggered排程器架構  假如你有很多tasks,你要如何使用一個task來取代他們。 這代表一個task可以執行不同的活動,這完全取決的系 統現在的「狀態」  Multi-State Task也用在很多嵌入式系統中 Read_Selector_Dial() Read_Start_Switch() Read_Water_Level() Read_Water_Temperature() Control_Detergent_Hatch() Control_Door_Lock() Control_Motor() Control_Pump() Control_Water_Heater() Control_Water_Valve()
  130. 130. 130 系統範例:洗衣機控制 (I)  系統操作流程 1. 使用者在操作面盤上選擇一個洗程 (wash program, 如羊毛, 棉質) 2. 按下開始(Start)按鈕 3. 洗衣機的門自動鎖住 4. 洗衣機打開進水閥,開始注水進滾筒(wash drum) 5. 洗程若包含洗衣精, 洗劑槽會打開。洗衣精流出後,洗劑槽關閉 6. 當檢測到滿水位(full water level)時,關閉進水閥 7. 如果洗程包含溫水洗淨,清水加熱器會啟用。當清水加熱到正確 的溫度後,關閉加熱器 8. 洗衣機馬達開始運轉。馬達會完成一系列的順逆轉(甚至配合不同 速度)來洗淨衣服(到底怎麼轉由洗程決定)。在wash cycle結束時, 馬達停止運作 9. 抽水機開始排水,當水排完後,關掉幫浦
  131. 131. 131 系統範例:洗衣機控制 (II)  要實現這個系統, 你大概需要幾個函式 Read_Selector_Dial() Read_Start_Switch() Read_Water_Level() Read_Water_Temperature() Control_Detergent_Hatch() Control_Door_Lock() Control_Motor() Control_Pump() Control_Water_Heater() Control_Water_Valve()
  132. 132. 132 系統範例:洗衣機控制 (III)  現在,找出要schedule的tasks。我們列出的那些函式, 每一個都是一個task。這實作起來可能很複雜。  以Control_Water_Heater()為例,我們只想在wash cycle 中的特定時間加熱水。所以,如果我們想要每100ms shcedule這個task,要這麼做:  實際上,我們需要一個”Systme Update”的task,他會定 期被調用,然後決定要呼叫誰 void TASK_Control_Water_Heater(void) { if (Switch_on_water_heater_G == 1) { Water_heater = ON; return; } // Switch off heater Water_pin = OFF; }
  133. 133. 133 // For demo purposes only /*--------------------------------------------------------------- */ void WASHER_Update(void) { static uint16 Time_in_state; switch (gSystem_state) { case START: { P1 = (uint8) gSystem_state; WASHER_Control_Door_Lock(ON); // 鎖門 WASHER_Control_Water_Valve(ON); // 注水 if (Detergent_G[Program_G] == 1) { WASHER_Control_Detergent_Hatch(ON); // 加入洗衣精 } gSystem_state = FILL_DRUM; // 系統狀態變更為注水 gTime_in_state = 0; break; } case FILL_DRUM: { P1 = (uint8) gSystem_state; // 水滿前都維持在此狀態 if (++gTime_in_state >= MAX_FILL_DURATION) { gSystem_state = ERROR; // 檢查是否注水太久 } if (WASHER_Read_Water_Level() == 1) { // 檢查水位, 已滿的話 if (Hot_Water_G[Program_G] == 1) { // 有設定加熱水嗎?有的話 WASHER_Control_Water_Heater(ON); // 打開加熱器 gSystem_state = HEAT_WATER; // 讓系統進入加熱狀態 gTime_in_state = 0; } else { gSystem_state = WASH_01; // 讓系統進入洗衣狀態1 gTime_in_state = 0; } } break; }
  134. 134. 134 case HEAT_WATER: { P1 = (uint8) gSystem_state; // 水溫達到前都維持在此狀態 if (++gTime_in_state >= MAX_WATER_HEAT_DURATION) { gSystem_state = ERROR; // 檢查是加熱太久 } if (WASHER_Read_Water_Temperature() == 1) { // 檢查水溫, 如果已OK gSystem_state = WASH_01; // 系統狀態轉移到洗衣狀態1 gTime_in_state = 0; } break; } case WASH_01: { P1 = (uint8) gSystem_state; WASHER_Control_Motor(ON); if (++Time_in_state >= WASH_01_DURATION) { gSystem_state = WASH_02; Time_in_state = 0; } break; } case WASH_02: { ... P1 = (uint8) gSystem_state; break; } case ERROR: { ... P1 = (uint8) gSystem_state; break; } } }
  135. 135. 135 Multi-State Task的特性  硬體資源  此架構能夠很有效率地使用系統資源  可靠度與安全性  具有良好的可靠度與安全性  移植性  有很好的移植性
  136. 136. 136 狀態機 (State-Machine)  當你有很多事情要處理,又想維持系統具有較好的組 織性時,運用狀態機(state machine)會是個不錯的方式  狀態機是一種標準的設計pattern,常見於嵌入式系統  簡單地說,當你調用一個狀態機,它能根據目前系統 的狀態自動地去做它應該做的事  幾乎所有狀態機都可以使用流程圖(flow chart)規劃出來
  137. 137. 137 以狀態為中心(State-centric)的狀態機  平敘地說,狀態機由很 大的if-else或switch程式 敘述所構成: while (1) { look for event switch (state) { case (green light): if (event is stop command) turn off green light turn on yellow light set state to yellow light start timer break; case (yellow light): if (event is timeout) turn off yellow light turn on red light set state to red light break; case (red light): if (event is go command) turn off red light turn on green light set state to green light break; default (unhandled state) error! } } case (state): if event valid for this state handle event prepare for new state set new state
  138. 138. 138 隱藏狀態轉移的細節  另一種實作狀態機的方法是將「狀態轉移的資訊」 抽離狀態機。理論上這是比較好的做法,因為這樣 可以維持較好的封裝性(encapsulation)並減少狀態 內文的相依性。  「next state」函式會處理每一個state並且將系統推 向它該進入的next state。 case (state): make sure current state is actively doing what it needs if event valid for this state call next state function case (green light): if (green light not on) turn on green light if (event is stop) turn off green light call next state function break; next state function: switch (state) { case (green light): set state to yellow light break; case (yellow light): set state to red light break; case (red light): set state to green light break;
  139. 139. 139 以事件為中心(Event-centric)的狀態機  還有一種方式來實作狀態機,那就是以事件來驅動 狀態的變化 switch (event) case (stop): if (state is green light) turn off green light go to next state // else do nothing break; Function to handle stop event if (state == green light) { turn off green light go to next state } case (event): if state transition for this event go to new state
  140. 140. 140 表格驅動(Table Driven)狀態機 (I)  所有的狀態與事件的對應關係在表格中一覽無遺。 每一格都顯示了「當系統在Y狀態,發生X事件時, 系統必須轉移到的「next state」。  在實作上,狀態機也就能直接用表格描述之,你需 要去讀取狀態表就能知道下一個狀態要往哪裡去。 若你需要很多個複雜的狀態機,表格式的作法會是 很好的選擇。
  141. 141. 141 表格驅動(Table Driven)狀態機 (II) struct sStateTableEntry { tLight light; // all states have associated lights tState goEvent; // state to enter when go event occurs tState stopEvent; // ... when stop event occurs tState timeoutEvent; // ... when timeout occurs }; // event handler void HandleEventGo(struct sStateTableEntry *currentState) { LightOff(currentState->light); currentState = currentState->go; LightOn(currentState->light); } typedef enum { kRedState = 0, kYellowState = 1, kGreenState = 2 } tState; struct sStateTableEntry stateTable[] = { { kRedLight, kGreenState, kRedState, kRedState}, // Red { kYellowLight, kYellowState, kYellowState, kRedState}, // Yellow { kGreenLight, kGreenState, kYellowState, kGreenState}, // Green }
  142. 142. 142 範例:紅綠燈  使用multi-state task機制來建造一個紅綠燈的交通 號誌系統。
  143. 143. 143 紅綠燈子系統 (以狀態為中心) /**************************************************************************************** Filename: TLight_subsys.h Revised: $Date: 2013-12-16 $ Revision: $Revision: $ Description: This is a traffic light sub-system *****************************************************************************************/ #include "hal_types.h" #include "hal_drivers.h" #include "hal_board_cfg.h" // Stay-time in each state (times are in 100ms, call update-task once per second) #define GREEN_DURATION (50) #define YELLOW_DURATION (30) #define YELLOW_FLASH_DURATION (10) #define RED_DURATION GREEN_DURATION #define RED_LIGHT_ON() HalLedSet(HAL_LED_1, HAL_LED_MODE_ON) #define RED_LIGHT_OFF() HalLedSet(HAL_LED_1, HAL_LED_MODE_OFF) #define YELLOW_LIGHT_ON() HalLedSet(HAL_LED_2, HAL_LED_MODE_ON) #define YELLOW_LIGHT_OFF() HalLedSet(HAL_LED_2, HAL_LED_MODE_OFF) #define YELLOW_LIGHT_TOGGLE() HalLedSet(HAL_LED_2, HAL_LED_MODE_TOGGLE) #define GREEN_LIGHT_ON() HalLedSet(HAL_LED_3, HAL_LED_MODE_ON) #define GREEN_LIGHT_OFF() HalLedSet(HAL_LED_3, HAL_LED_MODE_OFF) typedef enum { Green, Yellow, Yellow_flash, Red } enumLight_State; extern void TLight_subsys_init(const enumLight_State START_STATE); extern void TLight_subsys_update(void);
  144. 144. 144 /******************************************************************** Filename: TLight_subsys.c Revised: $Date: 2013-12-16 $ Revision: $Revision: $ Description: This is a traffic light sub-system *********************************************************************/ #include "hal_types.h" #include "TLight_subsys.h" /********************************************************************* * Local Global Variable */ static enumLight_State gLight_state; // The state of the system /*************************************************************************************************** * @fn TLight_subsys_init * @brief Initialize the traffic light subsystem, prepare for scheduled traffic light activity * @param eLight_State * @return None ***************************************************************************************************/ void TLight_subsys_init(const enumLight_State START_STATE) { gLight_state = START_STATE; }
  145. 145. 145 /*************************************************************************************************** * @fn TLight_subsys_update * @brief Must be called once per 100ms, the Time_in_state will be increased at every update. * @param None * @return None ***************************************************************************************************/ void TLight_subsys_update(void) { static uint16 Time_in_state; switch (gLight_state) { case Green: RED_LIGHT_OFF(); YELLOW_LIGHT_OFF(); GREEN_LIGHT_ON(); if (++Time_in_state == GREEN_DURATION) { gLight_state = Yellow; Time_in_state = 0; } break; case Yellow: RED_LIGHT_OFF(); YELLOW_LIGHT_ON(); GREEN_LIGHT_OFF(); if (++Time_in_state == YELLOW_DURATION) { gLight_state = Yellow_flash; Time_in_state = 0; } break; case Yellow_flash: RED_LIGHT_OFF(); YELLOW_LIGHT_ON(); GREEN_LIGHT_OFF(); if (((Time_in_state)%2)== 0) { YELLOW_LIGHT_TOGGLE(); } if (++Time_in_state == YELLOW_FLASH_DURATION) { gLight_state = Red; Time_in_state = 0; } break; case Red: RED_LIGHT_ON(); YELLOW_LIGHT_OFF(); GREEN_LIGHT_OFF(); if (++Time_in_state == RED_DURATION) { gLight_state = Green; Time_in_state = 0; } break; } }
  146. 146. 146 紅綠燈系統應用程式 #include "MyAPP1.h" void main() { HalDriverInit(); myOS_init(); TLight_subsys_init(Red); myOS_add_task(TLight_subsys_update, 0, 100); myOS_start(); while(1) { myOS_task_dispatcher(); } }
  147. 147. 147 紅綠燈子系統 (隱藏狀態轉移細節) /****************************************************************************** * @fn TLight_subsys_next_state * @brief This function take care of the "state transition" * @param None * @return None *****************************************************************************/ void TLight_subsys_next_state(void) { switch (gLight_state) { case Green: gLight_state = Yellow; break; case Yellow: gLight_state = Yellow_flash; break; case Yellow_flash: gLight_state = Red; break; case Red: gLight_state = Green; break; } }
  148. 148. 148 /*************************************************************************************************** * @fn TLight_subsys_update * @brief Must be called once per 100ms, the Time_in_state will be increased at every update. * @param None * @return None ***************************************************************************************************/ void TLight_subsys_update(void) { static uint16 Time_in_state; switch (gLight_state) { case Green: RED_LIGHT_OFF(); YELLOW_LIGHT_OFF(); GREEN_LIGHT_ON(); if (++Time_in_state == GREEN_DURATION) { TLight_subsys_next_state(); Time_in_state = 0; } break; case Yellow: RED_LIGHT_OFF(); YELLOW_LIGHT_ON(); GREEN_LIGHT_OFF(); if (++Time_in_state == YELLOW_DURATION) { TLight_subsys_next_state(); Time_in_state = 0; } break; case Yellow_flash: RED_LIGHT_OFF(); YELLOW_LIGHT_ON(); GREEN_LIGHT_OFF(); if (((Time_in_state)%2)== 0) { YELLOW_LIGHT_TOGGLE(); } if (++Time_in_state == YELLOW_FLASH_DURATION) { TLight_subsys_next_state(); Time_in_state = 0; } break; case Red: RED_LIGHT_ON(); YELLOW_LIGHT_OFF(); GREEN_LIGHT_OFF(); if (++Time_in_state == RED_DURATION) { TLight_subsys_next_state(); Time_in_state = 0; } break; } }
  149. 149. 149 混合式排程器 (Hybrid Schedulers)  在某些情況下,我們可以為co-operative scheduler 加上 一點preemptive的特性,不過得小心使用。  在一些應用中,系統可能同時存在long tasks (例如每1000 ms中要run 100 ms的task) 與short frequent task (例如每1 ms中要run 0.1 ms的task)。這兩種tasks的需求會彼此發生衝突,因為 在co-operative系統下你必須遵守:  混合式排程器允許(long) tasks為可先佔,不過排程器會 讓這個先佔特性保持在受控制的範圍。我們將實作的 混和式排程器不需要複雜的context-switching程式碼也 沒有複雜的inter-task通訊機制。 Duration of a Task < Tick Interval
  150. 150. 150 混合式排程器的特性  提供有限的 multi-tasking 能力  支援任意數量的co-operatively scheduled tasks (RAM如果 夠的話)  支援1個pre-emptive task  實作簡單  Scheduler同一時間要為two tasks分配記憶體空間  可以對外部事件快速反應  只要小心設計,與co-operative scheduler一樣穩定安全  警告: 在混合式排程器架構上, 單純的co-operative排程特性其實已經遭到破壞, 請謹慎使用。在不需要先佔的情況下,工程師只要使用co-operative scheduler即可。
  151. 151. 151 混合式排程器的目標  放寬所有tasks在tick intervals以內要執行完畢的條件:  1或多個co-operative tasks可能有執行時間大於tick interval的情況  與co-operative scheduler一樣,可排程任意數量的co-operative tasks, 不過, 只能夠安排1個pre-emptive task。  Pre-emptive task可以中斷掉co-operative tasks  Pre-emptive task只要一執行,就得跑到完:  這表示co-operative tasks不能中斷掉pre-emptive task。因此,pre-emptive task可 以視為擁有最高優先權者。  比起真正的pre-emptive scheduler,我們要做的會簡單許多,例如 我們沒有context switch的機制、也會簡化IPC (這都是因為我們限制 pre-emptive task只要run起來就得run到結束)。  只有short task (執行時間低於1個tick interval的一半者) 才可以作為 先佔task,否則系統整體性能會變差。
  152. 152. 152 void myOS_scheduler_update(void) { // Clear T2 Flag, this is done in hal_timer2.c. Just comment it out here // T2CON &= ~(T2CON_T2EX_IntFlag_Bit); uint8 task_id; for (task_id = 0; task_id < SCHEDULER_MAX_TASKS; task_id++) { if (gScheduler_tasks[task_id].pTask) { if (gScheduler_tasks[task_id].delay_ticks == 0) { if(gScheduler_tasks[task_id].co_op) { gScheduler_tasks[task_id].execute_me += 1; } else { gScheduler_tasks[task_id].execute_me += 1; (*gScheduler_tasks[task_id].pTask)(); gScheduler_tasks[task_id].execute_me -= 1; // If the task is "one-shot", remove it from the array if (gScheduler_tasks[task_id].period_ticks == 0) myOS_delete_task(task_id); } // If the task is set to run periodically, then schedule the task to run again if (gScheduler_tasks[task_id].period_ticks) { gScheduler_tasks[task_id].delay_ticks = gScheduler_tasks[task_id].period_ticks; } } else { // This implements the tick-time counter gScheduler_tasks[task_id].delay_ticks -= 1; } } } } 實作混合式排程器 typedef struct { void (*pTask)(void); uint16 delay_ticks; uint16 period_ticks; uint8 execute_me; uint8 co_op; } task_t;
  153. 153. 153 修改添加任務函式 /*************************************************************************************************** * @fn myOS_add_task * @brief Add a task (function) to be executed at regular intervals or after a user-defined delay ***************************************************************************************************/ uint8 myOS_add_task(void (*pFunc)(), const uint16 DELAY_TICK , const uint16 PERIOD_TICK, const uint8 CO_OP) { uint8 task_id = 0; // Find an empty space in the array (if there is one), and get the index of that position while ((gScheduler_tasks[task_id].pTask != 0) && (task_id < SCHEDULER_MAX_TASKS)) { task_id++; } if (task_id == SCHEDULER_MAX_TASKS) { // Check if end of the list is reached? // If it is, set gError_code to show "task list full" and return an error code gError_code = ERROR_SCH_TOO_MANY_TASKS; return gError_code; } // We're here since there is a space in the task array gScheduler_tasks[task_id].pTask = pFunc; gScheduler_tasks[task_id].delay_ticks = DELAY_TICK; gScheduler_tasks[task_id].period_ticks = PERIOD_TICK; gScheduler_tasks[task_id].execute_me = 0; gScheduler_tasks[task_id].co_op = CO_OP; return task_id; // return position of task (to allow later deletion) }
  154. 154. 154 任務分配器 (不用更改) /************************************************************************************************ * @fn myOS_task_dispatcher * @brief When a task (function) is due to run, myOS_task_dispatcher() will run it. * This function must be called (repeatedly) from the main loop. * @param None * @return None ************************************************************************************************/ void myOS_task_dispatcher(void) { uint8 task_id; // Dispatches (runs) the next task (if one is ready) for (task_id = 0; task_id < SCHEDULER_MAX_TASKS; task_id++) { if (gScheduler_tasks[task_id].execute_me > 0) { // It means the task is ready to run (*gScheduler_tasks[task_id].pTask)(); // Call that function gScheduler_tasks[task_id].execute_me -= 1; // Reset/Decrease execute_me flag // Periodic tasks will automatically run again ( Re-scheduled in myOS_scheduler_update() ) // If this is a 'one shot' task, remove it from the array here if (gScheduler_tasks[task_id].period_ticks == 0) myOS_delete_task(task_id); } } myOS_status_reporter(); // Call the reporter to report system status myOS_go_sleep(); // The scheduler enters idle mode at this point }
  155. 155. 155 範例:  SYSTEM_TICK 1000  (1 ms)  1個long task: 慢閃LED 每8秒schedule 1次,每次執行慢閃3秒  1個short task: 快閃! (20ms ON, 20 ms OFF) LED_flash_long(); LED_flash_long(); LED_flash_short(); LED_flash_long(); LED_flash_long(); LED_flash_short();
  156. 156. 156 範例應用程式 #include "MyAPP1.h“ uint8 volatile ms_counts = 0; void HW_Delay(uint16 time); void update_1ms_counts(uint8 timerId); void LED_flash_long(void); void LED_flash_short(void); void main() { HalDriverInit(); myOS_init(); myOS_add_task(LED_flash_long, 0, 8000, 1); myOS_add_task(LED_flash_short, 5, 20, 1); myOS_start(); while(1) { myOS_task_dispatcher(); } } void LED_flash_short(void) { HalLedSet(HAL_LED_3, HAL_LED_MODE_TOGGLE); }
  157. 157. 157 void LED_flash_long(void) { uint8 i; for (i = 0; i < 15; i++) { HalLedSet(HAL_LED_4, HAL_LED_MODE_TOGGLE); HW_Delay(200); } HalLedSet(HAL_LED_4, HAL_LED_MODE_OFF); } void HW_Delay(uint16 time) { ms_counts = time; HalTimerConfig(HAL_TIMER_0, HAL_TIMER_MODE_16BITS, HAL_INT_ENABLE, update_1ms_counts ); HalTimerInterruptEnable(HAL_TIMER_0, HAL_INT_ENABLE); HalTimerStart(HAL_TIMER_0, 1000); while(ms_counts); } void update_1ms_counts(uint8 timerId) { ms_counts--; if (ms_counts!=0) HalTimerStart(HAL_TIMER_0, 1000); }
  158. 158. 158 共用資源  若short task與long task都要存取相同資源(臨界區)? 別忘記資源共享的保護機制。 #include "MyAPP1.h" #define LOCKED 1 #define UNLOCKED 0 uint8 volatile ms_counts = 0; bool gLED4_Lock = UNLOCKED; void HW_Delay(uint16 time); void update_1ms_counts(uint8 timerId); void LED_flash_long(void); void LED_flash_short(void); void main() { HalDriverInit(); myOS_init(); gLED4_Lock = UNLOCKED; myOS_add_task(LED_flash_long, 0, 8000, 1); myOS_add_task(LED_flash_short, 5, 20, 0); myOS_start(); while(1) { myOS_task_dispatcher(); } } void LED_flash_short(void) { if (gLED4_Lock == LOCKED) return; gLED4_Lock = LOCKED; HalLedSet(HAL_LED_4, HAL_LED_MODE_TOGGLE); gLED4_Lock = UNLOCKED; } void LED_flash_long(void) { uint8 i; if (gLED4_Lock == LOCKED) return; gLED4_Lock = LOCKED; for (i = 0; i < 15; i++) { HalLedSet(HAL_LED_4, HAL_LED_MODE_TOGGLE); HW_Delay(200); } HalLedSet(HAL_LED_4, HAL_LED_MODE_OFF); gLED4_Lock = UNLOCKED; }
  159. 159. 159 總結

×