159
Chien-Jung Li Dec 2013 嵌入式系統進階

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

  • Upload
    simenli

  • View
    250

  • Download
    21

Embed Size (px)

Citation preview

Page 1: [嵌入式系統] 嵌入式系統進階

Chien-Jung Li

Dec 2013

嵌入式系統進階

Page 2: [嵌入式系統] 嵌入式系統進階

2

大綱

嵌入式系統進階觀念

關鍵字volatile、const、static與typedef

嵌入式系統平台

嵌入式作業系統

任務與排程

打造一個簡易型Embedded OS

Cooperative排程器實作

逾時控制

狀態機:以實作紅綠燈號誌系統為例

混合Cooperative與Pre-emptive任務之排程器實作

共用資源保護

Page 3: [嵌入式系統] 嵌入式系統進階

嵌入式系統進階觀念

Page 4: [嵌入式系統] 嵌入式系統進階

4

原廠評估板(Evaluation Board)

Evaluation Board (EVB)

Evaluation Module (EVM)

Evaluation Kits

Page 5: [嵌入式系統] 嵌入式系統進階

5

Target Board與Real Board

Page 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

Page 7: [嵌入式系統] 嵌入式系統進階

7

整合式開發環境(IDE) - PC

Integrated Development Environment (IDE)

Simulator + Editor + Compiler + Assembler, etc.

Page 8: [嵌入式系統] 嵌入式系統進階

8

整合式開發環境(IDE) – 嵌入式

Page 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

Page 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

Page 11: [嵌入式系統] 嵌入式系統進階

11

計算機啟動流程 Step1: CPU到特定位址抓取第一行指令 (特定Program

Counter或reset中斷ISR為boot())

Step2: User程式運行後,先對CPU初始化

Step3: 將程式從唯讀記憶體(ROM或Flash)載入RAM

Step4: 初始化應用程式會用到的硬體設備

Step5: 初始化各子系統 (如RTOS、動態記憶體管理等)

Step6: 執行應用程式主程式

Page 12: [嵌入式系統] 嵌入式系統進階

12

CPU透過中斷的啟動流程

Other functions

main()

boot()

DMA

key

address error

RESET

Page 13: [嵌入式系統] 嵌入式系統進階

13

中斷向量表 中斷向量表從某個位址開始,每4個Bytes(有些是8個Bytes)為一個單位(entry),每一個entry記錄一個函式的位址

沒有用到的中斷不但不應該發生,當他發生時還得視為一種錯誤

ISR_n

Page 14: [嵌入式系統] 嵌入式系統進階

14

CPU初始化 只發生一次 (hopefully!)

將所有變數、記憶體等東西初始化至known state

初始化變數、旗標、I/O與IRQs等

設定CPU的中斷控制

初始化中斷優先權、開中斷

清除等待中的中斷請求

中斷致能

Page 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");

...

Page 16: [嵌入式系統] 嵌入式系統進階

16

關於const關鍵字 (I) 關鍵字const有甚麼含意?

業餘者會回答:「它意味著常數」

專業者會回答:「它意味著唯讀」

下面的宣告是甚麼意思?

const int a;

int const a;

const int *a;

int* const a;

int const * a const;

Page 17: [嵌入式系統] 嵌入式系統進階

17

關於const關鍵字 (II) 你可能會問,即使不用關鍵字const,也還是可以寫出功能正確的程式,那為什麼還要如此看重關鍵字const呢?這有幾個理由:

1) const能給讀你程式碼的人傳達非常有用的資訊。實際上宣告一個變數為const是為了告訴了別人這個變數的應用目的。如果你曾花很多時間清理別人留下的垃圾,你就會很快學會感謝這點多餘的資訊。(當然,懂得用const的designer很少會留下垃圾讓別人來清理的。)

2) 通過給optimizer一些附加的資訊,使用關鍵字const也許能產生更緊湊的程式碼。

3) 合理地使用const可讓編譯器很自然地保護那些不希望被改變的變數,防止它們被無意的程式碼修改到。如此可以減少bug出現的機會。

Page 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; }

Page 19: [嵌入式系統] 嵌入式系統進階

19

關於volatile關鍵字 (II) 被修飾為volatile的變數是說:

這變數可能會被意想不到地改變,使用volatile修飾之後編譯器就不會去假設這個變數的值。精確地說,即optimizer用到這個變數時必須每次都小心地重新讀取它的值,而不是使用保存在暫存器裡的備份。

volatile變數的例子:

1) 設備的硬體暫存器(例如狀態暫存器)

2) 一個ISR會訪問到的非自動變數(non-automatic variable)

3) 多執行緒應用中被幾個tasks共用的變數

Page 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

Page 21: [嵌入式系統] 嵌入式系統進階

21

關於static關鍵字 關鍵字static的作用是什麼?這個簡單的問題很少有人能回答完全。

關鍵字static有三個明顯的作用:

1) 在函式體內被宣告為static的變數,在這一函式被調用過程中其值維持不變(也就是靜態的意思)。

2) 在模組內(但在函式體外)被宣告為static的變數可以被模組內所有函式訪問,但不能被模組外其它函式訪問。它是一個本地的全域變數。

3) 在模組內,被宣告為static的函式只可被這一模組內的其它函式呼叫。即,這個函式被限制在宣告它的模組的本地範圍內使用。

Page 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;

Page 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; }

Page 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

Page 25: [嵌入式系統] 嵌入式系統進階

25

單一中斷與多重中斷

有些MCUs只允許一個(single)中斷

有些MCUs允許巢狀中斷

為了很好地使用多重中斷,大多數處理器都支援中斷優先權排序 (8051系列允許巢狀中斷: 預設是關閉的)

高優先權事件可取代低優先權者

固定或可變的優先權

8051的中斷:

中斷優先權分為高/低兩種等級,可使用軟體控制

在同一種等級中,優先權由硬體控制,不可更改

中斷優先權

Page 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輸入取樣

Page 27: [嵌入式系統] 嵌入式系統進階

27

使用邊緣或位準觸發的時機 當中斷訊號的時間太長:邊緣觸發

e.g.: 60 Hz square wave for clock

使用Level sensitive會反應多次

當中斷訊號的時間太短:邊緣觸發

e.g.: very short pulse from a sensor

Level Sensitive會遺失事件

普通中斷線路:位準觸發

多重事件:位準觸發

Page 28: [嵌入式系統] 嵌入式系統進階

28

可以使用標準C函式庫嗎? 以printf()為例,輸出要印到哪裡去?編譯器廠商根本不知道你的硬體平台與應用是什麼?

螢幕?有螢幕嗎?寫到檔案嗎?

malloc()與free():編譯器廠商不知道你的硬體中有多少記憶體(起始位址?大小?),但通常廠商會提供一些方法讓你設定自己的硬體狀態,然後就可以使用編譯器廠商提供的malloc()與free()。

像strcpy()與strcat()這種跟硬體無關的標準C函式就可以直接使用沒有問題。

引用函式庫也必須考量嵌入式系統記憶體有限的問題。

Page 29: [嵌入式系統] 嵌入式系統進階

29

GNU C/C++及其Library GNU C/C++與library:

open source

成功移植到許多平台

技術文件齊備

使用ANSI C、POSIX等函式庫還是得小心。

例如在PC上printf()是印到螢幕,但嵌入式系統有時甚至連明顯的輸出設備都沒有。

每個平台或CPU都有其特性,要開發出在所有平台上都具有優良性能的演算法是不切實際的,而且大多函式庫的開發都是以PC作為平台,正確性會比性能重要(因為PC的CPU執行速度很快)。

Page 30: [嵌入式系統] 嵌入式系統進階

30

嵌入式系統團隊必須為護自己的SDK 建立嵌入式系統平台,提供應用程式開發人員舒適的開發環境與足夠的系統功能,SDK應包含 開發環境: tool-chain + IDE + debug tool 系統功能: 系統與硬體功能定義明確的API,並將其實作包裝為Library。若有必要可提供某些模組的source code供上層開發人員作客製化

API手冊與SDK使用手冊 模擬器環境 硬體環境: Evaluation board及其手冊與schematic 其他工具: 系統更新工具、系統性能統計工具、資源轉換工具等

包裝SDK使模組間的耦合鬆散並有較佳軟硬體分離 通過量產標準的系統與工具,可藉由SDK組織研發資產的累積

很容易可以完成prototype給客戶做評估 模組外包時,可提供廠商標準測試平台 一貫的SDK架構可使新人容易進入狀況 通過SDK可明確地將系統與應用程式區分開,更易維護

Page 31: [嵌入式系統] 嵌入式系統進階

嵌入式系統平台

Page 32: [嵌入式系統] 嵌入式系統進階

32

系統與平台(Platform) 平台其實是一種廣泛的說法

嵌入式系統軟體的層次

Boot-Loader與驅動程式

OS與API

子系統與library

應用程式

嵌入式系統平台

系統軟體與驅動程式

硬體平台

開發環境(編譯器等)

模擬器

程式編寫規範

Page 33: [嵌入式系統] 嵌入式系統進階

33

系統平台架構

Page 34: [嵌入式系統] 嵌入式系統進階

34

數據流 (Message Flow) 計算機系統 = 處理(輸入)事件並產生輸出結果

輸入來源:

(1) 監督程式(monitor)監控周邊設備狀態的改變 (此方法稱為polling或busy waiting)

(2) 由周邊設備主動產生中斷(interrupt)

當設備狀態變化時,相應的驅動程式會被執行。

(1) 若是polling,驅動程式由監督程式引發

(2) 若是中斷方式,則相應的ISR會被執行(因為中斷可能發生在任何時刻,當時CPU可能正在執行任何程式,所以ISR會有許多API不能使用,否則會有重進入的麻煩。)

Page 35: [嵌入式系統] 嵌入式系統進階

35

系統中的Message Loop 以中斷ISR來說明:

Application

Message Dispatcher

Driver ISRs Driver ISRs Driver ISRs

while(1) { os_MSG new_message; if(os_get_message(&new_message)) { os_process_message(&new_message); } else { drv_enter_idle_mode(); } }

Page 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("..."); ... }

Page 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

Page 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(); }

Page 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 } } }

Page 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; // 繼續讓其他模組處理這個訊息 } }

Page 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; }

Page 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.

Page 43: [嵌入式系統] 嵌入式系統進階

43

嵌入式作業系統,在哪裡? OS是一組管理計算機硬體與軟體資源的程序,同時也是計算機系統的核心基礎。OS身負管理配置記憶體空間(memory space)、系統資源優先序、I/O設備、網路與文件系統等基本事務,較複雜的OS有可提供一個讓使用者與系統互動的操作介面(UI)。

Page 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

Page 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)

Page 46: [嵌入式系統] 嵌入式系統進階

46

RTOS簡介 (II) Real-time又分為Hard Real-time與Soft Real-time

核電廠的控制系統,用Windows XP合適嗎?

不管應用為何,通常使用者不能忍受反應遲鈍的電子產品(更別說跟醫療、交通還有安全有關的東西),因此即時嵌入式OS有以下幾個基本要求:

任務的可中斷性(pre-emptive):

不管系統目前的狀態為何,當緊急事件發生時OS必須保證相應的程序會馬上被執行。

可預測性:

OS中所有函式與服務的執行時間必須是確定的。

穩定性與可靠度

Page 47: [嵌入式系統] 嵌入式系統進階

47

Soft- and Hard- Real-time System

Soft R-T Systems

錯過即時處理只會導致

系統性能下降

ATM提款機

交易處理

導航

存貨控制

Hard R-T Systems 錯過即時處理將會導致

系統崩潰或引發嚴重後果 或災難

生命維持器

引擎控制器

飛行控制器

機器人控制

核子反應爐控制

Page 48: [嵌入式系統] 嵌入式系統進階

48

RTOS的特性 可移植性高 (Portable)

系統必須可在ROM裡直接執行(ROMable)

可調整性(Scalable)與可重組性(Configurable)

多任務(Multi-tasking)與任務管理

可調整的任務排程 (Scheduling Algorithm)

Task(或稱thread)同步機制:如semaphore、mutex

Task間通訊機制 (IPC):如mail box、message queue

中斷管理

記憶體管理

資源管理

Page 49: [嵌入式系統] 嵌入式系統進階

49

事件驅動系統 事件發生時,系統的軟硬體一起合作對事件做出反應

對程序而言是非同步的 (不知道事件何時會發生)

系統必須對事件做出回應

多種事件可能同時發生

RTOS試圖配置CPU Time

當事件發生時:

其它處理被暫時擱置 處理該事件 結束後,之後繼續處理剛剛被擱置的東西

通常,這也稱作是“Real-Time”的系統

要如何知道有沒有事件發生? 輪詢 (其實我們已經接觸過用過輪詢方法來檢測事件了) 中斷

Page 50: [嵌入式系統] 嵌入式系統進階

50

輪詢事件 輪詢是檢測有無事件發生最簡單但最沒有效率的方式。

處理器平常甚麼事也不做,只是在一個迴圈裡面持續等待事件

輪詢經常用在簡單的I/O handlers:

等待(Wait) : Input buffer滿了嗎?

還沒: 繼續等

滿了: 讀取input data buffer然後繼續後續處理

Page 51: [嵌入式系統] 嵌入式系統進階

51

中斷 想像有一個按鈕,按下它會馬上強制CPU去呼叫一段特殊的子程式(subroutine),即中斷服務常式(ISR)。

ISR隨時可以被呼叫

硬體中斷(硬體呼叫程式):

允許對外部事件做出反應,需知道呼叫程式的位址

不與程式執行同步,隨時都可能發生,在程式的不定位置發生

可以最小化CPU所浪費的時間,最即時

不需要輪詢事件是否發生

軟體中斷(程式呼叫程式):

呼叫時不需要知道程式的位址

跟程式執行同步,在程式的特定位置發生

範例:系統呼叫。

Page 52: [嵌入式系統] 嵌入式系統進階

52

利用按鈕中斷來點燈的例子 事件:按下開關

中斷產生:選擇開燈ISR

中斷被處理的過程

CPU擱置main

CPU呼叫ISR

ISR儲存程式返回指標

除理完畢ISR返回main

Page 53: [嵌入式系統] 嵌入式系統進階

53

軟體設計 – Polling與IRQ

Page 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(); }

Page 55: [嵌入式系統] 嵌入式系統進階

55

臨界區段(Critical Code Segment) 一段不可被中斷的程序碼區(code sequence),否則可能造成錯誤 :

如果中斷在這些區段中發生,將會造成錯誤

最簡單的避免方式是,進入臨界區段之前關閉中斷,要出區段之前再打開中斷。

範例:兩個行程同時使用UART,但程式中有一個signal是以 UART中斷flag來做執行依據。中斷可能造成錯誤 (雖然實 際上可能機率很小,但還是不能忽略該問題)

當資源共享時會發生

需要互斥存取(Mutually Exclusive Access)

需要使用Semaphore (超級版的Mutex)

Page 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以進入睡眠。

執行時期系統就是這樣一直循環。

Page 57: [嵌入式系統] 嵌入式系統進階

57

系統中Task與ISR的關係 (I)

Page 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! }

Page 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中 }

Page 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); }

Page 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出亂子,則整個系統都會被影響

Page 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() { ... }

Page 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

Page 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(); } }

Page 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的時間都必須 是可預測的

Page 66: [嵌入式系統] 嵌入式系統進階

66

全域變數與臨界區域 Linux或Windows的應用程式與系統層分屬user mode與

kernel mode,每個應用程式有自己的地址空間,不會互相干擾

RTOS系統中的所有執行單位(系統、應用程式、ISR)共享地址空間,一不小心就可能互相影響,多個task與不定時發生的硬體事件觸發ISR都會使程式執行順序無法正確預測,更使共享的全域變數無安全性可言(必須提供保護機制)

可分成兩種狀況討論

Task與ISR之間

Task與task之間的critical section保護

Page 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完成後,離開臨界區前解鎖。

Page 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中操作全域變數 ... }

Page 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); ... }

Page 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、架構非常簡單、或是對效能要求很高時

Page 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);

Page 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 );

Page 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)」

Page 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; }

Page 75: [嵌入式系統] 嵌入式系統進階

75

共享記憶體 (Shared Memory) 前述的IPC機制總是可以用共享記憶體完成

缺點:

非執行緒安全(not thread-safe),除非有硬體鎖

無法控制以避免誤解

優點:

速度快

沒有RTOS overhead

Ring Buffer can be thread safe with one producer and one consumer.

Page 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

Page 77: [嵌入式系統] 嵌入式系統進階

77

排程器 (Schedulers) 兩種常用的排程演算機制:

Priority

Round-robin

勿與先佔式(preemption, 也稱time-slicing)或非先佔式(non-preemption, 或稱cooperative)的 multi-tasking搞混。

Scheduler屬於Kernel的一部分。

排程器可混合上述方法來實作

Page 78: [嵌入式系統] 嵌入式系統進階

78

Task Priorities與Blocking Task優先權必須審慎決定

若你創造了一個永遠不會被block掉的高優先權task,那麼低優先權的tasks將無法獲得執行

例如:

Task A的優先序為50,它用來定期輪詢周邊狀態暫存器(peripheral status register)。Task B的優先序為51(較低),用於串列命列介面(serial command interface)。此情況下,Task B永遠不會被執行。

Page 79: [嵌入式系統] 嵌入式系統進階

79

Round-Robin Scheduling 原則上,tasks會以預先定義的順序執行

例如:

你有3個tasks (T1, T2, T3)

執行順序為T1, T2, 然後 T3

在任何時候,當T1釋放CPU控制權後, 若T2已經就緒,那就允許執行T2

T2執行完後,若T3此時已就緒,就執行T3

當T3執行完後,T1則會獲得執行權,如此反覆

Page 80: [嵌入式系統] 嵌入式系統進階

80

Priority Scheduling 每個task都有其優先序

高優先序的tasks會比低優先序者先執行

例如:

Task T1的優先序最高,而T3最低

T2正在執行

T1跟T3同時間都已經就緒

T1會先被允許執行

Page 81: [嵌入式系統] 嵌入式系統進階

81

Non-Preemptive Kernel 每個task都必須do something以釋出CPU控制權

也稱為cooperative-multitasking,比先佔式的好實作

ISR可以中斷掉tasks

臨界區比較容易處理

Page 82: [嵌入式系統] 嵌入式系統進階

82

Preemptive Kernel 常被Real-Time applications所使用

最高優先序者總是可以執行

任務切換(A task switch)發生於

A task makes a higher priority task ready to run.

An ISR completes

實作比較複雜

臨界區的問題比較顯著

Page 83: [嵌入式系統] 嵌入式系統進階

83

死結 (Deadlock) Tasks與resources間有迴圈關係(circular dependency)

Task1取得MutexA,等待MutexB

Task2取得MutexB,等待MutexA

唯一解法:重開機

Page 84: [嵌入式系統] 嵌入式系統進階

84

優先序繼承 (Priority Inheritance)

Page 85: [嵌入式系統] 嵌入式系統進階

85

可預測性 (Determinism) 系統行為必須保證可被預測(預期)

優先序反轉會造成系統變成unpredictable

低優先序的task變得好像高優先序的task

無法保證即時性(最高優先序task的工作時間要等待低優先完成)

Page 86: [嵌入式系統] 嵌入式系統進階

86

優先序反轉 (Priority Inversion)

Page 87: [嵌入式系統] 嵌入式系統進階

打造一個簡易型Embedded OS

Page 88: [嵌入式系統] 嵌入式系統進階

88

Super Loop架構 (I) void main(void) { // Prepare run function X X_Init(); while(1) // 'for ever' (Super Loop) { X(); // Run function X } }

Page 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 } }

Page 90: [嵌入式系統] 嵌入式系統進階

90

避免浪費CPU的執行週期 要完成週期性的函式執行,並且避免CPU time被浪費,我們可以請「中斷」來幫忙!

我們都知道,中斷是「通知CPU有事情(event)發生」的硬體機制,計時器溢位就是其中一種。

計時器是硬體計時,延遲迴圈是軟體計時

可以利用硬體計時器來定期又精準地產生「中斷」,例如每1 ms發生一次溢位中斷;我們經常將這個精準的時間週期作為「系統的時間參考」,又稱為tick (很像時鐘每1秒鐘滴答1次)。

假使我們要打造一個作業系統,第一件事就是製造出「tick」,這樣子OS要處理的工作才有共同的時間概念(就好像掛在牆壁上的時鐘)。

Page 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溢位中斷發生時被調用。

Page 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... }

Page 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(); } }

Page 94: [嵌入式系統] 嵌入式系統進階

94

合作式排程器

Co-operative Scheduler: 合作式排程器

Single task架構

Tasks會定期(或one-shot)取得執行權

當CPU空閒,下一個等待中的task會被執行

Task執行完後會把CPU控制權還給scheduler

排程器一次只能安排記憶體給一個task

對外部事件反應迅速

合作式scheduler簡單、可預測、穩固與安全

Page 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,可預測性稍差

Page 96: [嵌入式系統] 嵌入式系統進階

96

簡易型混合式排程器

Hybrid Scheduler: 混合式排程器

提供非常有限的Multi-tasking能力

支援任意數量的co-operatively-scheduled tasks

支援1個pre-emptive task (它可以中斷掉co-operative tasks)

Scheduler要同時為兩個tasks準備記憶體

可用C語言實現

對外部事件的反應迅速

只要小心設計,穩固性等同於co-operative scheduler

Page 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.12.009 mA

100avgI

5 V 2.009 mA

2 99.9 11 0.1 =10.045 mW 2.009 mA

100

avgP

Page 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,也要為鮑率產生器預留計時器。

Page 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架構,因為它具有很好的可預測性。 例如:護士會定時照料病人。

Page 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)。

Page 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; // . . .

Page 102: [嵌入式系統] 嵌入式系統進階

102

Scheduler之實作 系統時基 (System tick)

Scheduler的重要元件

Scheduler data structure

初始函式

使用單一個ISR在固定週期更新scheduler

添加tasks到scheduler的函式

任務分派函式(dispatcher function),定時被呼叫以執行task

從scheduler刪除tasks的函式

Page 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

Page 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; }

Page 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; }

Page 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; }

Page 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(); }

Page 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); }

Page 109: [嵌入式系統] 嵌入式系統進階

109

系統操作邏輯

task_t gScheduler_tasks[SCHEDULER_MAX_TASKS];

Page 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];

Page 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;

Page 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);

Page 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

Page 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) }

Page 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 }

Page 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; } } } }

Page 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 }

Page 118: [嵌入式系統] 嵌入式系統進階

118

重要的設計issues (I)

Worst-case task execution time

設計者最好要確定task的execution time不會超過一個tick interval。

Page 119: [嵌入式系統] 嵌入式系統進階

119

重要的設計issues (II):Task重疊 假設應用程式有兩個tasks:

Task A (1秒執行1次)、Task B(每3秒執行1次)

若換個設定方式:

Page 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。

.

Page 121: [嵌入式系統] 嵌入式系統進階

121

重要的設計issues (IV):中斷原則 ‘One Interrupt per Microcontroller’ rule

OS初始化函式啟用計時器後,為了確保OS正確運行, 除了這個timer的中斷之外,其他所有interrupts都要關掉。

如果你沒這麼做,你就是試著將myOS當成是一種event-triggered的系統來用。如果你真的允許了其他中斷,你的系統就沒辦法保證完全正常,你可能會得到超乎預期的系統行為。

Page 122: [嵌入式系統] 嵌入式系統進階

122

合作式思考 Think “co-operatively”

Timing與task duration的限制

如果task不能在Tick Interval內完成,你要打斷他嗎?

當等待某個硬體操作時(例如等待ADC轉換完畢),你要如何確保系統不會懸宕住(hang)?

Duration of a Task < Tick Interval

while ((ADCON & ADCI) == 0);

Page 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);

Page 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)來運作。

Page 125: [嵌入式系統] 嵌入式系統進階

125

Multi-Stage Task 應用程式使用排程器的time-triggered架構

你如何確定一個long task不會影響到排程器的正常運作?合作式scheduler有一個重點,你要保證下一個tick來之前,task要工作完畢:

當然,我們可以使用「timeout逾時」的方式來保證

另一種方法:使用multi-stage tasks

系統設計範例:

溫度監控器,每隔5秒鐘回報鍋爐溫度給PC

Page 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

Page 127: [嵌入式系統] 嵌入式系統進階

127

Multi-Stage Task特點 硬體資源利用

使用multi-stage tasks會使CPU更有效率

穩定度與安全性

可使系統更responsive,因為tick interval可以維持較短

移植性

適用大多數的嵌入式系統

Page 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。

Page 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()

Page 130: [嵌入式系統] 嵌入式系統進階

130

系統範例:洗衣機控制 (I) 系統操作流程

1. 使用者在操作面盤上選擇一個洗程 (wash program, 如羊毛, 棉質)

2. 按下開始(Start)按鈕

3. 洗衣機的門自動鎖住

4. 洗衣機打開進水閥,開始注水進滾筒(wash drum)

5. 洗程若包含洗衣精, 洗劑槽會打開。洗衣精流出後,洗劑槽關閉

6. 當檢測到滿水位(full water level)時,關閉進水閥

7. 如果洗程包含溫水洗淨,清水加熱器會啟用。當清水加熱到正確的溫度後,關閉加熱器

8. 洗衣機馬達開始運轉。馬達會完成一系列的順逆轉(甚至配合不同速度)來洗淨衣服(到底怎麼轉由洗程決定)。在wash cycle結束時,馬達停止運作

9. 抽水機開始排水,當水排完後,關掉幫浦

Page 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()

Page 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; }

Page 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; }

Page 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; } } }

Page 135: [嵌入式系統] 嵌入式系統進階

135

Multi-State Task的特性 硬體資源

此架構能夠很有效率地使用系統資源

可靠度與安全性

具有良好的可靠度與安全性

移植性

有很好的移植性

Page 136: [嵌入式系統] 嵌入式系統進階

136

狀態機 (State-Machine)

當你有很多事情要處理,又想維持系統具有較好的組織性時,運用狀態機(state machine)會是個不錯的方式

狀態機是一種標準的設計pattern,常見於嵌入式系統

簡單地說,當你調用一個狀態機,它能根據目前系統的狀態自動地去做它應該做的事

幾乎所有狀態機都可以使用流程圖(flow chart)規劃出來

Page 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

Page 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;

Page 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

Page 140: [嵌入式系統] 嵌入式系統進階

140

表格驅動(Table Driven)狀態機 (I)

所有的狀態與事件的對應關係在表格中一覽無遺。每一格都顯示了「當系統在Y狀態,發生X事件時,系統必須轉移到的「next state」。

在實作上,狀態機也就能直接用表格描述之,你需要去讀取狀態表就能知道下一個狀態要往哪裡去。若你需要很多個複雜的狀態機,表格式的作法會是很好的選擇。

Page 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 }

Page 142: [嵌入式系統] 嵌入式系統進階

142

範例:紅綠燈

使用multi-state task機制來建造一個紅綠燈的交通號誌系統。

Page 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);

Page 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; }

Page 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; } }

Page 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(); } }

Page 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; } }

Page 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; } }

Page 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

Page 150: [嵌入式系統] 嵌入式系統進階

150

混合式排程器的特性 提供有限的 multi-tasking 能力

支援任意數量的co-operatively scheduled tasks (RAM如果夠的話)

支援1個pre-emptive task

實作簡單

Scheduler同一時間要為two tasks分配記憶體空間

可以對外部事件快速反應

只要小心設計,與co-operative scheduler一樣穩定安全

警告: 在混合式排程器架構上, 單純的co-operative排程特性其實已經遭到破壞,請謹慎使用。在不需要先佔的情況下,工程師只要使用co-operative scheduler即可。

Page 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,否則系統整體性能會變差。

Page 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;

Page 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) }

Page 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 }

Page 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();

Page 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); }

Page 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); }

Page 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; }

Page 159: [嵌入式系統] 嵌入式系統進階

159

總結