MCU实战经验:多种的按键处理
按键通常有:IO口按键(BUTTON),AD按键(通过AD采样电压),IR(遥控器)
按按键功能分:有短按键,长按键,连续按键。打个比方,遥控电视机,按一下音量键,音量增加1,这个就是短按键。按住音量键不放,音量连续加,这个就是连续按键。按住一个按键5s,系统会复位,这个是长按键。
1、IO口按键,就是我们比较常见的一个IO接一个按键,或者是一个矩阵键盘。很多新人的处理方法可能是采样延时的方法,当年我也是这样的,如下
- if(GETIO==low)
- {
- delay_10ms()
- if(GETIO==low)
- {
- //得到按键值
- }
- }
这种方法虽然简单,但是有很大弊端。首先 Delay浪费很多时间,影响系统。第二,无法判断长短按键,连续按键。第三,如果这个按键是开关机按键系统在低功耗状态下,需要中断唤醒,这种方法比较容易出问题,如STM8S系列的
halt 模式。
所以我们一般在产品开发的过程中,采用扫描的方法,就是每隔10ms 去检测IO的状态,看是否有按键,然后去抖动,判断按键功能。参考代码如下,这段代码是之前在一个论坛看到的比我自己写的更加优秀,所以拿出来和大家分享一下,也顺便感谢一下作者。这段代码,容易修改,可以根据自己的时间需要,进行长短按键,连续按键,还有组合按键的判断。
- /* 按键滤波时间50ms, 单位10ms
- *只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件
- */
- #define BUTTON_FILTER_TIME 5
- #define BUTTON_LONG_TIME 300 /* 持续1秒,认为长按事件 */
- /*
- 每个按键对应1个全局的结构体变量。
- 其成员变量是实现滤波和多种按键状态所必须的
- */
- typedef struct
- {
- /* 下面是一个函数指针,指向判断按键手否按下的函数 */
- unsigned char (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1表示按下 */
- unsigned char Count; /* 滤波器计数器 */
- unsigned char FilterTime; /* 滤波时间(最大255,表示2550ms) */
- unsigned short LongCount; /* 长按计数器 */
- unsigned short LongTime; /* 按键按下持续时间, 0表示不检测长按 */
- unsigned char State; /* 按键当前状态(按下还是弹起) */
- unsigned char KeyCodeUp; /* 按键弹起的键值代码, 0表示不检测按键弹起 */
- unsigned char KeyCodeDown; /* 按键按下的键值代码, 0表示不检测按键按下 */
- unsigned char KeyCodeLong; /* 按键长按的键值代码, 0表示不检测长按 */
- unsigned char RepeatSpeed; /* 连续按键周期 */
- unsigned char RepeatCount; /* 连续按键计数器 */
- }BUTTON_T;
- typedef enum
- {
- KEY_NONE = 0, /* 0 表示按键事件 */
- KEY_DOWN_Power, /* 按键键按下 */
- KEY_UP_Power, /* 按键键弹起 */
- KEY_LONG_Power, /* 按键键长按 */
- KEY_DOWN_Power_TAMPER /* 组合键,Power键和WAKEUP键同时按下 */
- }KEY_ENUM;
- BUTTON_T s_Powerkey;
- //是否有按键按下接口函数
- unsigned char IsKeyDownUser(void)
- {if (0==GPIO_ReadInputPin(POWER_KEY_PORT, POWER_KEY_PIN) ) return 1;return 0;}
- void PanakeyHard_Init(void)
- {
- GPIO_Init (POWER_KEY_PORT, POWER_KEY_PIN, GPIO_MODE_IN_FL_NO_IT);//power key
- }
- void PanakeyVar_Init(void)
- {
- /* 初始化USER按键变量,支持按下、弹起、长按 */
- s_Powerkey.IsKeyDownFunc = IsKeyDownUser; /* 判断按键按下的函数 */
- s_Powerkey.FilterTime = BUTTON_FILTER_TIME; /* 按键滤波时间 */
- s_Powerkey.LongTime = BUTTON_LONG_TIME; /* 长按时间 */
- s_Powerkey.Count = s_Powerkey.FilterTime / 2; /* 计数器设置为滤波时间的一半 */
- s_Powerkey.State = 0; /* 按键缺省状态,0为未按下 */
- s_Powerkey.KeyCodeDown = KEY_DOWN_Power; /* 按键按下的键值代码 */
- s_Powerkey.KeyCodeUp =KEY_UP_Power; /* 按键弹起的键值代码 */
- s_Powerkey.KeyCodeLong = KEY_LONG_Power; /* 按键被持续按下的键值代码 */
- s_Powerkey.RepeatSpeed = 0; /* 按键连发的速度,0表示不支持连发 */
- s_Powerkey.RepeatCount = 0; /* 连发计数器 */
- }
- void Panakey_Init(void)
- {
- PanakeyHard_Init(); /* 初始化按键变量 */
- PanakeyVar_Init(); /* 初始化按键硬件 */
- }
- /*
- *********************************************************************************************************
- * 函 数 名: bsp_DetectButton
- * 功能说明: 检测一个按键。非阻塞状态,必须被周期性的调用。
- * 形 参:按键结构变量指针
- * 返 回 值: 无
- *********************************************************************************************************
- */
- void Button_Detect(BUTTON_T *_pBtn)
- {
- if (_pBtn->IsKeyDownFunc())
- {
- if (_pBtn->Count < _pBtn->FilterTime)
- {
- _pBtn->Count = _pBtn->FilterTime;
- }
- else if(_pBtn->Count < 2 * _pBtn->FilterTime)
- {
- _pBtn->Count++;
- }
- else
- {
- if (_pBtn->State == 0)
- {
- _pBtn->State = 1;
- /* 发送按钮按下的消息 */
- if (_pBtn->KeyCodeDown > 0)
- {
- /* 键值放入按键FIFO */
- Pannelkey_Put(_pBtn->KeyCodeDown);// 记录按键按下标志,等待释放
- }
- }
- if (_pBtn->LongTime > 0)
- {
- if (_pBtn->LongCount < _pBtn->LongTime)
- {
- /* 发送按钮持续按下的消息 */
- if (++_pBtn->LongCount == _pBtn->LongTime)
- {
- /* 键值放入按键FIFO */
- Pannelkey_Put(_pBtn->KeyCodeLong);
- }
- }
- else
- {
- if (_pBtn->RepeatSpeed > 0)
- {
- if (++_pBtn->RepeatCount >= _pBtn->RepeatSpeed)
- {
- _pBtn->RepeatCount = 0;
- /* 常按键后,每隔10ms发送1个按键 */
- Pannelkey_Put(_pBtn->KeyCodeDown);
- }
- }
- }
- }
- }
- }
- else
- {
- if(_pBtn->Count > _pBtn->FilterTime)
- {
- _pBtn->Count = _pBtn->FilterTime;
- }
- else if(_pBtn->Count != 0)
- {
- _pBtn->Count--;
- }
- else
- {
- if (_pBtn->State == 1)
- {
- _pBtn->State = 0;
- /* 发送按钮弹起的消息 */
- if (_pBtn->KeyCodeUp > 0) /*按键释放*/
- {
- /* 键值放入按键FIFO */
- Pannelkey_Put(_pBtn->KeyCodeUp);
- }
- }
- }
- _pBtn->LongCount = 0;
- _pBtn->RepeatCount = 0;
- }
- }
- //功能说明: 检测所有按键。10MS 调用一次
- void Pannelkey_Polling(void)
- {
- Button_Detect(&s_Powerkey); /* USER 键 */
- }
- void Pannelkey_Put(void)
- {
- // 定义一个队列 放入按键值
- }
2、遥控器按键,遥控器解码的一般就有两种:脉宽调制和脉冲调制,这里就不细讲解码的过程了。这里详细解码之后,如何处理遥控器按键实现遥控器按键的长短按功能和连续按键功能。代码裁剪过,大家根据实际需要改动。其实AD按键,通过AD采样获得按键值之后,可以采取如下面的一样方法处理,提一个函数接口即可
- typedef struct
- {
- unsigned char count;//
- unsigned char LongkeyFlag;/*是否长按键,1代表是*/
- unsigned char PreKeyValue;/*按键值的一个备份,用于释放按键值*/
- }ScanKeyDef;
- #define SHORT_PRESS_TIME_IR 16 // 10ms
- #define SERIES_PRESS_TIME_IR 10
- #define LONG_PRESS_TIME_IR 22
- #define KEY_RELEASE_TIME_OUT_IR 12 // 10ms
- //提供5个接口函数,如下。
- unsigned char get_irkey(void);
- unsigned char ISSeriesKey(unsigned char temp);//按键是否需要做连续按键
- unsigned char changeSeriesKey(unsigned char temp);//转换连续按键值
- unsigned char ISLongKey(unsigned char temp);//按键是否需要做连续按键
- unsigned char changeToLongKey(unsigned char temp);//转换连续按键值
- unsigned char KeyScan(void)
- {
- unsigned char KeyValue = KEY_NONE,
- KeyValueTemp = KEY_NONE;
- static unsigned char KeyReleaseTimeCount =0;
- KeyValueTemp = get_irkey();
- if(KeyValueTemp != KEY_NONE)
- {
- ScanKeyDef.count++;
- KeyReleaseTimeCount =0;
- if(ScanKeyDef.count < LONG_PRESS_TIME_IR )
- {
- ScanKeyDef.LongkeyFlag = 0;
- if((ScanKeyDef.count == SERIES_PRESS_TIME_IR) && ISSeriesKey(KeyValueTemp)) //处理连续按键
- {
- KeyValue = changeSeriesKey ( KeyValueTemp );
- ScanKeyDef.PreKeyValue = KEY_NONE;
- }
- else if ( ScanKeyDef.count < SHORT_PRESS_TIME_IR )
- {
- ScanKeyDef.PreKeyValue = KeyValueTemp;
- }
- else
- {
- ScanKeyDef.PreKeyValue = KEY_NONE; // 无效按键
- }
- }
- else if ( ScanKeyDef.count == LONG_PRESS_TIME_IR )
- {
- if (ISLongKey(KeyValueTemp))
- {
- {
- ScanKeyDef.LongkeyFlag = 1;
- KeyValue = changeToLongKey ( KeyValueTemp );
- }
- }
- ScanKeyDef.PreKeyValue = KEY_NONE;
- }
- else if (ScanKeyDef.count > LONG_PRESS_TIME_IR )
- {
- ScanKeyDef.PreKeyValue = KEY_NONE; //无效按键
- }
- }
- else//release & no press
- {
- KeyReleaseTimeCount ++;
- if(KeyReleaseTimeCount >= KEY_RELEASE_TIME_OUT_IR)
- {
- if ( ScanKeyDef.PreKeyValue != KEY_NONE ) //释放按键值
- {
- if ( ScanKeyDef.LongkeyFlag == 0 )
- {
- KeyValue =ScanKeyDef.PreKeyValue ;
- }
- }
- ScanKeyDef.count = 0;
- ScanKeyDef.LongkeyFlag = 0;
- ScanKeyDef.PreKeyValue = KEY_NONE;
- }
- }
- return(KeyValue);
- }
MCU实战经验:多种的按键处理的更多相关文章
- (转)国内外三个不同领域巨头分享的Redis实战经验及使用场景
随着应用对高性能需求的增加,NoSQL逐渐在各大名企的系统架构中生根发芽.这里我们将为大家分享社交巨头新浪微博.传媒巨头Viacom及图片分享领域佼佼者Pinterest带来的Redis实践,首先我们 ...
- Redis实战经验及使用场景
随着应用对高性能需求的增加,NoSQL逐渐在各大名企的系统架构中生根发芽.这里我们将为大家分享社交巨头新浪微博.传媒巨头Viacom及图片分享领域佼佼者Pinterest带来的Redis实践,首先我们 ...
- 为什么要使用Redis? —— Redis实战经验
(序言,从一张思维导图开始,慢慢介绍我自己关于Redis的实战经验) 现在很多互联网应用的服务端都使用到了Redis,到底大家为什么要用Redis呢?Redis有很多特性,比如高性能.高可用.数据类型 ...
- 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之RAC 特殊问题和实战经验(五)
RAC 特殊问题和实战经验(五) 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习的汇总.然后形成体 ...
- MySQL数据库的优化-运维架构师必会高薪技能,笔者近六年来一线城市工作实战经验
原文地址:http://liangweilinux.blog.51cto.com/8340258/1728131 首先在此感谢下我的老师年一线实战经验,我当然不能和我的老师平起平坐,得到老师三分之一的 ...
- MySQL索引实战经验总结
MySQL索引对数据检索的性能至关重要,盲目的增加索引不仅不能带来性能的提升,反而会消耗更多的额外资源,本篇总结了一些MySQL索引实战经验. 索引是用于快速查找记录的一种数据结构.索引就像是数据库中 ...
- 第9期Unity User Group Beijing图文报道:《Unity实战经验分享》
时间来到了金秋九月,北京UUG活动也来到了第九期.本次活动的主题为<Unity实战经验分享>,为此我们邀请了3位资深的行业大神.这次我们仍然在北京市海淀区丹棱街5号微软大厦举行活动,在这里 ...
- ASP.NET Core & Docker 实战经验分享
一.前言 最近一直在研究和实践ASP.NET Core.Docker.持续集成.在ASP.NET Core 和 Dcoker结合下遇到了一些坑,在此记录和分享,希望对大家有一些帮助. 二.中间镜像 我 ...
- Jenkins高级用法 - Jenkinsfile 介绍及实战经验
系列目录 1.Jenkins 安装 2.Jenkins 集群 3.Jenkins 持续集成 - ASP.NET Core 持续集成(Docker&自由风格&Jenkinsfile) 4 ...
随机推荐
- Maven 介绍、安装使用
简介 Maven是一个强大的构建工具,能够帮我们自动化构建过程,从清理.编译.测试到生成报告,再到打包和部署.只要使用Maven配置好项目,然后执行命令(如mvn clean inst ...
- 安卓自定义View实现图片上传进度显示(仿QQ)
首先看下我们想要实现的效果如下图(qq聊天中发送图片时的效果): 再看下图我们实现的效果: 实现原理很简单,首先我们上传图片时需要一个进度值progress,这个不管是自己写的上传的方法还是使用第三方 ...
- Cocos2D实现RPG队伍菜单任意调整角色顺序的效果
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 前一篇我们实现了队伍实现拖尾效果,但是在实际游戏中我们往往需要 ...
- [Mysql]备份同库中一张表的历史记录 insert into ..select
需求 现在有个这么一个需求,mysql中有个表,数据增长的很快,但是呢这个数据有效期也就是1个月,一个月以前的记录不太重要了,但是又不能删除.为了保证这个表的查询速度,需要一个简单的备份表,把数据倒进 ...
- 【一天一道LeetCode】#350. Intersection of Two Arrays II
一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given t ...
- 【unix网络编程第三版】阅读笔记(二):套接字编程简介
unp第二章主要将了TCP和UDP的简介,这些在<TCP/IP详解>和<计算机网络>等书中有很多细致的讲解,可以参考本人的这篇博客[计算机网络 第五版]阅读笔记之五:运输层,这 ...
- UNIX环境高级编程——信号说明列表
$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGK ...
- 如何获得mysql数据库的所有的列
命令行下直接用:descrbe 表名 hive也是一样的. 用查询: SELECT COLUMN_NAME FROM `information_schema`.`COLUMNS` where ` ...
- C语言野指针
本文转载自:http://blog.csdn.net/xwdok/article/details/576497 "野指针"不是NULL指针,是指向"垃圾"内存的 ...
- Java实现简易的文件的迁移器
Java作为世界上最受欢迎的一门编程语言,自然是有原因的.比如说我们可以直接的方便的调用其中的函数来实现我们想要的功能. 一个偶然的机会,我浏览API文档时发现了一个名为FileDialog的类,然后 ...