(zigbee學習總結二)Z-stack按鍵機制


本文是自己學習zigbee時的知識梳理。

參考書:《ZigBee技術與實訓教程--基於CC2530的無線傳感網技術》----姜仲、劉丹 編著

Z-stack中提供了兩種方式采集按鍵數據:輪詢方式和中斷方式。輪詢方式:每隔一定時間,檢測按鍵狀態,進行相應處理;中斷方式:按鍵引起按鍵中斷,進行相應處理。Zstack在默認情況下,使用輪詢方式進行處理。

一、按鍵的宏定義

在HAL/include/hal_key.h中對按鍵進行了基本的定義:

/* 中斷使能和禁用*/
#define HAL_KEY_INTERRUPT_DISABLE    0x00
#define HAL_KEY_INTERRUPT_ENABLE     0x01

/* Key state - shift or nornal */
#define HAL_KEY_STATE_NORMAL          0x00
#define HAL_KEY_STATE_SHIFT           0x01

/* 搖桿和按鍵定義*/
#define HAL_KEY_SW_1 0x01  // Joystick up
#define HAL_KEY_SW_2 0x02  // Joystick right
#define HAL_KEY_SW_5 0x04  // Joystick center
#define HAL_KEY_SW_4 0x08  // Joystick left
#define HAL_KEY_SW_3 0x10  // Joystick down
#define HAL_KEY_SW_6 0x20  // Button S1 if available
#define HAL_KEY_SW_7 0x40  // Button S2 if available

/* 搖桿定義*/
#define HAL_KEY_UP     0x01  // Joystick up
#define HAL_KEY_RIGHT  0x02  // Joystick right
#define HAL_KEY_CENTER 0x04  // Joystick center
#define HAL_KEY_LEFT   0x08  // Joystick left
#define HAL_KEY_DOWN   0x10  // Joystick down

在HAL/Target/hal_key.c中,對按鍵進行了具體的配置:

#define HAL_KEY_RISING_EDGE   0
#define HAL_KEY_FALLING_EDGE  1
#define HAL_KEY_DEBOUNCE_VALUE  25

/* 配置按鍵和搖桿的中斷狀態寄存器*/
#define HAL_KEY_CPU_PORT_0_IF P0IF
#define HAL_KEY_CPU_PORT_2_IF P2IF

/* SW_6 位P0.1進行配置*/
#define HAL_KEY_SW_6_PORT   P0
#define HAL_KEY_SW_6_BIT    BV(1)  //由於SW_6在P0_1,所以定義為BV(1),BV(N)即為1向左移動N位
#define HAL_KEY_SW_6_SEL    P0SEL
#define HAL_KEY_SW_6_DIR    P0DIR

/* 中斷觸發方式配置*/
#define HAL_KEY_SW_6_EDGEBIT  BV(0)
#define HAL_KEY_SW_6_EDGE     HAL_KEY_FALLING_EDGE


/* SW_6 interrupts */
#define HAL_KEY_SW_6_IEN      IEN1  /* SW_6端口中斷使能寄存器 */
#define HAL_KEY_SW_6_IENBIT   BV(5) /* Mask bit for all of Port_0 */
#define HAL_KEY_SW_6_ICTL     P0IEN /* SW_6位中斷使能*/
#define HAL_KEY_SW_6_ICTLBIT  BV(1) /* P0IEN - P0.1 enable/disable bit */
#define HAL_KEY_SW_6_PXIFG    P0IFG /*SW_6位中斷標志寄存器*/

/*搖桿P2.0寄存器配置*/
#define HAL_KEY_JOY_MOVE_PORT   P2
#define HAL_KEY_JOY_MOVE_BIT    BV(0)
#define HAL_KEY_JOY_MOVE_SEL    P2SEL
#define HAL_KEY_JOY_MOVE_DIR    P2DIR

/* 中斷觸發方式*/
#define HAL_KEY_JOY_MOVE_EDGEBIT  BV(3)
#define HAL_KEY_JOY_MOVE_EDGE     HAL_KEY_FALLING_EDGE

/* 搖桿中斷寄存器*/
#define HAL_KEY_JOY_MOVE_IEN      IEN2  /* CPU interrupt mask register */
#define HAL_KEY_JOY_MOVE_IENBIT   BV(1) /* Mask bit for all of Port_2 */
#define HAL_KEY_JOY_MOVE_ICTL     P2IEN /* Port Interrupt Control register */
#define HAL_KEY_JOY_MOVE_ICTLBIT  BV(0) /* P2IENL - P2.0<->P2.3 enable/disable bit */
#define HAL_KEY_JOY_MOVE_PXIFG    P2IFG /* Interrupt flag at source */

二、按鍵代碼的初始化
按鍵的初始化屬於硬件的初始化,硬件的初始化通過在主函數main()中調用HalDriverInit()來集中處理。在HAL/Common /hal_drivers中 的HalDriverInit() 函數代碼如下:

void HalDriverInit (void)
{
  /* TIMER */
#if (defined HAL_TIMER) && (HAL_TIMER == TRUE)
  #error "The hal timer driver module is removed."
#endif

  /* ADC */
#if (defined HAL_ADC) && (HAL_ADC == TRUE)
  HalAdcInit();
#endif

  /* DMA */
#if (defined HAL_DMA) && (HAL_DMA == TRUE)
  // Must be called before the init call to any module that uses DMA.
  HalDmaInit();
#endif

  /* AES */
#if (defined HAL_AES) && (HAL_AES == TRUE)
  HalAesInit();
#endif

  /* LCD */
#if (defined HAL_LCD) && (HAL_LCD == TRUE)
  HalLcdInit();
#endif

  /* LED */
#if (defined HAL_LED) && (HAL_LED == TRUE)
  HalLedInit();
#endif

  /* UART */
#if (defined HAL_UART) && (HAL_UART == TRUE)
  HalUARTInit();
#endif

  /* KEY */
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
  HalKeyInit();
#endif

  /* SPI */
#if (defined HAL_SPI) && (HAL_SPI == TRUE)
  HalSpiInit();
#endif

  /* HID */
#if (defined HAL_HID) && (HAL_HID == TRUE)
  usbHidInit();
#endif
}

按鍵的初始化時在條件滿足情況下才編譯的。除此之外,使用搖桿時還要確保HAL_ADC為真,即Zstack協議棧使用AD采集。在HAL/Target/CC2530EB/config目錄下的hal_board_cfg.h中有:

#ifndef HAL_ADC
#define HAL_ADC TRUE
#endif
#ifndef HAL_KEY
#define HAL_KEY TRUE
#endif

由以上代碼可以看出,在缺省情況下,可以使用獨立的按鍵,也可以使用模擬的搖桿。
在上面的HalDriverInit()函數中,可以看到按鍵的初始化是通過調用HalKeyInit()函數來實現的。HalKeyInit()代碼如下:

void HalKeyInit( void )
{
  /*初始化按鍵為0 */
  halKeySavedKeys = 0;

  HAL_KEY_SW_6_SEL &= ~(HAL_KEY_SW_6_BIT);    /* 設定引腳為普通IO*/
  HAL_KEY_SW_6_DIR &= ~(HAL_KEY_SW_6_BIT);    /* 設定引腳為輸入*/

  HAL_KEY_JOY_MOVE_SEL &= ~(HAL_KEY_JOY_MOVE_BIT); /* 設定引腳為普通IO*/
  HAL_KEY_JOY_MOVE_DIR &= ~(HAL_KEY_JOY_MOVE_BIT); /* 設定引腳為輸入*/


  /* 初始化按鍵回調函數為空*/
  pHalKeyProcessFunction  = NULL;

  /* 初始化后,按鍵標示為沒有配置*/
  HalKeyConfigured = FALSE;
}

在該函數中,有三個全局變量值,其中,halKeySaveKeys用來保存按鍵值;PHalKeyProcessFunction為指向按鍵處理函數的指針;HalKeyConfigured用來標示按鍵是否被配置。
三、按鍵的配置

按鍵的配置函數在板載初始化函數InitBoard()中被調用,而板載初始化函數在主函數main()中被調用。InitBoard()代碼如下:

void InitBoard( uint8 level )
{
  if ( level == OB_COLD )
  {
    // IAR does not zero-out this byte below the XSTACK.
    *(uint8 *)0x0 = 0;
    // Interrupts off
    osal_int_disable( INTS_ALL );
    // Check for Brown-Out reset
    ChkReset();
  }
  else  // !OB_COLD
  {
    /* Initialize Key stuff */ HalKeyConfig(HAL_KEY_INTERRUPT_DISABLE, OnBoard_KeyCallback);   }
}

我們可以看到默認情況下按鍵配置函數HalKeyConfig()的第一個參數為HAL_KEY_INTERRUPT_DISABLE,即按鍵的處理方式為輪詢方式。按鍵的配置函數HalKeyConfig()代碼如下:

void HalKeyConfig (bool interruptEnable, halKeyCBack_t cback)
{
  /* Enable/Disable Interrupt or */
  Hal_KeyIntEnable = interruptEnable;  //Hal_KeyIntEnable為全局變量,缺省狀態下為false,表明按鍵處理方式
  /* Register the callback fucntion */
  pHalKeyProcessFunction = cback;

  /* Determine if interrupt is enable or not */
  if (Hal_KeyIntEnable)            //中斷方式
  {
    /* Rising/Falling edge configuratinn */

    PICTL &= ~(HAL_KEY_SW_6_EDGEBIT);    /* 清除觸發方式位*/
    /* For falling edge, the bit must be set. */
  #if (HAL_KEY_SW_6_EDGE == HAL_KEY_FALLING_EDGE)
    PICTL |= HAL_KEY_SW_6_EDGEBIT;
  #endif

/*
Interrupt configuration: * - Enable interrupt generation at the port * - Enable CPU interrupt * - Clear any pending interrupt */ HAL_KEY_SW_6_ICTL |= HAL_KEY_SW_6_ICTLBIT; //使能端口中斷寄存器 HAL_KEY_SW_6_IEN |= HAL_KEY_SW_6_IENBIT; //使能位中斷 HAL_KEY_SW_6_PXIFG = ~(HAL_KEY_SW_6_BIT); //清除中斷標志位
/* Rising/Falling edge configuratinn */ HAL_KEY_JOY_MOVE_ICTL &= ~(HAL_KEY_JOY_MOVE_EDGEBIT); /* Clear the edge bit */ /* For falling edge, the bit must be set. */ #if (HAL_KEY_JOY_MOVE_EDGE == HAL_KEY_FALLING_EDGE) HAL_KEY_JOY_MOVE_ICTL |= HAL_KEY_JOY_MOVE_EDGEBIT; #endif
/*
Interrupt configuration: * - Enable interrupt generation at the port * - Enable CPU interrupt * - Clear any pending interrupt */ HAL_KEY_JOY_MOVE_ICTL |= HAL_KEY_JOY_MOVE_ICTLBIT; HAL_KEY_JOY_MOVE_IEN |= HAL_KEY_JOY_MOVE_IENBIT; HAL_KEY_JOY_MOVE_PXIFG = ~(HAL_KEY_JOY_MOVE_BIT); /* Do this only after the hal_key is configured - to work with sleep stuff */ if (HalKeyConfigured == TRUE) { osal_stop_timerEx(Hal_TaskID, HAL_KEY_EVENT); /* Cancel polling if active */ } } else /* Interrupts NOT enabled */ { HAL_KEY_SW_6_ICTL &= ~(HAL_KEY_SW_6_ICTLBIT); /* don't generate interrupt */ HAL_KEY_SW_6_IEN &= ~(HAL_KEY_SW_6_IENBIT); /* Clear interrupt enable bit */ osal_set_event(Hal_TaskID, HAL_KEY_EVENT); } /* Key now is configured */ HalKeyConfigured = TRUE; }

輪詢方式為按鍵的默認處理方式。在輪詢方式完成后,調用函數osal_set_evevt(Hal_TaskID,HAL_KEY_EVENT),觸發事件HAL_KEY_EVENT,其任務ID位Hal_TaskID,檢測到事件HAL_KEY_EVENT,則調用相應的處理函數Hal_ProcessEvent()。
如果按鍵設置為中斷方式,需要設置按鍵是上升沿還是下降沿觸發,同時需要將按鍵對應的IO口配置為允許中斷,即中斷允許。缺省狀態下為上升沿觸發。按鍵配置為中斷狀態時,在程序中沒有定時觸發HAL_KEY_EVENT 的事件,而是交由中斷函數處理,當有按鍵按下時中斷函數就會捕獲中斷,然后調用按鍵的處理函數進一步進行相關處理。

四、輪詢方式按鍵處理

輪詢方式配置完成后,Zstack調用函數osal_set_event(Hal_TaskID,HAL_KEY_EVENT)觸發事件HAL_KEY_EVENT,其任務ID為Hal_TaskID,在Zstack主循環中,檢測到事件HAL_KEY_EVENT,則調用相應的HAL層的事件處理函數Hal_ProcessEvent(),在HAL/common /hal_drivers.c中Hal_ProcessEvent()的有如下代碼:

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;
  }

 由以上代碼可以看出處理HAL_KEY_EVENT事件時調用了函數HalKeyPoll(),函數HalKeyPoll()負責去檢測是否有按鍵按下。在HalKeyPoll()檢測完按鍵后,有if語句判斷呢按鍵是否為輪詢方式處理,這里是以輪詢方式處理按鍵,所以滿足if條件判斷語句的條件,即執行函數osal_start_timeEx()定時再次觸發事件HAL_KEY_EVENT,定時長度為100ms,由此定時觸發事件HAL_KEY_EVENT完成對按鍵的定時輪詢。

檢測是否有按鍵按下的的函數HalKeyPoll()的代碼如下:

void HalKeyPoll (void)
{
  uint8 keys = 0;
if ((HAL_KEY_JOY_MOVE_PORT & HAL_KEY_JOY_MOVE_BIT)) /* 搖桿按下,Key is active HIGH */ { keys = halGetJoyKeyInput(); //獲得搖桿是哪個輸入狀態 } /* 輪詢方式下,通過對比前后按鍵的狀態來判斷是否有按鍵按下*/ if (!Hal_KeyIntEnable) { if (keys == halKeySavedKeys) { /* Exit - since no keys have changed */ return; } /* Store the current keys for comparation next time */ halKeySavedKeys = keys; } else { /* 中斷方式下按鍵處理 */ } if (HAL_PUSH_BUTTON1()) { keys |= HAL_KEY_SW_6; } /* Invoke Callback if new keys were depressed */ if (keys && (pHalKeyProcessFunction)) { (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL); } }

在HalKeyPoll中對所有按鍵都進行了檢測,首先判斷是否是搖桿按下,如果是,則通過調用函數halGetJoyInput()獲得搖桿的按鍵狀態,在輪詢方式下通過對比前后按鍵的狀態是否相同,來判斷是否有按鍵按下,如果沒有,則返回不進行處理;如果有,則把按鍵狀態賦值給全局變量hal_KeySavedKeys,以便於下次進行比較。接下來檢測按鍵1是否按下,如果按下,置位keys中相應位,最后當keys不為0並且在HalKeyConfig()中為按鍵配置了回調函數OnBoard_KeyCalback()時,即可用回調函數對按鍵進行處理。回調函數OnBoard_KeyCallback()的代碼如下:

void OnBoard_KeyCallback ( uint8 keys, uint8 state )
{
  uint8 shift;
  (void)state;

  shift = (keys & HAL_KEY_SW_6) ? true : false;

  if ( OnBoard_SendKeys( keys, shift ) != ZSuccess )
  {
    // Process SW1 here
    if ( keys & HAL_KEY_SW_1 )  // Switch 1
    {
    }
    // Process SW2 here
    if ( keys & HAL_KEY_SW_2 )  // Switch 2
    {
    }
    // Process SW3 here
    if ( keys & HAL_KEY_SW_3 )  // Switch 3
    {
    }
    // Process SW4 here
    if ( keys & HAL_KEY_SW_4 )  // Switch 4
    {
    }
    // Process SW5 here
    if ( keys & HAL_KEY_SW_5 )  // Switch 5
    {
    }
    // Process SW6 here
    if ( keys & HAL_KEY_SW_6 )  // Switch 6
    {
    }
  }
}

需要注意的是Z-stack中將SW6看作是shift鍵,在Onboard_KeyCalback()函數中調用了OnBoard_SendKeys()進一步處理。OnBoard_SendKeys()中將會將按鍵的值和按鍵的狀態進行"打包"發送到注冊過按鍵的那一層。具體代碼如下:

uint8 OnBoard_SendKeys( uint8 keys, uint8 state )
{
  keyChange_t *msgPtr;

  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 );
  }
  else
    return ( ZFailure );
}

如果要使用按鍵必須給按鍵注冊,但按鍵只能注冊給一個任務層,Z-stack對按鍵信息進行打包處理,封裝到信息包msgPtr中,將要觸發的事件KEY_CHANGE,按鍵的狀態state和按鍵的值keys一並封裝。然后調用osal_msg_send()將按鍵的信息發送到注冊按鍵的應用層。按鍵注冊函數代碼如下:

uint8 RegisterForKeys( uint8 task_id )
{
  // Allow only the first task
  if ( registeredKeysTaskID == NO_TASK_ID )
  {
    registeredKeysTaskID = task_id;
    return ( true );
  }
  else
    return ( false );
}

在SampleAPP工程中,在輪詢按鍵的處理過程中,Z-STACK最終觸發了SampleApp應用層的事件處理函數KEY_CHANGE事件,代碼如下:

uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
  afIncomingMSGPacket_t *MSGpkt;
  (void)task_id;  // Intentionally unreferenced parameter

  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;
.
.
.
.
}

在處理HAL_KEY_EVENT事件時調用了應用層按鍵處理函數Sample_HandleKeys()對按鍵進行了進一步的處理,其代碼如下:

void SampleApp_HandleKeys( uint8 shift, uint8 keys )
{
  (void)shift;  // Intentionally unreferenced parameter
  
  if ( keys & HAL_KEY_SW_1 )
  {
    /* This key sends the Flash Command is sent to Group 1.
     * This device will not receive the Flash Command from this
     * device (even if it belongs to group 1).
     */
    SampleApp_SendFlashMessage( SAMPLEAPP_FLASH_DURATION );
  }

  if ( keys & HAL_KEY_SW_2 )
  {
    /* The Flashr Command is sent to Group 1.
     * This key toggles this device in and out of group 1.
     * If this device doesn't belong to group 1, this application
     * will not receive the Flash command sent to group 1.
     */
    aps_Group_t *grp;
    grp = aps_FindGroup( SAMPLEAPP_ENDPOINT, SAMPLEAPP_FLASH_GROUP );
    if ( grp )
    {
      // Remove from the group
      aps_RemoveGroup( SAMPLEAPP_ENDPOINT, SAMPLEAPP_FLASH_GROUP );
    }
    else
    {
      // Add to the flash group
      aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group );
    }
  }
}

在按鍵處理函數中根據按鍵值的不同調用不同的函數完成其功能。

五、中斷方式按鍵處理

在按鍵配置函數HalKeyconfig()中將按鍵配置為中斷方式后,使能了按鍵相對應的I/O口的中斷,當發生了按鍵的動作時,就會觸發按鍵事件,從而調用端口的中斷處理函數。

 P0端口中斷處理函數在HAL/Target/Drivers目錄下的hal_key.c中,其代碼如下:

HAL_ISR_FUNCTION( halKeyPort2Isr, P2INT_VECTOR )
{
  HAL_ENTER_ISR();
  
  if (HAL_KEY_JOY_MOVE_PXIFG & HAL_KEY_JOY_MOVE_BIT)
  {
    halProcessKeyInterrupt();
  }

  /*
    Clear the CPU interrupt flag for Port_2
    PxIFG has to be cleared before PxIF
    Notes: P2_1 and P2_2 are debug lines.
  */
  HAL_KEY_JOY_MOVE_PXIFG = 0;
  HAL_KEY_CPU_PORT_2_IF = 0;

  CLEAR_SLEEP_MODE();
  HAL_EXIT_ISR();
}

在中斷函數中調用了按鍵halProcessKeyInterrupt()對中斷進行處理,且將P0口中斷標志位清零。

中斷處理函數halProcessKeyInterrupt()代碼如下:

void halProcessKeyInterrupt (void)
{
  bool valid=FALSE;

  if (HAL_KEY_SW_6_PXIFG & HAL_KEY_SW_6_BIT)  /* Interrupt Flag has been set */
  {
    HAL_KEY_SW_6_PXIFG = ~(HAL_KEY_SW_6_BIT); /* Clear Interrupt Flag */
    valid = TRUE;
  }

  if (HAL_KEY_JOY_MOVE_PXIFG & HAL_KEY_JOY_MOVE_BIT)  /* Interrupt Flag has been set */
  {
    HAL_KEY_JOY_MOVE_PXIFG = ~(HAL_KEY_JOY_MOVE_BIT); /* Clear Interrupt Flag */
    valid = TRUE;
  }

  if (valid)
  {
    osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_DEBOUNCE_VALUE);
  }
}

函數中局部變量valid標示了是否有按鍵按下,如果有按鍵按下則定時觸發HAL_KEY_EVENT事件。在該函數中通過檢測按鍵對應位的中斷標志位是否為1來判斷按鍵是否按下。CC2530的每一個I/O都可以產生中斷,如果有按鍵按下則要將對應位的中斷標志位清零,同時將變量設置為TRUE,從而觸發HAL_KEY_EVENT事件對按鍵進行處理。

在按鍵中斷處理函數中halProcessKeyInterrupt()中並沒有讀取按鍵的值,而是定時觸發了HAL_KEY_EVENT事件,在處理HAL_KEY_EVENT事件時進行讀取按鍵。定時時長HAL_KEY_DEBOUNCE_VALUE為25ms,功能是為了按鍵消抖。

在Zstack主循環中,檢測到事件HAL_KEY_EVENT,則調用對應的HAL層的事件處理函數Hal_ProcessEvent(),余下的過程與輪詢方式就完全相同了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM