OSAL之按键驱动

来源:互联网 发布:骚扰电话 软件 编辑:程序博客网 时间:2024/05/21 11:32

本博文根据协议栈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

0 0