Z-stack按键机制

参考书:《ZigBee技术与实训教程--基于CC2530的无线传感网技术》----姜仲、刘丹 编著
https://www.cnblogs.com/perfect2014/p/4122706.html

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(),余下的过程与轮询方式就完全相同了。


 


从现在起,开始努力。

猜你喜欢

转载自blog.csdn.net/leansmall/article/details/80262374
今日推荐