Zigbee協議棧內核分析 - 按鍵分析

Zigbee協議棧內核分析 - 按鍵分析

                                                                                                                                                                                                                                                                                   Jesse

協議棧按鍵代碼分析 - 思維導圖(放大可看)

 

一、綜述

上圖從協議棧的 main() 函數開始分析,羅列出了 main()  函數裏調用的函數。接下來我們將會對函數一個一個的分析,有關於 key 的函數我將會用黃色方框表示。

 

二、協議棧代碼分析(按照上圖逐步分析)

********************************************************    輪詢    ********************************************************

1、osal_int_disable( INTS_ALL );           //關閉總中斷


2、HalDriverInit(); 

調用 HalKeyInit();           //按鍵初始化函數

HalKeyInit()
{
  ......
  /*Initialize callback function */
 pHalKeyProcessFunction  = NULL;        //初始化按鍵回調函數爲NULL
 
  /*Start with key is not configured */
 HalKeyConfigured = FALSE;                
}

3、osal_init_system();  //系統初始化函數

osal_init_system(); 裏調用 osalInitTasks(); 初始化任務;

osalInitTasks()
{
  ......
  SampleApp_Init( taskID );            //用戶在 應用層 自定義的任務初始化。
}
SampleApp_Init( taskID )
{
  ......
  RegisterForKeys( SampleApp_TaskID );
  ......
}

在自定義的任務初始化裏面調用了 RegisterForKeys(SampleApp_TaskID ); ,裏面的 registeredKeysTaskID = task_id; ,這個很關鍵,它將用戶自定義的 SampleApp_Task 任務註冊爲按鍵任務。

 

4、osal_int_enable( INTS_ALL );            //使能了總中斷


5、InitBoard( OB_READY );

調用了 HalKeyConfig(HAL_KEY_INTERRUPT_DISABLE,OnBoard_KeyCallback); 來配置按鍵。

HalKeyConfig(HAL_KEY_INTERRUPT_DISABLE,OnBoard_KeyCallback)
{
   ... ...
   (void)osal_set_event(Hal_TaskID, HAL_KEY_EVENT);    //用 tasksEvents[task_id] |= event_flag; 來通知Hal_Task任務有消息,使Hal_Task任務進入就緒態
   ... ...
   pHalKeyProcessFunction = cback;        //將 OnBoard_KeyCallback 函數賦值給按鍵回調函數指針
}


6、最後就是進入我們的 osal_start_system(); 操作系統,無限循環啦。

首先是先執行:

  do{
   if (tasksEvents[idx])      // Task is highest priority that is ready.
    {
     break;
    }
  }while (++idx < tasksCnt);

來查詢是否有任務進入了就緒狀態。我們回去看看第 5 步,在第 5 步的時候有調用到 osal_set_event(Hal_TaskID,HAL_KEY_EVENT); 設置了一個消息給 Hal_Task 任務使Hal_Task任務進入了就緒態。

所以接下來協議棧要執行:

 if(idx < tasksCnt)       //如果任務是有登記的
  {
   uint16 events;
   halIntState_t intState;
   HAL_ENTER_CRITICAL_SECTION(intState);
   events = tasksEvents[idx];
   tasksEvents[idx] = 0;  // Clearthe Events for this task.
   HAL_EXIT_CRITICAL_SECTION(intState);
   activeTaskID = idx;
   events = (tasksArr[idx])( idx, events );       //這一句調用了按鍵的處理函數
   activeTaskID = TASK_NO_TASK;
   HAL_ENTER_CRITICAL_SECTION(intState);
   tasksEvents[idx] |= events;  //Add back unprocessed events to the current task.
   HAL_EXIT_CRITICAL_SECTION(intState);
  }

events = (tasksArr[idx])( idx, events ); 這一句調用了Hal_Task的處理函數。讓我們進入 tasksArr[idx] 看看,有了,Hal_Task任務對應的Hal_ProcessEvent 硬件抽象層的事件處理函數,我們進去看看。

uint16 Hal_ProcessEvent( uint8 task_id,uint16 events )
{
   ... ...
   #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
   ... ...
}

我們先看一下 HalKeyPoll();函數:

HalKeyPoll()
{
  ......
  if(newKeys && pHalKeyProcessFunction)
  {
   (pHalKeyProcessFunction)(newKeys, HAL_KEY_STATE_NORMAL);  //調用了按鍵處理回調函數
  }
  ......
}

按鍵輪詢函數裏調用了 (pHalKeyProcessFunction)(newKeys,HAL_KEY_STATE_NORMAL); 按鍵處理回調函數來處理按鍵事件,那麼這個函數指針到底指向哪呢?讓我們回去看看上面,有兩個地方出現了回調函數指針的賦值。

(1)在第 2 步的時候 pHalKeyProcessFunction  = NULL; 初始化按鍵回調函數指針爲NULL;

(2)在第 5 步的時候調用了 HalKeyConfig(HAL_KEY_INTERRUPT_DISABLE, OnBoard_KeyCallback); 函數將 OnBoard_KeyCallback函數賦值給按鍵回調函數指針。

所以現在我們就去找 OnBoard_KeyCallback()看看它是怎麼處理按鍵事件的吧。

OnBoard_KeyCallback()
{
   ... ...
   if ( OnBoard_SendKeys( keys, shift ) != ZSuccess )
   ... ...
}

OnBoard_SendKeys() 函數將發送一個消息給 registeredKeysTask 任務。

OnBoard_SendKeys()
{
  ......
  if( registeredKeysTaskID != NO_TASK_ID )
  {
   // Send the address to the task
   msgPtr = (keyChange_t *)osal_msg_allocate( sizeof(keyChange_t) );
   if ( msgPtr )
    {
     msgPtr->hdr.event = KEY_CHANGE;     //將消息打包
     msgPtr->state = state;
     msgPtr->keys = keys;
     //將消息發送給按鍵任務
     osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );
    }
   return ( ZSuccess );
  }
  ......
}

看上面的代碼,協議棧首先將消息打包,然後通過 osal_msg_send() 函數將消息發送給 registeredKeysTask 任務(也就是用戶自定義的SampleApp_Task任務)。

我們進去 osal_msg_send()函數 裏看看它是怎麼發送消息的吧。

osal_msg_send()
{
  ......
  //queue message
  osal_msg_enqueue(&osal_qHead, msg_ptr );
  //Signal the task that a message is waiting
 osal_set_event( destination_task, SYS_EVENT_MSG );
  ......
}

看看上面的代碼,我把最重要的兩句列了出來:

首先是osal_msg_enqueue( &osal_qHead, msg_ptr ); ,將消息放入消息隊列中;

最後果然還是要用到我們的 osal_set_event( destination_task, SYS_EVENT_MSG ); 函數來通知任務有消息發過來了!那這次是通知那個任務呢,讓我們回去看看destination_task指的是什麼任務吧。原來是第 6 步的按鍵回調函數裏一直往裏面看可以看到osal_msg_send(registeredKeysTaskID, (uint8 *)msgPtr );,所以通知的是registeredKeysTask任務,也就是用戶自定義的SampleApp_Task任務。

然後 osal_run_system()又進入

  do{
   if (tasksEvents[idx])  // Task ishighest priority that is ready.
    {
     break;
    }
  }while (++idx < tasksCnt);

此時,託osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr);的福,我們的SampleApp_Task終於進入了就緒態。

那麼接下來的 events = (tasksArr[idx])( idx, events );,我們進入的是我們用戶自定義的 SampleApp_ProcessEvent 處理函數。

SampleApp_ProcessEvent()
{
   if ( events & SYS_EVENT_MSG )
  {
    MSGpkt = (afIncomingMSGPacket_t*)osal_msg_receive( SampleApp_TaskID );
    while ( MSGpkt )
    {
      switch ( MSGpkt->hdr.event )
      {
        // Received when a key is pressed
        case KEY_CHANGE:
          SampleApp_HandleKeys( ((keyChange_t*)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );
          break;
        … …
}

然後就進入了SampleApp_HandleKeys按鍵處理函數了!

就這樣,按鍵從協議棧系統一開始,到執行了一次用戶自定義的按鍵處理函數的過程就全部結束啦!
然後是不是按鍵就這樣結束了呢,那怎麼叫輪詢呢?!!!!

讓我們看回到第 6 步的 Hal_ProcessEvent處理函數裏面吧,它除了調用了按鍵輪詢函數外,還調用了osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);,這個函數的作用是 100ms 後再次讓Hal_TaskID任務進入準備態,此時就會無限循環的重複第 6 步,從而達到輪詢的功能!



********************************************************    中斷    ********************************************************

上面分析的是按鍵的輪詢模式,那按鍵的中斷模式協議棧又是怎麼執行的呢,接下來我們一起來看一下吧~

在第 5 步的時候,也就是第 2 次板載初始化的時候,該函數 InitBoard( OB_READY ); 會調用 HalKeyConfig(HAL_KEY_INTERRUPT_DISABLE, OnBoard_KeyCallback);,協議棧原設置的參數時 HAL_KEY_INTERRUPT_DISABLE,如果要使用按鍵的中斷模式,我們要將 HAL_KEY_INTERRUPT_DISABLE 改爲 HAL_KEY_INTERRUPT_ENABLE,接下來我們就進入 HalKeyConfig() 裏面看看吧。
void HalKeyConfig(bool interruptEnable, halKeyCBack_t cback)
{
  if ((Hal_KeyIntEnable = interruptEnable))
  {
    HAL_KEY_CLR_INT();             // Clear spurious ints.
    PICTL |= 0x01;                 // P1ICONL: Falling edge ints on pins 0-3.
    P1IEN |= PUSH1_BV | PUSH2_BV;  // Enable specific P1 bits for ints by bit mask.
    IEN2  |= 0x10;                 // Enable general P1 interrupts.
  }
  else
  {
    (void)osal_set_event(Hal_TaskID, HAL_KEY_EVENT);
  }
  pHalKeyProcessFunction = cback;
}
如果參數爲 HAL_KEY_INTERRUPT_ENABLE,將執行 if 裏面的語句初始化按鍵中斷模式。也就是沒有執行 osal_set_event(Hal_TaskID, HAL_KEY_EVENT); 了,也就不會進行按鍵輪詢了,那麼當我們按下按鍵的時候,協議棧又是怎麼進行終端處理的呢?

接下來我們來看看 Hal_key.c 裏面的 HAL_ISR_FUNCTION() 函數。
HAL_ISR_FUNCTION( halKeyPort0Isr, P0INT_VECTOR )
{
  HAL_ENTER_ISR();

  if (HAL_KEY_SW_6_PXIFG & HAL_KEY_SW_6_BIT)
  {
    halProcessKeyInterrupt();
  }

  /*
    Clear the CPU interrupt flag for Port_0
    PxIFG has to be cleared before PxIF
  */
  HAL_KEY_SW_6_PXIFG = 0;
  HAL_KEY_CPU_PORT_0_IF = 0;
  
  CLEAR_SLEEP_MODE();
  HAL_EXIT_ISR();
}
當有按鍵按下的時候,則會進入這個外部中斷服務函數,調用 halProcessKeyInterrupt(); 來處理按鍵事件。
void halProcessKeyInterrupt(void)
{
	... ...
	  if (valid)
         {
              osal_start_timerEx(Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_DEBOUNCE_VALUE);
         }
	... ...
}
在這個按鍵中斷處理函數裏面我們又見到了非常熟悉的 osal_start_timerEx() 函數了,這個函數在這起什麼作用呢?
作用有兩個:
1、經過 25ms 之後才發送事件是爲了延時去抖動。
2、發送一條按鍵事件消息給 Hal_Task 任務。
既然 Hal_Task 任務收到了按鍵事件消息,那接下來協議棧處理的流程也就和按鍵輪詢後面的執行流程一樣了。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章