-STACK按键的使用总结
#define HAL_KEY_SW_6_ENABLE // SW_6的IO端口 #define HAL_KEY_SW_6_PORT P0 //SW6接到IO端口的位数 P0.1 #define HAL_KEY_SW_6_BIT HAL_KEY_BIT1 // SW_6的IO端口选择 #define HAL_KEY_SW_6_SEL P0SEL // SW_6的IO端口方向 #define HAL_KEY_SW_6_DIR P0DIR // SW_6的IO端口中断使能 #define HAL_KEY_SW_6_IEN IEN1 // SW_6的IO端口中断使能的掩码 #define HAL_KEY_SW_6_IENBIT HAL_KEY_BIT5 // SW_6的IO端口中断的边沿选择 #define HAL_KEY_SW_6_EDGE HAL_KEY_RISING_EDGE // SW_6的IO端口边沿掩码 #define HAL_KEY_SW_6_EDGEBIT HAL_KEY_BIT0 // SW_6的IO端口总中断 #define HAL_KEY_SW_6_ICTL PICTL // SW_6的IO端口总中断掩码 #define HAL_KEY_SW_6_ICTLBIT HAL_KEY_BIT3 // SW_6的IO端口中断标志位 #define HAL_KEY_SW_6_PXIFG P0IFG 按键主要使用的是IO来设置的,这里需要设置的参数主要有按键设置在哪个端口以及掩码、按键中断使能标志以及掩码、引起中断的上升沿还是下降沿以及掩码 涉及的主要寄存器有 PICTL端口输入中断控制
IEN1端口0总中断使能
IEN2 端口1和2总中断使能 比如需要设置HAL_KEY_SW_6为P0.4为输入下降沿有效中断设置如下: #define HAL_KEY_SW_6_ENABLE #define HAL_KEY_SW_6_PORT P0 #define HAL_KEY_SW_6_BIT HAL_KEY_BIT4 #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_FALLING_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_BIT4 #define HAL_KEY_SW_6_PXIFG P0IFG 比如需要设置HAL_KEY_SW_6为P2.1为输入上升沿有效中断设置如下: #define HAL_KEY_SW_6_ENABLE #define HAL_KEY_SW_6_PORT P2 #define HAL_KEY_SW_6_BIT HAL_KEY_BIT4 #define HAL_KEY_SW_6_SEL P2SEL #define HAL_KEY_SW_6_DIR P2DIR #define HAL_KEY_SW_6_IEN IEN2 #define HAL_KEY_SW_6_IENBIT HAL_KEY_BIT1 #define HAL_KEY_SW_6_EDGE HAL_KEY_RISING_EDGE #define HAL_KEY_SW_6_EDGEBIT HAL_KEY_BIT2 #define HAL_KEY_SW_6_ICTL PICTL #define HAL_KEY_SW_6_ICTLBIT HAL_KEY_BIT5 #define HAL_KEY_SW_6_PXIFG P2IFG 这样设置后就可以正常使用KEY 中断。 按键有两种工作模式:轮询(Poll)和中断(Interrupt) 轮询 按键处理函数是HalKeyPoll (void),这个函数会在HAL的事件处理Hal_ProcessEvent()中的HAL_KEY_EVENT事件处理过程中被调用,轮询周期由#define HAL_KEY_POLLING_VALUE 100这边定义,通过在配置函数中设置软件定时器osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT,HAL_KEY_POLLING_VALUE);来启动定时器, 当溢出的时候向HAL发出HAL_KEY_EVENT事件。 在事件处理函数中会检查当前是否使能中断模式,如果使能的话就跳到中断模式操作,否则重新启动定时器,轮询时间按100ms算。 HalKeyPoll()函数中会调用按键回调函数,根据按键状态来处理按键信息。 中断 对应处理函数是 halProcessKeyInterrupt (void)会在对应管脚的中断处理函数中被调用。 根据配置函数的参数interruptEnable的情况来设置中断或者轮询。 中断处理函数如下: 按键中断处理函数: 当检查到有按键按下则会启动一个定时事件,定时长度有HAL_KEY_DEBOUNCE_VALUE定义。最终执行函数还是在HAL的事件处理函数中的HAL_KEY_EVENT中被执行。 区别 按键中断和轮询的主要区别是轮询需要CPU定期的检查按键状态,当检测到按键按下则发出HAL_KEY_EVENT消息到HAL层来执行处理,中断则是当有按键按下立刻向HAL_KEY_EVENT发出消息,不需要CPU的检查。轮询的实时性比中断要差点,推荐用中断方式。 处理流程图 Z-stack中对按键的处理 在基于Z-stack的应用程序设计中,HAL(硬件抽象层)是这样运行的: void osalInitTasks( void ) {…… Hal_Init( taskID++ ); …… } 打开 Hal_Init( ),似乎什么也没有做,只是完成了一件事情,给这个任务一个ID,实际上,对硬件的初始化的工作,在任务启动之前都已经开始做了,任务的运行,只是可以接收发给它的事件和消息。 那么,在HAL任务运行之前,系统对任务做了些什么呢? 当然是初始化。在ZMain.c文件中,有main函数,这是所有程序的入口。由于硬件是所有程序运行的基础,在这里要完成两个重要的函数: // Initialize board I/O InitBoard( OB_COLD ); // Initialze HAL drivers HalDriverInit(); 显然,它们的执行时间是早于任务的运行,在InitBoard()完成对板级I/O的设置。进去看一下: void InitBoard( byte level ) { if ( level == OB_COLD ) { // Interrupts off osal_int_disable( INTS_ALL ); } else // !OB_COLD { OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE; HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback); } } 代码太多,不再全部列出,主要是完成对Led。Timer和key的配置。这里重点看按键有关的。由于InitBoard函数的参数是OB_COLD,郁闷的HalKeyConfig()没有机会运行。 在经过耐心的等待之后,main()需要再次对开发板初始化,调用函数: InitBoard( OB_READY ); 机会来了,这时可以处理按键了, HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback)函数可以运行,在该函数中定义了是以中断的方式还是以查询的方式检测按键的状态,如果是查询方式,使用: osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE); 延迟100ms后向任务Hal_TaskID发送一个事件。在事件处理代码中使用函数 HalKeyPoll()查询是否有按键按下。 如果是中断方式,使用 osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_DEBOUNCE_VALUE); 延迟25ms后使用函数HalKeyPoll()获取按键值。
安装IAR 8051 7.30B 运行安装程序EW8051-EV-730B.exe,这里说一下如何快速的查找代码,按下Ctrl+Shift+f 可以在整个项目中查找你想要的关键字,注意选择和你workspace工作空间对应的文件,通常有CC2430DB和CC2430EB两个。把光标放在函数名上,右键 选择Go to definition fo XX就可以跳到该函数定义处,工具栏的Navigate Backward 和Navigate Forward 可以让你来回穿梭,还有很多功能,这里不多说了。 安装ZigBee2006 下载Zigbee协议栈压缩包swrc073d.zip,安装后一般在C盘可以找到Texas Instruments文件夹,把它复制,考到D盘,我的IAR装在D盘,有必要看下Documents里面的文档,如Create New Application For The CC2430DB_F8W-2005-0033_.pdf如何新建项目;其它的就不多说了,下面是按键的简单说明,可以初步了解一下OSAL;例子目录为: Texas Instruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\SimpleApp\CC2430DB Workspace 选择 simplecollectorEB ; 我们先从主函数说起,如果不知主函数在哪,可以Ctrl+Shift+f输入int main查找,...........为省略 ZSEG int main( void ) { // Turn off interrupts osal_int_disable( INTS_ALL ); ……………….. // Initialze HAL drivers HalDriverInit(); //HalKeyInit();初始化按键 ……………….. // Determine the extended address zmain_ext_addr(); //HalKeyRead();读取按键 ………………….. osal_init_system(); //RegisterForKeys( sapi_TaskID ); 注册按键任务 //或许你的协议栈注册按键不在这里面。那就是在应用层任务初始化里面 …………………….. // Final board initialization InitBoard( OB_READY ); /*HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);配置 按键,默认为轮询方式*/ ………………… osal_start_system(); // No Return from here 进入系统大循环 } // main() 从主函数可以看出,里面都是初始化函数init,执行过程HalDriverInit()àHalKeyInit();在HalKeyInit()里基本完成了相应管脚的输入输出配置,然后到zmain_ext_addr();时,判断物理扩展地址是否合法,如果不合法,则LED1一直闪烁,等while ( HAL_KEY_SW_5 != HalKeyRead() )按下把无效的地址初始化为有效地物理地址,然后到 osal_init_system();àosalInitTasks(); SAPI_Init( taskID ); RegisterForKeys( sapi_TaskID );注册按键事件, //注意:可能你的协议栈注册按键不在这里,而在应用层任务初始化里面。你的协议栈可能也没有SAPI_Init( taskID ); 最后InitBoard( OB_READY ); HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback);配置按键为中断方式还是轮询方式,从 /* Initialize Key stuff */ OnboardKeyIntEnable = HAL_KEY_INTERRUPT_DISABLE; HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback); 可以看出默认是配置为轮询方式的,这就是主函数大致对按键的处理过程,接下来从HalKeyConfig()入手, void HalKeyConfig (bool interruptEnable, halKeyCBack_t cback) { #if (HAL_KEY == TRUE) /* Enable/Disable Interrupt or */ Hal_KeyIntEnable = interruptEnable; /* Register the callback fucntion */ pHalKeyProcessFunction = cback; //指向回调函数 /* Determine if interrupt is enable or not */ if (Hal_KeyIntEnable) //如果设为中断方式 { ………………..进行一些中断的相关配置 } else /* Interrupts NOT enabled */ //否则为轮询方式 { ……………………. osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_POLLING_VALUE); /* Kick off polling */ } …………………….. } 可以看出,配置为轮询方式是时启动osal_start_timerEx()函数,那么这个函数是干什么的呢?这个是系统软定时器,在HAL_KEY_POLLING_VALUE时间(100ms)内会触发系统任务事件,也就是触发uint16 Hal_ProcessEvent( uint8 task_id, uint16 events );触发时会把Hal_TaskID、HAL_KEY_EVENT两个参数传给Hal_ProcessEvent();然后看看Hal_ProcessEvent()里面又做了些什么事, uint16 Hal_ProcessEvent( uint8 task_id, uint16 events ) { uint8 *msgPtr; ………………………. 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(); { osal_start_timerEx( Hal_TaskID, HAL_KEY_EVENT, 100); } #endif // HAL_KEY return events ^ HAL_KEY_EVENT; } …………………….. } 函数里面执行完HalKeyPoll();后,如果还是轮询方式,则再一次启动osal_start_timerEx();如此一来,就会每隔100ms循环进入Hal_ProcessEvent()函数读取按键,也就是说系统每隔100ms扫描一次按键,那么HalKeyPoll()又是干什么的呢?我们继续看看, void HalKeyPoll (void) { ……………… #if defined (HAL_KEY_SW_6_ENABLE) if (!(HAL_KEY_SW_6_PORT & HAL_KEY_SW_6_BIT)) /* Key is active low */ { keys |= HAL_KEY_SW_6; } #endif #if defined (HAL_KEY_SW_5_ENABLE) if (HAL_KEY_SW_5_PORT & HAL_KEY_SW_5_BIT) /* Key is active high */ { keys |= HAL_KEY_SW_5; } #endif ………….. 调用HalAdcRead()得出操纵杆的值,是通过AD进来了模拟电压值得出; /* Invoke Callback if new keys were depressed */ if (keys && (pHalKeyProcessFunction)) { (pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL); //回调函数 } } 该函数读出按键值keys,并执行了回调函数(pHalKeyProcessFunction) (keys, HAL_KEY_STATE_NORMAL); pHalKeyProcessFunction是在 void HalKeyConfig (bool interruptEnable, halKeyCBack_t cback)里面pHalKeyProcessFunction = cback;进行赋函数指针的,这样我们就进入回调函数了,我们来看一下回调函数: void OnBoard_KeyCallback ( uint8 keys, uint8 state ) //回调函数 { uint8 shift; // 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 ) != ZSuccess )//ZFailure,如果不成功则执行下面 { // Process SW1 here if ( keys & HAL_KEY_SW_1 ) // Switch 1 { } ………………… } } 回调函数里面又调用了OnBoard_SendKeys( keys, shift );接着看 byte OnBoard_SendKeys( byte keys, byte state ) { keyChange_t *msgPtr; if ( registeredKeysTaskID != NO_TASK_ID )//之前是否RegisterForKeys( sapi_TaskID );注册过? { // Send the address to the task 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 ); } 如果之前注册过按键事件,那么就会调用osal_msg_send( registeredKeysTaskID, (uint8 *)msgPtr );发送系统消息,它又会调用 osal_set_event(registeredKeysTaskID, SYS_EVENT_MSG );设置事件发生标志, byte osal_set_event( byte task_id, UINT16 event_flag ) { if ( task_id < tasksCnt ) { halIntState_t intState; HAL_ENTER_CRITICAL_SECTION(intState); // Hold off interrupts tasksEvents[task_id] |= event_flag; // Stuff the event bit(s) 置任务标志, HAL_EXIT_CRITICAL_SECTION(intState); // Release interrupts } else return ( INVALID_TASK ); return ( ZSUCCESS ); } 然后触发SAPI_ProcessEvent()应用层处理事件,SAPI_ProcessEvent()再调用zb_HandleKeys()函数进行最终的按键处理事件: void zb_HandleKeys( uint8 shift, uint8 keys ) { uint8 startOptions; uint8 logicalType; if ( keys & HAL_KEY_SW_5 )//我自己加的sw5按键处理 { P1_0=~P1_0; } ………….. } 经过了层层函数,最终到达了zb_HandleKeys()按键处理函数,其中的各种函数关系我们应该理清,这样对整个系统的OSAL编程有一定的了解,其中按键有两种处理方式,轮询和中断方式,系统默认为轮询方式, 下面再看一下中断方式的过程: 如果修改InitBoard( OB_READY )里的 OnboardKeyIntEnable = HAL_KEY_INTERRUPT_ ENABLE;//原HAL_KEY_INTERRUPT_DISABLE HalKeyConfig( OnboardKeyIntEnable, OnBoard_KeyCallback); 那么会把按键配置为中断方式,具体可看上面提到的HalKeyConfig()函数;此时如有按键按下,则会进入中断服务函数: HAL_ISR_FUNCTION( halKeyPort0Isr, P0INT_VECTOR ) { /* P0IF is cleared by HW for CHVER < REV_E */ halProcessKeyInterrupt(); //按键的中断处理 if( CHVER >= REV_E ) { …………………… } } 在中断函数中会执行halProcessKeyInterrupt()函数,我们看看 void halProcessKeyInterrupt (void) { #if (HAL_KEY == TRUE) bool valid=FALSE; #if defined (HAL_KEY_SW_6_ENABLE) 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; } #endif #if defined (HAL_KEY_SW_5_ENABLE) if (HAL_KEY_SW_5_PXIFG & HAL_KEY_SW_5_BIT) /* Interrupt Flag has been set */ { HAL_KEY_SW_5_PXIFG = ~(HAL_KEY_SW_5_BIT); /* Clear Interrupt Flag */ valid = TRUE; } #endif if (valid) { osal_start_timerEx (Hal_TaskID, HAL_KEY_EVENT, HAL_KEY_DEBOUNCE_VALUE);//25ms } #endif /* HAL_KEY */ } 我们终于发现了osal_start_timerEx()函数,在HAL_KEY_DEBOUNCE_VALUE时间(25ms)后再次触发,用于按键去抖,然后osal_start_timerEx()会触发Hal_ProcessEvent()函数,这样就和轮询方式的后半部分是一样的,也就是说中断法和轮询法在前面的不同,一旦遇到Hal_ProcessEvent(),那么后面的也就一样了,这就是整个按键的处理过程.
|