圖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.
* /
if (typeof 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);
執行緒同步
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程式碼需要做以下修改。
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;
static void buttonHwiFxn(PIN_Handle hPin, PIN_Id pinId)
{
SimpleBLEPeripheral_enqueueMsg(SBP_BTN_EVT, 0, NULL);
}
#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;
//...
}
}
// 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);
//
// 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
};
清單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
};
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);