完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
项目里经常处理按键消抖, 本来这个消抖的过程是与具体按下的键无关的, 可以前的代码总 是在消抖的同时处理具体的按键值, 再加上长按短按组合键混在一起, 成一锅粥. 最近在 一个项目中痛下决心, 想弄个通用版本的, 这样下个项目只要将文件包含一下, 处理具体按 键值就可以了, 不必再关心消抖部分的代码了. 另外还发现, 这样做可以同时做出几套不同 的按键处理方式. 思路是: 按照面向过程的编程方式, 将数据与过程分离. 把和按键状态相关的东西统统塞 到结构里, 把消抖的代码放在一个函数中. //key.h 头文件------------------------------------------------------------- #ifndef _KEY_H #define _KEY_H #define _KEY_NONE 0 #define _HAS_NO_KEY 0 #define _HAS_KEY_DOWN 1 #define _HAS_KEY_SURE 2 #define _HAS_KEY_WAITUP 3 #define _REENTER 1 #define _NO_REENTER 2 typedef struct { WORD PreDownKey; //上次检测到的键 BYTE KeyState; //状态 WORD SameKeyCntr; //同一键检测到按下的次数 WORD CurKey; //当前检测到的键, 用于处理长按的情况 BYTE (*KeyDownCallBack)(WORD, WORD); //键确认按下的回调函数指针 void (*KeyUpCallBack)(WORD); //键抬起的回调函数指针 } struct_KeyInfo; void DitherlessKey(struct_KeyInfo* pInfo);//消抖的处理函数 #endif//_KEY_H //消抖动的代码-------------------------------------------------------------- #include "Key.h" //定时消抖的按键处理函数, 通常在定时中断中调用, void DitherlessKey(struct_KeyInfo* pInfo) { switch(pInfo->KeyState) { case _HAS_NO_KEY: pInfo->SameKeyCntr = 0; if(pInfo->CurKey != _KEY_NONE) { pInfo->KeyState = _HAS_KEY_DOWN; //进入有键按下状态 } break; case _HAS_KEY_DOWN: if(pInfo->PreDownKey ==pInfo->CurKey) { pInfo->KeyState = _HAS_KEY_SURE; //确认键已按下状态 } else { pInfo->KeyState = _HAS_NO_KEY; //回到无键状态 } break; case _HAS_KEY_SURE: if(pInfo->CurKey ==pInfo->PreDownKey) { pInfo->KeyState = _HAS_KEY_WAITUP; if(pInfo->KeyDownCallBack) { //这里回调函数的返回值决定了是否允许出现长按的情况 if(_REENTER ==pInfo->KeyDownCallBack(pInfo->CurKey, pInfo->SameKeyCntr)) { pInfo->KeyState = _HAS_KEY_SURE; ++pInfo->SameKeyCntr; } } } else { pInfo->KeyState = _KEY_NONE; } break; case _HAS_KEY_WAITUP: if(pInfo->CurKey !=pInfo->PreDownKey) { pInfo->KeyState = _HAS_NO_KEY; if(pInfo->KeyUpCallBack) { pInfo->KeyUpCallBack(pInfo->PreDownKey); } } break; default: break; } pInfo->PreDownKey = pInfo->CurKey; //保存上次按键值 return; } //应用代码片段--------------------------------------------------------------------------------------- ...... //声明按键回调函数 BYTE KeyDownCallBack(WORD Key, WORD times); BYTE KeyDownCallBack2(WORD Key, WORDTimes); //按键处理数据结构 static struct_KeyInfo g_KeyInfo1 = {0, 0,0, 0, KeyDownCallBack}; static struct_KeyInfo g_KeyInfo2 = {0, 0,0, 0, KeyDownCallBack2}; ////////////////////////////////////////////////////////////////////////// //TIMER2 initialize - prescale:1024 // WGM: Normal // desired value: 100Hz // actual value: 101.024Hz (1.0%) #pragma interrupt_handlertimer2_ovf_isr:iv_TIM2_OVF void timer2_ovf_isr(void) { WORD temp; _TIMER2_LOAD; //reload counter value temp = Read165() ^ _KEY_MASK; //输入信息 g_KeyInfo1.CurKey = temp & 0x00FF; DitherlessKey(&g_KeyInfo1); g_KeyInfo2.CurKey = temp & 0xFF00; //同一个消抖函数处理不同的按键 DitherlessKey(&g_KeyInfo2); } //在回调函数中处理具体的键值 BYTE KeyDownCallBack(WORD Key, WORD Times) { switch(Key) { case _KEY_F2: if(Times < 200) //长按2s { return _REENTER; //2s 内允许长按 } break; case _KEY_CLR_CNTR: if(Times < 1000) //四个键长按10s { return _REENTER; //允许长按 } default: break; } g_DownKey = Key; //输出按键信息, 给主循环处理. 这个回调函数是由定时中断中的代码 调用的. return _NO_REENTER; //其余键, 不允许长按 } BYTE KeyDownCallBack2(WORD Key, WORD Times) { switch(Key) { case _KEY_I: if(Times == 20) //数值x 10 ms { g_DownKey |= _KEY_I; } else if(Times == 300) //长按3s 时执行一个动作, 只会执行一次 { g_I++; } break; default: break; } return _REENTER; //始终允许长按, 直到键抬起 } 本质就是个状态机. 把键分为四个状态: _HAS_NO_KEY:未按下, _HAS_KEY_DOWN:检测到一次按下, _HAS_KEY_SURE:又检测到一次按下, 两次都检测到按下, 就认为确实按下了, 达到消抖 的目的, 如果想再增加可靠性, 可以增加状态或者给每个按键设置个计数器. _HAS_KEY_WAITUP:等待键抬起. 状态转换图如下: /-----检测到键-----> /--第二次检测到键--> /--该键仍被检测到--> / / / _HAS_NO_KEY _HAS_KEY_DOWN _HAS_KEY_SURE_HAS_KEY_WAITUP / / / <--本次与上次不同--/ / / / / <--------------------本次与上次不同---------------/ / / <---------------------------------本次与上次不同----------------------------------/ ' 状态是与具体的键相关的, 如果不考虑通用性的话, 可以把具体的键值写到代码里. 这里 想把状态从处理过程中分离出来, 就定义了struct_KeyInfo 结构用来保存键值和键的状态, 同时也把对键的处理以回调函数(函数指针)的形式放到结构里了, 由它去处理具体的按键值, 这样就把对具体键的处理与消抖分离了. 由于使用的状态机, 消抖只关心状态改变的条件, 而不关心状态本身, 这样就可以把按键 检测放到定时中断中执行了. 同样消抖过程也不关心按键值的获得过程, 扫描也好, 直读也 行. 上面的例子是用并转串方式得到键值的. 键本身是否允许长按与短按是通过回调函数的返回值控制的, 至于长按的时间长短, 是通 过回调函数的Times 参数给出, 由用户的键处理代码判断的. 在使用时 可以根据程序当前的状态来灵活处理. 对于组合键, 是通过键值的定义实现的, 比如: #define _KEY_1 0x0080 #define _KEY_2 0x0040 #define _KEY_3 0x0020 #define _KEY_4 0x0010 #define _KEY_5 0x0008 #define _KEY_6 0x0004 #define _KEY_7 0x0002 #define _KEY_8 0x0001 #define _KEY_LOAD_DEFAULT (_KEY_1 | _KEY_8 |_KEY_7 | _KEY_6 | _KEY_5 | _KEY_4) #define _KEY_SAVE_MANUFACTURE (_KEY_2 | _KEY_3| _KEY_5) #define _KEY_LOAD_MANUFACTURE (_KEY_1 | _KEY_8| _KEY_4 | _KEY_5) |
|
相关推荐
1 个讨论
|
|
你正在撰写讨论
如果你是对讨论或其他讨论精选点评或询问,请使用“评论”功能。
【「从算法到电路—数字芯片算法的电路实现」阅读体验】+一本介绍基础硬件算法模块实现的好书
780 浏览 0 评论
【社区工程师专题系列第九期】一个狂热的开源爱好者和传播者——乔楚
741 浏览 0 评论
【书籍评测活动NO.51】具身智能机器人系统 | 了解AI的下一个浪潮!
1153 浏览 12 评论
【书籍评测活动NO.50】亲历芯片产线,轻松图解芯片制造,揭秘芯片工厂的秘密
683 浏览 18 评论
《算力芯片 高性能 CPUGPUNPU 微架构分析》第二篇阅读心得:芯片拓扑学:并行扩展与CPU设计的巨头对决
735 浏览 0 评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-22 23:56 , Processed in 0.616191 second(s), Total 42, Slave 32 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号