分享

协议栈按键流程(转)

 韦小枫 2012-11-30

我使用的协议栈版本及例子信息:
ZigBee2006\Texas Instruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\SampleApp

记录下个人对按键流程的理解协议栈按键流程 - 小峰 - happy~

在hal_key.c中有一段说明:
/*********************************************************************
 NOTE: If polling is used, the hal_driver task schedules the KeyRead()
       to occur every 100ms.  This should be long enough to naturally
       debounce(去抖动) the keys.  The KeyRead() function remembers the key
       state of the previous poll and will only return a non-zero
       value if the key state changes.

 NOTE: If interrupts are used, the KeyRead() function is scheduled
       25ms after the interrupt occurs by the ISR.  This delay is used
       for key debouncing.  The ISR disables any further Key interrupt
       until KeyRead() is executed.  KeyRead() will re-enable Key
       interrupts after executing.  Unlike polling, when interrupts
       are enabled, the previous key state is not remembered.  This
       means that KeyRead() will return the current state of the keys
       (not a change in state of the keys).

 NOTE: If interrupts are used, the KeyRead() fucntion is scheduled by
       the ISR.  Therefore, the joystick movements will only be detected
       during a pushbutton interrupt caused by S1 or the center joystick
       pushbutton.

 NOTE: When a switch like S1 is pushed, the S1 signal goes from a normally
       high state to a low state.  This transition is typically clean.  The
       duration of the low state is around 200ms.  When the signal returns
       to the high state, there is a high likelihood of signal bounce, which
       causes a unwanted interrupts.  Normally, we would set the interrupt
       edge to falling edge to generate an interrupt when S1 is pushed, but
       because of the signal bounce, it is better to set the edge to rising
       edge to generate an interrupt when S1 is released.  The debounce logic
       can then filter out the signal bounce.  The result is that we typically
       get only 1 interrupt per button push.  This mechanism is not totally
       foolproof because occasionally, signal bound occurs during the falling
       edge as well.  A similar mechanism is used to handle the joystick
       pushbutton on the DB.  For the EB, we do not have independent control
       of the interrupt edge for the S1 and center joystick pushbutton.  As
       a result, only one or the other pushbuttons work reasonably well with
       interrupts.  The default is the make the S1 switch on the EB work more
       reliably.

*********************************************************************/

对本协议中,对按键有两种处理方式
1、中断法:有按键按下,则进入中断,开启一软定时器25ms后,读取键值进行相应处理
2、查询法:开启一软定时器,系统每隔100ms进行轮询,如有按键按下读取键值进行相应处理
                   (软定时器即软件定时器,参见“系统时钟定时器”说明)                  

首先看下协议栈对KEY的初始化,在InitBoard()函数中:

     /* Initialize Key stuff */
    OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE;
    HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);

从而知道协议栈默认的按键处理机制是查询法.就先来看下查询法的流程:

 

1、查询法(或者轮询法)
首先看下HalKeyConfig()
这个函数用于把按键/开关/操纵杆服务配置为轮询或中断驱动。它还为服务配置一个回调函数。如果不使用中断,轮询在100ms后自动开始。按键/开关/操纵杆将每100ms被轮询一次。如果使用中断,就使用一个ISR来处理这种情况.在中断发生后,有一个25ms的延时以消除回跳.
/***************************************************************************
void HalKeyConfig (bool interruptEnable, halKeyCBack_t cback)
{
   ……………………

  if (Hal_KeyIntEnable)
  {

     ………… (清除中断标志,设置上升沿下降沿触发外部中断等)

    /* 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 */
  {

    ………… (清除中断标志等)
    osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE); /* Kick off polling */
  }

}
/***************************************************************************

可以看到查询法中,开启了一个系统软定时器,任务是HAL层任务,事件是HAL_KEY_POLLING_VALUE,系统要定时轮询按键事件得到按键值再作相应处理。轮询时间为#define HAL_KEY_POLLING_VALUE   100,即100ms.跟前面的英文说明一致。当这个软定时器溢出时,便设置事件发生标志调用HAL层任务事件处理函数,然后从系统时钟链表中删除这个软定时器设置(后面会看到当处理完按键事件后又会重新启动这个软定时器).
下面就来看下它所调用的HAL层任务事件处理函数:

/***************************************************************************
uint16 Hal_ProcessEvent( uint8 task_id, uint16 events )
{
  uint8 *msgPtr;

 ………… (其它事件处理)

  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);  //因为在 HalKeyInit()时,如果采用
    }                        //非中断方式,已经初始化设置过一个按键事件软定时器,当这个软定时器
                             //溢出时,便设置事件发生标志调用事件处理函数,然后从系统软定时器链表
                             //中删除这个定时器设置,因此这里调用HalKeyPoll()处理完后续工作后
                             //如果还是采用非中断方式,则要重新开启一个软定时器!
#endif  // HAL_KEY       

    return events ^ HAL_KEY_EVENT;
  }

 …………  (其它事件处理)

}
/***************************************************************************

可以看到HAL层任务事件处理函数中对按键事件HAL_KEY_EVENT调用HalKeyPoll()进行下一步处理,在HalKeyPoll()处理完后跳出来,立马开启一个新的定时器,跟前面采用查询法(非中断:!Hal_KeyIntEnable)时开启的定时器一样,准备100ms后的下一次查询!
下面看下
HalKeyPoll()

/***************************************************************************
void HalKeyPoll (void)
{
 …………
(调用HalAdcRead(),检测模拟电压值最终得出键值keys)

  /* Exit if polling and no keys have changed */
  if (!Hal_KeyIntEnable)
  {
    if (keys == halKeySavedKeys)
    {
      return;
    }
    halKeySavedKeys = keys;     /* Store the current keys for comparation next time */
  }

  /* Invoke Callback if new keys were depressed */
  if (keys && (pHalKeyProcessFunction))
  {
    (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
  }

#endif

}
/***************************************************************************

可以看到这里通过一系列处理读取键值,最后调用按键的回调函数 (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);这之前有个存储键值为下一次比较做准备,这里是查询法与中断法之间键值读取的区别点,查询法需要存储键值,而中断法则直接读取当前键值就好,具体我不钻了。回到回调函数,在InitBoard()里调用HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback),即把回调函数初始化为OnBoard_KeyCallback(),下面来看下这个函数

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

  // shift key (S1) is used to generate key interrupt
  // applications should not use S1 when key interrupt is enabled

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

  if ( OnBoard_SendKeys( keys, shift ) != ZSuccess )
  {
 …………  (如果不成功进行的一些处理)
  }
}
/***************************************************************************

可以看到得到一个参数shift,然后调用了OnBoard_SendKeys( keys, shift ),来看下这个函数

/***************************************************************************
byte OnBoard_SendKeys( byte keys, byte state )
{
  keyChange_t *msgPtr;

  if ( registeredKeysTaskID != NO_TASK_ID )  //按键事件被注册在sampleAPP应用registeredKeysTaskID=SampleApp_TaskID
  {
    // 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 );
}
/***************************************************************************

可以看到首先涉及到一个参数registeredKeysTaskID,在OnBoard.c中它被定义为:
static byte registeredKeysTaskID = NO_TASK_ID;
如果是这样的话,那按键事件处理到这里就返回一个ZFailure,没有任何反应了,但在SampleApp这个例子中是按SW1:发送闪烁消息到组1,按SW2:进/退组…这是为何?这里涉及到一个函数RegisterForKeys( byte task_id ),来看下:

/***************************************************************************
 * Keyboard Register function
 *
 * The keyboard handler is setup to send all keyboard changes to
 * one task (if a task is registered).
 *
 * If a task registers, it will get all the keys. You can change this
 * to register for individual keys.

 ************************************
byte RegisterForKeys( byte task_id )  //task_id=SampleApp_TaskID
{
  // Allow only the first task
  if ( registeredKeysTaskID == NO_TASK_ID )
  {
    registeredKeysTaskID = task_id;
    return ( true );
  }
  else
    return ( false );
}
/***************************************************************************
可以看到一个应用任务要得到按键值,那必须首先进行注册,这样一旦有按键发生,这个任务才能得到键值进行相应的处理,而协议栈中唯一一个用户应用任务SampleApp就对其进行了注册,具体在SampleApp_Init()函数中有这么一句:

// Register for all key events - This app will handle all key events
RegisterForKeys( SampleApp_TaskID );

即可由这个任务SampleApp来处理按键事件。通过函数参数传递易知最后registeredKeysTaskID=SampleApp_TaskID,然后再回到OnBoard_SendKeys()这个函数中,此时registeredKeysTaskID != NO_TASK_ID,所以调用 osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr ),而这个函数里又调用osal_set_event(destination_task, SYS_EVENT_MSG)来触发事件发生标志,系统调用相应任务事件处理函数,任务ID为SampleApp_TaskID,即 SampleApp_ProcessEvent(),通过参数SYS_EVENT_MSGOnBoard_SendKeys()里的msgPtr->hdr.event = KEY_CHANGE可知任务事件处理函数SampleApp_ProcessEvent()最终调用SampleApp_HandleKeys(),SampleApp_HandleKeys()里面对按键进行了处理,包括按SW1:发送闪烁消息到组1,按SW2:进/退组。
   
至此,协议栈的按键查询法流程结束,一次下来两次触发系统事件(第一次通过osal_start_timerEx()启动一个软定时器触发,第二次通过osal_msg_send()发送系统消息触发;这俩的确是重点函数),分别调用相应任务事件处理函数,第一次是HAL层的Hal_ProcessEvent()来查询按键得到键值,一系列处理,第二次是APP层的SampleApp_ProcessEvent()把传送过来的按键事件进行最终处理.查询法函数调用流程如下:

HalKeyConfig()配置一定时器为轮询按键作准备——时间一到触发系统任务事件调用Hal_ProcessEvent()—— 调用HalKeyPoll()得到按键值——调用OnBoard_KeyCallback()进一步处理——调用OnBoard_SendKeys()构造消息包,准备触发应用按键事件【注意这个应用之前必须通过RegisterForKeys()注册接收按键事件的任务ID】——调用osal_msg_send()向系统发送消息——调用osal_set_event()设置事件发生标志——调用SampleApp_ProcessEvent()处理事件——最终调用SampleApp_HandleKeys()处理具体按键事件

 

2、中断法
按键引起的中断应属于外部中断.在HalKeyConfig()中有这样一段描述:
       Work around for CC2430DB when interrupt is enabled and SW5 (center joystick)
       is used. This SW5 uses P2 which also has debug lines connected to it. This
       causes contant interruption on P2INT_VECTOR. Disable the usage of P2 interrupt
       will stop this problem.

操纵杆SW5(上,下,左,右,中)可以触发P2口外部中断,协议默认中断向量为P2INT_VECTOR.函数HalKeyConfig()对其配置如下:

/***************************************************************************
void HalKeyConfig (bool interruptEnable, halKeyCBack_t cback)
{
#if (HAL_KEY == TRUE)
  /* Enable/Disable Interrupt or */
  Hal_KeyIntEnable = interruptEnable;

  /* Register the callback fucntion */
  pHalKeyProcessFunction = cback;

  /* Determine if interrupt is enable or not */
  if (Hal_KeyIntEnable)
  {

    /*
       Work around for CC2430DB when interrupt is enabled and SW5 (center joystick)
       is used. This SW5 uses P2 which also has debug lines connected to it. This
       causes contant interruption on P2INT_VECTOR. Disable the usage of P2 interrupt
       will stop this problem.
    */

    #if defined (HAL_BOARD_CC2430DB)
      #undef HAL_KEY_SW_5_ENABLE                      /* Dis-allow SW5 when key interrupt is enable */
    #endif

#if defined (HAL_KEY_SW_5_ENABLE)
    PICTL &= ~(HAL_KEY_SW_5_EDGEBIT);                 /* Set rising or falling edge */
  #if (HAL_KEY_SW_5_EDGE == HAL_KEY_FALLING_EDGE)
    PICTL |= HAL_KEY_SW_5_EDGEBIT;
  #endif
    HAL_KEY_SW_5_ICTL |= HAL_KEY_SW_5_ICTLBIT;        /* Set interrupt enable bit */
    HAL_KEY_SW_5_IEN |= HAL_KEY_SW_5_IENBIT;
    HAL_KEY_SW_5_PXIFG = ~(HAL_KEY_SW_5_BIT);        /* Clear any pending interrupts */
#endif

#if defined (HAL_KEY_SW_6_ENABLE)
    PICTL &= ~(HAL_KEY_SW_6_EDGEBIT);                 /* Set rising or falling edge */
  #if (HAL_KEY_SW_6_EDGE == HAL_KEY_FALLING_EDGE)
    PICTL |= HAL_KEY_SW_6_EDGEBIT;
  #endif
    HAL_KEY_SW_6_ICTL |= HAL_KEY_SW_6_ICTLBIT;        /* Set interrupt enable bit */
    HAL_KEY_SW_6_IEN |= HAL_KEY_SW_6_IENBIT;
    HAL_KEY_SW_6_PXIFG = ~(HAL_KEY_SW_6_BIT);        /* Clear any pending interrupts */
#endif                                                
  //(清除推迟的中断)清除中断标志位

    /* 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 */
  {

    ………… (查询法,见上)
  }
}
/***************************************************************************

可以看到具体设置了是上升沿还是下降沿来触发中断,并清除中断标志。
当按键/操作杆触发中断后,进入中断向量为P2INT_VECTOR的中断函数如下:

/***************************************************************************
HAL_ISR_FUNCTION( halKeyPort2Isr, P2INT_VECTOR )
{
  if( CHVER <= REV_D )
  {
    P2IF = 0;
  }

  halProcessKeyInterrupt();

  if( CHVER >= REV_E )
  {
    P2IF = 0;
    CLEAR_SLEEP_MODE();
  }
}
/***************************************************************************

进入中断函数后首先清除中断标志么?前后两个if语句还不清楚何用,先不钻了……反正进入处理按键中断函数halProcessKeyInterrupt()

/***************************************************************************
void halProcessKeyInterrupt (void)
{

#if (HAL_KEY == TRUE)

  bool    valid=FALSE;

#if defined (HAL_KEY_SW_6_ENABLE)
  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;
  }
#endif

#if defined (HAL_KEY_SW_5_ENABLE)
  if (HAL_KEY_SW_5_PXIFG & HAL_KEY_SW_5_BIT)      /* Interrupt Flag has been set */
  {
    HAL_KEY_SW_5_PXIFG = ~(HAL_KEY_SW_5_BIT);    /* Clear Interrupt Flag */
    valid = TRUE;
  }
#endif

  if (valid)
  {
    osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_DEBOUNCE_VALUE);
  }
#endif   /* HAL_KEY */
}
/***************************************************************************
可以看到这里倒是真的清除中断标志,最后启动一个软定时器,相应事件为HAL_KEY_EVENT,时间为HAL_KEY_DEBOUNCE_VALUE,且#define HAL_KEY_DEBOUNCE_VALUE  25,即25ms,这与开头的英文说明一致,在中断发生后,需等待25ms再读键值,以消除按键的回跳。对于osal_start_timerEx()这个函数我想应该很熟悉了,触发任务事件,即当25ms到了以后,触发HAL层任务HAL_KEY_EVENT事件,调用HAL层任务事件处理函数Hal_ProcessEvent(),然后就回到了与查询法一样的线路上(具体参照上面的查询法流程),调用HalKeyPoll()读取键值,再调用按键的回调函数即OnBoard_KeyCallback(),再调用OnBoard_SendKeys(),再调用 osal_msg_send() ……触发sampleApp事件,并调用SampleApp_ProcessEvent()对按键事件进入最终处理。

至此,协议栈的按键中断法流程结束,与查询法一样,一次下来两次触发系统事件(第一次通过osal_start_timerEx()启动一个软定时器触发,第二次通过osal_msg_send()发送系统消息触发),分别调用相应任务事件处理函数,第一次是HAL层的Hal_ProcessEvent()来查询按键得到键值,一系列处理,第二次是APP层的SampleApp_ProcessEvent()把传送过来的按键事件进行最终处理.中断法函数调用流程如下:

HalKeyConfig()进行按键中断配置——按键引起中断进入中断函数HAL_ISR_FUNCTION()——调用halProcessKeyInterrupt()对按键中断进行下一步处理:清除中断标志,启动一定时器——时间一到触发系统任务事件调用Hal_ProcessEvent()—— 调用HalKeyPoll()得到按键值——调用OnBoard_KeyCallback()进一步处理——调用OnBoard_SendKeys()构造消息包,准备触发应用按键事件【注意这个应用之前必须通过RegisterForKeys()注册接收按键事件的任务ID】——调用osal_msg_send()向系统发送消息——调用osal_set_event()设置事件发生标志——调用SampleApp_ProcessEvent()处理事件——最终调用SampleApp_HandleKeys()处理具体按键事件


3、从上面对按键查询法和中断法的总结,可以看到中断法就比查询法大致多出两步:进入中断函数HAL_ISR_FUNCTION()与调用调用halProcessKeyInterrupt(),后面都是开始开启一软定时器触发相同事件HAL_KEY_EVENT,然后……………………。

   虽然我现在没有实验平台,无法做相关实验,但个人在想,如果现在自己在协议栈中添加了一应用(事实上具体添加任务我还好多不清楚),应用中需要用到按键,如果硬件配置按协议栈默认来配置,那基本只要完成这两件事情:
(1)、在自己的应用中注册按键:RegisterForKeys(MyAPP_TaskID),这样用户应用就得接收到所有按键事件
(2)、在自己的应用任务事件处理函数中配置具体处理函数:比如SampleApp中按SW1,发送闪烁信息给组1,按     SW2,进/退组1.

一下子发好几篇小结,自已都晕乎乎的…协议栈按键流程 - 小峰 - happy~……协议栈按键流程 - 小峰 - happy~……

 

说明:
本文作者所记录,以上基本为个人见解,错误之处还请高手指点,本人随时更新,转载请注明出处,谢谢!

 最近更新:2010.6.01

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约