今天介绍一下z-stack的key驱动程序。先看一下我板子上的按键连接图
可以看到总共有六个按键,不算复位键,up、down、left、right四个为摇杆的方向键,PUSH为摇杆的中心键,这个五个键接的是P20,通过接到P06的AD转换功能来判断出是哪个按键按下。当按下五个键中任何一个时,P20就变为高电平,在上升沿触发中断标志。TI官方的板子跟这个电路图有点不同,z-stack里面key驱动是按照TI的官方板子写的,我把其中需要改的地方改过来了,等哈会提到。
先看hal_key.h这个头文件
#define HAL_KEY_SW_1 0x01 // Joystick up
#define HAL_KEY_SW_2 0x02 // Joystick right
#define HAL_KEY_SW_5 0x04 // Joystick center
#define HAL_KEY_SW_4 0x08 // Joystick left
#define HAL_KEY_SW_3 0x10 // Joystick down P06
#define HAL_KEY_SW_6 0x20 // Button S5 if available
#define HAL_KEY_SW_7 0x40 // Button S2 if available P05 板子上是P0.5
这个源程序的定义,我板子上没有SW6,其中SW7对应的是板子的Button S2,如上图连接的是P05,这里对按键采用位图的定义,即某一位对应一个按键,对后面的按键的处理有很好的帮助,值得借鉴。
然后定义了key回调函数的函数指针typedef void (*halKeyCBack_t) (uint8 keys, uint8 state);参数keys即为上面按键定义的组合,如keys=HAL_KEY_SW_1|HAL_KEY_SW_5,参数state为keys的状态,在上面可以看到这两个定义
/* Key state - shift or normal */
#define HAL_KEY_STATE_NORMAL 0x00
#define HAL_KEY_STATE_SHIFT 0x01
HAL_KEY_STATE_NORMAL对应普通按键,HAL_KEY_STATE_SHIFT对应的摇杆的按键。接下来看下hal_key.c这个源文件。先看下这个两个宏定义
#define HAL_KEY_DEBOUNCE_VALUE 25 //中断方式 中断之后延迟25ms进入按键处理程序
#define HAL_KEY_POLLING_VALUE 100 //查询方式 每100ms检查是否有按键按下
正像我后面作出的注释,z-stack对按键的处理采用了中断和查询方式。中断方式和查询方式有一半相同点有一半不同,相同的是在按键按下之后过某个设定的时间后设置事件标志HAL_KEY_EVENT,然后在下一次系统轮询中对此事件进行处理调用相关的按键处理函数,不同的是对于查询方式,CPU需要定期检查按键的状态,当检测到按键按下延迟100ms向HAL层发出HAL_KEY_EVENT事件,对于中断方式,当按键按下时,则立刻发出HAL_KEY_EVENT事件,不需要CPU的检查,中断方式比查询方式实时性好。这两个宏定义就是针对两种不同key处理方式而设置的延迟时间!在后面再讲key的处理过程。先看看key驱动都是什么东东!
/* SW_6 is at P0.1 */
/*板子上面没有SW_6,改成SW_7 at P0.5*/
#define HAL_KEY_SW_7_PORT P0
#define HAL_KEY_SW_7_BIT BV(5)
#define HAL_KEY_SW_7_SEL P0SEL
#define HAL_KEY_SW_7_DIR P0DIR
对于我的板子 上面没有SW_6而SW_7对应于S2 at P0.5,所以讲所有的HAL_KEY_SW_6_X改成HAL_KEY_SW_7_X,且之前的设置是在P01,而SW_7是在P05,所以相关的寄存器设置都相应改过来。这里有一大堆宏定义看datasheet,对于学过单片机的同学就很简单,这里不一一列举出来了。
看一下几个全局变量halKeySavedKeys /* used to store previous key state in polling mode */如注释,用于查询方式的保存之前按键的状态。pHalKeyProcessFunction这个是按键的回调函数。HalKeyConfigured是key是否配置,如果是,则置为TRUE,否则FALSE;Hal_KeyIntEnable表示是否采用中断方式,如果是则为TRUE;
全局函数halProcessKeyInterrupt()是对按键的处理函数;halGetJoyKeyInput()是获取摇杆按键的具体按键值,即通过调用它就知道是具体哪个按键按下了。
按键初始化
HalKeyInit()
{
/* Initialize previous key to 0 */
halKeySavedKeys = 0;
HAL_KEY_SW_7_SEL &= ~(HAL_KEY_SW_7_BIT); /* Set pin function to GPIO */
HAL_KEY_SW_7_DIR &= ~(HAL_KEY_SW_7_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 */
/* Initialize callback function */
pHalKeyProcessFunction = NULL;
/* Start with key is not configured */
HalKeyConfigured = FALSE;
}
初始化做的工作就是设置按键对应的P05,P20,方向为输入,功能为GPIO,然后置回调函数为空,将HalKeyConfigured 置为FALSE,即还没有配置key。
key配置函数
void HalKeyConfig (bool interruptEnable, halKeyCBack_t cback)
{
/* Enable/Disable Interrupt or */
Hal_KeyIntEnable = interruptEnable;
/* Register the callback fucntion */
pHalKeyProcessFunction = cback;
/* Determine if interrupt is enable or not */
if (Hal_KeyIntEnable)
{
/* Rising/Falling edge configuratinn */
PICTL &= ~(HAL_KEY_SW_7_EDGEBIT); /* Clear the edge bit */
/* For falling edge, the bit must be set. */
#if (HAL_KEY_SW_7_EDGE == HAL_KEY_FALLING_EDGE)
PICTL |= HAL_KEY_SW_7_EDGEBIT;
#endif
/* Interrupt configuration:
* - Enable interrupt generation at the port
* - Enable CPU interrupt
* - Clear any pending interrupt
*/
HAL_KEY_SW_7_ICTL |= HAL_KEY_SW_7_ICTLBIT;
HAL_KEY_SW_7_IEN |= HAL_KEY_SW_7_IENBIT;
HAL_KEY_SW_7_PXIFG &= ~(HAL_KEY_SW_7_BIT);
/* Rising/Falling edge configuratinn */
//纠正错误将HAL_KEY_JOY_MOVE_ICTL改成PICTL
PICTL &= ~(HAL_KEY_JOY_MOVE_EDGEBIT); /* Clear the edge bit */
/* For falling edge, the bit must be set. */
#if (HAL_KEY_JOY_MOVE_EDGE == HAL_KEY_FALLING_EDGE)
PICTL |= HAL_KEY_JOY_MOVE_EDGEBIT;
#endif
/* Interrupt configuration:
* - Enable interrupt generation at the port
* - Enable CPU interrupt
* - Clear any pending interrupt
*/
HAL_KEY_JOY_MOVE_ICTL |= HAL_KEY_JOY_MOVE_ICTLBIT;
HAL_KEY_JOY_MOVE_IEN |= HAL_KEY_JOY_MOVE_IENBIT;
HAL_KEY_JOY_MOVE_PXIFG = ~(HAL_KEY_JOY_MOVE_BIT);
/* 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 */
{
HAL_KEY_SW_7_ICTL &= ~(HAL_KEY_SW_7_ICTLBIT); /* don't generate interrupt */
HAL_KEY_SW_7_IEN &= ~(HAL_KEY_SW_7_IENBIT); /* Clear interrupt enable bit */
osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE); /* Kick off polling */
}
/* Key now is configured */
HalKeyConfigured = TRUE;
}
将参数interruptEnable传递给Hal_KeyIntEnable,表示是否采用中断方式,然后将回调函数赋值给pHalKeyProcessFunction,配置函数根据key不同方式采用了不同的配置。如果是中断方式,就将相应中断的屏蔽位置1,使能中断,清零相应的中断标志位。最后
if (HalKeyConfigured == TRUE)
{
osal_stop_timerEx( Hal_TaskID, HAL_KEY_EVENT); /* Cancel polling if active */
}
这句代码又是干什么呢?其实这个是在进入睡眠模式之前用的,如果在进入睡眠之前重新配置了key,则就停止key事件的触发,即系统不接收按键的事件了。如果是查询模式就禁用相应中断。并最后调用
osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE);这是一个系统软定时器,即想任务Hal_TaskID在经过HAL_KEY_POLLING_VALUE即100ms后发送HAL_KEY_EVENT事件。
最后将HalKeyConfigured置为TRUE说明已经配置好了。
按键读取键值函数
uint8 HalKeyRead ( void )
{
uint8 keys = 0;
#ifdef HAL_BOARD_CC2530EB_REV17
if ( !(HAL_KEY_SW_7_PORT & HAL_KEY_SW_7_BIT)) /* Key is active LOW */
#elif defined (HAL_BOARD_CC2530EB_REV13)
if (!(HAL_KEY_SW_7_PORT & HAL_KEY_SW_7_BIT)) /* Key is active low */
#endif
{
keys |= HAL_KEY_SW_7; //即板子上的P0.5
}
if ((HAL_KEY_JOY_MOVE_PORT & HAL_KEY_JOY_MOVE_BIT)) /* Key is active low */
//扯淡 明显是active high
{
keys |= halGetJoyKeyInput();
}
return keys;
}
条件编译可以先不用管。这里我根据我板子上改正源程序中的几个地方。对于SW7是低电平按键有效,而源程序中是高电平,对于摇杆按键是高电平有效,源程序是低电平。如果是HAL_KEY_SW_7_PORT 为低电平,则keys就包含了SW_7,如果HAL_KEY_JOY_MOVE_PORT为高电平则调用halGetJoyKeyInput()这个函数根据ADC读取按键的电压值来判断是哪个键按下了,最后keys就是这些按键的组合,因为这些按键是按位定义的,所以刚好将他们相或就能组合在一起,这就是这样定义的好处。
按键查询函数
void HalKeyPoll (void)
{
uint8 keys = 0;
if (!(HAL_KEY_SW_7_PORT & HAL_KEY_SW_7_BIT)) /* Key is active low */
{
keys |= HAL_KEY_SW_7;
}
if ((HAL_KEY_JOY_MOVE_PORT & HAL_KEY_JOY_MOVE_BIT)) /* Key is active HIGH */
{
keys |= halGetJoyKeyInput();
}
/* 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;
}
/* Store the current keys for comparation next time */
halKeySavedKeys = keys;
}
else
{
/* Key interrupt handled here */
}
/* Invoke Callback if new keys were depressed */
if (keys && (pHalKeyProcessFunction))
{
(pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL);
}
}
按键查询函数就是根据查询到的按键值最终调用按键回调函数,这个回调函数由用户实现。
看下中断方式的按键中断函数
HAL_ISR_FUNCTION( halKeyPort0Isr, P0INT_VECTOR )
{
if (HAL_KEY_SW_7_PXIFG & HAL_KEY_SW_7_BIT)
{
halProcessKeyInterrupt();
}
/*
Clear the CPU interrupt flag for Port_0
PxIFG has to be cleared before PxIF
*/
HAL_KEY_SW_7_PXIFG = 0;
HAL_KEY_CPU_PORT_0_IF = 0;
}
这是其中P0的中断函数,调用的halProcessKeyInterrupt(); 这个函数,
void halProcessKeyInterrupt (void) //按键的中断方式
{
bool valid=FALSE;
if (HAL_KEY_SW_7_PXIFG & HAL_KEY_SW_7_BIT) /* Interrupt Flag has been set */
{
HAL_KEY_SW_7_PXIFG = ~(HAL_KEY_SW_7_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;
}
if (valid)
{
osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_DEBOUNCE_VALUE);
}
}
这就是中断方式的发送按键事件的函数当中断标志置位后,将其清零,最终调用系统定时器函数,在HAL_KEY_DEBOUNCE_VALUE即25ms之后发送HAL_KEY_EVENT事件,这个事件会被HAL任务处理函数Hal_ProcessEvent()所接收并处理,这个函数在hal_drivers.c文件中。以上就是按键驱动的几个重要函数。下面看一下整个按键处理的流程。
我们看一下main函数第二次调用InitBoard函数此时level!=OB_COLD则
OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE;
HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);
看到没,这里采用查询方式,回调函数配置为OnBoard_KeyCallback。因为在HalKeyConfig函数中查询方式的话调用的是osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE);即经过HAL_KEY_POLLING_VALUE向HAL驱动层发送HAL_KEY_EVENT事件,此时看一下HAL驱动层的任务处理函数Hal_ProcessEvent()
{
......
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);
}
#endif // HAL_KEY
return events ^ HAL_KEY_EVENT;
}
.......
}
看到么,在任务处理函数中调用了按键查询函数HalKeyPoll();这个函数在上面讲过了就是查询是哪个按键按下了,如果有按键按下就会调用按键的回调函数。如果没有就直接返回了。最后继续调用osal_start_timerEx延迟100ms向hal层发送按键事件。即查询方式话,系统每隔100ms查询是否有按键按下,如果有就进行相关的处理,如果没有则继续查询。查询的方式相对来说比较耗CPU,而且如果需要用按键的话系统不能进入睡眠模式,中断方式就不同,可以在任何时候处理按键。其实按键对于我们开发ZigBee项目来说很重要,比如按下一个键之后协调器建立网络,或者按下某个键,协调器就允许其他节点来跟它绑定!但是在最终的产品中可以不需要按键模块,这个只是在开发过程中比较有用!
以上就是z-stack的整个key驱动的详解过程,如果有理解不到位的地方还希望指出来!