OSAL的按键设计分析

cc2540的OSAL的按键做的很复杂,真心没搞懂为什么做成这样。为了理解OSAL的按键设计,特此分析一下。
 
我们以按键的状态变化为线索,看看各个模块的作用,以及变量是怎么变化的。
 
按键设计总体上可以分为2部分,按键初始化和按键运行,按键初始化主要负责跟按键相关的IO端口设置、中断设置。按键运行就是OSAL检测按键的过程。
 
按键初始化
1、初始化按键
在main函数调用函数
void HalDriverInit (void);

HalDriverInit 对HAL层(硬件抽象层)做初始化,并在其内部调用

void HalKeyInit( void );

 完成按键的初步初始化,按键初始化主要是把按键对应到IO口,并指定IO口的方向,并且把全局变量HalKeyConfigured设置为FALSE,这是因为虽然按键已经初始化了(指定了端口),但是还没有配置中断,也没有设置key的处理函数(用于向指定task发送key的消息)。

HalKeyInit函数对按键的初始化代码如下:
  halKeySavedKeys = 0;  // Initialize previous key to 0.
  HAL_KEY_SW_6_SEL &= ~(HAL_KEY_SW_6_BIT);    /* Set pin function to GPIO */
  HAL_KEY_SW_6_DIR &= ~(HAL_KEY_SW_6_BIT);    /* Set pin direction to Input */
  HAL_KEY_JOY_MOVE_SEL &= ~(HAL_KEY_JOY_MOVE_BIT); /* Set pin function to GPIO */
  HAL_KEY_JOY_MOVE_DIR &= ~(HAL_KEY_JOY_MOVE_BIT); /* Set pin direction to Input */
  P2INP |= PUSH2_BV;  /* Configure GPIO tri-state. */
   /* Initialize callback function */
  pHalKeyProcessFunction  = NULL;//key的处理函数还未设置,需要在HalKeyConfig函数中设置
  /* Start with key is not configured */
  HalKeyConfigured = FALSE;

 代码中将SW6按键绑定到了P0_1端口,P0是一个8bit的端口,P0_1是第二位。当读取P0口时,如果第二位为1,则为高电平,第二位为0,则为低电平。

 
2、配置按键
在main函数下调用HalDriverInit 函数完成按键初始化后,接着调用函数
void InitBoard( uint8 level )

完成按键的配置。

InitBoard函数中执行按键配置,按键配置代码如下:
    /* Initialize Key stuff */
    OnboardKeyIntEnable = HAL_KEY_INTERRUPT_ENABLE;           //打开中断

    //OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE;
    HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);    //调用HalKeyConfig做按键配置,将OnBoard_KeyCallback作为按键处理函数。

HalKeyConfig的作用是设置按键回调函数、配置按键对应IO口的中断。

HalKeyConfig其实还能给OSAL发消息,但是只有在HalKeyConfigured = TRUE的时候才能发消息。因为在初始化按键的时候把HalKeyConfigured 设置为了FALSE,所以第一次调用HalKeyConfig不会发OSAL消息。
 
 
按键运行
当OSAL开始运行后,按键的状态有三种:
  • 未按下
  • 按下的瞬间
  • 按下还未弹起
 
1、未按下
未按下时,OSAL处于等待中断触发的状态中,以P0口的中断为例,P0对应的是SW6按键,中断函数是:
HAL_ISR_FUNCTION( halKeyPort0Isr, P0INT_VECTOR )

 2、按下的瞬间

当按键被按下,中断也被触发,这时候中断函数HAL_ISR_FUNCTION会调用按键中断处理函数
void halProcessKeyInterrupt (void)

对按键作出处理,并且调用osal_start_timerEx 函数在HAL_KEY_DEBOUNCE_VALUE时间(25ms,防止按键抖动)后向OSAL的HAL task发出HAL_KEY_EVENT消息:

osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_DEBOUNCE_VALUE);

 OSAL把这个消息发给Hal_TaskID,Hal task会接受这个消息,继而调用Hal_ProcessEvent函数,Hal_ProcessEvent包括了多种硬件处理,例如LED UART KEY等,因为接受的消息是HAL_KEY_EVENT,所以进入按键处理分支:

uint16 Hal_ProcessEvent( uint8 task_id, uint16 events )
{
     .....//删略了其他HAL层收到的硬件消息,例如PERIOD_RSSI_RESET_EVT\HAL_LED_BLINK_EVENT等等

if (events & HAL_KEY_EVENT)           //进入按键处理分支
  {
    /* Check for keys */
    HalKeyPoll();                               //读取按键的值
    /* if interrupt disabled, do next polling */
    if (!Hal_KeyIntEnable)
    {
      osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);
    }
    return events ^ HAL_KEY_EVENT;
  }
}
View Code

 Hal_ProcessEvent在按键处理代码中,先调用HalKeyPoll函数读取key的值,HalKeyPoll函数对SW6按键的处理的代码如下

void HalKeyPoll (void)
{
  uint8 keys = 0;
  uint8 notify = 0;
   if (!(HAL_KEY_SW_6_PORT & HAL_KEY_SW_6_BIT))    /* Key is active low */
  {
    keys |= HAL_KEY_SW_6;                //如果SW_6_PORT是低电平,则将keys的对应位设置为1

  }
   /* If interrupts are not enabled, previous key status and current key status
   * are compared to find out if a key has changed status.
   */
  if (!Hal_KeyIntEnable)
  {
    if (keys == halKeySavedKeys)
    {
      /* Exit - since no keys have changed */
      return;
    }
    else
    {
      notify = 1;
    }
  }
  else
  {
    /* Key interrupt handled here */
    if (keys)
    {
      notify = 1;
    }
  }
   /* Store the current keys for comparation next time */
  halKeySavedKeys = keys;
   /* Invoke Callback if new keys were depressed */
  if (notify && (pHalKeyProcessFunction))//pHalKeyProcessFunction实际上就是回调函数,在hal_drive中中使用
  {
    (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
  }
}
View Code

HalKeyPoll首先判断sw6的端口是否按下,如果按下,此时HAL_KEY_SW_6_PORT是低电平,值为0。

然后,因为有keys!=0,并且开启了中断,需要将notify = 1。
最后,调用
(pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);

 pHalKeyProcessFunction是全局变量,在InitBoard中,已经调用HalKeyConfig将其设置为了OnBoard_KeyCallback。

 
OnBoard_KeyCallback函数如下:
void OnBoard_KeyCallback ( uint8 keys, uint8 state )
{
  uint8 shift;
  (void)state;
   // 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 ) != SUCCESS )//向注册的task发送按键消息
  {
    //blabla
  }
   /* If any key is currently pressed down and interrupt
     is still enabled, disable interrupt and switch to polling */
  if( keys != 0 )
  {
    if( OnboardKeyIntEnable == HAL_KEY_INTERRUPT_ENABLE )//如果开启了中断,将其设置为不开中断,接下来就按照每100ms调用一次keypoll
    {
      OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE;
      HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
    }
  }
  /* If no key is currently pressed down and interrupt
     is disabled, enable interrupt and turn off polling */
  else
  {
    if( OnboardKeyIntEnable == HAL_KEY_INTERRUPT_DISABLE )//如果key==0,并且中断关闭,则重新打开终端。
    {
      OnboardKeyIntEnable = HAL_KEY_INTERRUPT_ENABLE;
      HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
    }
  }
}
View Code

 OnBoard_KeyCallback主要做俩件事,调用OnBoard_SendKeys函数将按键发送给注册过的task,也就是我们自己开发的task。OnBoard_SendKeys代码如下:

uint8 OnBoard_SendKeys( uint8 keys, uint8 state )

{
     ......

     osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );//把按键消息发给注册ID registeredKeysTaskID的task

     ......
}

OnBoard_KeyCallback做的第二件事是开关中断,当按键触发中断后,OnBoard_KeyCallback将中断关闭,这样在Hal_ProcessEvent函数中将每100ms调用一次HalKeyPoll对key做轮询,并且在按键按下的过程中不再次进入OnBoard_KeyCallback ,这样就不会再次向注册的task发出按键消息。

至此,按键按下瞬间完成,接下来进入的是按下还未弹起的状态。
 
3、按下还未弹起
在上一个按键按下瞬间状态, 调用了
HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);

关闭中断,此时会进入不配置中断的代码分支,如下:

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

     //给HAL task发送HAL_KEY_EVENT,进入key polling
    osal_set_event(Hal_TaskID, HAL_KEY_EVENT);
}

 接下来,由于给hal task发了消息,将进入hal的event处理函数

uint16 Hal_ProcessEvent( uint8 task_id, uint16 events );

因为event=HAL_KEY_EVENT,所以再次进入HAL_KEY_EVENT的处理代码:

if (events & HAL_KEY_EVENT)           //进入按键处理分支
{
    /* Check for keys */
    HalKeyPoll();
     /* if interrupt disabled, do next polling */
    if (!Hal_KeyIntEnable)
    {
      osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);
    }
}

跟前面处理中断不同的是,这次调用完HalKeyPoll()后,调用了:

osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);

启动了定时器,在100ms后,重新给Hal_TaskID发送HAL_KEY_EVENT消息,重新进入polling。

 
现在反过来看看按下还未弹起状态下的的HalKeyPoll()做了什么:
if (!Hal_KeyIntEnable)
  {
    if (keys == halKeySavedKeys)      //如果还是按下状态,则keys没有变化,此时不返回任何值。
    {
      /* Exit - since no keys have changed */
      return;
    }
    else
    {
      notify = 1;                               //如果按键弹起了,则将notify置1
    }
  }
    ....

/* Store the current keys for comparation next time */
  halKeySavedKeys = keys;
   /* Invoke Callback if new keys were depressed */
  if (notify && (pHalKeyProcessFunction))           //如果还是按下状态,notify=0,此时不执行pHalKeyProcessFunction。
  {                                                                     //如果按键弹起,则进入pHalKeyProcessFunction
    (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
  }
View Code

 如果按下状态没有改变,函数HalKeyPoll()不做任何事,当按键弹起,notify被置1,进入pHalKeyProcessFunction对按键做处理。

 
pHalKeyProcessFunction其实就是OnBoard_KeyCallback,这个在前面的按键按下瞬间中分析过。现在来看看按键弹起时,OnBoard_KeyCallback怎么处理的:
 
/* If no key is currently pressed down and interrupt
     is disabled, enable interrupt and turn off polling */
  else
  {
    if( OnboardKeyIntEnable == HAL_KEY_INTERRUPT_DISABLE )
    {
      OnboardKeyIntEnable = HAL_KEY_INTERRUPT_ENABLE;
      HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
    }
  }

当key==0,并且中断关闭,这时候重新打开中断。等待下一次按键触发中断。

至此,整个按键过程执行了一轮。
 
总的来说,还是感觉按键处理过于复杂,这里还有一些IO口配置的宏没有列出来,看了简直要晕倒。
posted @ 2013-08-15 11:19  yametech  阅读(1757)  评论(2编辑  收藏  举报