天天看點

CC2640R2F BLE5.0 應用程式架構應用程式

CC2640R2F BLE5.0 應用程式架構應用程式
CC2640R2F BLE5.0 應用程式架構應用程式
CC2640R2F BLE5.0 應用程式架構應用程式

應用程式

從這個章節開始,我們将詳細講解CC2640R2F BLE5.0的應用程式架構,在之前我們希望已經按照我們學習線路圖儲備了CC2640R2F平台的軟硬體架構知識。明白應用工程區分App和Stack工程管理。這裡我們主要是講解基于TI-RTOS的App應用程式架構。

CC2640R2F BLE5.0 應用程式架構應用程式

這裡介紹以 simple_peripheral Demo應用程式部分,包括以下内容:

  • Pre-main initialization
  • ICall
  • Simple Peripheral Task
  • Intertask Messages
注意: GAPRoleTask也是工程的一部分,但是我們将它放在協定棧部分進行讨論,其功能與協定棧密切相關。

Pre-main initialization

main

函數包含在IDE Startup 檔案夾的資源檔案main.c中。作為程式的入口,其主要完成 全局中斷禁止、外設驅動初始化、電源管理,TI-RTOS任務建立或構造,啟用SYS / BIOS核心排程時完成全局中斷使能。main函數不傳回,将在整個項目生命周期内保留其資源;

基本main.c功能。

int main()
{
  /* Register Application callback to trap asserts raised in the Stack */
  RegisterAssertCback(AssertHandler);

  PIN_init(BoardGpioInitTable);

  #ifndef POWER_SAVING
    /* Set constraints for Standby, powerdown and idle mode */
    Power_setConstraint(PowerCC26XX_SB_DISALLOW);
    Power_setConstraint(PowerCC26XX_IDLE_PD_DISALLOW);
  #endif // POWER_SAVING

  /* Initialize ICall module */
  ICall_init();

  /* Start tasks of external images - Priority 5 */
  ICall_createRemoteTasks();

  /* Kick off profile - Priority 3 */
  GAPRole_createTask();

  SimpleBLEPeripheral_createTask();

  /* enable interrupts and start SYS/BIOS */
  BIOS_start();

  return 0;
}      
注意:以上代碼ICALL消息子產品必須由ICALL_init()完成初始化,并且通過ICall_createRemoteTasks()完成協定棧任務建立 。

ICALL

介紹

在軟體架構章節我們講解了由于曆史相容原因将整個應用工程區分App和Stack兩個工程管理,正是基于這樣的兩個工程設計,App和Stack之間的通信就需要重新考慮了,因為我們沒有辦法像正常API調用和全局變量方式完成消息傳遞。TI引入了ICALL消息機制完成App和Stack獨立工程管理的彼此通信。以下我們着重了解原理和代碼實作,因其占了我們程式很大部分。

Indirect Call Framework (ICall 消息架構)基于TI-RTOS提供服務(例如,同步線程、消息、事件)完成BLE協定棧和應用程式在兩個工程的消息互動,保證了應用程式和協定棧在統一的TI-RTOS環境中完成高效運作、互相通信和資源共享。

ICall架構和的核心元件是其消息排程(dispatcher),其幫助程式在兩個鏡像/工程中完成BLE5-Stack協定棧以及庫配置中的應用程式互動。盡管大多數ICall互動在BLE5-Stack API(例如GAP,HCI等)中已經被抽象化(已經被封裝為消息原語函數),但是我們必須了解其在BLE-Stack和多線程RTOS環境中正常運作的基礎架構。

ICALL源碼在應用工程(例如我們這裡的simple_peripheral)的 ICALL BLE/ICALL 檔案夾路徑。

CC2640R2F BLE5.0 應用程式架構應用程式

ICall BLE5協定棧服務端

如上圖所示,ICALL實作包含一個用戶端實體(例如,我們的應用程式)和一個伺服器實體(即這裡的BLE5.0協定棧)之間的通信。

注意 :正确區分ICALL架構的CS架構和GATT伺服器和用戶端的結構,後者主要由BLE協定棧所定義實作。

TI之是以這樣設計,前面我們已經講到:

  • 實作應用程式和BLE協定棧的獨立管理和固件更新;
  • 從傳統平台(即CC254x的OSAL)移植到CC2640R2F的TI-RTOS,保持API一緻性;

ICall 作為 BLE5協定棧API的服務接口。當我們需要調用一些協定棧接口的時候,ICall子產品會自動将指令分發(即排程)到BLE5協定棧,并将消息從BLE5協定棧結果回傳到應用程式。

因為ICall消息子產品本身就作為是應用程式工程的一部分,應用任務可以通過函數調用方式直接通路ICall。由于BLE協定棧任務總是以最高優先級執行,但是應用程式在沒有資料傳回時缺處于阻塞狀态,必然有些API在會在協定棧任務立即響應,應用任務隻有在消息通過ICALL分發時才能喚醒處理。另外一些API卻隻有等待應用任務通過ICAlL(事件更新)異步傳回結果。

ICALL原語服務

ICALl包含一系列的原語服務都被抽象成基于RTOS相關的函數接口。由于共享資源和線程之間的通信,應用程式必須使用以下ICALL原語服務功能:

  • 消息傳遞和線程同步
  • 堆配置設定與管理

消息傳遞和線程同步

ICall同協定棧的消息傳遞和線程同步都是是基于TI-RTOS多線程。

ICall兩個任務的消息傳遞發生在通過消息隊列發送一個阻塞消息。發送方動态分配置設定一段記憶體,将消息的内容寫入記憶體,然後将這段記憶體發送(即排隊)到接收線程,然後再使用事件标志。接受任務在收到事件标志後喚醒,複制記憶體消息,并且處理,之後再并将這段記憶體塊釋放。

協定棧使用ICall通知和發送消息到應用程式。ICall傳遞這些服務消息,應用程式任務接收它然後處理。

堆配置設定與管理

ICall為應用提供了全局堆管理的API用以動态記憶體配置設定。其堆的大小通過宏

HEAPMGR_SIZE

 配置。有關管理動态記憶體的更多詳細資訊,請參閱動态記憶體配置設定。BLE 協定棧的ICall使用此堆管理進行所有消息相關的記憶體管理,同樣我們建議應用程式也使用這些ICall API來配置設定動态記憶體。

ICALL初始化和注冊

要執行個體化和初始化ICall服務,應用程式必須在啟動TI-RTOS核心排程程式之前調用main()中的代碼片段中的函數:

使用ICall的必需代碼。

/* 初始化ICall子產品 */ 
ICall _init ();

/* 啟動協定棧任務 - 優先級 */ 
ICall _createRemoteTasks ();      

調用ICall_init()初始化ICALL原語服務(例如,堆管理)和架構,調用 ICall _createRemoteTasks()建立但不啟動BLE5-Stack任務。在使用ICall協定服務之前,伺服器和用戶端分别完成登記和注冊。服務端在編譯的時候就需要登記一個服務。登記函數使用一個全局的唯一辨別符區分每個服務,也是作為後面通信位址。例如,BLE協定使用 

ICALL_SERVICE_CLASS_BLE

做些藍牙協定棧ICALL的消息互動的辨別。

對于服務端的登記,包含在

osal_icall_ble.c

檔案

/ *ICALL服務端登記* / 
ICall _enrollService (ICALL _SERVICE_CLASS_BLE ,NULL ,&entity ,&syncHandle );         

用戶端在ICALL排程程式發送和/或接收消息之前需要注冊。

對于使用BLE5API的用戶端(例如應用程式任務),用戶端必須首先向ICall注冊其任務 。該注冊通常發生在應用程式的任務初始化功能中。下面的代碼片段是 

simple_peripheral_init

 simple_peripheral.c中注冊。

//ICALL用戶端注冊
ICall _registerApp (&selfEntity ,&syncEvent );       

完成用戶端注冊前,需要傳入結構體變量

selfEntity

syncEvent

,其值在函數傳回時被初始化,以後服務端通過這兩個變量來進行消息傳遞。

syncEvent

參數表示事件辨別,

selfEntity

表示處理消息的目的任務,也是用戶端實體以後通信的源位址,每個注冊ICALL的用戶端都需要使用唯一的

syncEvent

selfEntity

注意: 在用戶端注冊ICALL服務之前,在

ICallBLEApi.c

檔案裡面定義的ICALL相關API都不能調用的。

ICall線程同步

ICALL使用TI-RTOS的事件用以線程同步而不是信号量。

為了讓用戶端或服務端在收到消息前都保持阻塞狀态, ICall提供以下阻塞型API保持任務阻塞狀态直到關聯的信号量被Post:

UInt Event_pend(Event_Handle handle, UInt andMask, UInt orMask, UInt32 timeout);      

handle

表示是構造的Event_Handle的句柄(辨別符)。

andMask

并 

orMask

 為使用者選擇要阻塞/挂起的事件标志。

timeout

 是以毫秒為機關的逾時周期。如果在此逾時時間範圍内後尚未傳回,該函數将傳回。

Event_pend

 函數表示阻塞目前任務等待某一事件标志位發生。

一共有32個事件标志位(int類型),這些辨別位可以根據其特定用途進行定義。不過需要注意的是,ICall消息隊列已經保留了特定事件标志位。

與TI-RTOS線程關聯的事件由Event_post所需标志調用時發出/釋出

Event_post

用以用戶端/服務端将某個任務阻塞任務激活成運作狀态,并且通過發送的對應的标志位執行指定動作。

void Event_post(Event_Handle handle,UInt eventMask);      

以上的事件句柄

handle

在服務端ICall _enrollService()和ICall _registerApp()調用後獲得。

危險: 不要從協定棧回調中調用ICall函數。此操作可能導緻ICall中止(使用ICall_abort())并中斷系統。

示例ICall用法

下圖顯示了一個通過 ICall架構從應用程式發送到BLE5-Stack的示例指令,并将相應的傳回值傳回給應用程式。

ICall _init()完成初始化 ICall子產品執行個體,而 ICall_createRemoteTasks()使用已知位址的入口函數為協定棧建立任務。

初始化後的iCall,App作為用戶端通過ICall_registerApp()完成注冊。

在SYS / BIOS排程程式啟動并且應用程式任務運作之後,應用程式發送一個ble_dispatch_JT.c 如GAP_GetParamValue()中定義的協定指令。

協定指令不在應用程式的線程中執行,而是封裝在ICall消息中,并通過ICall架構發送到BLE5-Stack任務。該指令被發送到ICALL排程程式,它在BLE5-Stack上下文中排程和執行。同時應用程式線程阻塞,直到接收到相應的指令狀态消息。BLE5-Stack完成執行指令,然後通過ICall将指令狀态消息響應發送回應用程式線程。這種交換的示例圖如下所示。

CC2640R2F BLE5.0 應用程式架構應用程式

BLE-Stack 工程是如何作為App中TI-RTOS一個任務運作的

  • 直接走讀App代碼,以下代碼片段建立了協定棧任務。
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c  ICall_createRemoteTasks  Line 519

void ICall_createRemoteTasks(void {
  size_t i;
  UInt keytask;
  /* Cheap locking mechanism to lock tasks
   * which may attempt to access the service call dispatcher
   * till all services are registered.
   */
  keytask = Task_disable();

  for (i = 0; i < ICALL_REMOTE_THREAD_COUNT; i++)  {
    Task_Params params;
    Task_Handle task;
    ICall_CSState key;

    Task_Params_init(&params);
    params.priority = ICall_threadPriorities[i];
    params.stackSize = ICall_threadStackSizes[i];
    params.arg0 = (UArg) icall_threadEntries[i];
    params.arg1 = (UArg) ICall_getInitParams(i);      
  • 并且該任務的任務函數實體ICall_taskEntry是通過arg0 參數強制轉換得到。
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c ICall_taskEntry  Line 482
static Void ICall_taskEntry(UArg arg0, UArg arg1) {
  ICall_RemoteTaskEntry entryfn = (ICall_RemoteTaskEntry) arg0;

  entryfn(&ICall_taskEntryFuncs, (void *) arg1);
}      
  • 任務實體

    對于arg0的參數位址,我們就要區分工程編譯選項了。這點我們已經在軟體架構章節詳細講解過。對于

    FlashROM_StackLirary

    這裡直接連結的編譯在靜态庫中的

    startup_entry

    函數。
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c  ICall_createRemoteTasks Line 540
params.arg0 = (UArg) icall_threadEntries[i];

//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c Line228
static const ICall_RemoteTaskEntry icall_threadEntries[] = ICALL_ADDR_MAPS;

//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\inc\icall_addrs.h Line99
#ifdef STACK_LIBRARY
extern void startup_entry( const ICall_RemoteTaskArg *arg0, void *arg1 );
//extern ICall_RemoteTaskEntry startup_entry;
#define ICALL_ADDR_MAPS \
{ \
  (ICall_RemoteTaskEntry) (startup_entry) \
}
#else  /* ! STACK_LIBRARY */
#define ICALL_ADDR_MAPS \
{ \
  (ICall_RemoteTaskEntry) (ICALL_STACK0_ADDR) \
}
#endif /* STACK_LIBRARY */      

而對于

FlashROM

編譯選項這裡的

arg0

為一個絕對位址,該絕對位址通過邊界工具

frontier.exe

計算協定棧工程的編譯生成的存儲映射檔案*.map所得。并将計算結果儲存在應用工程的

iar_boundary.xcl

檔案。

//Project->Options(Alt+F7)->Build Actions
"$TOOLS_BLE$/frontier/frontier.exe" iar "$PROJ_DIR$/$CONFIG_NAME$/List/simple_peripheral_cc2640r2lp_stack.map" "$PROJ_DIR$/../config/iar_boundary.bdef" "$PROJ_DIR$/../config/iar_boundary.xcl"
           
/*
** Stack Frontier Generator 1.1.0 (2017-06-28 14:28:12.107000)
**
** WARNING - Auto-generated file. Modifications could be lost!
*/
--config_def ICALL_RAM0_START=0x20003fe8
--config_def ICALL_STACK0_START=0x00016a00
--config_def ICALL_STACK0_ADDR=0x00016a01
      

Okay,大概說下結論,對于協定棧工程是作為App工程的一個任務啟動的,該任務建立區分協定棧是編譯成獨立鏡像,還是靜态連結庫方式,對于前置通過工具自動計算首位址作為協定棧的任務的入口位址,對于後者直接連結協定棧的入口位址作為協定棧任務的任務實體。

嘗試走讀一個ICALL的消息流程

  • 服務端登記

    在協定棧任務中完成ICall服務端的登記,并且通過

    ICALL_SERVICE_CLASS_BLE_MSG

    作為服務端的源位址。
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\ble5stack\simple_peripheral\src\stack\osal_icall_ble.c  stack_main  Line 229
if (ICall_enrollService(ICALL_SERVICE_CLASS_BLE_MSG,
                      (ICall_ServiceFunc) osal_service_entry,
                      &osal_entity,
                      &osal_syncHandle) != ICALL_ERRNO_SUCCESS)
{      
  • 用戶端注冊

    用戶端根據不同任務進行注冊,我們的這裡的

    SimpleBLEPeripheral_taskFxn

    完成該任務的Icall用戶端注冊,并且傳回

    selfEntity

    作為改用戶端Icall通信的源位址。
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\ble5stack\simple_peripheral\src\app\simple_peripheral.c  SimpleBLEPeripheral_init  Line 452

  ICall_registerApp(&selfEntity, &syncEvent);
      
  • 發送消息

    所有應用程式Icall消息發送都是封裝在

    icall_directAPI

    函數中。
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\ble5stack\simple_peripheral\src\app\simple_peripheral.c  SimpleBLEPeripheral_init  Line 497

// Setup the GAP
GAP_SetParamValue(TGAP_CONN_PAUSE_PERIPHERAL, DEFAULT_CONN_PAUSE_PERIPHERAL);

//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\inc\icall_ble_api.h GAP_SetParamValue Line 313
#define GAP_SetParamValue(...)                                                          (icall_directAPI(ICALL_SERVICE_CLASS_BLE, (uint32_t) IDX_GAP_SetParamValue , ##__VA_ARGS__))
      

而對于

icall_directAPI

,将所有變參封裝到一個的消息結構體,并且調用

ICall_sendServiceMsg

->

ICall_send

//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c icall_directAPI Line3819
    icallLiteMsg_t liteMsg;
    liteMsg.hdr.len = sizeof(icallLiteMsg_t);
    liteMsg.hdr.next = NULL;
    liteMsg.hdr.dest_id = ICALL_UNDEF_DEST_ID;
    liteMsg.msg.directAPI  = id;
    liteMsg.msg.pointerStack = (uint32_t*)(*((uint32_t*)(&argp)));
    ICall_sendServiceMsg(ICall_getEntityId(), service,
                       ICALL_MSG_FORMAT_DIRECT_API_ID, &(liteMsg.msg));
                       
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c  ICall_sendServiceMsg Line 2419
  return (ICall_send(src, dstentity, format, msg));
      

最終

ICall_send

是直接将消息放入了目的Icall消息實體的消息隊列中,并且觸發事件,通知該任務喚醒解析處理消息。

//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c  Icall_Send Line 2661
  ICall_msgEnqueue(&ICall_entities[dest].task->queue, msg);
  ICALL_SYNC_HANDLE_POST(ICall_entities[dest].task->syncHandle);      

如何調試協定棧任務

對于協定棧任務,因為分布在另外一個工程,是以我們沒有向原來那樣直接加斷點調試。上面我們詳細講解了協定棧是如何作為一個任務在應用工程中啟動的,是以找到協定任務入口位址就是我們調試協定棧任務的關鍵。

在調試協定棧任務之前,我們建議将分别設定協定棧和應用工程優化等級Project->Opitons->C/C++ Compiler Optimizations->Level->None 為無,這對我們正常調試協定棧任務至關重要。

CC2640R2F BLE5.0 應用程式架構應用程式

上面我們已經詳細講解過,對于

FlashROM

編譯選項,協定棧任務入口位址在

iar_boundary.xcl

位址給出,對于

FlashROM_StackLibrary

編譯選項,我們可以通過編譯生成的*.map檔案查找

startup_entry

 符号進而找到入口位址。

CC2640R2F BLE5.0 應用程式架構應用程式
CC2640R2F BLE5.0 應用程式架構應用程式

拿到協定棧任務的人口位址後,我們就可以在彙編視窗View->Disassembly 直接輸入該位址,然後加上斷點,等待協定棧任務建立後運作,進而跳轉到協定棧工程進行調試。

CC2640R2F BLE5.0 應用程式架構應用程式

當然如果我們不知道協定棧任務的入口位址,直接通過建立協定棧任務的任務實體入口,直接跳轉到

ICall_taskEntry

->

entryfn

然後按F11進入協定棧任務調試。

CC2640R2F BLE5.0 應用程式架構應用程式
注意:這裡F11 Step Into可能會優化選項跳轉失敗,是以建議關閉優化選項,或者直接到聚焦到彙編視窗 BX R2 F11 跳轉。

Simple Peripheral Task

簡單外設任務作為應用程式任務是系統中最低優先級的任務。該任務的代碼是在Application IDE檔案夾中的simple_peripheral 檔案夾

simple_peripheral.c

中。

應用程式初始化功能

TI-RTOS概述詳細介紹如何了任務建構。建構任務并啟動SYS / BIOS核心排程程式後,構造過程中傳遞的函數會在任務準備就緒時運作(例如 SimpleBLEPeripheral_taskFxn)。任務實體函數運作前這裡會先運作任務初始化。

simple_peripheral任務函數僞代碼

static  void  SimpleBLEPeripheral_taskFxn (UArg  a0 , UArg  a1 ) { 
  //初始化應用程式
  SimpleBLEPeripheral_init ();

  // Application main loop 
  for  (;;) {

  } 
}      

這個初始化函數(simple_peripheral_init)為任務配置了幾個服務,并設定了幾個硬體和軟體配置設定和參數。以下清單包含一些常見示例:

  • 初始化GATT用戶端
  • 設定各種配置檔案的服務讀寫等回調函數
  • 設定GAPRole
  • 建立 Bond 管理器
  • 初始化 GAP層
  • 配置LCD或SPI等硬體子產品。
注意:在應用程式初始化函數中,調用任何協定棧API之前,必須調用ICall _registerApp()完成注冊。

任務功能中的事件處理

simple_peripheral實作以事件驅動的任務功能。任務函數進入一個無限循環,使其不間斷地為一個獨立的任務處理,并且始終不會運作到完成。在這個無限循環中,任務保持阻塞并等待,直到事件标志更新後進入事件處理函數。:

ICall任務保持阻塞并等待,直到發信号通知進行處理。

// Waits for an event to be posted associated with the calling thread.
// Note that an event associated with a thread is posted when a
// message is queued to the message receive queue of the thread
events = Event_pend(syncEvent, Event_Id_NONE, SBP_ALL_EVENTS,
                    ICALL_TIMEOUT_FOREVER);      

當事件發生并被處理後,任務又等待事件标志并且保保持阻塞狀态,直到有另一個事件發生。

任務事件

當BLE5-Stack通過ICAll消息子產品在應用程式任務中設定事件時,任務事件發生。一個比較好的例子就是調用HCI_EXT_ConnEventNoticeCmd()來訓示協定棧

connection event

結束。表示該事件結束的任務事件也将顯示在simple_peripheral的任務函數中:

SBP任務檢查任務事件。

if (events)
{
  ICall_EntityID dest;
  ICall_ServiceEnum src;
  ICall_HciExtEvt *pMsg = NULL;

  if (ICall_fetchServiceMsg(&src, &dest,
                            (void **)&pMsg) == ICALL_ERRNO_SUCCESS)
  {
    uint8 safeToDealloc = TRUE;

    if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity))
    {
      ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg;

      // Check for BLE stack events first
      if (pEvt->signature == 0xffff)
      {
        if (pEvt->event_flag & SBP_CONN_EVT_END_EVT)
        {
          // Try to retransmit pending ATT Response (if any)
          SimpleBLEPeripheral_sendAttRsp();
        }
      }
      else
      {
        // Process inter-task message
        safeToDealloc = SimpleBLEPeripheral_processStackMsg((ICall_Hdr *)pMsg);
      }
    }

    if (pMsg && safeToDealloc)
    {
      ICall_freeMsg(pMsg);
    }
  }

  // Additional Event Processing
}      
注意:在目前代碼中,如果事件來自BLE5-Stack ,則pEvt->signature總是等于0xFFFF。

當為一個事件選擇一個事件值的時候,該值對于給定的任務必須是唯一的,并且必須是2的幂(隻有1bit被設定為1)。因為

pEvt->event

變量被初始化為 

uint16_t

類型,也就是最多運作允許16個事件。有一個不能使用的事件值是已經用于BLE5-Stack OSAL全局事件(bcomdef.h中所述)的事件值:

清單49. BLE OSAL事件在bcomdef.h中定義。

/************************************************************
* BLE OSAL GAP GLOBAL Events
*/
#define GAP_EVENT_SIGN_COUNTER_CHANGED 0x4000 //!< The device level sign counter changed
      
注意:這些任務間事件是與 

請求和處理協定棧事件

 中提到的内部事件不同的事件集。

Intertask消息

這些消息通過ICall從一個任務(如BLE5-Stack Service/ICALL 服務端)傳遞給應用程式任務(ICALL 用戶端)。

正如以下情形:

  • 協定棧成功收到發送的無線資料ACK,需要發送确認從協定棧到應用。
  • 與HCI指令相對應的事件。
  • GATT用戶端操作的響應(請參閱直接使用GATT層)

Task事件來自simple_peripheral的主要任務循環的一個例子。

使用TI-RTOS事件子產品

所有BLE5-Stack 1.00.00項目使用TI-RTOS事件子產品擷取ICall堆棧消息事件。ICall線程同步中描述了使用情況 ,有關事件子產品的更多文檔,請參見“ TI RTOS核心使用者指南”。

處理隊列的應用程式消息

使用Util_enqueueMsg() 函數将應用程式消息放入隊列以先進先出的順序進行處理。當UTIL_QUEUE_EVENT_ID釋出事件時,應用程式應該從消息隊列取出消息處理後并釋放。

下面的代碼片段顯示了simple_peripheral如何處理應用程式消息。

#define SBP_QUEUE_EVT   UTIL_QUEUE_EVENT_ID // Event_Id_30

// If TI-RTOS queue is not empty, process app message.
if (events & SBP_QUEUE_EVT)
{
    while (!Queue_empty(appMsgQueue))
    {
        sbpEvt_t *pMsg = (sbpEvt_t *)Util_dequeueMsg(appMsgQueue);
        if (pMsg)
        {
            // Process message.
            SimpleBLEPeripheral_processAppMsg(pMsg);

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

請求和處理協定棧事件

某些API可以選擇在BLE5-Stack中發生特定事件時通知應用程式。啟用此類事件通知的API将包含一個

taskEvent

參數。

taskEvent

 對于給定的ICall-aware任務,該值必須是唯一的 。應用程式可以通過檢查是否

taskEvent

包含在資料結構

ICall_Stack_Event

uint16_t event_flag

變量中來處理所請求的協定棧事件。

注意 :在event_flag不與的TI-RTOS事件子產品事件混淆。

下面的代碼片段顯示了simple_peripheral如何請求協定棧事件标志。

連接配接間隔結束時請求通知的應用程式。

// Application specific event ID for HCI Connection Event End Events
#define SBP_HCI_CONN_EVT_END_EVT              0x0001

static uint8_t SimpleBLEPeripheral_processGATTMsg(gattMsgEvent_t *pMsg)
{
    // See if GATT server was unable to transmit an ATT response
    if (pMsg->hdr.status == blePending)
    {
        // No HCI buffer was available. Let's try to retransmit the response
        // on the next connection event.
        if (HCI_EXT_ConnEventNoticeCmd(pMsg->connHandle, selfEntity,
                                       SBP_HCI_CONN_EVT_END_EVT) == SUCCESS)
        {
            // First free any pending response
            SimpleBLEPeripheral_freeAttRsp(FAILURE);

            // Hold on to the response message for retransmission
            pAttRsp = pMsg;

            //...
        }
     //...
    }
    //...
}      

下面的代碼片段顯示了simple_peripheral如何處理協定棧事件标志。

處理請求BLE5-Stack事件

// Application specific event ID for HCI Connection Event End Events
#define SBP_HCI_CONN_EVT_END_EVT              0x0001

static void SimpleBLEPeripheral_taskFxn(UArg a0, UArg a1)
{

    // Application main loop
    for (;;)
    {
        uint32_t events;

        // Waits for an event to be posted associated with the calling thread.
        // Note that an event associated with a thread is posted when a
        // message is queued to the message receive queue of the thread
        events = Event_pend(syncEvent, Event_Id_NONE, SBP_ALL_EVENTS,
                            ICALL_TIMEOUT_FOREVER);

        if (events)
        {
            ICall_EntityID dest;
            ICall_ServiceEnum src;
            ICall_HciExtEvt *pMsg = NULL;

            if (ICall_fetchServiceMsg(&src, &dest,
                                    (void **)&pMsg) == ICALL_ERRNO_SUCCESS)
            {
                uint8 safeToDealloc = TRUE;

                if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity))
                {
                    ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg;

                    // Check for BLE stack events first
                    if (pEvt->signature == 0xffff)
                    {
                        if (pEvt->event_flag & SBP_HCI_CONN_EVT_END_EVT)
                        {
                            // Try to retransmit pending ATT Response (if any)
                            SimpleBLEPeripheral_sendAttRsp();
                        }
                    }
                    else
                    {
                        // Process inter-task message
                        safeToDealloc = SimpleBLEPeripheral_processStackMsg((ICall_Hdr *)pMsg);
                    }
                }
            }
        }
    }
}      

回調

應用程式代碼還包括對協定棧層,配置檔案和TI-RTOS子產品的各種回調函數。為了確定線程的安全性,回調要盡量做最少事情,大部分的處理應該發生在應用程式上下文中。每個回調定義了兩個函數。一個是回調本身,另外一個就是在任務環境中被處理回調事件函數。回調不直接處理,通過事件方式到任務中。可以參考 GAPRole 狀态改變回調,其用以處理GAPRole狀态變化。

危險:在回調函數中執行阻塞TI-RTOS函數調用或協定棧API是很危險的,這樣的函數調用可能導緻中止或未定義的行為。

simple_peripheral狀态變化回調。

static  void  SimpleBLEPeripheral_stateChangeCB (gaprole_States_t  newState ) { 
  SimpleBLEPeripheral_enqueueMsg (SBP_STATE_CHANGE_EVT , newState ); 
}      

上面的代碼片段顯示了通過

SimpleBLEPeripheral_gapRoleCBs

GAPRole_StartDevice()

。回調隻是在隊列中放置一個消息來通知應用程式喚醒。一旦回調傳回其父任務進入休眠狀态。應用程式喚醒從消息隊列取出消息處理同時調用以下代碼片段。

simple_peripheral任務環境中的狀态變化事件處理。

static  void  SimpleBLEPeripheral_processStateChangeEvt (gaprole_States_t  newState ) { 
  // ... 
}      

加入我們

文章所有代碼、工具、文檔開源。加入我們QQ群 591679055擷取更多支援,共同研究CC2640R2F&BLE5.0。

CC2640R2F BLE5.0 應用程式架構應用程式

© Copyright 2017, 成都樂控暢聯科技有限公司.

繼續閱讀