完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
一、按键的消抖
按键消抖:通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,因而在闭合及断开的瞬间均伴随有一连串的抖动,按键抖动会引起一次按键被误读多次。 抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。 软件消抖:硬件方法将导致系统硬件电路设计复杂化,常采用软件方法进行消抖。 软件方法去抖,即检测出键闭合后执行一个延时程序,5ms~10ms的延时,让前沿抖动消失后再一次检测键的状态,如果仍保持闭合状态电平,则确认为真正有键按下。 二、按键状态机实现 0.状态机模式 简单理解为:将一个事件划分为有限个状态,满足相应的条件,在有限个状态之间跳转;可以使用状态图来描述事件处理过程,这种方式使得程序逻辑思路更加清晰严谨。以按键为例,按键检测的过程可以分为三个状态:按键检测状态、按键确认状态、按键释放状态;而在这三个状态之间跳转的条件为当前状态下按键的值。 在单片机中实现状态机最常用的语句便是switch case语句。 【状态机中如何非阻塞消抖】:使用定时器中断,定时每10ms执行一次switch case语句,即两个状态之间跳转的时间为10ms,这样便代替了delay延时。当定时中断发生时,才跳转到中断服务函数执行。 1.单个按键检测 独立按键电路 单个按键的状态转移图如下: S1状态为按键检测,S2为按键确认,S3为释放按键;状态跳转条件为当前状态下读取到的按键高低电平Key,当Result为1时,表示按键已经成功按下。 单个按键检测的代码实现: #ifdef SingleKeyEvent typedef enum { KEY_CHECK = 0, KEY_COMFIRM = 1, KEY_RELEASE = 2 }KEY_STATE; KEY_STATE KeyState =KEY_CHECK; // 初始化按键状态为检测状态 u8 g_KeyFlag = 0; // 按键有效标志,0: 按键值无效; 1:按键值有效 /** * 单个按键检测事件 * 功能:使用状态机方式,扫描单个按键;扫描周期为10ms,10ms刚好跳过抖动; * 状态机使用switch case语句实现状态之间的跳转 * */ void Key_Scan(void) { switch (KeyState) { //按键未按下状态,此时判断Key的值 case KEY_CHECK: if(!Key) { KeyState = KEY_COMFIRM; //如果按键Key值为0,说明按键开始按下,进入下一个状态 } break; //按键按下状态: case KEY_COMFIRM: if(!Key) //查看当前Key是否还是0,再次确认是否按下 { KeyState = KEY_RELEASE; //进入下一个释放状态 g_KeyFlag = 1; //按键有效值为1, 按键确认按下,按下就执行按键任务; } else //当前Key值为1,确认为抖动,则返回上一个状态 { KeyState = KEY_CHECK; //返回上一个状态 } break; //按键释放状态 case KEY_RELEASE: if(Key) //当前Key值为1,说明按键已经释放,返回开始状态 { KeyState = KEY_CHECK; // g_KeyFlag = 1; //如果置于此,则在按键释放状态后,才执行按键任务; } break; default: break; } } #endif 2.单个按键实现长按和短按 单个按键实现短按和长按是个很常用的方式,区别单个按键是否是长按和短按依靠检测按键按下的持续时间。 此处将短按时间T设为 10ms < T <1 s;长按时间的T设置为:T > 1s. 在上面的按键实现基础上可继续实现长按和短按判断,具体程序如下: 代码如下(示例): #ifdef SingleKey_LongShort_Event /** * 单个按键检测短按和长按事件 * 短按:时间 10ms < T < 1 s, 长按:时间 T >1 s * 功能:使用状态机方式,扫描单个按键;扫描周期为10ms,10ms刚好跳过抖动; * 状态机使用switch case语句实现状态之间的跳转 * lock变量用于判断是否是第一次进行按键确认状态 * :按键释放后才执行按键事件 */ void Key_Scan(void) { static u8 TimeCnt = 0; static u8 lock = 0; switch (KeyState) { //按键未按下状态,此时判断Key的值 case KEY_CHECK: if(!Key) { KeyState = KEY_COMFIRM; //如果按键Key值为0,说明按键开始按下,进入下一个状态 } TimeCnt = 0; //计数复位 lock = 0; break; case KEY_COMFIRM: if(!Key) //查看当前Key是否还是0,再次确认是否按下 { if(!lock) lock = 1; TimeCnt++; } else { if(lock) // 不是第一次进入, 释放按键才执行 { /*按键时长判断*/ if(TimeCnt > 100) // 长按 1 s { g_KeyActionFlag = LONG_KEY; TimeCnt = 0; } else // Key值变为了1,说明此处动作为短按 { g_KeyActionFlag = SHORT_KEY; // 短按 } /*按键时长判断*/ KeyState = KEY_RELEASE; // 需要进入按键释放状态 } else // 当前Key值为1,确认为抖动,则返回上一个状态 { KeyState = KEY_CHECK; // 返回上一个状态 } } break; case KEY_RELEASE: if(Key) //当前Key值为1,说明按键已经释放,返回开始状态 { KeyState = KEY_CHECK; } break; default: break; } } #endif 按键释放之后,才检测不太合理,做如下调整,长按事件需要达到时长就执行,短按可以在按键释放后执行。 /** * 单个按键检测短按和长按事件 * 短按:时间 10ms < T < 1 s, 长按:时间 T >1 s * 功能:使用状态机方式,扫描单个按键;扫描周期为10ms,10ms刚好跳过抖动; * 状态机使用switch case语句实现状态之间的跳转 * lock变量用于判断是否是第一次进行按键确认状态 * :长按键事件提前执行,短按键事件释放后才执行 */ void Key_Scan(void) { static u8 TimeCnt = 0; static u8 lock = 0; switch (KeyState) { //按键未按下状态,此时判断Key的值 case KEY_CHECK: if(!Key) { KeyState = KEY_COMFIRM; //如果按键Key值为0,说明按键开始按下,进入下一个状态 } TimeCnt = 0; //计数复位 lock = 0; break; case KEY_COMFIRM: if(!Key) //查看当前Key是否还是0,再次确认是否按下 { if(!lock) lock = 1; TimeCnt++; /*按键时长判断*/ if(TimeCnt > 100) // 长按 1 s { g_KeyActionFlag = LONG_KEY; TimeCnt = 0; lock = 2; //防止退出长按时,又执行一次短按 } } else { if(1==lock) // 不是第一次进入, 释放按键才执行 { g_KeyActionFlag = SHORT_KEY; // 短按 KeyState = KEY_RELEASE; // 需要进入按键释放状态 } else // 当前Key值为1,确认为抖动,则返回上一个状态 { KeyState = KEY_CHECK; // 返回上一个状态 } } break; case KEY_RELEASE: if(Key) //当前Key值为1,说明按键已经释放,返回开始状态 { KeyState = KEY_CHECK; } break; default: break; } } 三、长按和短按测试示例 头文件 /** * 使用定时器来轮询Key_Scan()函数,定时节拍为2ms, * 状态转换时间为10ms,即每次进入switch case语句的时间差为10ms * 利用该10ms的间隙跳过按键抖动 */ #ifndef __BUTTON_H #define __BUTTON_H #include "stm32f10x.h" //按键对应的IO管脚 KEY1 PA.15 #define KEY_IO_RCC RCC_APB2Periph_GPIOA #define KEY_IO_PORT GPIOA #define KEY_IO_PIN GPIO_Pin_15 //Key: 1:高电平,按键未按下, 0:低电平,按键按下 #define Key GPIO_ReadInputDataBit(KEY_IO_PORT,KEY_IO_PIN) typedef enum { KEY_CHECK = 0, KEY_COMFIRM = 1, KEY_RELEASE = 2 }KEY_STATE; typedef enum { NULL_KEY = 0, SHORT_KEY =1, LONG_KEY }KEY_TYPE; //extern u8 g_KeyFlag; //extern KEY_TYPE g_KeyActionFlag; //单个按键事件 //#define SingleKeyEvent //单个按键实现长按和短按 #define SingleKey_LongShort_Event 1 void Key_Init(void); void Key_Scan(void); main函数 KEY_STATE KeyState =KEY_CHECK; // 初始化按键状态为检测状态 u8 g_KeyFlag = 0; // 按键有效标志,0: 按键值无效; 1:按键值有效 KEY_TYPE g_KeyActionFlag; //用于区别长按和短按 int main() { Key_Init(); Timer_init(19,7199);//10Khz的计数频率,计数到20为2ms while(1) { switch(g_KeyActionFlag) { case SHORT_KEY: /* 执行短按对应的事件 */ g_KeyActionFlag = 0; //状态回到空 break; case LONG_KEY: /* 执行长按对应的事件 */ g_KeyActionFlag = 0; //状态回到空 default: break; } } } void TIM3_IRQHandler(void) //TIM3 每 2ms 中断一次 { static u8 cnt; if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 { cnt++; if(cnt>5) // 每10ms 执行一次按键扫描程序 { Key_Scan(); cnt = 0; } TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx的中断待处理位:TIM 中断源 } } 四 、多按键检测 同样的,多按键也是一样的; 示例如下: Buttion.h 头文件中只需要把Key做下修改; #define KEY0 GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5)//读取按键0 #define KEY1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_15)//读取按键1 #define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//读取按键2 #define Key (KEY0 && KEY1 && (!WK_UP)) typedef enum { KEY_CHECK = 0, KEY_COMFIRM = 1, KEY_RELEASE = 2, }KEY_STATE; //对应的按键值, typedef enum { KEY_NULL = 0, KEY_0, KEY_1, KEY_WK_UP }KEY_VALUE; 对应的状态机中对按键值进行区分即可: void Key_Scan(void) { switch (KeyState) { //按键未按下状态,此时判断Key的值 case KEY_CHECK: if(!Key) { KeyState = KEY_COMFIRM; //如果按键Key值为0,说明按键开始按下,进入下一个状态 } break; //按键按下状态: case KEY_COMFIRM: if(!Key) //查看当前Key是否还是0,再次确认是否按下 { KeyState = KEY_RELEASE; //进入下一个释放状态 //g_KeyFlag = 1; //按键有效值为1, 按键确认按下,按下就执行按键任务; // 多按键判断 if(0 == KEY0) g_Key = KEY_0; else if(0 == KEY1) g_Key = KEY_1; else if(1 == WK_UP) g_Key = KEY_WK_UP; } else //当前Key值为1,确认为抖动,则返回上一个状态 { KeyState = KEY_CHECK; //返回上一个状态 } break; //按键释放状态 case KEY_RELEASE: if(Key) //当前Key值为1,说明按键已经释放,返回开始状态 { KeyState = KEY_CHECK; } break; default: break; } } main函数中也是一样,使用switch case 语句执行按键事件即可: extern KEY_VALUE g_Key; switch(g_Key) { case KEY_0: /* KEY 0 按键事件 */ g_Key=KEY_NULL; break; case KEY_1: /* KEY 1 按键事件 */ g_Key=KEY_NULL; break; case KEY_WK_UP: /* KEY_WK_UP 按键事件 */ g_Key=KEY_NULL; break; default: break; } 总结 以上便是对按键状态机程序的总结,对长按和短按的判断实现…… |
|
|
|
只有小组成员才能发言,加入小组>>
3308 浏览 9 评论
2988 浏览 16 评论
3490 浏览 1 评论
9049 浏览 16 评论
4083 浏览 18 评论
1167浏览 3评论
601浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
592浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2329浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1892浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-18 09:54 , Processed in 1.105277 second(s), Total 49, Slave 39 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号