键盘机制(转自天运科技)

Zstack 的按键机制
这一节我们看按键机制,后续会陆续看 UART 机制和绑定机制,通过这三大机制深入学习 Zstack 消息的传递机制,掌握 Zstack 开发流程。
1、 了解 TI  EB 板按键
TI EB 板按键资源有一个独立按键和一个摇杆。独立按键连接到 P0_1 ,摇杆四个方向串联电阻连接到 P0_6 ,摇杆的确认按钮(中心按键)连接到 P0_5 ,类似与独立按键。
TI  EB 板按键部分原理图:
A) 独立按键原理图:
file:///C:\Users\WANGJI~1\AppData\Local\Temp\ksohtml\wps_clip_image-27971.png
1 EB 板独立按键原理图
由原理图可以看出按键为低电平有效。
B) 摇杆按键原理图
file:///C:\Users\WANGJI~1\AppData\Local\Temp\ksohtml\wps_clip_image-18030.png
1 EB 板摇杆按键原理图(这个图想横着占一页)
由原理图可以看出摇杆的确认按钮(中心按键)是高电平有效。
2、 轮询与中断
Zstack 中提供了两种方式采集按键数据:轮询方式和中断方式。
轮询方式:
中断方式:
3、 按键的初始化
A) 、相关宏定义
key.c 文件中定义了关于按键的宏。注意按键初始化配置属于底层驱动的配置,在涉及到底层时会遇到大量的 CC2430 寄存器,要养成查看 datasheet 的好习惯。
①、按键 6 SW6 )对应原理图的独立按键 S1 ,按键 6 SW6 )相关的宏定义:
#define HAL_KEY_BIT0   0x01
#define HAL_KEY_BIT1   0x02
#define HAL_KEY_BIT2   0x04
#define HAL_KEY_BIT3   0x08
#define HAL_KEY_BIT4   0x10
#define HAL_KEY_BIT5   0x20
#define HAL_KEY_BIT6   0x40
#define HAL_KEY_BIT7   0x80
#if defined (HAL_BOARD_CC2430EB) || defined (HAL_BOARD_CC2430BB)
  #define HAL_KEY_SW_6_ENABLE
  #define HAL_KEY_SW_6_PORT                     P0                  
  #define HAL_KEY_SW_6_BIT                             HAL_KEY_BIT1     
  #define HAL_KEY_SW_6_SEL                       P0SEL                  
  #define HAL_KEY_SW_6_DIR                       P0DIR         
  #define HAL_KEY_SW_6_IEN                        IEN1        
  #define HAL_KEY_SW_6_IENBIT                    HAL_KEY_BIT5      
  #define HAL_KEY_SW_6_EDGE                      HAL_KEY_RISING_EDGE
  #define HAL_KEY_SW_6_EDGEBIT                   HAL_KEY_BIT0      
  #define HAL_KEY_SW_6_ICTL                      PICTL           
  #define HAL_KEY_SW_6_ICTLBIT                   HAL_KEY_BIT3     
  #define HAL_KEY_SW_6_PXIFG                     P0IFG               
  #define HAL_KEY_P0INT_LOW_USED           HAL_KEY_SW_6_BIT   
#endif       
②、按键 5 SW5 )对应原理图的摇杆的确认按钮(中心按键),摇杆相关宏定义:
#if defined (HAL_BOARD_CC2430EB)
  #define HAL_KEY_JOYSTICK_ENABLE
  #define HAL_KEY_JOY_CHN   HAL_ADC_CHANNEL_6
  #define HAL_KEY_SW_5_ENABLE
  #define HAL_KEY_SW_5_PORT                            P0                     
  #define HAL_KEY_SW_5_BIT                             HAL_KEY_BIT5        
  #define HAL_KEY_SW_5_SEL                       P0SEL                  
  #define HAL_KEY_SW_5_DIR                       P0DIR                 
  #define HAL_KEY_SW_5_INP                       P0INP                 
  #define HAL_KEY_SW_5_IEN                        IEN1                 
  #define HAL_KEY_SW_5_IENBIT                     HAL_KEY_BIT5         
  #define HAL_KEY_SW_5_EDGE                       HAL_KEY_RISING_EDGE   
  #define HAL_KEY_SW_5_EDGEBIT                   HAL_KEY_BIT2         
  #define HAL_KEY_SW_5_ICTL                      PICTL                 
  #define HAL_KEY_SW_5_ICTLBIT                   HAL_KEY_BIT4         
  #define HAL_KEY_SW_5_PXIFG                     P0IFG                 
  #define HAL_KEY_POINT_HIGH_USED           HAL_KEY_SW_5_BIT   
#endif
B )、代码分析:
按键的初始化属于硬件的初始化,在 Zstack 中硬件驱动在HalDriverInit()集中处理。在主函数 Main 中调用了函数HalDriverInit()进行硬件驱动的初始化,该函数根据编译选项对硬件逐个进行了初始化。HalDriverInit()代码如下:
程序代码:
void HalDriverInit (void)
{
  /* 定时器 */
  HalTimerInit();
/* ADC */
  #if (defined HAL_ADC) && (HAL_ADC == TRUE)
  HalAdcInit();
  #endif
  ……  
  /* LED 灯*/
  #if (defined HAL_LED) && (HAL_LED == TRUE)
  HalLedInit();
  #endif
  
  /* 串口 */
  #if (defined HAL_UART) && (HAL_UART == TRUE)
  HalUARTInit();
  #endif
  
  /* 按键 */
  #if (defined HAL_KEY) && (HAL_KEY == TRUE)
  HalKeyInit();
  #endif
}
        由上述代码可以看出除定时器以外其它初始化都是根据条件进行初始化的,这里满足按键初始化条件。注:硬件驱动初始化没有完全列举,可以参见具体协议栈。
程序代码:
/*
  Set to TRUE enable KEY usage, FALSE disable it
  Notes: On 2430EB/DB analog joystick is used to simulate
         keys. Keys won't work unless HAL_ADC is also set
         to TRUE
*/
#ifndef HAL_KEY
#define HAL_KEY TRUE
#endif
        上述代码摘自 hal_board_cfg.h 文件,从上述代码可以看出: TI Zstack 协议栈默认是使用独立按键的。
使用摇杆的时候还要确保HAL_ADC为真,即 Zstack 协议栈使用 AD 采集。关于HAL_ADC代码如下:
程序代码:
/* Set to TRUE enable ADC usage, FALSE disable it */
#ifndef HAL_ADC
#define HAL_ADC TRUE
#endif
        上述代码同样摘自 hal_board_cfg.h 文件,可以看出 Zstack 协议栈默认使用 AD 转换器。由上述#define HAL_KEY TRUE和#define HAL_ADC TRUE可以知道在 TI Zstack 协议栈默认情况既可以使用普通的独立按键也可以使用模拟的摇杆。
void HalKeyInit( void )
{
#if (HAL_KEY == TRUE)
  halKeySavedKeys = 0;
#if defined (HAL_KEY_SW_6_ENABLE)
  HAL_KEY_SW_6_SEL &= ~(HAL_KEY_SW_6_BIT);           // 设定为通用 I/O
  HAL_KEY_SW_6_DIR &= ~(HAL_KEY_SW_6_BIT);                  // 设置为输入模式
#endif
#if defined (HAL_KEY_SW_5_ENABLE)
  HAL_KEY_SW_5_SEL &= ~(HAL_KEY_SW_5_BIT);                  // 设定为通用 I/O
  HAL_KEY_SW_5_DIR &= ~(HAL_KEY_SW_5_BIT);                 // 设置为输入模式
  HAL_KEY_SW_5_INP |= HAL_KEY_SW_5_BIT;                      // 设置为三态模式
#endif
  pHalKeyProcessFunction  = NULL;
  HalKeyConfigured = FALSE;
#endif /* HAL_KEY */
}
按键驱动初始化函数HalKeyInit()说明:
        ⑴、配置了三个全局变量。全局变量halKeySavedKeys是用来保存按键值的,初始化时将其初始化为 0 pHalKeyProcessFunction为指向按键处理函数的指针,当有按键按下时调用按键处理函数对按键进行处理,初始化时将其初始化为 NULL ,在按键的配置函数中对其进行配置;全局变量HalKeyConfigured用来标示按键是否被配置,初始化时没有配置按键,所以此时该变量被初始化为 FALSE
⑵、配置了两个按键的 I/O 口。函数中用条件语句判定是否使能了 SW5 SW6 ,由前面的宏定义可以看出,如果使用的是 TI EB 板,则 SW5 SW6 都将被使能。有上述代码可以看出按键驱动初始化函数HalKeyInit()将与 SW5 SW6 相连接的 I/O 设定通用 I/O 口,并都设置为输入模式。而且将 SW5 的输入模式设定为三态。
4 、按键的配置
        按键的初始化只是简单的对按键进行了初始化与 I/O 的配置,如果要使用 Zstack 的按键仍需要进一步对按键进行配置,下面我们着重理清按键的配置。 Zstack 对按键处理提供了两种方法:轮询法和中断法。在配置按键时也是分为两种情况与之对应。我们这里对两种配置模式逐一说明。 Zstack 默认使用轮询的方式处理,我们先以轮询入手。
A )、配置轮询方式的按键
        TI Zstack为什么会默认使用轮询方式对按键进行处理?其实最重要的一点是因为 TI EB ED 板都有摇杆。因为摇杆的四个方向是以电压的大小区分的,是不能产生中断的。如果使用了中断方式对按键进行处理那么摇杆的四个方向键就是摆设了,所以 TI Zstack 采取轮询的方式处理按键。为什么要使用摇杆?其实原因也很简单,为了节省 I/O 口资源, CC2430 只有 21 个通用 I/O 口,如果使用一个摇杆就可以就可以节省使用三个独立 I/O 口。
代码分析:
        按键的配置函数在板载初始化函数 InitBoard() 中被调用 , 函数 InitBoard() 负责板载的初始化与配置。 HalKeyConfig() 决定了将按键的处理方式为轮询方式或者是中断方式。
程序代码:
#define HAL_KEY_INTERRUPT_DISABLE    0x00
#define HAL_KEY_INTERRUPT_ENABLE     0x01
void InitBoard( byte level )
{
        ……  
    OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE;// 使用轮询方式
HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
……
}
板载初始化函数 InitBoard() 说明:
由代码  HAL_KEY_INTERRUPT_DISABLE 得知处理方式被设置为轮询方式。如果我们要设置为中断方式,只需要给变量 OnboardKeyIntEnable 赋值HAL_KEY_INTERRUPT_ENABLE即可。另一个参数 OnBoard_KeyCallback 是按键处理的回调函数的函数名,如果有按键按下时将会调用这个回调函数进行处理。该按键回调函数会在按键处理详细说明。
程序代码:
void HalKeyConfig (bool interruptEnable, halKeyCBack_t cback)
{
#if (HAL_KEY == TRUE)
  Hal_KeyIntEnable = interruptEnable;// 保存参数决定处理方式是轮询或是中断
  pHalKeyProcessFunction = cback;   // 保存参数按键处理的回调函数
  if (Hal_KeyIntEnable)// 如果为真则配置为中断方式
  {
        ……
  }
  else   // 否则配置为轮询方式
  {
        ……
#if defined (HAL_KEY_SW_6_ENABLE)
HAL_KEY_SW_6_ICTL &= ~(HAL_KEY_SW_6_ICTLBIT); // 清除中断  HAL_KEY_SW_6_IEN &= ~(HAL_KEY_SW_6_IENBIT);
#endif
#if defined (HAL_KEY_SW_5_ENABLE)
HAL_KEY_SW_5_ICTL &= ~(HAL_KEY_SW_5_ICTLBIT);       HAL_KEY_SW_5_IEN &= ~(HAL_KEY_SW_5_IENBIT);
#endif
    osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, L_KEY_POLLING_VALUE);
                // 定时触发事件HAL_KEY_EVENT,溢出时间为HAL_KEY_POLLING_VALUE。   
  }
  HalKeyConfigured = TRUE;
#endif /* HAL_KEY */
}
按键配置函数HalKeyConfig () 说明:
        ⑴、配置三个全局变量。Hal_KeyIntEnable保存了按键处理方式,全局变量pHalKeyProcessFunction保存了按键处理的回调函数,在按键处理工程中将被调用。第三个变量仍是HalKeyConfigured用来标示按键是否配置,当按键配置完成将其设定为 TRUE        
⑵、配置两个按键。由于上述代码使用的是轮询方式对按键事件进行配置,就不允许按键产生中断。在配置按键 SW5 SW6 时将其对应的中断使能禁止。具体配置代码需参见按键相关宏定义和 CC2340 datasheet 进行理解。
⑶、事件HAL_KEY_EVENT,在将按键配置为轮询方式后, Zstack 会定时触发事件HAL_KEY_EVENT检测是否有按键按下。定时器的溢出时长为HAL_KEY_POLLING_ VALUE。
事件的处理我们后续讲解,这里完成了轮询方式的配置,接下来我们看如何将按键配置为中断方式。
B )、配置中断方式的按键
        为了让大家能更加深入了解 Zstack 中机制和数据流的传递。虽然 TI Zstack 默认是以轮询方式处理按键,但其中断方式的代码都给我们准备好了,只需我们稍微改动一点即可。下面一起看如何将按键配置为中断方式。
程序代码:
代码分析:
按键的配置函数在板载初始化函数 InitBoard() 中被调用 , 函数 InitBoard() 负责板载的初始化与配置。在函数 InitBoard() 调用按键配置函数 HalKeyConfig() 根据参数值对按键进行配置,决定了将按键的处理方式为轮询方式或者是中断方式。
程序代码:
#define HAL_KEY_INTERRUPT_DISABLE    0x00
#define HAL_KEY_INTERRUPT_ENABLE     0x01
void InitBoard( byte level )
{
        ……  
    OnboardKeyIntEnable = HAL_KEY_INTERRUPT_ENABLE;// 使用中断方式
HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
……
}
程序代码:
void HalKeyConfig (bool interruptEnable, halKeyCBack_t cback)
{
#if (HAL_KEY == TRUE)
  Hal_KeyIntEnable = interruptEnable;// 保存参数决定处理方式是轮询或是中断
  pHalKeyProcessFunction = cback;   // 保存参数按键处理的回调函数
if (Hal_KeyIntEnable)
  {
……
#if defined (HAL_KEY_SW_5_ENABLE)
PICTL &= ~(HAL_KEY_SW_5_EDGEBIT);      // 设定上升沿或者下降沿触发
#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;     // 设定中断使能
HAL_KEY_SW_5_IEN |= HAL_KEY_SW_5_IENBIT;
HAL_KEY_SW_5_PXIFG = ~(HAL_KEY_SW_5_BIT);      // 清除中断标志
#endif
#if defined (HAL_KEY_SW_6_ENABLE)                               
PICTL &= ~(HAL_KEY_SW_6_EDGEBIT);      // 设定上升沿或者下降沿触发         
#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;     // 设定中断使能
HAL_KEY_SW_6_IEN |= HAL_KEY_SW_6_IENBIT;
HAL_KEY_SW_6_PXIFG = ~(HAL_KEY_SW_6_BIT);      / 清除中断标志
#endif
……
  }
  else   
  ……
HalKeyConfigured = TRUE;
#endif /* HAL_KEY */
}
按键配置函数HalKeyConfig () 说明:
        ⑴、配置三个全局变量。
⑵、配置两个按键。我们这里是要将按键配置为中断方式。需要将按键配置为上升沿或是下降沿触发,同时需要将按键的对应 I/O 口配置为允许中断,即中断使能。在配置触发沿时首先默认配置为上升沿,然后检测按键相关宏定义决定是否需要配置为下降沿。在配置完中断使能后清除中断标志位允许按键中断。具体配置代码需参见按键相关宏定义和 CC2340 datasheet 进行理解。
⑶、将按键配置为中断方式,在程序中没有触发类似HAL_KEY_EVENT的事件,而是交由中断函数进行处理,当有按键按下时中断函数就会捕获中断,从而调用按键的处理函数进一步进行相关处理。关于中断方式的按键处理将在后面的进行梳理。
5 、按键的处理
A) 、轮询方式处理
        轮询方式是 TI Zstack 对按键默认的处理方式,Zstack 会每隔 100ms 对按键检测一次,如果有按键按下就执行相关的处理。 Zstack 是以每隔 100ms 触发一次 HAL_KEY_EVENT 事件实现的轮询的,定时器溢出时间HAL_KEY_POLLING_VALUE即为轮询的的间隔。
        Zstack 每次轮询按键都会与保存的按键值(halKeySavedKeys)进行对比,保存按键值在按键初始化函数被初始化为 0 。如果没有按下即与先前保存的按键值相等, Zstack 不进行处理。如果有按键按下,那么按键值与保存的按键值(halKeySavedKeys)不同,将会调用按键处理回调处理函数进行处理,并将此时得到的按键值保存以便下次比较。
        在轮询方式配置完成后, Zstack 便触发了事件 HAL_KEY_EVENT ,其任务 ID Hal_TaskID,则对应的处理函数即为 HAL 层的事件处理函数Hal_ProcessEvent() 。触发了 HAL 层的 HAL_KEY_EVENT 标志着开始了按键的轮询。详细代码如下:
程序代码:
uint16 Hal_ProcessEvent( uint8 task_id, uint16 events )
{
……
if (events & HAL_KEY_EVENT)
  {
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
     HalKeyPoll();
    if (!Hal_KeyIntEnable)
    {
      osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);
    }
#endif // HAL_KEY
    return events ^ HAL_KEY_EVENT;
  }
……
}
HAL_KEY_EVENT事件处理说明:
⑴、在处理HAL_KEY_EVENT事件时调用了函数 HalKeyPoll() ,函数 HalKeyPoll() 负责检测是否有按键按下,如果有按键按下会触发相应的回调函数。
⑵、在调用函数 HalKeyPoll() 检测完按键过后,if 条件判断语句检测按键是否是轮询方式处理,这里我们是以轮询方式处理按键,所以满足 if 条件判断语句的条件,即执行函数osal_start_timerEx() 定时再次触发事件HAL_KEY_EVENT,定时长度为 100ms ,由此可以看出如此的循环的触发事件HAL_KEY_EVENT即完成了对按键的定时轮询。
处理HAL_KEY_EVENT事件时调用了函数 HalKeyPoll() HalKeyPoll() 函数进一步来去检测是否有按键按下,其详细代码如下:
程序代码:
#define HAL_KEY_STATE_NORMAL                     0x00
#define HAL_KEY_STATE_SHIFT                          0x01
void HalKeyPoll (void)
{
  uint8 keys = 0;    初始键值为 0
#if defined (HAL_KEY_SW_6_ENABLE)
  if (!(HAL_KEY_SW_6_PORT & HAL_KEY_SW_6_BIT))   //SW6 低电平有效
  {
    keys |= HAL_KEY_SW_6;
  }
#endif
#if defined (HAL_KEY_SW_5_ENABLE)
  if (HAL_KEY_SW_5_PORT & HAL_KEY_SW_5_BIT)     //SW5 高电平有效
  {
    keys |= HAL_KEY_SW_5;
  }
#endif
  if (!Hal_KeyIntEnable)      // 轮询方式
  {
    if (keys == halKeySavedKeys)
    {
      return;
    }
halKeySavedKeys = keys;  // 保存按键值下次比较
  }
  if (keys && (pHalKeyProcessFunction))
  {
          // 调用按键回调函数对按键进一步处理
    (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
  }
}
HalKeyPoll() 函数说明:
⑴、HalKeyPoll() 函数对所有的按键进行检测,这里我们只是列举了 SW5 SW6 两个按键,摇杆的四个方向是利用 AD 采集的数值不同决定的,具体可以参见协议栈代码,这里不一一列举了。
⑵、按键值的采集。首先函数定义了一个 uint8 的局部变量keys用来存储按键的值,并将其值初始化为 0 。通过 if 条件语句判定是否有 SW5 或者 SW6 被按下。注意程序中的代码在检测SW5 时是检测对应位是否为高电平,而检测 SW6 时检测对应位是否为低电平。这里的高低电平与我们最初分析原理图时一致。如果有按键按下则将其对应的数值赋给局部变量keys。
        ⑶、轮询处理。如果是轮询方式首先要对读取的按键进行判别,如果读取的按键值为上次的按键值直接返回不进行处理。如果读取的按键值和上次的按键值不同,则将读取的按键值保存到全局变量halKeySavedKeys以便下一次比较。并调用函数进行处理。
⑷、回调函数处理按键。当有按键按下后则keys值不为 0 ,并且在按键配置函数HalKeyConfig ()的时候我们为按键配置了回调函数 OnBoard_KeyCallback () 。所以  if (keys && (pHalKeyProcessFunction))中的两个判断条件都为真,即可以用回调函数对按键进行处理。
当有按键按下, Zstack 的底层获取了按键的按键值会触发按键的回调函数 OnBoard_ KeyCallback () 进一步处理,将按键信息传到上层(应用层)。按键回调函数代码如下:
程序代码:
void OnBoard_KeyCallback ( uint8 keys, uint8 state )
{
  ……
// 调用 OnBoard_SendKeys 将信息发送到上层
if ( OnBoard_SendKeys( keys, shift ) != ZSuccess )
  {
……
   }
}
OnBoard_KeyCallback () 函数说明:
OnBoard_KeyCallback () 函数中调用了函数 OnBoard_SendKeys() 进一步处理,但是 Zstack 并没有给出 OnBoard_SendKeys() 处理失败的代码,用户可以根据实际需要自行添加。
       
        在函数 OnBoard_SendKeys() 将会将按键的值和按键的状态进行“打包”发送到注册过按键的那一层。具体代码如下:
程序代码:
byte OnBoard_SendKeys( byte keys, byte state )
{
  keyChange_t *msgPtr;
  if ( registeredKeysTaskID != NO_TASK_ID )// 这里必须要被注册
  {
    // 分配内存空间
    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 );
}
OnBoard_SendKeys () 函数说明
        ⑴、按键的注册。 if ( registeredKeysTaskID != NO_TASK_ID ) 用来判断按键是否被注册。在 Zstack 中,如果要使用按键必须要注册。类似于端点 endpoint 的注册。但按键的注册只能注册给一个层。这里我们以 SampleApp 实例进行说明。
        在 SampleApp 实例的应用层初始化代码中调用了按键注册函数RegisterForKeys ()进行按键注册,其传递的任务 ID SampleApp_TaskID。按键注册函数代码如下:
程序代码:
byte RegisterForKeys( byte task_id )
{
  //  仅允许注册一次
  if ( registeredKeysTaskID == NO_TASK_ID ) // 判断条件,确定没有被注册
  {
    registeredKeysTaskID = task_id;// 将传来的 ID 赋给了 registeredKeysTaskID
    return ( true );
  }
  else
    return ( false );
}
按键注册函数仅允许注册一次,即只能有一个层注册按键。在按键注册时首先检测了全局变量 registeredKeysTaskID (初始化为 NO_TASK_ID )是否等于 NO_TASK_ID ,如果等于则证明按键没有被注册,可以被注册。按键的注册实际上就是将函数传递来的任务 ID 赋给
全局变量 registeredKeysTaskID 的过程。
⑵、数据的发送。在确定按键已经被注册的前提下, Zstack 对按键信息进行打包处理,封装到信息包 msgPtr 中,将将要触发的事件 KEY_CHANGE ,按键的状态 state 和按键的键值 keys 一并封装。然后调用 osal_msg_send() 将按键信息发送到注册按键的对应层。
这里我们以 SampleApp 工程为例, Zstack 最终触发了 SampleApp 应用层的处理函数中的 KEY_CHANGE 事件。代码如下:
程序代码:
uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
        ……
    case KEY_CHANGE:
      SampleApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );
    break;
……
}
SampleApp_ProcessEvent() 在处理HAL_KEY_EVENT事件时调用了应用层的按键处理函数 SampleApp_HandleKeys() 。按键处理函数 SampleApp_HandleKeys() 对按键进一步处理,其代码如下:
程序代码:
void SampleApp_HandleKeys( uint8 shift, uint8 keys )
{
  if ( keys & HAL_KEY_SW_1 )// 如果按键值 keys&HAL_KEY_SW_1  1 ,执行以下代码
  {   
    SampleApp_SendFlashMessage( SAMPLEAPP_FLASH_DURATION );
  }
  if ( keys & HAL_KEY_SW_2 )// 如果按键值 keys&HAL_KEY_SW_2  1 ,执行以下代码
  {
   ……
  }
}
        在按键处理函数 SampleApp_HandleKeys() 中根据按键值的不同调用了不同的函数,按键完成了其使命。以上分析就是以轮询方式处理按键。
B )、中断方式处理
        在按键配置函数HalKeyConfig () 将按键配置为中断方式后,使能了按键相对应的 I/O 口。这里以 P0 口为例进行梳理。 P0 口中断函数代码如下:
程序代码:
HAL_ISR_FUNCTION( halKeyPort0Isr, P0INT_VECTOR )
{
  halProcessKeyInterrupt();
  P0IF = 0;
  ……
}
P0 口外部中断函数说明
        在该中断函数调用了按键中断处理函数halProcessKeyInterrupt()对中断进行处理,且将 P0 口中断标志位清零。中断处理函数halProcessKeyInterrupt()代码如下:
程序代码:
void halProcessKeyInterrupt (void)
{
  bool    valid=FALSE;
#if defined (HAL_KEY_SW_6_ENABLE)
  if (HAL_KEY_SW_6_PXIFG & HAL_KEY_SW_6_BIT)   
  {
HAL_KEY_SW_6_PXIFG = ~(HAL_KEY_SW_6_BIT);  
valid = TRUE;
  }
#endif
#if defined (HAL_KEY_SW_5_ENABLE)
  if (HAL_KEY_SW_5_PXIFG & HAL_KEY_SW_5_BIT)   
  {
HAL_KEY_SW_5_PXIFG = ~(HAL_KEY_SW_5_BIT);   
valid = TRUE;
  }
#endif
  if (valid)
  {
    osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_DEBOUNCE_VALUE);
  }
}
按键中断处理halProcessKeyInterrupt () 说明:
        ⑴、局部变量valid说明。函数中的局部变量valid标示了是否有按键按下,如果有按键按下则定时触发HAL_KEY_EVENT事件。这里说明一点, P0 口外部中断不只是我们这里的两个按键。
⑵、按键的检测。在该函数中通过检测按键对应位的中断标志位是否为 1,判断按键按下。 CC2340 的每一个 I/O 都可以产生中断。如果有按键按下则要将对应位的中断标志位置为 0 ,并将局部变量valid设置为 TRUE ,从而触发AL_KEY_EVENT事件对按键处理。
⑶、HAL_KEY_EVENT事件。如果有按键按下则会定时触发HAL_KEY_EVENT事件,定时长度为HAL_KEY_DEBOUNCE_VALUE( 25ms )。这里说明一下,在按键中断处理函数halProcessKeyInterrupt () 并没有读取按键的值,而是定时触发了HAL_KEY_EVENT事件,在处理HAL_KEY_EVENT事件时读取。定时时长HAL_KEY_DEBOUNCE_VALUE( 25ms )是为了按键消抖。
按键中断处理halProcessKeyInterrupt () 所触发的HAL_KEY_EVENT事件在 HAL 层事件处理函数 Hal_ProcessEvent() 被处理,具体代码如下:
程序代码:
uint16 Hal_ProcessEvent( uint8 task_id, uint16 events )
{
……
if (events & HAL_KEY_EVENT)
  {
#if (defined HAL_KEY) && (HAL_KEY == TRUE)
     HalKeyPoll();
    if (!Hal_KeyIntEnable)
    {
      osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100);
    }
#endif // HAL_KEY
    return events ^ HAL_KEY_EVENT;
  }
……
}
HAL_KEY_EVENT事件处理说明:
⑴、在处理HAL_KEY_EVENT事件时调用了函数 HalKeyPoll() ,函数 HalKeyPoll() 负责检测是否有按键按下,如果有按键按下会触发相应的回调函数。
⑵、在调用函数 HalKeyPoll() 检测完按键过后,用 if 条件判断语句检测按键是否是轮询方式处理,这里我们是以中断方式处理按键,所以不满足 if 条件判断语句的条件。
处理HAL_KEY_EVENT事件时调用了函数 HalKeyPoll() HalKeyPoll() 函数进一步来去检测是否有按键按下,其详细代码如下:
程序代码:
#define HAL_KEY_STATE_NORMAL                     0x00
#define HAL_KEY_STATE_SHIFT                          0x01
void HalKeyPoll (void)
{
  uint8 keys = 0;    初始键值为 0
#if defined (HAL_KEY_SW_6_ENABLE)
  if (!(HAL_KEY_SW_6_PORT & HAL_KEY_SW_6_BIT))   //SW6 低电平有效
  {
    keys |= HAL_KEY_SW_6;
  }
#endif
#if defined (HAL_KEY_SW_5_ENABLE)
  if (HAL_KEY_SW_5_PORT & HAL_KEY_SW_5_BIT)     //SW5 高电平有效
  {
    keys |= HAL_KEY_SW_5;
  }
#endif
  if (!Hal_KeyIntEnable)      // 轮询方式
  {
    if (keys == halKeySavedKeys)
    {
      return;
    }
halKeySavedKeys = keys;  // 保存按键值下次比较
  }
  if (keys && (pHalKeyProcessFunction))
  {
          // 调用按键回调函数对按键进一步处理
    (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
  }
}
HalKeyPoll() 函数说明:
⑴、HalKeyPoll() 函数对所有的按键进行检测,这里我们只是列举了 SW5 SW6 两个按键,摇杆的四个方向是利用 AD 采集的数值不同决定的,具体可以参见协议栈代码,这里不一一列举了。
⑵、按键值的采集。首先函数定义了一个 uint8 的局部变量keys用来存储按键的值,并将其值初始化为 0 。通过 if 条件语句判定是否有 SW5 或者 SW6 被按下。注意程序中的代码在检测 SW5 时是检测对应位是否为高电平,而检测 SW6 时检测对应位是否为低电平。这里的高低电平与我们最初分析原理图时一致。如果有按键按下则将其对应的数值赋给局部变量keys。
⑶、回调函数处理按键。当有按键按下后则keys值不为 0 ,并且在按键配置函数HalKeyConfig () 的时候我们为按键配置了回调函数 OnBoard_KeyCallback () 。所以  if (keys && (pHalKeyProcessFunction))中的两个判断条件都为真,即可以用回调函数对按键进行处理。
后续代码与轮询方式一致,这里不再赘述。具体参见轮询方式的梳理内容。
按键处理流程图:
file:///C:\Users\WANGJI~1\AppData\Local\Temp\ksohtml\wps_clip_image-10884.png

你可能感兴趣的:(zigbee)