天天看點

淺析CC2540的OSAL原理

http://www.baidu.com/link?url=SIaYicr9vNVSFxvVwlIbueni2hnjmb0d5U3mntLWnPiP9fXDc6ApIHZy1XaK89wdlLodRnZhryPJD6ITi50Jmj-_fuQHOI5wYdDDb2DjKJS

http://blog.csdn.net/eliot_shao/article/details/8609259

一概述

  OSAL (Operating System Abstraction Layer),翻譯為“作業系統抽象層”。OSAL就是一種支援多任務運作的系統資源配置設定機制。OSAL與标準的作業系統還是有很大的差別的。簡單而言,OSAL實作了類似作業系統的某些功能,但并不能稱之為真正意義上的作業系統。

二、OSAL任務運作方式

         我們以TI1.2.1的BLE協定棧中的SimpleBLEPeripheral為例,分析一下OSAL。其中有一個simpleBLEPeripheral.c檔案,裡面有2個比較重要的函數:SimpleBLEPeripheral_Init和SimpleBLEPeripheral_ProcessEvent。SimpleBLEPeripheral_Init是任務的初始化函數,而SimpleBLEPeripheral_ProcessEvent則負責處理傳遞給此任務的事件。

大概浏覽一下SimpleBLEPeripheral_ProcessEvent這個函數,我們可以發現,此函數的主要功能是判斷由參數傳遞的事件類型,然後執行相應的事件處理函數。由此,可以推斷出BLE協定棧應用程式的運作機制如下圖所示:

淺析CC2540的OSAL原理

當有一個事件發生的時候,OSAL負責将此事件配置設定給能夠處理此事件的任務,然後此任務判斷事件的類型,調用相應的事件處理程式進行處理。

明白了這個問題,新的問題又擺在了我們的面前:OSAL是如何傳遞事件給任務的。

三、OSAL的事件傳遞機制

在試圖弄清楚這個問題之前,我們需要弄清楚另外一個十分基礎而重要的問題。那就是如何向我們的應用程式中添加一個任務。

  我們先來看看simpleBLEPeripheral.c是如何添加任務的。

  我們打開OSAL_SimpleBLEPeripheral.c檔案。這裡我們可以找到一個很重要的數組tasksArr和一個同樣很重要的函數osalInitTasks。

  TaskArr這個數組裡存放了所有任務的事件處理函數的位址,在這裡事件處理函數就代表了任務本身,也就是說事件處理函數辨別了與其對應的任務。

  osalInitTasks是OSAL的任務初始化函數,所有任務的初始化工作都在這裡面完成,并且自動給每個任務配置設定一個ID。

  要添加新任務,我們需要編寫新任務的事件處理函數和初始化函數,然後将事件處理函數的位址加入此數組。然後在osalInitTasks中調用此任務的初始化函數。在此例中,我們此前提到過的SimpleBLEPeripheral_ProcessEvent這個函數被添加到了數組的末尾, SimpleBLEPeripheral_Init這個函數在osalInitTasks中被調用。

值得注意的是,TaskArr數組裡各任務函數的排列順序要與osalInitTasks函數中調用各任務初始化函數的順序必須一緻,隻有這樣才能夠保證每個任務能夠通過初始化函數接收到正确的任務ID。

  另外,為了儲存任務初始化函數所接收的任務ID,我們需要給每一個任務定義一個全局變量來儲存這個ID。在SimpleBLEPeripheral中SimpleBLEPeripheral.c中定義了一個全局變量SimpleBLEPeripheral_TaskID;并且在SimpleBLEPeripheral_Init函數中進行了指派

  {

              SimpleBLEPeripheral_TaskID = task_id;

  }

  這條語句将配置設定給SimpleBLEPeripheral的任務ID儲存了下來。

  到此,我們就給應用程式中完整的添加了一個任務。

  我們回到OSAL如何将事件配置設定給任務這個問題上來

  在OSAL_SimpleBLEPeripheral.c這個檔案中,在定義TaskArr這個數組之後,又定義了兩個全局變量。

  tasksCnt這個變量儲存了目前的任務個數。

  tasksEvents是一個指向數組的指針,此數組儲存了目前任務的狀态。在任務初始化函數中做了如下操作

  {

        tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);

        osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));

  }

  我們可以看出所有任務的狀态都被初始化為0。代表了目前任務沒有需要響應的事件。

  緊接着,我們來到了main()函數。此SimpleBLEPeripheral_Main.c檔案中。略過許多對目前來說并非重要的語句,我們先來看osal_init_system()這個函數。在此函數中,osalInitTasks()被調用,進而tasksEvents中的所有内容被初始化為0。

  之後,在main()函數中,我們進入了osal_start_system()函數,此函數為一個死循環,在這個循環中,完成了所有的事件配置設定。

  首先我們來看這樣一段代碼:

  {

        do

        {

                if (tasksEvents[idx])

                {

                        break;

                }

        } while (++idx < tasksCnt);

  }

  當tasksEvents這個數組中的某個元素不為0,即代表此任務有事件需要相應,事件類型取決于這個元素的值。這個do-while循環會選出目前優先級最高的需要響應的任務,

  {

        events = (tasksArr[idx])( idx, events );

  }

  此語句調用tasksArr數組裡面相應的事件處理函數來響應事件。如果我們新添加的任務有了需要響應的事件,那麼此任務的事件處理程式将會被調用。

  就這樣,OSAL就将需要響應的事件傳遞給了對應的任務處理函數進行處理。

附:詳解events = (tasksArr[idx])( idx, events );

(tasksArr[idx])( idx, events )是一個函數指針數組。那麼什麼是函數指針數組呢?顧名思義,函數指針數組是一個數組,數組中存放的元素類型是函數的指針。表達式舉例:char(*p[])(int i) ;對于這個表達式我們從文法上解釋為,p是一個數組變量名,數組變量類型是char(*)(int i),存放元素的類型是:char(int i)函數的指針。

tasksArr[idx]就是一個函數指針數組,裡面存儲的就是函數的指針。

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

};

假設idx=11;

那麼events = (tasksArr[11])( 11, events );

也就是調用了SimpleBLEPeripheral_ProcessEvent這個函數,其中傳入的參數就是(11,events)

也就是調用了events = (tasksArr[11])( 11, events )

其實就是執行了SimpleBLEPeripheral_ProcessEvent(11,events);

四、事件的捕獲

  不過接下來就有了更加深入的問題了,事件是如何被捕獲的?直覺一些來說就是,tasksEvents這個數組裡的元素是什麼時候被設定為非零數,來表示有事件需要處理的?為了詳細的說明這個過程,我将以SimpleBLEPeripheral這個例程中響應按鍵的過程來進行說明。其他的事件雖然稍有差别,卻是大同小異。

  按鍵在我們的應用裡面應該屬于硬體資源,是以OSAL理應為我們提供使用和管理這些硬體的服務。稍微留意一下我們之前說過的tasksArr這樣一個數組,它儲存了所有任務的事件處理函數。我們從中發現了一個很重要的資訊:Hal_ProcessEvent。HAL(Hardware Abstraction Layer)翻譯為“硬體抽象層”。許多人在這裡經常把将BLE的硬體抽象層與實體層混為一談。在這裡,我們應該将BLE的硬體抽象層與實體層區分開來。硬體抽象層所包含的範圍是我們目前硬體電路上面所有對于系統可用的裝置資源。而實體層則是針對無線通信而言,它所包含的僅限于支援無線通訊的硬體裝置。

  通過這個重要的資訊,我們可以得出這樣一個結論:OSAL将硬體的管理也作為一個任務來處理。那麼我們很自然的去尋找Hal_ProcessEvent這個事件處理函數,看看它究竟是如何管理硬體資源的。

  在“HAL\Commn\ hal_drivers.c”這個檔案中,我們找到了這個函數。我們直接分析與按鍵有關的一部分。

  {

        if (events & HAL_KEY_EVENT)

        {

                #if (defined HAL_KEY) && (HAL_KEY == TRUE)

                HalKeyPoll();

                if (!Hal_KeyIntEnable)

                {

                        osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);

                }

                #endif // HAL_KEY

                return events ^ HAL_KEY_EVENT;

        }

  }

  在事件處理函數接收到HAL_KEY_EVENT這樣一個事件後,首先執行HalKeyPoll()函數。由于這個例程的按鍵采用查詢的方法擷取,是以是禁止中斷的,于是表達式(!Hal_KeyIntEnable)的值為真。那麼osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100)得以執行。osal_start_timerEx這是一個很常用的函數,它在這裡的功能是經過100毫秒後,向Hal_TaskID這個ID所标示的任務(也就是其本身)發送一個HAL_KEY_EVENT事件。這樣以來,每經過100毫秒,Hal_ProcessEvent這個事件處理函數都會至少執行一次來處理HAL_KEY_EVENT事件。也就是說每隔100毫秒都會執行HalKeyPoll()函數。

  那麼我們來看看HalKeyPoll函數到底在搞什麼鬼!

  代碼中給的注釋為:

  

  HalKeyPoll();

  于是我們推斷這個函數的作用是檢查目前的按鍵情況。進入函數一看,果不其然。雖然這個函數很長很複雜,不過憑借着非凡的聰明才智,我們還是十厘清楚的明白了,經過一系列的if語句和指派語句,在接近函數末尾的地方, keys變量(在函數起始位置定義的)獲得了目前按鍵的狀态。最後,有一個十分重要的函數調用。

  (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);

  pHalKeyProcessFunction這個函數指針指向了哪個函數我們現在依然不清楚,但是為了我們有個清晰而不間斷的思路,我在這裡先告訴大家。在這裡調用的是

  void OnBoard_KeyCallback ( uint8 keys, uint8 state )

  這個函數。此函數在“OnBoard .c”檔案中可以找到。在這個函數中,又調用了

  void OnBoard_KeyCallback ( uint8 keys, uint8 state )

  在這個函數中,按鍵的狀态資訊被封裝到了一個消息結構體中(對于消息,我們稍後再說)。最後有一個極其重要的函數被調用了。

  osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );

  與前面的pHalKeyProcessFunction相同,我先直接告訴大家registeredKeysTaskID所訓示的任務正是我們需要響應按鍵的SimpleBLEPeripheral這個任務。

  那麼也就是說,在這裡我們向SimpleBLEPeripheral發送了一個附帶按鍵資訊的消息。在osal_msg_send函數中

  osal_set_event( destination_task, SYS_EVENT_MSG );

  被調用,它在這裡的作用是設定destination_task這個任務的事件為SYS_EVENT_MSG。而這個destination_task正式由osal_msg_send這個函數通過參數傳遞而來的,它也訓示的是SimpleBLEPeripheral這個任務。在osal_set_event這個函數中,有這樣一個語句:

  {

        tasksEvents[task_id] |= event_flag;

  }

  至此,剛才所提到的問題得到了解決。我們再将這個過程整理一遍。

  首先,OSAL專門建立了一個任務來對硬體資源進行管理,這個任務的事件處理函數是Hal_ProcessEvent。在這個函數中通過調用osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);這個函數使得每隔100毫秒就會執行一次HalKeyPoll()函數。HalKeyPoll()擷取目前按鍵的狀态,并且通過調用OnBoard_KeyCallback函數向SimpleBLEPeripheral任務發送一個按鍵消息,并且設定tasksEvents中SimpleBLEPeripheral所對應的值為非零。如此,當main函數裡這樣一段代碼

  {

        do

        {

                if (tasksEvents[idx])

                {

                        break;

                }

        } while (++idx < tasksCnt);

  }

  執行了以後,SimpleBLEPeripheral這個任務就會被挑選出來。然後通過

  events = (tasksArr[idx])( idx, events );

  這個函數調用其事件處理函數,完成事件的響應。

現在,我們回過頭來處理我們之前遺留下來的問題。

  第一、pHalKeyProcessFunction這個函數指針為何指向了OnBoard_KeyCallback函數。

  在HAL\Common\ hal_drivers.c這個檔案中,我們找到了HalDriverInit這個函數,在這個函數中,按鍵的初始化函數HalKeyInit被調用。在HalKeyInit中有這樣的語句:

  {

        pHalKeyProcessFunction  = NULL;

  }

  這說明在初始化以後pHalKeyProcessFunction并沒有指向任何一個函數。那pHalKeyProcessFunction是什麼時候被指派的呢?

  就在HalKeyInit的下方有一個這樣的函數HalKeyConfig。其中有這樣一條語句:

  pHalKeyProcessFunction = cback;

  cback是HalKeyConfig所傳進來的參數,是以,想要知道它所指向的函數,必須找到其調用的地方。經過簡單的搜尋我們不難找出答案。在main函數中有這樣一個函數調用:InitBoard( OB_READY );此函數中做了如下調用:

  {

        HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);

  }

  第二、registeredKeysTaskID為什麼辨別了SimpleBLEPeripheral這個任務?

  由于OSAL是一個支援多任務的排程機制,是以在同一時間内将會有多個任務同時運作。但是從邏輯上來講,一個事件隻能由一個任務來處理。按鍵事件也不例外。

  那麼如何向OSAL聲明處理按鍵事件的任務是SimpleBLEPeripheral呢?

  在SimpleBLEPeripheral_Init(SimpleBLEPeripheral的任務初始化函數)中有這麼一個語句:

  {

        RegisterForKeys( SimpleBLEPeripheral_TaskID );

  }

  RegisterForKeys函數向OSAL聲明按鍵事件将由SimpleBLEPeripheral任務來處理。在RegisterForKeys函數中:

  {

        registeredKeysTaskID = task_id;

  }

  我想我不用再做多餘的解釋了,聰明的您肯定可以了解。

五、消息隊列

  首先我需要向大家解釋清楚消息與事件的聯系。事件是驅動任務去執行某些操作的條件,當系統産生了一個事件,将這個傳遞給相應的任務後,任務才能執行一個相應的操作。但是某些事件在它發生的同時,又伴随着一些附加資訊的産生。任務的事件處理函數在處理這個事件的時候,還需要參考其附加資訊。最典型的一類便是按鍵消息,它同時産生了一個哪個按鍵被按下了附加資訊。是以在OnBoard_SendKeys這個函數中,不僅向SimpleBLEPeripheral發送了事件,還通過調用osal_msg_send函數向SimpleBLEPeripheral發送了一個消息,這個消息記錄了這個事件的附加資訊。在SimpleBLEPeripheral_ProcessEvent中,通過

  {

        MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SimpleBLEPeripheral_TaskID );

  }

  擷取了這樣一個消息,然後再進一步處理。

  OSAL在背景維護了一個消息隊列,每一個消息都會被放到這個消息隊列中去,當任務接收到事件以後,從消息隊列中擷取屬于自己的消息,然後進行處理。

  以上就是我就将OSAL這樣一個事件驅動的多任務的資源配置設定機制做了一個簡明扼要的介紹,希望對大家有所幫助。

覺得文章對你有幫助,可以掃描二維碼捐贈給部落客,謝謝!

淺析CC2540的OSAL原理
如需轉載請标明出處:http://blog.csdn.net/itas109

繼續閱讀