转自小峰博客协议栈按键流程.docx
《转自小峰博客协议栈按键流程.docx》由会员分享,可在线阅读,更多相关《转自小峰博客协议栈按键流程.docx(14页珍藏版)》请在冰豆网上搜索。
![转自小峰博客协议栈按键流程.docx](https://file1.bdocx.com/fileroot1/2023-5/29/bb2049a1-5c2a-4b9e-a2c5-fc1219208f14/bb2049a1-5c2a-4b9e-a2c5-fc1219208f141.gif)
转自小峰博客协议栈按键流程
最近看到小峰的笔记感觉不错,这里和大家分享一下,感谢小峰为我们初学者提供这么多笔记,真的感谢他,看了他的笔记使我少走了不少弯路。
我使用的协议栈版本及例子信息:
ZigBee2006\TexasInstruments\ZStack-1.4.3-1.2.1\Projects\zstack\Samples\SampleApp
记录下个人对按键流程的理解
在hal_key.c中有一段说明:
/*********************************************************************
NOTE:
Ifpollingisused,thehal_drivertaskschedulestheKeyRead()
tooccurevery100ms. Thisshouldbelongenoughtonaturally
debounce(去抖动)thekeys. TheKeyRead()functionremembersthekey
stateofthepreviouspollandwillonlyreturnanon-zero
valueifthekeystatechanges.
NOTE:
Ifinterruptsareused,theKeyRead()functionisscheduled
25msaftertheinterruptoccursbytheISR. Thisdelayisused
forkeydebouncing. TheISRdisablesanyfurtherKeyinterrupt
untilKeyRead()isexecuted. KeyRead()willre-enableKey
interruptsafterexecuting. Unlikepolling,wheninterrupts
areenabled,thepreviouskeystateisnotremembered. This
meansthatKeyRead()willreturnthecurrentstateofthekeys
(notachangeinstateofthekeys).
NOTE:
Ifinterruptsareused,theKeyRead()fucntionisscheduledby
theISR. Therefore,thejoystickmovementswillonlybedetected
duringapushbuttoninterruptcausedbyS1orthecenterjoystick
pushbutton.
NOTE:
WhenaswitchlikeS1ispushed,theS1signalgoesfromanormally
highstatetoalowstate. Thistransitionistypicallyclean. The
durationofthelowstateisaround200ms. Whenthesignalreturns
tothehighstate,thereisahighlikelihoodofsignalbounce,which
causesaunwantedinterrupts. Normally,wewouldsettheinterrupt
edgetofallingedgetogenerateaninterruptwhenS1ispushed,but
becauseofthesignalbounce,itisbettertosettheedgetorising
edgetogenerateaninterruptwhenS1isreleased. Thedebouncelogic
canthenfilteroutthesignalbounce. Theresultisthatwetypically
getonly1interruptperbuttonpush. Thismechanismisnottotally
foolproofbecauseoccasionally,signalboundoccursduringthefalling
edgeaswell. Asimilarmechanismisusedtohandlethejoystick
pushbuttonontheDB. FortheEB,wedonothaveindependentcontrol
oftheinterruptedgefortheS1andcenterjoystickpushbutton. As
aresult,onlyoneortheotherpushbuttonsworkreasonablywellwith
interrupts. ThedefaultisthemaketheS1switchontheEBworkmore
reliably.
*********************************************************************/
对本协议中,对按键有两种处理方式:
1、中断法:
有按键按下,则进入中断,开启一软定时器25ms后,读取键值进行相应处理
2、查询法:
开启一软定时器,系统每隔100ms进行轮询,如有按键按下读取键值进行相应处理
(软定时器即软件定时器,参见“系统时钟定时器”说明)
首先看下协议栈对KEY的初始化,在InitBoard()函数中:
/*InitializeKeystuff*/
OnboardKeyIntEnable=HAL_KEY_INTERRUPT_DISABLE;
HalKeyConfig(OnboardKeyIntEnable,OnBoard_KeyCallback);
从而知道协议栈默认的按键处理机制是查询法.就先来看下查询法的流程:
1、查询法(或者轮询法)
首先看下HalKeyConfig()
这个函数用于把按键/开关/操纵杆服务配置为轮询或中断驱动。
它还为服务配置一个回调函数。
如果不使用中断,轮询在100ms后自动开始。
按键/开关/操纵杆将每100ms被轮询一次。
如果使用中断,就使用一个ISR来处理这种情况.在中断发生后,有一个25ms的延时以消除回跳.
/***************************************************************************
voidHalKeyConfig(boolinterruptEnable,halKeyCBack_tcback)
{
……………………
if(Hal_KeyIntEnable)
{
…………(清除中断标志,设置上升沿下降沿触发外部中断等)
/*Dothisonlyafterthehal_keyisconfigured-toworkwithsleepstuff*/
if(HalKeyConfigured==TRUE)
{
osal_stop_timerEx(Hal_TaskID,HAL_KEY_EVENT); /*Cancelpollingifactive*/
/*如果采用中断方式则不用定时器来查询事件,关闭以前为这个事件开启的系统时钟*/
}
}
else /*InterruptsNOTenabled*/
{
…………(清除中断标志等)
osal_start_timerEx(Hal_TaskID,HAL_KEY_EVENT,HAL_KEY_POLLING_VALUE);/*Kickoffpolling*/
}
}
/***************************************************************************
可以看到查询法中,开启了一个系统软定时器,任务是HAL层任务,事件是HAL_KEY_POLLING_VALUE,系统要定时轮询按键事件得到按键值再作相应处理。
轮询时间为#defineHAL_KEY_POLLING_VALUE 100,即100ms.跟前面的英文说明一致。
当这个软定时器溢出时,便设置事件发生标志调用HAL层任务事件处理函数,然后从系统时钟链表中删除这个软定时器设置(后面会看到当处理完按键事件后又会重新启动这个软定时器).
下面就来看下它所调用的HAL层任务事件处理函数:
/***************************************************************************
uint16Hal_ProcessEvent(uint8task_id,uint16events)
{
uint8*msgPtr;
…………(其它事件处理)
if(events&HAL_KEY_EVENT)
{
#if(definedHAL_KEY)&&(HAL_KEY==TRUE)
/*Checkforkeys*/
HalKeyPoll();
/*ifinterruptdisabled,donextpolling*/
if(!
Hal_KeyIntEnable)
{
osal_start_timerEx(Hal_TaskID,HAL_KEY_EVENT,100); //因为在HalKeyInit()时,如果采用
} //非中断方式,已经初始化设置过一个按键事件软定时器,当这个软定时器
//溢出时,便设置事件发生标志调用事件处理函数,然后从系统软定时器链表
//中删除这个定时器设置,因此这里调用HalKeyPoll()处理完后续工作后
//如果还是采用非中断方式,则要重新开启一个软定时器!
#endif //HAL_KEY
returnevents^HAL_KEY_EVENT;
}
………… (其它事件处理)
}
/***************************************************************************
可以看到HAL层任务事件处理函数中对按键事件HAL_KEY_EVENT调用HalKeyPoll()进行下一步处理,在HalKeyPoll()处理完后跳出来,立马开启一个新的定时器,跟前面采用查询法(非中断:
!
Hal_KeyIntEnable)时开启的定时器一样,准备100ms后的下一次查询!
下面看下HalKeyPoll()
/***************************************************************************
voidHalKeyPoll(void)
{
…………(调用HalAdcRead(),检测模拟电压值最终得出键值keys)
/*Exitifpollingandnokeyshavechanged*/
if(!
Hal_KeyIntEnable)
{
if(keys==halKeySavedKeys)
{
return;
}
halKeySavedKeys=keys; /*Storethecurrentkeysforcomparationnexttime*/
}
/*InvokeCallbackifnewkeysweredepressed*/
if(keys&&(pHalKeyProcessFunction))
{
(pHalKeyProcessFunction)(keys,HAL_KEY_STATE_NORMAL);
}
#endif
}
/***************************************************************************
可以看到这里通过一系列处理读取键值,最后调用按键的回调函数(pHalKeyProcessFunction)(keys,HAL_KEY_STATE_NORMAL);这之前有个存储键值为下一次比较做准备,这里是查询法与中断法之间键值读取的区别点,查询法需要存储键值,而中断法则直接读取当前键值就好,具体我不钻了。
回到回调函数,在InitBoard()里调用HalKeyConfig(OnboardKeyIntEnable,OnBoard_KeyCallback),即把回调函数初始化为OnBoard_KeyCallback(),下面来看下这个函数
/***************************************************************************
voidOnBoard_KeyCallback(uint8keys,uint8state)
{
uint8shift;
//shiftkey(S1)isusedtogeneratekeyinterrupt
//applicationsshouldnotuseS1whenkeyinterruptisenabled
shift=(OnboardKeyIntEnable==HAL_KEY_INTERRUPT_ENABLE)?
false:
((keys&HAL_KEY_SW_6)?
true:
false);
if(OnBoard_SendKeys(keys,shift)!
=ZSuccess)
{
………… (如果不成功进行的一些处理)
}
}
/***************************************************************************
可以看到得到一个参数shift,然后调用了OnBoard_SendKeys(keys,shift),来看下这个函数
/***************************************************************************
byteOnBoard_SendKeys(bytekeys,bytestate)
{
keyChange_t*msgPtr;
if(registeredKeysTaskID!
=NO_TASK_ID) //按键事件被注册在sampleAPP应用registeredKeysTaskID=SampleApp_TaskID
{
//Sendtheaddresstothetask
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);
}
/***************************************************************************
可以看到首先涉及到一个参数registeredKeysTaskID,在OnBoard.c中它被定义为:
staticbyteregisteredKeysTaskID=NO_TASK_ID;
如果是这样的话,那按键事件处理到这里就返回一个ZFailure,没有任何反应了,但在SampleApp这个例子中是按SW1:
发送闪烁消息到组1,按SW2:
进/退组…这是为何?
这里涉及到一个函数RegisterForKeys(bytetask_id),来看下:
/***************************************************************************
*KeyboardRegisterfunction
*
*Thekeyboardhandlerissetuptosendallkeyboardchangesto
*onetask(ifataskisregistered).
*
*Ifataskregisters,itwillgetallthekeys.Youcanchangethis
*toregisterforindividualkeys.
************************************
byteRegisterForKeys(bytetask_id) //task_id=SampleApp_TaskID
{
//Allowonlythefirsttask
if(registeredKeysTaskID==NO_TASK_ID)
{
registeredKeysTaskID=task_id;
return(true);
}
else
return(false);
}
/***************************************************************************
tuzhuke
2010-11-0509:
39:
14
可以看到一个应用任务要得到按键值,那必须首先进行注册,这样一旦有按键发生,这个任务才能得到键值进行相应的处理,而协议栈中唯一一个用户应用任务SampleApp就对其进行了注册,具体在SampleApp_Init()函数中有这么一句:
//Registerforallkeyevents-Thisappwillhandleallkeyevents
RegisterForKeys(SampleApp_TaskID);
即可由这个任务SampleApp来处理按键事件。
通过函数参数传递易知最后registeredKeysTaskID=SampleApp_TaskID,然后再回到OnBoard_SendKeys()这个函数中,此时registeredKeysTaskID!
=NO_TASK_ID,所以调用osal_msg_send(registeredKeysTaskID,(uint8*)msgPtr),而这个函数里又调用osal_set_event(destination_task,SYS_EVENT_MSG)来触发事件发生标志,系统调用相应任务事件处理函数,任务ID为SampleApp_TaskID,即SampleApp_ProcessEvent(),通过参数SYS_EVENT_MSG和OnBoard_SendKeys()里的msgPtr->hdr.event=KEY_CHANGE可知任务事件处理函数SampleApp_ProcessEvent()最终调用SampleApp_HandleKeys(),SampleApp_HandleKeys()里面对按键进行了处理,包括按SW1:
发送闪烁消息到组1,按SW2:
进/退组。
至此,协议栈的按键查询法流程结束,一次下来两次触发系统事件(第一次通过osal_start_timerEx()启动一个软定时器触发,第二次通过osal_msg_send()发送系统消息触发;这俩的确是重点函数),分别调用相应任务事件处理函数,第一次是HAL层的Hal_ProcessEvent()来查询按键得到键值,一系列处理,第二次是APP层的SampleApp_ProcessEvent()把传送过来的按键事件进行最终处理.查询法函数调用流程如下:
HalKeyConfig()配置一定时器为轮询按键作准备——时间一到触发系统任务事件调用Hal_ProcessEvent()——调用HalKeyPoll()得到按键值——调用OnBoard_KeyCallback()进一步处理——调用OnBoard_SendKeys()构造消息包,准备触发应用按键事件【注意这个应用之前必须通过RegisterForKeys()注册接收按键事件的任务ID】——调用osal_msg_send()向系统发送消息——调用osal_set_event()设置事件发生标志——调用SampleApp_ProcessEvent()处理事件——最终调用SampleApp_HandleKeys()处理具体按键事件
2、中断法
按键引起的中断应属于外部中断.在HalKeyConfig()中有这样一段描述:
WorkaroundforCC2430DBwheninterru