码迷,mamicode.com
首页 > 其他好文 > 详细

OSAL之按键驱动

时间:2015-06-08 23:29:18      阅读:2088      评论:0      收藏:0      [点我收藏+]

标签:按键   事件   消息   发送   osal   

本博文根据协议栈1.3.2,尊重原创,注明出处,欢迎转载


学习按键驱动的主要有两大块:

第一:按键引脚设置
第二:按键事件的触发检测与轮询,以及按键消息的发送
先说明第一大块,按键引脚设置, 超出cc2540片子从机的按键两个按键,按键是共地。所以它的触发方式是下降沿触发按键中断,同时程序对按键按下这个动作检测是中断方式检测,一旦有按键按下,触发一个按键轮询函数执行,同时把对按键的检测方式换成轮询模式。所以对按键引脚设置的内容也就出来了。主要一下几个方面:
1、设置按键映射到的物理引脚GPIO特性
2、对引脚的输入输出模式设置,
3、最重要的是对按键引脚中断寄存器的设置

//触发按键轮询函数执行先延时15毫秒,达到去抖动的效果
#define HAL_KEY_DEBOUNCE_VALUE  15   //15ms的去抖动, 
/* CPU port interrupt */ //这里对寄存器特定位这样宏定义是有好处的,好好体会
#define HAL_KEY_CPU_PORT_0_IF P0IF    //P0口中断标志位
#define HAL_KEY_CPU_PORT_2_IF P2IF    //P2口中断标志位 ,0无中断,1有中断发生
#if defined ( CC2540_MINIDK ) || (iTA)    //按键引脚定义封装
/* SW_1 is at P0.0闪烁功能 */
#define HAL_KEY_SW_1_PORT   P0 //按键映射到物理引脚P0引脚,但是没有P0.0这样定义,这是一个处理方法
#define HAL_KEY_SW_1_BIT    BV(0)    //这个宏提供了对P0.0的精确控制,对P0.0置1这两个宏组合可以发现P0.0按键按下,是我们按键轮询的依据
#define HAL_KEY_SW_1_SEL    P0SEL    //P0SEL 引脚功能选择 0 GPIO ,1 外设
#define HAL_KEY_SW_1_DIR    P0DIR     //设置引脚输入 0 /输出 1

/* SW_2 is at P0.1 模拟来电*/
#define HAL_KEY_SW_2_PORT   P0
#define HAL_KEY_SW_2_BIT    BV(1)
#define HAL_KEY_SW_2_SEL    P0SEL
#define HAL_KEY_SW_2_DIR    P0DIR

/* SW_3 is at P0.2 模式转换*/
#define HAL_KEY_SW_3_PORT   P0
#define HAL_KEY_SW_3_BIT    BV(2)
#define HAL_KEY_SW_3_SEL    P0SEL
#define HAL_KEY_SW_3_DIR    P0DIR
//P0.0 按键中断设置
#define HAL_KEY_SW_1_IEN      IEN1  /* CPU interrupt mask register P0口中断总开关,对P0.0~P0.7所有端口控制*/
#define HAL_KEY_SW_1_ICTL     P0IEN /* Port Interrupt Control register端口P0.0~P0.7中断开关,每个端口的分别控制 */
//下面这两个宏是对上面两个宏进行设置的宏
#define HAL_KEY_SW_1_ICTLBIT  BV(0) /* P0IEN - P0.0 enable/disable bit */
#define HAL_KEY_SW_1_IENBIT   BV(5) /* Mask bit for all of Port_0 */
//当P0.0~P0.7发生中断时P0IFG相应位置位
#define HAL_KEY_SW_1_PXIFG    P0IFG /* Interrupt flag at source 当P0.0到P0.7有中断发生时,相应位置位*/

//P0.1 按键中断设置
#define HAL_KEY_SW_2_IEN      IEN1  /* CPU interrupt mask register */
#define HAL_KEY_SW_2_ICTL     P0IEN /* Port Interrupt Control register */
#define HAL_KEY_SW_2_ICTLBIT  BV(1) /* P0IEN - P0.1 enable/disable bit */
#define HAL_KEY_SW_2_IENBIT   BV(5) /* Mask bit for all of Port_0 */
#define HAL_KEY_SW_2_PXIFG    P0IFG /* Interrupt flag at source */
//P0.2 按键中断设置
#define HAL_KEY_SW_3_IEN      IEN1  /* P0口中断开关 */
#define HAL_KEY_SW_3_ICTL     P0IEN /* P0.0~P0.7中断开关,相应位置一开中断 */
#define HAL_KEY_SW_3_ICTLBIT  BV(2) /* 开P0.2中断位掩码 */
#define HAL_KEY_SW_3_IENBIT   BV(5) /*开P0口中断位掩码*/
#define HAL_KEY_SW_3_PXIFG    P0IFG //中断标志位
#define HAL_KEY_SW_1_EDGEBIT  BV(0) /*按键中断触发方式选择,这里是下降沿触发 因为共地接法*/

这上面主要对一些按键引脚,定时器的一些位,以及定时器进行了宏定义;下面将用这些宏对按键引脚端口属性进行设置。

在halkeyinit()函数中就做了一件事情,把按键引脚设置成GPIO模式同时是输入模式。那么就完成了第一大块1、2两件事情

HAL_KEY_SW_1_SEL &= ~(HAL_KEY_SW_1_BIT);    /* Set pin function to GPIO */ 
HAL_KEY_SW_1_DIR &= ~(HAL_KEY_SW_1_BIT);  /* Set pin direction to Input */ 
HAL_KEY_SW_2_SEL &= ~(HAL_KEY_SW_2_BIT);    /* Set pin function to GPIO */ 
HAL_KEY_SW_2_DIR &= ~(HAL_KEY_SW_2_BIT);   /* Set pin direction to Input*/ 
//增加P0.2的模式选择按键,GPIO,输入设置
HAL_KEY_SW_3_SEL &= ~(HAL_KEY_SW_3_BIT);   /* Set pin function to GPIO */
HAL_KEY_SW_3_DIR &= ~(HAL_KEY_SW_3_BIT);   /* Set pin direction to input*/

//同时对按键回调函数初始化为空,以及标记现在按键还没设置完
/* Initialize callback function */
pHalKeyProcessFunction = NULL;
//这个函数主要是用来发送按键消息,同时有按键按下将对按键的检测方式改为轮询
//下面可以发现现在回调函数是OnBoard_KeyCallback
/* Start with key is not configured */
HalKeyConfigured = FALSE;

下面这个函数完成了第一大块的第三件事情
HalKeyConfig函数,这个函数在对按键设置里面有非常重要的作用。分为两大块。就是这个函数完成在中断方式和轮询方式的转换,可以看到在OnBoard_KeyCallback函数里面就对这个函数进行了调用。
1这部分代码设置按键进入中断模式

/* Enable/Disable Interrupt or */
// Hal_KeyIntEnable是中断方式和轮询方式的标志位
  Hal_KeyIntEnable = interruptEnable;
  /* Register the callback fucntion */
//按键回调函数OnBoard_KeyCallback,在按键halkeypoll函数调用
  pHalKeyProcessFunction = cback;
  /* Determine if interrupt is enable or not */
  if (Hal_KeyIntEnable)        //如果是开按键中断,进行中断设置
  {
#if defined ( CC2540_MINIDK ) || (iTA)
    /* Rising/Falling edge configuratinn *///设置P0口中断为下降沿触发
  /* Set the edge bit to set falling edge to give interrupt */
    PICTL |= HAL_KEY_SW_1_EDGEBIT;  //设置最后位即P0端口为下降沿给中断

    /* enable interrupt generation at port *///启动P0.0~P0.7中断P0IEN
    HAL_KEY_SW_1_ICTL |= HAL_KEY_SW_1_ICTLBIT;
    /* enable CPU interrupt *///启动P0总中断IEN1
    HAL_KEY_SW_1_IEN |= HAL_KEY_SW_1_IENBIT; 
    /* Clear any pending interrupt */  //清除任何中断标志位
    HAL_KEY_SW_1_PXIFG &= ~(HAL_KEY_SW_1_BIT); 

    HAL_KEY_SW_2_ICTL |= HAL_KEY_SW_2_ICTLBIT; /* enable interrupt generation at port */
    HAL_KEY_SW_2_IEN |= HAL_KEY_SW_2_IENBIT;   /* enable CPU interrupt */
    HAL_KEY_SW_2_PXIFG &= ~(HAL_KEY_SW_2_BIT); /* Clear any pending interrupt */

    HAL_KEY_SW_3_ICTL |= HAL_KEY_SW_3_ICTLBIT; // 启动p0.2中断
    HAL_KEY_SW_3_IEN |= HAL_KEY_SW_3_IENBIT;    //启动P0口中断
    HAL_KEY_SW_3_PXIFG &=~(HAL_KEY_SW_3_BIT);   //清零P0.2的中断标志位

这上面就完成了对所有按键中断寄存器,以及中断寄存器位的设置;然后
/* Key now is configured */
HalKeyConfigured = TRUE;
说明按键设置完成了。上面是按键初始化在这个函数执行的代码,当然这个函数不止这些代码,其他代码是在运行时候执行的,用于中断方式和轮询方式的转换用。
2这部分代码是设置引脚进入轮询模式的代码

else    /* Interrupts NOT enabled */    //按键中断没开
  {
#if defined ( CC2540_MINIDK ) || ( iTA )
    HAL_KEY_SW_1_ICTL &= ~(HAL_KEY_SW_1_ICTLBIT); /* don‘t generate interrupt */
    HAL_KEY_SW_1_IEN &= ~(HAL_KEY_SW_1_IENBIT);   /* Clear interrupt enable bit */

    HAL_KEY_SW_2_ICTL &= ~(HAL_KEY_SW_2_ICTLBIT); /* don‘t generate interrupt */
    HAL_KEY_SW_2_IEN &= ~(HAL_KEY_SW_2_IENBIT);   /* Clear interrupt enable bit */

    HAL_KEY_SW_3_ICTL &= ~( HAL_KEY_SW_3_ICTLBIT );   //不产生中断
    HAL_KEY_SW_3_IEN &= ~( HAL_KEY_SW_3_IENBIT );     //清除P0口中断使能
    osal_set_event(Hal_TaskID, HAL_KEY_EVENT);

这里可以看到在中断标志位为0的情况下执行上面这部分代码,是在有按键的按下的情况下执行,其实就是对相应IO口中断功能给关了,同时设置一个HAL_KEY_EVENT事件为轮询做准备, 跟踪代码可发现就是调用了按键轮询函数HalKeyPoll,然后设置了一个定时器周期触发HAL_KEY_EVENT事件然后去轮询按键。

//下面这部分代码是向按键按下然后释放了,它的if条件说明了在对按键初始化配置的时候是不会执行的。那么再次调用HalKeyConfig函数把1部分代码给执行了引脚重新回到中断方式,在执行下面这个代码关闭轮询定时器以可以进入睡眠模式,

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

第二大块:是按键事件的触发检测,轮询,按键消息发送
假设有一个按键刚按下:处理流程如下
第一级触发检测:CC2540里面使用中断去扑捉按键按下动作
HAL_ISR_FUNCTION这个函数是P1端口的中断服务程序

#if defined ( CC2540_MINIDK ) || (iTA) //这里的宏是选择硬件平台
  if ((HAL_KEY_SW_1_PXIFG & HAL_KEY_SW_1_BIT) || (HAL_KEY_SW_2_PXIFG & HAL_KEY_SW_2_BIT) || (HAL_KEY_SW_3_PXIFG & HAL_KEY_SW_3_BIT) )//这里是分别对S1 S2 S3按键按下情况检测
//如果有按键按下,调用下面按键中断处理函数
#else
  if (HAL_KEY_SW_6_PXIFG & HAL_KEY_SW_6_BIT)
#endif
  {
    halProcessKeyInterrupt();
  }

然后清除P0.0 P0.1按键 的中断状态标志位,全清或者对每一位单独清零都行,中断状态标志位只是为了进入中断服务程序,所以必须清零。然后用其他函数再去轮询按键获得按键的当前的真实状态。

#if defined ( CC2540_MINIDK ) || ( iTA )
  HAL_KEY_SW_1_PXIFG = 0;
  HAL_KEY_SW_2_PXIFG = 0;

#else
  HAL_KEY_SW_6_PXIFG = 0;
#endif
  HAL_KEY_CPU_PORT_0_IF = 0;    //清除P0口的中断标志位

这里需要注意的是P0端口每个引脚的中断标志位清零要在P0端口清零之前进行。

在进入halProcessKeyInterrupe()函数对按键进行进一步的盘查

void halProcessKeyInterrupt (void)
{
  bool valid=FALSE;
#if defined ( CC2540_MINIDK ) || ( iTA )
//这里任何一个if分支为真都能是valid为真,然后触发定时器,在15ms后去执行
HAL_KEY_EVENT事件
  if( HAL_KEY_SW_1_PXIFG & HAL_KEY_SW_1_BIT) /* Interrupt Flag has been set by SW1 */
  {
    HAL_KEY_SW_1_PXIFG = ~(HAL_KEY_SW_1_BIT); /* Clear Interrupt Flag */
    valid = TRUE;
  }
  if (HAL_KEY_SW_2_PXIFG & HAL_KEY_SW_2_BIT)  /* Interrupt Flag has been set by SW2 */
  {
    HAL_KEY_SW_2_PXIFG = ~(HAL_KEY_SW_2_BIT); /* Clear Interrupt Flag */
    valid = TRUE;
  }
  if (HAL_KEY_SW_3_PXIFG & HAL_KEY_SW_3_BIT)
  {
    HAL_KEY_SW_3_PXIFG &= ~(HAL_KEY_SW_3_BIT);
    valid =TRUE;
  }
#else
  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;
  }
#endif
  if (valid)    //valid为真说明按下了按键,触发 HAL_KEY_EVENT事件为了轮询按键
  {
    osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_DEBOUNCE_VALUE);
  }
}

//友情提醒,上面所有代码都在HAL_KEY.C文件中,可以说上面所有的工作都是为了按键检测能自动扑捉的前提工作。可以说到此中断这层对按键触发的检测是完成了。
………………..通过这个定时器,我们在往上走到了hal_drivers.c文件的事件处理函数,到了硬件抽象层啦。定位到HAL_KEY_EVENT事件,然后调用了HalKeyPoll()这个函数,它就是去获得那个按键按下了,然后记录按键号
这些是在hal_key.h文件定义的按键号

#define HAL_KEY_SW_1     0x01   
#define HAL_KEY_SW_2     0x02 
#define HAL_KEY_SW_5     0x04  
#define HAL_KEY_SW_4     0x08  
#define HAL_KEY_SW_3     0x10 

HalKeyPoll()函数代码解析
这一段代码是轮询代码的精髓,定位每个按键,同时将所有按键按下的按键号记录到keys变量中,keys有八位那么可以记录八个按键的信息

if (    !(HAL_KEY_SW_1_PORT & HAL_KEY_SW_1_BIT)    )    /* Key is active low */
  {
    keys |= HAL_KEY_SW_1;
  }
  if ( !(HAL_KEY_SW_2_PORT & HAL_KEY_SW_2_BIT) )    /* Key is active low */
  {
    keys |= HAL_KEY_SW_2;
  }

  if (!(HAL_KEY_SW_3_PORT & HAL_KEY_SW_3_BIT))      //P0.2 模式选择
  {
    keys |= HAL_KEY_SW_3;
  }

下一步:中断是否开启来判定是否是中断方式或者轮询方式,Hal_KeyIntEnable =FALSE 轮询方式,Hal_KeyIntEnable =TRUE,说明在中断方式。因为这里是按键刚按下说明还处于中断模式下,那么执行红色代码
特别提醒:Hal_KeyIntEnable变量是中断方式或者轮询方式的标志位,0轮询方式,1中断方式。

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;

——————————–华丽丽分割线————————————–
以上就完成了对按键事件触发的检测,下面就是发送按键消息,同时呢把按键检测工作模式转换为轮询模式。
轮询函数最后这里憋了一个大招,回调代码。回调函数pHalKeyProcessFunction = OnBoard_KeyCallback,
这个函数在Onboard.c文件里面。也可以看到这里传递的参数是按键号keys,以及HAL_KEY_STATE_NORMAL宏,这个宏具体的作用是没有的。

if (notify && (pHalKeyProcessFunction))
  {
    (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
  }

那我们就跳到OnBoard_KeyCallback函数里面,总结分析这个函数功能,就做了两件事,第一件事:发送按键消息,第二件事:对按键工作模式转换为轮询模式进行设置。
第一件事:发送按键消息
if ( OnBoard_SendKeys( keys, shift ) != SUCCESS )
第二件事:转换为轮询方式,这是很重要的的一部分
//当前如果有任何按键按下而且中断仍然开启,那么关闭中断以启动轮询方式。

if( keys != 0 )
  {
    if( OnboardKeyIntEnable == HAL_KEY_INTERRUPT_ENABLE )
    {
      OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE;
      HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
       //再次进入这个函数就是执行上面那2段代码的。关闭中断,同时设置HAL_KEY_EVENT事件
    }
  }
  //当前如果没有按键按下而且中断关闭,那么就要开启中断以按键进入中断工作方式
  else
  {
    if( OnboardKeyIntEnable == HAL_KEY_INTERRUPT_DISABLE )
    {
      OnboardKeyIntEnable = HAL_KEY_INTERRUPT_ENABLE;
      HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
//再次进入这个函数就是执行上面1段代码,开启中断,同时关闭HAL_KEY_EVENT事件的软件定时器
    }
  }

因为按键按下了那么执行的是if分支,然后进入轮询模式,每100ms调用轮询函数HalKeyPoll()去轮询按键状态,然后这个时候halkeypoll是执行

if (!Hal_KeyIntEnable)
  {
    if (keys == halKeySavedKeys)
    {
      /* Exit - since no keys have changed */
      return;
    }
    else
    {
      notify = 1;
    }

这段代码,轮询的功能是检查按键跟上次轮询获得按键状态是否一致,若一致那么退出函数执行,也就不会再次调用OnBoard_KeyCallback函数。如果不一致,比如新按下一个按键,那么重新封装按键消息发送到上层应用。让上层应用获取当前按键按下的最新信息。
突然某个时间,按键释放了,但是程序还是处于轮询工作模式,那么相比按下按键状态keys变量是有变化的,所以在释放按键的时候也是会产生一个按键消息的。相当于上层按键处理函数会获得两个按键消息,一个是按键按下的时候,包含了按键号,另外一个是按键释放的时候,按键号keys = 0。然后keys = 0说明按键释放了。设置中断模式,按键重新进入中断工作模式,并把定时器给关了。

但是这里有个小问题是:按键释放了,然后重新设置进入中断模式,那么在轮询模式下的引脚的中断标志位是会置位的,那么会不会错误发送一个按键按下的消息给上层呢。虽然我们把P0口,P0.0和P0.1的中断使能给关了不会中断响应。但是我们没有对相应的中断标志位清零的。所以再次转入中断模式下呢会触发中断服务函数HAL_ISR_FUNCTION( halKeyPort0Isr, P0INT_VECTOR )执行并调用halProcessKeyInterrupt 函数去查看中断标志是否有置位,当然有的,然后设置了HAL_KEY_EVENT事件,触发halkeypoll函数轮询按键,还好因为keys =0 所以不会发生任何事情。执行一次而已。其实我们以前写中断服务程序都是一个函数写到黑,那么这些问题就不能避免了,但是这样用函数调用的方式很好的避免了这样的问题。不愧是意外之喜。
2015/06/03 23:17

OSAL之按键驱动

标签:按键   事件   消息   发送   osal   

原文地址:http://blog.csdn.net/jq_ak47/article/details/46417327

(0)
(1)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!