找回密碼
 立即注冊(cè)

QQ登錄

只需一步,快速開始

搜索
查看: 17212|回復(fù): 2
收起左側(cè)

淺析CC2540的OSAL原理

[復(fù)制鏈接]
ID:72008 發(fā)表于 2015-1-12 22:36 | 顯示全部樓層 |閱讀模式
一概述
  OSAL (Operating System Abstraction Layer),翻譯為操作系統(tǒng)抽象層OSAL就是一種支持多任務(wù)運(yùn)行的系統(tǒng)資源分配機(jī)制。OSAL與標(biāo)準(zhǔn)的操作系統(tǒng)還是有很大的區(qū)別的。簡單而言,OSAL實(shí)現(xiàn)了類似操作系統(tǒng)的某些功能,但并不能稱之為真正意義上的操作系統(tǒng)。
二、OSAL任務(wù)運(yùn)行方式
         我們以TI1.2.1BLE協(xié)議棧中的SimpleBLEPeripheral為例,分析一下OSAL。其中有一個(gè)simpleBLEPeripheral.c文件,里面有2個(gè)比較重要的函數(shù):SimpleBLEPeripheral_InitSimpleBLEPeripheral_ProcessEvent。SimpleBLEPeripheral_Init是任務(wù)的初始化函數(shù),而SimpleBLEPeripheral_ProcessEvent則負(fù)責(zé)處理傳遞給此任務(wù)的事件。
大概瀏覽一下SimpleBLEPeripheral_ProcessEvent這個(gè)函數(shù),我們可以發(fā)現(xiàn),此函數(shù)的主要功能是判斷由參數(shù)傳遞的事件類型,然后執(zhí)行相應(yīng)的事件處理函數(shù)。由此,可以推斷出BLE協(xié)議棧應(yīng)用程序的運(yùn)行機(jī)制如下圖所示:


當(dāng)有一個(gè)事件發(fā)生的時(shí)候,OSAL負(fù)責(zé)將此事件分配給能夠處理此事件的任務(wù),然后此任務(wù)判斷事件的類型,調(diào)用相應(yīng)的事件處理程序進(jìn)行處理。
明白了這個(gè)問題,新的問題又?jǐn)[在了我們的面前:OSAL是如何傳遞事件給任務(wù)的。
三、OSAL的事件傳遞機(jī)制
在試圖弄清楚這個(gè)問題之前,我們需要弄清楚另外一個(gè)十分基礎(chǔ)而重要的問題。那就是如何向我們的應(yīng)用程序中添加一個(gè)任務(wù)。
  我們先來看看simpleBLEPeripheral.c是如何添加任務(wù)的。
  我們打開OSAL_SimpleBLEPeripheral.c文件。這里我們可以找到一個(gè)很重要的數(shù)組tasksArr和一個(gè)同樣很重要的函數(shù)osalInitTasks。
  TaskArr這個(gè)數(shù)組里存放了所有任務(wù)的事件處理函數(shù)的地址,在這里事件處理函數(shù)就代表了任務(wù)本身,也就是說事件處理函數(shù)標(biāo)識(shí)了與其對(duì)應(yīng)的任務(wù)。
  osalInitTasksOSAL的任務(wù)初始化函數(shù),所有任務(wù)的初始化工作都在這里面完成,并且自動(dòng)給每個(gè)任務(wù)分配一個(gè)ID。
  要添加新任務(wù),我們需要編寫新任務(wù)的事件處理函數(shù)和初始化函數(shù),然后將事件處理函數(shù)的地址加入此數(shù)組。然后在osalInitTasks中調(diào)用此任務(wù)的初始化函數(shù)。在此例中,我們此前提到過的SimpleBLEPeripheral_ProcessEvent這個(gè)函數(shù)被添加到了數(shù)組的末尾,  SimpleBLEPeripheral_Init這個(gè)函數(shù)在osalInitTasks中被調(diào)用。
值得注意的是,TaskArr數(shù)組里各任務(wù)函數(shù)的排列順序要與osalInitTasks函數(shù)中調(diào)用各任務(wù)初始化函數(shù)的順序必須一致,只有這樣才能夠保證每個(gè)任務(wù)能夠通過初始化函數(shù)接收到正確的任務(wù)ID
  另外,為了保存任務(wù)初始化函數(shù)所接收的任務(wù)ID,我們需要給每一個(gè)任務(wù)定義一個(gè)全局變量來保存這個(gè)ID。在SimpleBLEPeripheralSimpleBLEPeripheral.c中定義了一個(gè)全局變量SimpleBLEPeripheral_TaskID;并且在SimpleBLEPeripheral_Init函數(shù)中進(jìn)行了賦值
  {
              SimpleBLEPeripheral_TaskID = task_id;
  }
  這條語句將分配給SimpleBLEPeripheral的任務(wù)ID保存了下來。
  到此,我們就給應(yīng)用程序中完整的添加了一個(gè)任務(wù)。
  我們回到OSAL如何將事件分配給任務(wù)這個(gè)問題上來
  在OSAL_SimpleBLEPeripheral.c這個(gè)文件中,在定義TaskArr這個(gè)數(shù)組之后,又定義了兩個(gè)全局變量。
  tasksCnt這個(gè)變量保存了當(dāng)前的任務(wù)個(gè)數(shù)。
  tasksEvents是一個(gè)指向數(shù)組的指針,此數(shù)組保存了當(dāng)前任務(wù)的狀態(tài)。在任務(wù)初始化函數(shù)中做了如下操作
  {
         tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);
         osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));
  }
/*osal_mem_alloc()為當(dāng)前OSAL中的各任務(wù)分配存儲(chǔ)空間(實(shí)際上是一個(gè)任務(wù)數(shù)組),
  函數(shù)返回指向任務(wù)緩沖區(qū)的指針,因此tasksEvents指向該任務(wù)數(shù)組(任務(wù)隊(duì)列).注意
  tasksEvents和后面談到的tasksArr[]里的順序是一一對(duì)應(yīng)的, tasksArr[ ]中的第i個(gè)
  事件處理函數(shù)對(duì)應(yīng)于tasksEvents中的第i個(gè)任務(wù)的事件.*/

   /*osal_memset()把開辟的內(nèi)存全部設(shè)置為0sizeof( uint16 )2個(gè)字節(jié),即一個(gè)任務(wù)的長度(任務(wù)函數(shù)同樣是uint16定義),乘以任務(wù)數(shù)量tasksCnt,即全部內(nèi)存空間*/

  
  我們可以看出所有任務(wù)的狀態(tài)都被初始化為0。代表了當(dāng)前任務(wù)沒有需要響應(yīng)的事件。
  緊接著,我們來到了main()函數(shù)。此SimpleBLEPeripheral_Main.c文件中。略過許多對(duì)當(dāng)前來說并非重要的語句,我們先來看osal_init_system()這個(gè)函數(shù)。在此函數(shù)中,osalInitTasks()被調(diào)用,從而tasksEvents中的所有內(nèi)容被初始化為0。
  之后,在main()函數(shù)中,我們進(jìn)入了osal_start_system()函數(shù),此函數(shù)為一個(gè)死循環(huán),在這個(gè)循環(huán)中,完成了所有的事件分配。
  首先我們來看這樣一段代碼:
  {
         do
         {
                 if (tasksEvents[idx])
                 {
                         break;
                 }
         } while (++idx < tasksCnt);
  }
  當(dāng)tasksEvents這個(gè)數(shù)組中的某個(gè)元素不為0,即代表此任務(wù)有事件需要相應(yīng),事件類型取決于這個(gè)元素的值。這個(gè)do-while循環(huán)會(huì)選出當(dāng)前優(yōu)先級(jí)最高的需要響應(yīng)的任務(wù),
  {
         events = (tasksArr[idx])( idx, events );
  }
  此語句調(diào)用tasksArr數(shù)組里面相應(yīng)的事件處理函數(shù)來響應(yīng)事件。如果我們新添加的任務(wù)有了需要響應(yīng)的事件,那么此任務(wù)的事件處理程序?qū)?huì)被調(diào)用。
  就這樣,OSAL就將需要響應(yīng)的事件傳遞給了對(duì)應(yīng)的任務(wù)處理函數(shù)進(jìn)行處理。
附:詳解events = (tasksArr[idx])( idx, events )
(tasksArr[idx])( idx, events )是一個(gè)函數(shù)指針數(shù)組。那么什么是函數(shù)指針數(shù)組呢?顧名思義,函數(shù)指針數(shù)組是一個(gè)數(shù)組,數(shù)組中存放的元素類型是函數(shù)的指針。表達(dá)式舉例:char*p[](int i) ;對(duì)于這個(gè)表達(dá)式我們從語法上解釋為,p是一個(gè)數(shù)組變量名,數(shù)組變量類型是char*)(int i),存放元素的類型是:charint i)函數(shù)的指針。
tasksArr[idx]就是一個(gè)函數(shù)指針數(shù)組,里面存儲(chǔ)的就是函數(shù)的指針。
const pTaskEventHandlerFn tasksArr[] =
{
   LL_ProcessEvent,                                                  // task 0
   Hal_ProcessEvent,                                                 // task 1
   HCI_ProcessEvent,                                                 // task 2
#if defined ( OSAL_CBTIMER_NUM_TASKS )
   OSAL_CBTIMER_PROCESS_EVENT( osal_CbTimerProcessEvent ),           // task 3
#endif
   L2CAP_ProcessEvent,                                               // task 4
   GAP_ProcessEvent,                                                 // task 5
   GATT_ProcessEvent,                                                // task 6
   SM_ProcessEvent,                                                  // task 7
   GAPRole_ProcessEvent,                                             // task 8
   GAPBondMgr_ProcessEvent,                                          // task 9
   GATTServApp_ProcessEvent,                                         // task 10
   SimpleBLEPeripheral_ProcessEvent                                  // task 11
};
假設(shè)idx=11;
那么events = (tasksArr[11])( 11, events );
也就是調(diào)用了SimpleBLEPeripheral_ProcessEvent這個(gè)函數(shù),其中傳入的參數(shù)就是(11,events
也就是調(diào)用了events = (tasksArr[11])( 11, events )
其實(shí)就是執(zhí)行了SimpleBLEPeripheral_ProcessEvent(11,events);

四、事件的捕獲
  不過接下來就有了更加深入的問題了,事件是如何被捕獲的?直觀一些來說就是,tasksEvents這個(gè)數(shù)組里的元素是什么時(shí)候被設(shè)定為非零數(shù),來表示有事件需要處理的?為了詳細(xì)的說明這個(gè)過程,我將以SimpleBLEPeripheral這個(gè)例程中響應(yīng)按鍵的過程來進(jìn)行說明。其他的事件雖然稍有差別,卻是大同小異。
  按鍵在我們的應(yīng)用里面應(yīng)該屬于硬件資源,所以OSAL理應(yīng)為我們提供使用和管理這些硬件的服務(wù)。稍微留意一下我們之前說過的tasksArr這樣一個(gè)數(shù)組,它保存了所有任務(wù)的事件處理函數(shù)。我們從中發(fā)現(xiàn)了一個(gè)很重要的信息:Hal_ProcessEvent。HALHardware Abstraction Layer)翻譯為硬件抽象層。許多人在這里經(jīng)常把將BLE的硬件抽象層與物理層混為一談。在這里,我們應(yīng)該將BLE的硬件抽象層與物理層區(qū)分開來。硬件抽象層所包含的范圍是我們當(dāng)前硬件電路上面所有對(duì)于系統(tǒng)可用的設(shè)備資源。而物理層則是針對(duì)無線通信而言,它所包含的僅限于支持無線通訊的硬件設(shè)備。
  通過這個(gè)重要的信息,我們可以得出這樣一個(gè)結(jié)論:OSAL將硬件的管理也作為一個(gè)任務(wù)來處理。那么我們很自然的去尋找Hal_ProcessEvent這個(gè)事件處理函數(shù),看看它究竟是如何管理硬件資源的。
  在“HAL\Commn\ hal_drivers.c”這個(gè)文件中,我們找到了這個(gè)函數(shù)。我們直接分析與按鍵有關(guān)的一部分。
  {
         if (events & HAL_KEY_EVENT)
         {
                 #if (defined HAL_KEY) && (HAL_KEY == TRUE)
               
/* Check for keys */
                 HalKeyPoll();
                 /* if interrupt disabled, do next polling */
                 if (!Hal_KeyIntEnable)
                 {
                         osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);
                 }
                 #endif // HAL_KEY
                 return events ^ HAL_KEY_EVENT;
         }
  }
  在事件處理函數(shù)接收到HAL_KEY_EVENT這樣一個(gè)事件后,首先執(zhí)行HalKeyPoll()函數(shù)。由于這個(gè)例程的按鍵采用查詢的方法獲取,所以是禁止中斷的,于是表達(dá)式(!Hal_KeyIntEnable)的值為真。那么osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100)得以執(zhí)行。osal_start_timerEx這是一個(gè)很常用的函數(shù),它在這里的功能是經(jīng)過100毫秒后,向Hal_TaskID這個(gè)ID所標(biāo)示的任務(wù)(也就是其本身)發(fā)送一個(gè)HAL_KEY_EVENT事件。這樣以來,每經(jīng)過100毫秒,Hal_ProcessEvent這個(gè)事件處理函數(shù)都會(huì)至少執(zhí)行一次來處理HAL_KEY_EVENT事件。也就是說每隔100毫秒都會(huì)執(zhí)行HalKeyPoll()函數(shù)。
  那么我們來看看HalKeyPoll函數(shù)到底在搞什么鬼!
  代碼中給的注釋為:
  /* Check for keys */
  HalKeyPoll();
  于是我們推斷這個(gè)函數(shù)的作用是檢查當(dāng)前的按鍵情況。進(jìn)入函數(shù)一看,果不其然。雖然這個(gè)函數(shù)很長很復(fù)雜,不過憑借著非凡的聰明才智,我們還是十分清楚的明白了,經(jīng)過一系列的if語句和賦值語句,在接近函數(shù)末尾的地方, keys變量(在函數(shù)起始位置定義的)獲得了當(dāng)前按鍵的狀態(tài)。最后,有一個(gè)十分重要的函數(shù)調(diào)用。
  (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
  pHalKeyProcessFunction這個(gè)函數(shù)指針指向了哪個(gè)函數(shù)我們現(xiàn)在依然不清楚,但是為了我們有個(gè)清晰而不間斷的思路,我在這里先告訴大家。在這里調(diào)用的是
  void OnBoard_KeyCallback ( uint8 keys, uint8 state )
  這個(gè)函數(shù)。此函數(shù)在“OnBoard .c”文件中可以找到。在這個(gè)函數(shù)中,又調(diào)用了
  void OnBoard_KeyCallback ( uint8 keys, uint8 state )
  在這個(gè)函數(shù)中,按鍵的狀態(tài)信息被封裝到了一個(gè)消息結(jié)構(gòu)體中(對(duì)于消息,我們稍后再說)。最后有一個(gè)極其重要的函數(shù)被調(diào)用了。
  osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );
  與前面的pHalKeyProcessFunction相同,我先直接告訴大家registeredKeysTaskID所指示的任務(wù)正是我們需要響應(yīng)按鍵的SimpleBLEPeripheral這個(gè)任務(wù)。
  那么也就是說,在這里我們向SimpleBLEPeripheral發(fā)送了一個(gè)附帶按鍵信息的消息。在osal_msg_send函數(shù)中
  osal_set_event( destination_task, SYS_EVENT_MSG );
  被調(diào)用,它在這里的作用是設(shè)destination_task這個(gè)任務(wù)的事件為SYS_EVENT_MSG。而這個(gè)destination_task正式由osal_msg_send這個(gè)函數(shù)通過參數(shù)傳遞而來的,它也指示的是SimpleBLEPeripheral這個(gè)任務(wù)。在osal_set_event這個(gè)函數(shù)中,有這樣一個(gè)語句:
  {
         tasksEvents[task_id] |= event_flag;
  }
  至此,剛才所提到的問題得到了解決。我們?cè)賹⑦@個(gè)過程整理一遍。
  首先,OSAL專門建立了一個(gè)任務(wù)來對(duì)硬件資源進(jìn)行管理,這個(gè)任務(wù)的事件處理函數(shù)是Hal_ProcessEvent。在這個(gè)函數(shù)中通過調(diào)用osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);這個(gè)函數(shù)使得每隔100毫秒就會(huì)執(zhí)行一次HalKeyPoll()函數(shù)。HalKeyPoll()獲取當(dāng)前按鍵的狀態(tài),并且通過調(diào)用OnBoard_KeyCallback函數(shù)向SimpleBLEPeripheral任務(wù)發(fā)送一個(gè)按鍵消息,并且設(shè)置tasksEventsSimpleBLEPeripheral所對(duì)應(yīng)的值為非零。如此,當(dāng)main函數(shù)里這樣一段代碼
  {
         do
         {
                 if (tasksEvents[idx])
                 {
                         break;
                 }
         } while (++idx < tasksCnt);
  }
  執(zhí)行了以后,SimpleBLEPeripheral這個(gè)任務(wù)就會(huì)被挑選出來。然后通過
  events = (tasksArr[idx])( idx, events );
  這個(gè)函數(shù)調(diào)用其事件處理函數(shù),完成事件的響應(yīng)。
現(xiàn)在,我們回過頭來處理我們之前遺留下來的問題。
  第一、pHalKeyProcessFunction這個(gè)函數(shù)指針為何指向了OnBoard_KeyCallback函數(shù)。
  在HAL\Common\ hal_drivers.c這個(gè)文件中,我們找到了HalDriverInit這個(gè)函數(shù),在這個(gè)函數(shù)中,按鍵的初始化函數(shù)HalKeyInit被調(diào)用。在HalKeyInit中有這樣的語句:
  {
         pHalKeyProcessFunction  = NULL;
  }
  這說明在初始化以后pHalKeyProcessFunction并沒有指向任何一個(gè)函數(shù)。那pHalKeyProcessFunction是什么時(shí)候被賦值的呢?
  就在HalKeyInit的下方有一個(gè)這樣的函數(shù)HalKeyConfig。其中有這樣一條語句:
  pHalKeyProcessFunction = cback
  cbackHalKeyConfig所傳進(jìn)來的參數(shù),所以,想要知道它所指向的函數(shù),必須找到其調(diào)用的地方。經(jīng)過簡單的搜索我們不難找出答案。在main函數(shù)中有這樣一個(gè)函數(shù)調(diào)用:InitBoard( OB_READY );此函數(shù)中做了如下調(diào)用:
  {
         HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
  }
  第二、registeredKeysTaskID為什么標(biāo)識(shí)了SimpleBLEPeripheral這個(gè)任務(wù)?
  由于OSAL是一個(gè)支持多任務(wù)的調(diào)度機(jī)制,所以在同一時(shí)間內(nèi)將會(huì)有多個(gè)任務(wù)同時(shí)運(yùn)行。但是從邏輯上來講,一個(gè)事件只能由一個(gè)任務(wù)來處理。按鍵事件也不例外。
  那么如何向OSAL聲明處理按鍵事件的任務(wù)是SimpleBLEPeripheral呢?
  在SimpleBLEPeripheral_InitSimpleBLEPeripheral的任務(wù)初始化函數(shù))中有這么一個(gè)語句:
  {
         RegisterForKeys( SimpleBLEPeripheral_TaskID );
  }
  RegisterForKeys函數(shù)向OSAL聲明按鍵事件將由SimpleBLEPeripheral任務(wù)來處理。在RegisterForKeys函數(shù)中:
  {
         registeredKeysTaskID = task_id;
  }
  我想我不用再做多余的解釋了,聰明的您肯定可以理解。
五、消息隊(duì)列
  首先我需要向大家解釋清楚消息與事件的聯(lián)系。事件是驅(qū)動(dòng)任務(wù)去執(zhí)行某些操作的條件,當(dāng)系統(tǒng)產(chǎn)生了一個(gè)事件,將這個(gè)傳遞給相應(yīng)的任務(wù)后,任務(wù)才能執(zhí)行一個(gè)相應(yīng)的操作。但是某些事件在它發(fā)生的同時(shí),又伴隨著一些附加信息的產(chǎn)生。任務(wù)的事件處理函數(shù)在處理這個(gè)事件的時(shí)候,還需要參考其附加信息。最典型的一類便是按鍵消息,它同時(shí)產(chǎn)生了一個(gè)哪個(gè)按鍵被按下了附加信息。所以在OnBoard_SendKeys這個(gè)函數(shù)中,不僅向SimpleBLEPeripheral發(fā)送了事件,還通過調(diào)用osal_msg_send函數(shù)向SimpleBLEPeripheral發(fā)送了一個(gè)消息,這個(gè)消息記錄了這個(gè)事件的附加信息。在SimpleBLEPeripheral_ProcessEvent中,通過
  {
         MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SimpleBLEPeripheral_TaskID );
  }
  獲取了這樣一個(gè)消息,然后再進(jìn)一步處理。
  OSAL在后臺(tái)維護(hù)了一個(gè)消息隊(duì)列,每一個(gè)消息都會(huì)被放到這個(gè)消息隊(duì)列中去,當(dāng)任務(wù)接收到事件以后,從消息隊(duì)列中獲取屬于自己的消息,然后進(jìn)行處理。
  以上就是我就將OSAL這樣一個(gè)事件驅(qū)動(dòng)的多任務(wù)的資源分配機(jī)制做了一個(gè)簡明扼要的介紹,希望對(duì)大家有所幫助。

回復(fù)

使用道具 舉報(bào)

ID:104074 發(fā)表于 2016-1-22 11:13 | 顯示全部樓層
言簡意賅,簡明扼要
回復(fù)

使用道具 舉報(bào)

ID:117280 發(fā)表于 2016-4-28 13:08 | 顯示全部樓層
感謝lz的文章,寫的挺好
回復(fù)

使用道具 舉報(bào)

本版積分規(guī)則

手機(jī)版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表