2020-10-19

2020-10-21 03:00:28

TI-RTOS 使用介紹

  1. TI-RTOS介紹
    2012 年 12 月 17 日,北京訊日前,德州儀器 (TI) 宣佈推出面向 MCU平臺、基於搶佔式多執行緒核心的完整實時作業系統 TI-,加大對嵌入式處理軟體及工具產業環境的投入。軟體設計已變得更加便捷的今天,微控制器 (MCU) 開發人員可將更多的時間和精力集中在獨特應用開發上。TI 在為實時應用提供生產品質級作業系統 (OS) 方面擁有超過 20 年的豐富經驗,現已將其專業技術應用於各種 元件(包括普及型 SYS/™ 實時核心與網路開發套件 NDKTCP/IP 協定棧),並將其整合,建立了完整的微控制器 。該最新 OS 可顯著加速軟體開發,設計人員無需編寫和維護諸如排程工具、協定棧以及低階驅動器等複雜的系統軟體程式。TI-RTOS 的市場獨特性在於,可在整個 TI 完整 MCU 產品系列中提供統一的嵌入式軟體平臺,幫助開發人員便捷地擴充套件設計,通過將原有應用移植至最新處理器來更新或新增功能。此外,該統一平臺還可為 TI 設計網路軟體產業環境的合作伙伴帶來優勢,為其提供一種無專利限制的廣泛應用型免費平臺。
    TI MCU 副總裁 Scott Roller 指出:「有了高度整合的 MCU,嵌入式硬體開發現已變得更加便捷。但是,由於整合了更多外設、記憶體以及連線選項,軟體開發也已經變得更加複雜,因而我們推出了 TI-RTOS。現在開發人員可構建支援網際網路及 USB 連線的 MCU 設計,無需擔心軟體開發過於繁重耗時。」
    TI-RTOS 的特性與優勢:
    · 便捷的軟體開發:提供完整、成熟與穩定的嵌入式操作環境,可通過中介軟體與驅動器的全面啟動增加更多產品功能。這些元件包括:
    確定性實時多工核心 (SYS/);
    TCP/IP 協定棧,包括網路應用;
    USB、EMAC、MMC/SD 主機及器件協定棧以及類驅動器;
    與 C RTS 檔案 I/O 功能全面整合的 FAT 相容型檔案系統;
    乙太網、USB、、I2C 與 SD 器件驅動器;
    雙核器件的低開銷核心間通訊機制。
    · 直接開始軟體開發,實現網路連線:提供已整合並經過測試的元件。使用者無需拼湊程式碼,也不會出現元件版本不匹配問題,可確保其應用在多執行緒環境下工作;
    · 使用新功能便捷改進現有軟體基礎:新增新任務不會中斷重要系統功能的實時響應;
    · 在雙核器件間移動函數,優化效能:在 ARM® 與 DSP 核心上使用相同的 TI-RTOS 核心;
    · 接受高穩健檔案與範例來擴充套件設計,包括適用於多工開發與整合的範例和 API,有助於評估 TI-RTOS 並獲得培訓;
    · 支援片上記憶體限制:RTOS 基於支援小型封裝的模組化架構,可便捷地移除應用不需要的軟體功能。此外,元件也可延伸,可進一步降低記憶體需求;
    · 可在熟悉的環境中無縫開發:TI-RTOS 全面整合於 TI Composer Studio™ 整合型開發環境 (IDE),提供電路板支援套件與開發套件,包括 TI MCU LaunchPads 等;
    · 通過 TI 廣泛的設計網路軟體開發商網路獲得專用軟體:Interniche 和 Simma 等合作伙伴可提供更多配合 TI-RTOS 工作的通訊協定棧;
    · 無提前支付或執行時許可證費,提供免費支援:由 TI 提供直接支援的全面 C 語言原始碼。
  2. TI-RTOS概述
    TI-RTOS是CC2640R2F裝置上低功耗藍芽專案的執行環境。TI-RTOS核心是傳統SYS/BIOS核心的客製化版本,是一個具有驅動程式,同步和排程工具的實時搶佔式多執行緒作業系統。
  3. 執行緒模組
    TI-RTOS核心管理執行緒執行的四個不同的任務級別。執行緒模組列表如下圖所示,按照優先順序降序排列。
    • 硬體中斷
    • 軟體中斷
    • 任務
    • 後臺空閒功能的空閒任務
    在這裡插入圖片描述

圖1. TI-RTOS執行執行緒
這一節將要介紹四個執行執行緒以及整個TI-RTOS中用於訊息傳遞和同步的各種結構。在大多數情況下,TI-RTOS函數在util.c(Util)中已被抽象為更高階別的函數。較低階的TI-RTOS函數在TI-RTOS Kernel API Guide中有描述,你可以在TI-RTOS核心使用者指南中檢視。本檔案還定義了TI-RTOS包含的軟體包和模組。
3.1硬體中斷(Hwi)
Hwi執行緒(也稱為中斷服務程式或ISR)是TI-RTOS應用程式中具有最高優先順序的執行緒。Hwi執行緒用於執行有嚴格時間限制的關鍵任務。它們被觸發以響應在實時環境中發生的外部非同步事件(中斷)。Hwi執行緒總是執行至完成,但是如果有其他的Hwi中斷使能,它也可以暫時地被其他中斷觸發的Hwi執行緒搶佔,這就是所謂的中斷巢狀。有關中斷巢狀,嚮導和功能的具體資訊,可以在CC26XX技術參考手冊中檢視。
一般來說中斷服務程式執行時間較短,不影響硬實時系統的要求。另外,由於Hwis總是執行至完成,所以在在其上下文中不會呼叫阻塞API。
中斷的TI-RTOS驅動程式將初始化分配的外設所需的中斷。有關詳細資訊,請參閱外設驅動。
注意:
外部資源(External Resources)提供了使用GPIO和Hwis的範例。雖然SDK包含一個外設驅動程式庫抽象了對硬體暫存器的存取,但建議使用執行緒>安全的TI-RTOS驅動程式,如外設驅動中所述。
CC2640R2F的Hwi模組還支援零延遲中斷。這些中斷不通過TI-RTOS Hwi排程程式,因此比標準中斷更靈敏,但是該功能禁止其中斷服務程式直接呼叫任何TI-RTOS核心API。ISR要保護自己的上下文防止它干擾核心的排程程式。
為了能讓低功耗藍芽協定棧滿足RF嚴格的時序要求,所有應用程式定義的Hwis都要以最低優先順序執行。TI向系統新增新的Hwis時,建議不要修改預設的Hwi優先順序。為了不破壞低功耗藍芽協定棧中TI-RTOS的嚴格時序,應用程式中定義了臨界段程式碼。執行臨界段程式碼時中斷會被關閉,在臨界段程式碼執行完畢之後才會重新啟用中斷。
3.2 軟體中斷(Swi)
軟體中斷執行緒(Swis)是在Hwi執行緒和任務執行緒之間提供的一個額外的優先順序。與Hwis由硬體中斷觸發不同,Swis是通過在程式編寫過程中呼叫某些Swi模組的API來觸發中斷。由於時間限制,Swis中斷服務程式不能作為任務來執行,其截止時間不如硬體中斷服務程式那麼嚴格。Swi也總是執行至完成,它允許Hwis將不太重要的中斷處理放到較低優先順序的執行緒來處理,從而最小化CPU在中斷服務程式中花費的時間。Swis需要足夠的空間來儲存每個Swi中斷優先順序的上下文,它的每個執行緒都使用單獨的堆疊。
與Hwis類似,Swis需要保持簡短,它不應該包含任何阻塞API的呼叫。這保證了諸如無線協定棧等高優先順序任務能根據需要執行。在Swis中建議釋出某些TI-RTOS同步物件,然後把具體處理放在任務上下文中。圖2說明了這種用例。
在這裡插入圖片描述

                圖2. 搶佔情景

Swi上下文中常常會由時鐘模組呼叫,對於Swi服務函數,不呼叫阻塞API,保證較短的執行時間是很重要的。
3.3任務
任務執行緒的優先順序高於空閒任務執行緒,低於軟體中斷。任務與軟體中斷的不同之處在於,任務可以在執行期間等待(阻塞),直到有必要的資源可用。每個任務執行緒都有一個獨立的堆疊。TI-RTOS提供了可用於任務間通訊和同步的多種機制。這些包括號誌,事件,訊息佇列和郵箱。
有關詳細資訊,可以在本文後面的任務中檢視。
3.4空閒任務
空閒任務執行緒在TI-RTOS應用程式中優先順序最低,它會執行一個空閒迴圈。在主函數返回之後,TI-RTOS應用程式會呼叫每個TI-RTOS模組的啟動程式,然後進入空閒迴圈。每個執行緒在被再次呼叫之前都必須等待所有其他執行緒執行完成。空閒迴圈在沒有更高優先順序的任務需要執行的時候會一直執行。只有沒有嚴格截止期限的函數才能在空閒迴圈中執行。
對於CC2640R2F裝置,空閒任務支援電源策略管理器設定為允許的最低功率節省模式。
4. 核心設定
TI-RTOS應用程式可以使用工程中的.cfg檔案來設定TI-RTOS核心。在IAR和CCS工程中,組態檔在應用程式專案工作區中的TOOLS資料夾下。
該設定通過選擇性地包括或使用可用於核心的RTSC模組來實現。.cfg中通過呼叫xdc.useModule()函數來設定TI-RTOS核心使用者指南中定義的各種選項來啟用一個模組。
注意:
BLE5-Stack中的專案(如simple_peripheral)通常會包含一個app_ble.cfg組態檔。
可以在.cfg檔案中設定的一些選項(但不限於這些):
• 啟動選項
• Hwi,Swi和任務優先順序的數量
• 異常和錯誤處理
• 系統刻度的持續時間(TI-RTOS核心中最基本的時間單位)。
• 定義應用程式的入口點和中斷向量
• TI-RTOS堆和堆疊(不要與其他堆管理器混淆!)
• 包括預編譯的核心和TI-RTOS驅動程式庫
• 系統設定(for System_printf())
一旦.cfg檔案發生變動時,您需要重新執行XDCTools的configuro工具。在IAR和CCS提供的範例中這一步作為預構建步驟已經為您提供。
注意:
.cfg的名稱並不重要。但是專案只能包含一個.cfg檔案。
對於CC2640R2F,TI-RTOS核心儲存在ROM中。通常為了節省Flash的存取足跡,.cfg也會包含在核心的ROM模組中,如清單1所示。
清單1. 如何把TI-RTOS核心包含到ROM中

/ * ================ ROM configuration================ * / 
/ * 
*To use BIOS in flash, comment out the code block below.
* / 
iftypeof NO_ROM == 'undefined' ||typeof NO_ROM != 'undefined' && NO_ROM == 0 ))
{ 
	var ROM = xdc.useModule('ti.sysbios.rom.ROM'; 
	if(program.cpu.deviceName .match(/CC26/)){ 
		ROM.romName = ROM.CC2640R2F; 
	} 
	else if(Program.cpu.deviceName.match(/CC13/)){ 
		ROM.romName = ROM.CC1350;
	} 
}

ROM中的TI-RTOS核心針對效能進行了優化。如果您的應用程式需要額外的工具(通常用於偵錯),則必須將TI-RTOS核心包含在Flash中,這將增加Flash消耗。下面顯示的是在ROM中使用TI-RTOS核心的簡要列表。
• BIOS.assertsEnabled必須設定為false
• BIOS.logsEnabled必須設定為false
• BIOS.taskEnabled必須設定為true
• BIOS.swiEnabled必須設定為true
• BIOS.runtimeCreatesEnabled 必須設定為true
• BIOS必須使用該ti.sysbios.gates.GateMutex模組
• Clock.tickSource必須設定為Clock.TickSource_TIMER
• Semaphore.supportsPriority一定是false
• Swi,Task和Hwi hooks不容許
• Swi,Task和Hwi name instances不容許
• 任務堆疊檢查被禁用
• Hwi.disablePriority必須設定為0x20
• Hwi.dispatcherAutoNestingSupport必須設定為true
• 預設的Heap instance必須設定為ti.sysbios.heaps.HeapMem管理者
有關上述列表外的其他檔案,可以在TI-RTOS核心使用者指南中檢視。
5. 創造與構建
大多數TI-RTOS模組通常都有_create()和_construct()APIs用來初始化最初的例程。這兩個API之間執行時的主要差異是記憶體分配和錯誤處理。
在初始化之前,建立API會從預設的TI-RTOS堆執行記憶體分配。因此,應用程式必須在繼續之前檢查有效控制程式碼的返回值。
清單2. 建立一個號誌

Semaphore_Handle sem;
Semaphore_Params semParams;
Semaphore_Params_init(&semParams);
 sem = Semaphore_create(0,&semParams,NULL;/*Memory allocated in here*/
 if (sem == NULL) /* Check if the handle is valid */
 {
 	System_abort("Semaphore could not be created");
 }

構造API提供了一個用於儲存範例變數的資料結構。由於記憶體已被預先分配給範例,構建後不再需要進行錯誤檢查。
清單3. 構造一個號誌

Semaphore_Handle  SEM ;
Semaphore_Params  semParams ; 
Semaphore_Struct structSem; /* Memory allocated at build time */
Semaphore_Params_init(&semParams);
Semaphore_construct(&structSem, 0, &semParams);
 /* It's optional to store the handle */
sem = Semaphore_handle(&structSem);
  1. 執行緒同步
    TI-RTOS核心提供了幾個諸如號誌,事件和佇列用於同步任務的模組。以下部分討論這些常見的TI-RTOS同步模組。
    6.1號誌
    號誌通常用於TI-RTOS應用中的任務同步和互斥。圖3.顯示了號誌的功能。號誌可以分為計數號誌或二進位制號誌。程式通過Semaphore_post()來釋放號誌,計數號誌會記錄跟蹤號誌釋出的次數。當一組資源在任務之間共用時,號誌很有用。在使用這些資源之前,任務會呼叫Semaphore_pend()來檢視資源是否可用,只有共用資源被釋放出來之後處於等待狀態的任務得到該資源才能執行。二進位制號誌只能有兩種狀態:可用(count = 1)和不可用(count = 0)。二進位制號誌可用於在任務之間共用一個資源,或者用於基本信令機制,可以多次釋出號誌。二進位制訊號不跟蹤計數, 他們只跟蹤號誌是否已經發布。
    在這裡插入圖片描述

                圖3. 號誌功能
    

初始化號誌
以下程式碼描述瞭如何在TI-RTOS中初始化號誌。號誌可以建立和構造,如本文上面的建立與構造中所述。
有關如何建立號誌,請參見清單2。
有關如何構造號誌,請參見清單3。
等待號誌
Semaphore_pend()是一個阻塞函數呼叫。它只能在任務中呼叫。當任務呼叫此阻塞函數後將會等待號誌的釋放(post),這時就緒的低優先順序任務可以執行。呼叫Semaphore_pend()如果計數器為0,任務將阻塞,否則計數器會遞減1,任務執行。在另一個執行緒呼叫Semaphore_post()釋放號誌或者提供的系統滴答時鐘超時之前,任務都會保持阻塞狀態。通過讀取其返回值Semaphore_pend()可以區分號誌是否釋出或超時。
清單4. 等待一個號誌

bool isSuccessful;
uint32_t timeout = 1000 * (1000/Clock_tickPeriod);
  /* Pend (approximately) up to 1 second */
 isSuccessful = Semaphore_pend(sem, timeoutInTicks);
  if (isSuccessful)
 {
		System_printf("Semaphore was posted");
}
else
 {
		System_printf("Semaphore timed out");
}

注意:
預設的TI-RTOS系統滴答時鐘週期為1毫秒。在CC26xx和CC13xx裝置中通過設定.cfg檔案中的Clock.tickPeriod = 10,將此預設值重新配
置為10微秒。
給定一個10微秒的系統滴答時鐘,timeout在清單4中約為1秒。
釋出號誌
號誌的釋出是通過呼叫Semaphore_post()完成的。在此釋出的號誌上掛起的任務將從阻塞狀態轉換到就緒狀態。如果此時正好沒有更高優先順序的執行緒準備執行,得到該號誌的任務將會執行。如果號誌上沒有掛起任務,呼叫Semaphore_post()將時號誌計數器加1。二進位制號誌的最大計數為1。
清單5. 釋出號誌
1 Semaphore_post (sem );
6.2事件
號誌提供了執行緒之間的基本同步。有些情況下,號誌本身就足夠了解什麼程序需要觸發。然而有些同步的特定原因也需要跨執行緒傳遞。為了實現這一點,可以使用TI-RTOS 事件(Event)模組。事件類似於號誌,每個事件實際上都包含一個號誌。使用事件的附加優點在於可以以執行緒安全的方式向任務通知特定事件。
初始化事件
建立和構建事件同樣遵循本文上面的建立與構建中說明的準則。如清單6所示,是一個關於如何構造Event範例的例子。
清單6. 構造事件

Event_Handle event;
Event_Params eventParams;
Event_Struct structEvent; /* Memory allocated at build time */
Event_Params_init(&eventParams);
Event_construct(&structEvent, 0, &eventParams);
/* It's optional to store the handle */
 event = Event_handle(&structEvent);

事件等待
類似於Semaphore_pend(),任務執行緒會在呼叫Event_pend()時阻塞, 直到事件通過一個Event_post()釋出或者等待超時。清單7展示了一個任務等待下面顯示的3個範例事件ID中的任意一個釋出的程式碼片段。 BIOS_WAIT_FOREVER引數設定表示不會等待超時,只要時間沒釋出會永遠等待下去。因此, Event_pend()將在返回的位掩碼值中釋出一個或多個事件。
Event_pend()返回的每個事件會以執行緒安全的方式在事件範例中自動清除。因此,對於釋出的事件只需保留本地副本。有關使用Event_pend()的詳細介紹 ,可以在TI-RTOS核心使用者指南中檢視。
清單7. 事件掛起

#define START_ADVERTISING_EVT         Event_Id_00
 #define START_CONN_UPDATE_EVT         Event_Id_01
#define CONN_PARAM_TIMEOUT_EVT        Event_Id_02

void TaskFxn(..)
{
     /* Local copy of events that have been posted */
   uint32_t events;

     while(1)
     {
        /* Wait for an event to be posted */
        events = Event_pend(event,
                            Event_Id_NONE,
                           START_ADVERTISING_EVT |
                           START_CONN_UPDATE_EVT |
                          CONN_PARAM_TIMEOUT_EVT,
                            BIOS_WAIT_FOREVER);

        if (events & START_ADVERTISING_EVT)
        {
           /* Process this event */
        }

        if (events & START_CONN_UPDATE_EVT)
       {
            /* Process this event */
       }

        if (events & CONN_PARAM_TIMEOUT_EVT)
        {
            /* Process this event */
       }
    }
 }

事件釋出
事件可以從一些TI-RTOS核心任務中釋出(Post),通過呼叫Event_post()就可以釋出事件實體和事件ID。清單8.顯示了高優先順序執行緒(如Swi)如何釋出特定事件。
清單8. 釋出事件

#define START_ADVERTISING_EVT         Event_Id_00
#define START_CONN_UPDATE_EVT         Event_Id_01
#define CONN_PARAM_TIMEOUT_EVT        Event_Id_02

void SwiFxn(UArg arg)
{
    Event_post(event, START_ADVERTISING_EVT);
 }    

6.3佇列
TI-RTOS佇列是一個基於先入先出(FIFO),執行緒安全的單向訊息傳遞模組。佇列通常用於高優先順序執行緒將訊息傳遞給較低優先順序的執行緒以進行延遲處理;因此允許低優先順序任務阻塞直到需要執行。
在圖24中,佇列被設定為從任務A到任務B的單向通訊。任務A將訊息放入到佇列中,任務B從佇列獲取訊息。
在這裡插入圖片描述

                 圖4. 佇列訊息傳遞過程

在BLE5-Stack中,TI-RTOS佇列功能在util.c中已經被抽象為介面函數,在TI-RTOS核心使用者指南的佇列模組檔案中可以檢視有關的基礎功能 。util.c中的函數結合佇列模組中的佇列與事件模組中的事件來實現執行緒之間訊息傳遞。
在CC2640R2F軟體中,ICall使用來自各自模組的佇列和事件在應用程式和協定棧任務之間傳遞訊息。高優先順序任務,Hwi或Swi將訊息釋出到佇列中傳遞給應用程式任務。然後,當沒有其他高優先順序執行緒執行時,應用程式任務將在其上下文中處理此訊息。
util模組包含一組抽象的TI-RTOS佇列功能,如下所示:
• Util_constructQueue()建立一個佇列。
• Util_enqueueMsg()將專案放入佇列。
• Util_dequeueMsg()從佇列中獲取專案。
功能範例
圖5和圖6說明了佇列如何將按鍵訊息從Hwi排入佇列(到Swi中的板卡按鍵模組),然後在任務上下文中釋出後處理。這個範例來自於BLE5-Stack中的simple_central工程。
在這裡插入圖片描述

              圖5. 訊息加入佇列的時序圖

在中斷使能的情況下,引腳中斷可能會在Hwi上下文中非同步發生 。為了使中斷儘可能短,與中斷相關的工作推遲到任務來處理。在BLE5-Stack中的simple_central範例中,引腳中斷通過板卡按鍵模組進行抽象。該模組通過Swi註冊回撥通知函數 。在這種情況下,SimpleBLECentral_keyChangeHandler是註冊的回撥函數。
圖5中的步驟1:展示了當鍵按下的時候回撥SimpleBLECentral_keyChangeHandler。該事件被放入應用程式的佇列中等待處理。
清單9. 定義

SimpleBLECentral_keyChangeHandler()
 void SimpleBLECentral_keyChangeHandler(uint8 keys)
 {
	SimpleBLECentral_enqueueMsg(SBC_KEY_CHANGE_EVT, keys, NULL);
 }

圖5中的步驟2:顯示了按鍵訊息是如何被壓入simple_central任務的佇列中的。首先通過ICall_malloc()分配記憶體,以便訊息可以放入佇列中。一旦訊息新增進佇列,Util_enqueueMsg()將釋出一個UTIL_QUEUE_EVENT_ID事件來通知應用程式進行處理。

清單10. 定義SimpleBLECentral_enqueueMsg()
static uint8_t SimpleBLECentral_enqueueMsg(uint8_t event, uint8_t state, uint8_t *pData)
{
  sbcEvt_t *pMsg = ICall_malloc(sizeof(sbcEvt_t));

   // Create dynamic pointer to message.
   if (pMsg)
   {
    pMsg->hdr.event = event;
     pMsg->hdr.state = state;
     pMsg->pData = pData;

     // Enqueue the message.
     return Util_enqueueMsg(appMsgQueue, sem, (uint8_t *)pMsg);
   }
   return FALSE;
 }
 

在這裡插入圖片描述

圖6. 訊息出隊的時序圖
圖6中的步驟3:simple_central應用程式一直在檢查是否有訊息被放置在佇列中需要進行處理,當UTIL_QUEUE_EVENT_ID事件釋出之後,它也就被解除阻塞,開始處理訊息。
清單11. 處理應用程式訊息

 // If TI-RTOS queue is not empty, process app message
while (!Queue_empty(appMsgQueue))
{
 	 sbcEvt_t *pMsg = (sbcEvt_t *)Util_dequeueMsg(appMsgQueue);
    if (pMsg)
   {
   // Process message
	   SimpleBLECentral_processAppMsg(pMsg);

	   // Free the space from the message
    ICall_free(pMsg);
  }
}

圖6中的步驟4:simple_central應用取出佇列中的訊息並對其進行處理。
清單12. 處理按鍵中斷訊息

static void SimpleBLECentral_processAppMsg(sbcEvt_t *pMsg)
{
  switch (pMsg->hdr.event)
 {
    case SBC_KEY_CHANGE_EVT:
     SimpleBLECentral_handleKeys(0, pMsg->hdr.state);
     break;
    //...
  }
}

圖6中的步驟5:simple_central應用程式處理完訊息後可以釋放在步驟2中所分配的記憶體。
7. 任務
TI-RTOS任務(或稱為執行緒)就是一段簡單的程式,通常是一段死迴圈。實際上,將處理器從一個工作切換到另一個任務有助於實現並行。每個任務總是處於以下執行狀態之一:
• 執行:任務當前正在執行
• 就緒:任務準備好等待執行
• 阻塞:任務被暫停執行
• 終止:任務終止執行
• 無效:任務處於無效列表中(還不受TI-RTOS管理)
有且只有一個任務總是在CPU中執行,當然它有可能只是空閒任務( 見圖21.)。當前執行的任務可以被某些喚醒的高優先順序任務以及其他模組(如Semaphores)的功能阻止執行。當前任務也可以自行終止執行。在任一情況下,處理器都切換到準備執行的最高優先順序任務。有關這些功能的更多資訊,請參見TI-RTOS核心使用者指南中TI.sysbios.knl軟體包中的任務模組。
每個任務都會分配相應的優先順序,多個任務可以具有相同的優先順序。任務是從最高優先順序向最低優先順序執行的; 相同優先順序的任務按照到達順序進行執行。當前執行的任務的優先順序永遠不會低於任何就緒任務的優先順序。當有更高優先順序的任務就緒時,正在執行的任務才會被搶佔並重新安排執行。
在simple_peripheral應用中,低功耗藍芽協定棧任務被給予最高優先順序(5),應用任務被給予最低優先順序(1)。
初始化任務
初始化任務時,會給它分配獨立的執行堆疊,用於儲存區域性變數以及進一步巢狀函數呼叫。在單個程式中執行的所有任務共用一組通用的全域性變數,根據C語言標準規範中的函數存取範圍進行存取。分配的執行堆疊就是任務的上下文。以下是正在構建的應用程式任務的範例。
清單13. TI-RTOS任務

#include <xdc/std.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>

 /* Task's stack */
 uint8_t sbcTaskStack[TASK_STACK_SIZE];

/* Task object (to be constructed) */
 Task_Struct task0;

 /* Task function */
void taskFunction(UArg arg0, UArg arg1)
 {
     /* Local variables. Variables here go onto task stack!! */

     /* Run one-time code when task starts */

     while (1) /* Run loop forever (unless terminated) */
     {
        /*
         * Block on a signal or for a duration. Examples:
          *  ``Sempahore_pend()``
          *  ``Event_pend()``
        *  ``Task_sleep()``
         *
          * "Process data"
          */
   }
 }

int main() {

    Task_Params taskParams;

    // Configure task
    Task_Params_init(&taskParams);
    taskParams.stack = sbcTaskStack;
    taskParams.stackSize = TASK_STACK_SIZE;
    taskParams.priority = TASK_PRIORITY;

    Task_construct(&task0, taskFunction, &taskParams, NULL);

    BIOS_start();
 }

在啟動TI-RTOS核心的排程程式BIOS_start()之前,在main()中需要完成所有任務的建立。任務在排程程式啟動後按其分配的優先順序執行。
TI建議儘量使用現有的任務應用程式進行特定應用的處理。在工程中新增額外的任務時,必須在TI-RTOS組態檔(.cfg)中定義的TI-RTOS優先順序範圍內為該任務分配優先順序。
提示:
儘量減少任務優先順序別數量,以在TI-RTOS組態檔(.cfg)中節省出額外的RAM:
Clock.tickPeriod = 6;
不要新增具有等於或高於低功耗藍芽協定棧任務優先順序的任務。有關係統任務層次結構的詳細資訊,可以在標準工程任務層次結構中檢視。
確保任務的堆疊大小至少為512位元組的預定義記憶體。必須分配足夠大的堆疊空間來保證程式的正常執行以及任務搶佔上下文的儲存。任務搶佔上下文是當一個任務由於中斷產生或者被較高優先順序任務搶佔而被儲存的上下文。使用IDE的TI-RTOS分析工具,可以分析任務以確定任務堆疊使用情況的峰值。
注意:
這裡用術語講了如何去構建任務。在TI-RTOS中實際是如何構建任務的你可以在本文前面的建立與構造中檢視。
任務功能
當任務被初始化時,任務函數的函數指標會傳遞給Task_construct函數。當任務第一次有機會執行時,這個函數就是由TI-RTOS管理執行的函數了。清單13.顯示了此任務函數的一般拓撲關係。
就典型的使用情況而言,任務大部分時間都通過呼叫稱為_pend()的API(例如Semaphore_pend())而處於阻塞狀態。通常,高優先順序執行緒(例如Hwis或Swis)使用_post()API(例如Semaphore_post())來解除某些任務的阻塞。
8.時鐘
時鐘範例是可以在一定數量的系統時鐘節拍之後執行的函數。時鐘範例可以是單次或週期性的。這些範例可以設定為在建立後立即開始或者在一段延遲後啟動,並可以隨時停止。所有的時鐘範例當它們在Swi的上下文中時鐘溢位就會被執行。以下範例顯示了TI-RTOS組態檔(.cfg)中設定的TI-RTOS時鐘週期的最小解析度。
注意:
預設的TI-RTOS核心刻度週期為1毫秒。對於CC2640R2F器件,在TI-RTOS組態檔(.cfg)中重新設定:
Clock.tickPeriod = 10 ;
每個系統時鐘節拍會啟動一個時鐘物件來為節拍計數,然後再和一系列的時鐘進行比較以確定相關函數是否該執行。對於需要更高解析度的定時器,TI建議使用16位元硬體定時器通道或感測器控制器來做。有關這些功能的更多資訊,請參見TI-RTOS核心使用者指南的 TI.sysbios.knl軟體包中的Clock模組。
您可以直接在應用程式中使用核心的Clock API,Util模組中提供了一組抽象的TI-RTOS時鐘功能,如下所示:
• Util_constructClock()建立一個Clock物件。
• Util_startClock()啟動一個現有的Clock物件。
• Util_restartClock()停止,重新啟動一個現有的Clock物件。
• Util_isActive()檢查Clock物件是否正在執行。
• Util_stopClock()停止一個現有的Clock物件。
• Util_rescheduleClock()重新設定一個現有的Clock物件。
功能範例
以下範例來自BLE5-Stack中的simple_peripheral專案。
在這裡插入圖片描述

                    圖7. 觸發時鐘物件

步驟1:在圖7中,通過Util_constructClock()來構建時鐘物件。在範例進入連線狀態後,它將通過Util_startClock()啟動Clock物件。
清單14. 在simple_peripheral中的構建periodicClockClock物件

// Clock instances for internal periodic events.
static Clock_Struct periodicClock;

// Create one-shot clocks for internal periodic events.
Util_constructClock(&periodicClock, SimpleBLEPeripheral_clockHandler,
    SBP_PERIODIC_EVT_PERIOD, 0, false, SBP_PERIODIC_EVT);

步驟2:在圖7中,Clock物件的計時器到期後,它將在Swi上下文中執行SimpleBLEPeripheral_clockHandler()。由於此呼叫不能被阻止並能阻止所有的任務,所以呼叫simple_peripheral中的Event_post(SBP_PERIODIC_EVT)來進行事件釋出以儘可能保證其處理過程足夠短暫。
清單15. 定義

SimpleBLEPeripheral_clockHandler()
static void SimpleBLEPeripheral_clockHandler(UArg arg)
{
    /* arg is passed in from Clock_construct() */
    Event_post(events, arg);
}

注意:
時鐘功能不能呼叫阻塞核心的API或TI-RTOS驅動程式的API!時鐘功能的處理時間過長將影響分配給無線協定棧的高優先順序任務中的實時約束!
步驟3:在圖7中,在Event_post(SBP_PERIODIC_EVT)釋出了事件之後simple_peripheral任務被解除阻塞,然後繼續呼叫SimpleBLEPeripheral_performPeriodicTask()函數。最後如果要重新啟動此功能的定期執行,需要重新啟動periodicClock時鐘物件。
清單16.維護SBP_PERIODIC_EVT事件

if (events & SBP_PERIODIC_EVT)
{
  // Perform periodic application task
  SimpleBLEPeripheral_performPeriodicTask();

  Util_startClock(&periodicClock);
}

9.驅動
TI-RTOS提供了一套可以新增到應用程式的CC26xx外設驅動程式。驅動程式為應用程式提供了與CC26xx板載外設介面以及外部裝置通訊的機制。這些驅動程式在DriverLib中抽象了對暫存器的存取。
有關BLE5-Stack中每個TI-RTOS驅動程式的重要檔案及有關具體位置,請參閱BLE5-Stack release notes。本節僅概述了驅動程式如何適應軟體生態系統。有關可用的功能和驅動程式API的說明,請參閱TI-RTOS API Reference。
9.1新增驅動程式
某些驅動程式會作為原始檔被新增到工程專案工作區中Drivers資料夾下的相應資料夾中,如圖8所示。
在這裡插入圖片描述

                  圖8. 驅動程式資料夾

驅動的原始檔可以在 T I R T O S D R I V E R S B A S E TI_RTOS_DRIVERS_BASE TIRTOSDRIVERSBASE\ti\drivers的相應資料夾中找到。( T I R T O S D R I V E R S B A S E TI_RTOS_DRIVERS_BASE TIRTOSDRIVERSBASE指的是安裝位置,可以在IAR Tools\Configure Custom Argument Variables選單中檢視。對於CCS,相應的路徑變數在Project Options\ Resource\Linked Resources索引標籤中定義)
例如,ECC和TRNG驅動程式是BLE5-Stack而不是TI-RTOS的一部分,它們分別位於
<SDK_INSTALL_DIR>\source\ti\ble5stack\common\cc26xx\ecc和
<SDK_INSTALL_DIR>\source\ti\ble5stack\hal\src\target_common中。
要向工程中新增驅動程式,需要將相應驅動程式的標頭檔案包含在參照驅動程式API的應用程式檔案中。
例如,要新增用於讀取或控制輸出I/O引腳的PIN驅動程式,請新增以下內容:
#include <ti/drivers/pin/PINCC26XX.h>
還需要將以下TI-RTOS驅動程式檔案新增到Drivers\PIN資料夾下的專案中:
• PINCC26XX.c
• PINCC26XX.h
• PIN.h
9.2板檔案
板檔案為將通過設定相應的引數將固定的驅動設定修改為特定的板設定,例如設定PIN驅動程式的GPIO表或者定義將哪些引腳分配3給I2C,SPI或UART。
有關板檔案的更多資訊,以及如何在TI EMs和LPs或本地硬體埠之間切換,請參見TI提供的板檔案部分部分。
9.3可用驅動程式
本節給大家介紹一些可用的驅動程式,並提供一些基本範例演示如何將驅動程式新增到simple_peripheral專案中。有關每個驅動程式的詳細資訊,可在TI-RTOS API Reference中檢視。
9.4引腳
PIN驅動程式用於控制GPIO的I/O引腳或著連線在上面的硬體外圍裝置。如TI提供的板檔案部分所述,引腳在main()中必須首先初始化為安全狀態(在板檔案中設定)。此初始化後,任何模組都可以使用PIN驅動程式設定一組引腳供使用。以下是在simple_peripheral任務中將一個引腳設定成作為中斷使用,另一個設定成作為輸出使用的範例,在中斷髮生時交換它們的角色。IOID_x引腳號對應於CC26XX技術參考手冊中參照的DIO引腳號 。下表列出了使用的引腳及其在CC2640R2F LaunchPad上的對映,這些已在板檔案中定義了。
訊號名稱 針號 CC2640R2F LaunchPad對映
CC2640R2_LAUNCHXL_PIN_RLED IOID_6 DIO6(紅色)
CC2640R2_LAUNCHXL_PIN_BTN1 IOID_13 DIO13(BTN_1)
simple_peripheral.c程式碼需要做以下修改。

  1. 包括PIN驅動程式檔案:
    #include <ti / drivers / pin / PINCC26xx.h>
  2. 宣告引腳設定表和引腳狀態並申明simple_peripheral任務使用的變數:
    清單17. 引腳設定表
static PIN_Config SBP_configTable[] =
{
    CC2640R2_LAUNCHXL_PIN_RLED | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW | PIN_PUSHPULL | PIN_DRVSTR_MAX,
    CC2640R2_LAUNCHXL_PIN_BTN1 | PIN_INPUT_EN | PIN_PULLUP | PIN_IRQ_BOTHEDGES | PIN_HYSTERESIS,
    PIN_TERMINATE
};

static PIN_State sbpPins;
static PIN_Handle hSbpPins;
static uint8_t LED_value = 0;
  1. 宣告在Hwi上下文中執行ISR:
    清單18. 宣告ISR
static void buttonHwiFxn(PIN_Handle hPin, PIN_Id pinId)
{
    SimpleBLEPeripheral_enqueueMsg(SBP_BTN_EVT, 0, NULL);
}
  1. 在SimpleBLEPeripheral_processAppMsg中,新增一個case來處理上面的事件,並定義事件:
    清單19. ISR事件處理
#define SBP_BTN_EVT

static void SimpleBLEPeripheral_processAppMsg(sbcEvt_t *pMsg)
{
    switch (pMsg->hdr.event)
    {
        case SBP_BTN_EVT:
            //toggle red LED
            if (LED_value)
            {
                PIN_setOutputValue(hSbpPins, CC2640R2_LAUNCHXL_PIN_RLED , LED_value--);
            }
            else
            {
                PIN_setOutputValue(hSbpPins, CC2640R2_LAUNCHXL_PIN_RLED, LED_value++);
            }
            break;
            //...
    }
}
  1. 開啟引腳使用,並在simple_peripheral_init()中設定中斷:
    清單20. 開啟引腳並設定中斷
// Open pin structure for use
hSbpPins = PIN_open(&sbpPins, SBP_configTable);
// Register ISR
PIN_registerIntCb(hSbpPins, buttonHwiFxn);
// Configure interrupt
PIN_setConfig(hSbpPins, PIN_BM_IRQ, CC2640R2_LAUNCHXL_PIN_BTN1 | PIN_IRQ_NEGEDGE);
// Enable wakeup
PIN_setConfig(hSbpPins, PINCC26XX_BM_WaKEUP, CC2640R2_LAUNCHXL_PIN_BTN1|PINCC26XX_WAKEUP_NEGEDGE);
  1. 編譯
  2. 下載
  3. 執行
    注意:
    按下CC2640R2F LaunchPad上的BTN-1按鈕可切換紅色LED,這裡沒有做去抖動處理。
    9.5 GPIO
    GPIO模組允許您通過簡單易用的API來管理通用I/O引腳。GPIO引腳的行為通常是靜態設定的,但也可以在執行時進行重新設定。
    由於其簡單性,GPIO驅動程式不遵循其他TI-RTOS驅動程式型別,其中驅動程式的API具有單獨的特定於裝置的實現。在GPIOxxx_Config結構中,這種差異最為明顯,它不需要您指定特定的函數表或物件,而其他驅動程式在各自的xxx_config中則要指定函數表和物件,你可以在驅動程式的組態檔中檢視,例如:CC2640R2_LAUNCHXL.c。
    以下是一個如何設定GPIO引腳,並利用註冊中斷回撥函數來控制LED燈的開啟關閉的範例。
  4. 在simple_peripheral.c中包含GPIO驅動程式檔案:
    #include <ti / drivers / GPIO.h>
    在Board.c中做一下新增:
  5. 新增一組GPIO_PinConfig元素,用於定義應用程式使用的每個引腳的初始設定。引腳型別(即INPUT/OUTPUT),初始狀態(即OUTPUT_HIGH或LOW),中斷特性(RISING/FALLING邊沿等)以及器件特定引腳標識。以下是對於CC26XX裝置GPIO_PinConfig陣列的特定設定範例:
    清單21. 設定GPIO引腳設定陣列
//
// Array of Pin configurations
// NOTE: The order of the pin configurations must coincide with what was
//       defined in CC2640R2_LAUNCH.h
// NOTE: Pins not used for interrupts should be placed at the end of the
//       array.  Callback entries can be omitted from callbacks array to
//       reduce memory usage.
//
GPIO_PinConfig gpioPinConfigs[] = {
    // Input pins
    GPIOCC26XX_DIO_13 | GPIO_CFG_IN_PU | GPIO_CFG_IN_INT_RISING,  // Button 0
    GPIOCC26XX_DIO_14 | GPIO_CFG_IN_PU | GPIO_CFG_IN_INT_RISING,  // Button 1
    // Output pins
    GPIOCC26XX_DIO_07 | GPIO_CFG_OUT_STD | GPIO_CFG_OUT_STR_HIGH | GPIO_CFG_OUT_LOW,     //  Green LED
    GPIOCC26XX_DIO_06 | GPIO_CFG_OUT_STD | GPIO_CFG_OUT_STR_HIGH | GPIO_CFG_OUT_LOW,     //  Red LED
};
  1. 新增一組GPIO_CallbackFxn元素,用於儲存設定有中斷的GPIO引腳的回撥函數指標。這些陣列元素的索引對應於GPIO_PinConfig陣列中定義的引腳。這些函數指標可以通過參照陣列元素中的回撥函數名來靜態定義,也可以通過動態地將陣列元素設定為NULL並在執行時使用GPIO_setCallback()來插入回撥條目。不用於中斷的引腳可以從回撥陣列中省略,以減少記憶體使用(如果它們位於GPIO_PinConfig陣列的末尾)。回撥函數語法應符合以下條件:
    void (* GPIO_CallbackFxn )(unsigned int index );
    index引數與傳遞給GPIO_setCallback()的索引相同。這允許通過使用索引來識別哪個GPIO發生了中斷,可以將相同的回撥函數用於多個GPIO中斷。以下是CC26XX裝置的GPIO_CallbackFxn陣列的特定範例:
清單22. 設定GPIO回撥函數陣列
 //
 // Array of callback function pointers
 // NOTE: The order of the pin configurations must coincide with what was
 //       defined in CC2640R2_LAUNCH.h
 // NOTE: Pins not used for interrupts can be omitted from callbacks array to
 //       reduce memory usage (if placed at end of gpioPinConfigs array).
 //
 GPIO_CallbackFxn gpioCallbackFunctions[] = {
     NULL,  //  Button 0
     NULL,  //  Button 1
 };
  1. 將前面提到的兩個陣列以及每個陣列的元素數量新增到GPIOCC26XX_Config結構體中用於驅動程式介面呼叫。此處還指定了所有能產生中斷的引腳的中斷優先順序。中斷優先順序的值是是跟裝置有關的,同一個引腳的中斷優先順序對於不同裝置是不同的。在將此引數設定為非預設值之前,應熟悉裝置中使用的中斷控制器。優先順序的預設值用於表示應使用最低可能的優先順序。以下是初始化GPIOCC26XX_Config結構的範例:
    清單23. 設定GPIO設定結構
 const GPIOCC26XX_Config GPIOCC26XX_config = {
      .pinConfigs = (GPIO_PinConfig *)gpioPinConfigs,
      .callbacks = (GPIO_CallbackFxn *)gpioCallbackFunctions,
      .numberOfPinConfigs = sizeof(gpioPinConfigs)/sizeof(GPIO_PinConfig),
      .numberOfCallbacks = sizeof(gpioCallbackFunctions)/sizeof(GPIO_CallbackFxn),
      .intPriority = (~0)
  };

以下是需要在simple_peripheral.c中新增的內容:
5. 按鍵回撥函數:
清單24. 設定按鍵的回撥函數

 //
 //  ======== gpioButtonFxn0 ========
 //  Callback function for the GPIO interrupt on CC2640R2_LAUNCHXL_PIN_BTN1.
 //
 void gpioButtonFxn0(unsigned int index)
 {
     // Toggle the LED
     GPIO_toggle(CC2640R2_LAUNCHXL_PIN_BTN1);
 }
6.	初始化和使用GPIO(將其新增到simple_peripheral_init()):

清單25. 初始化和使用的GPIO

// Call GPIO driver init function
GPIO_init();

// Turn on user LED
GPIO_write(CC2640R2_LAUNCHXL_PIN_RLED, Board_GPIO_LED_ON);

// install Button callback
GPIO_setCallback(CC2640R2_LAUNCHXL_PIN_BTN1, gpioButtonFxn0);

// Enable interrupts
GPIO_enableInt(CC2640R2_LAUNCHXL_PIN_BTN1);
  1. 編譯
  2. 下載
  3. 執行
    9.6其他驅動程式
    TI-RTOS附帶的其他驅動程式有:UART,SPI,加密(AES),I2C,PDM,Power,RF和UDMA。由於協定棧使用了電源,RF和UDMA,因此在使用它們的時候,必須特別小心。與其他驅動程式一樣,這些驅動程式也在檔案中被詳細記錄,BLE5-Stack中提供了範例。
    10.功耗管理
    所有功耗管理功能由外設驅動程式和低功耗藍芽協定棧進行處理。可以通過包含或移除POWER_SAVING前處理器定義的符號來啟用或禁用此功能。當POWER_SAVING被使能時,裝置根據低功耗藍芽事件,外設事件,應用計時器等的要求進入和退出休眠狀態。當POWER_SAVING未定義時,裝置保持喚醒。有關修改前處理器定義的符號的步驟,請參閱用CCS開發中的存取前處理器符號或者用IAR開發中的存取前處理器符號。
    有關電源管理功能的更多資訊,包括API和自定義UART驅動程式的範例,可以在安裝的TI-RTOS中包含的CC26xx的TI-RTOS電源管理中找到。只有使用自定義驅動程式時,才需要這些API。
    另請參閱Measuring Bluetooth Smart Power Consumption (SWRA478),以分析系統功耗和電池壽命。