OpenEdv-开源电子网

 找回密码
 立即注册

扫一扫,访问微社区

正点原子全套STM32/FPGA开发资料,上千讲STM32视频教程,RT1052教程免费下载啦...
查看: 13621|回复: 704

开源键扫例程--- 无须延时消抖等待,能稳定可靠地一体满足普通、短按(单击/双击)、长按、组合等应用需求的键盘扫描程序

  [复制链接]

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
发表于 2018-8-15 22:29:18 | 显示全部楼层 |阅读模式
本帖最后由 warship 于 2018-9-18 14:09 编辑

//////////////////////////////////////////////////////////////////////////////////         
本键盘扫描模块的特点:
一、使用灵活:一体实现按键的普通、单击、双击、长按、保持以及组合等功能,无须事前为每个按键每种键值逐一进行宏定义,也无须逐一编写各事件的条件判断,                     
                     只须为需要的按键事件编写相应的响应代码即可,同时留有特殊键组合等的扩展接口;
                     可以选择每一按键事件的处理实时性,从而能够使强实时性的紧急按键优先得到处理,可自由选择中断处理及查询处理或二者混合的处理方式,
                     灵活适配使应用项目能够兼备按键的强实时性要求以及超长(主循环执行一遍的时间长达1秒以上的)程序的适应性。

二、注重通用:模块设计时注重通用性,按键事件(键值)依简单易懂的标准事件格式编写;除能满足几乎所有按键应用需求外,在按键数量上,
                     从少到2-4个按键直到最大32个按键(包括端口直联、行列式矩阵、矩阵加直联混合)都可适用。(注:新写了一个4*4矩阵加4键直联混合共20个键的例子参见153楼)


三、稳定可靠:后台智能抖动消除、按键干扰杂波滤除措施有力,获取按键稳定可靠,不会产生重复按键,即使在CPU非常繁忙时也不会漏失按键。

         
四、移植简便:所有可调整参数(数量不多)均以宏定义列出,除与硬件相关(按键个数及连接端口)的部分须根据具体系统修改外,其它均无须变化,很容易移植。
                     程序可读性强,注释详尽丰富,其中包括函数调用关系及详细运用修改说明,如有未尽事宜,可提出探讨,本人尽量解答修改。


五、高效节能:消抖无须延时等待,同时采取自适应变频扫键、键盘闲置检测、消抖读键双进程周期差异等多项智能措施尽量减少占用CPU的计算资源。


//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//测试平台:ALIENTEK战舰STM32开发板
//按键驱动代码           
//正点原子@ALIENTEK
//引用请注明出处:http://www.openedv.com/forum.php?mod=viewthread&tid=277263,有问题可在本帖中提出讨论,谢谢。
//在网友@学习stm32f4 的建议下,部分源码上传到了https://github.com/ShuifaHe/STM32.git,新司机上路,请多关照。如果觉得对您有用的话,请按 “星” 号点一下赞
//修改日期:2018/9/1
//版本:V2.2,本帖研讨过程详见2楼,鸣谢相关坛友,感谢@正点原子 的支持肯定。
//Made by warship
//////////////////////////////////////////////////////////////////////////////////
下面给出经由原子试验3改造测试通过的程序范例:
回复后可获取解压密码,谢谢支持
游客,如果您要查看本帖隐藏内容请回复






ATOM_TEST_Key_New.rar

282.83 KB, 下载次数: 1471

9月1日更新

我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-18 14:52:09 | 显示全部楼层
wdgao 发表于 2018-8-18 12:19
感谢楼主不断地更新、补充,不知楼主能否利用该按键实现思想将原子的按键实验改写一下,以便大家对比研究 ...

已分享工程,在一楼,欢迎下载测试。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 3 反对 0

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-18 20:58:26 | 显示全部楼层
本帖最后由 warship 于 2018-9-2 12:56 编辑
学习stm32f4 发表于 2018-8-18 20:30
这个阻容消抖电路是抄自*火的板子,电路参数一样,只是多了个斯密特触发器,估计能用吧。(待证)
也可以 ...

(借楼)
本帖更新历程:

(2018年8月15日)受三行键盘扫描程序http://www.openedv.com/forum.php ... hlight=%C8%FD%D0%D0
的启发,使用SYSTICK系统嘀哒消抖,具体尝试了短按、长按、组合等实现方法,贴出源码供网友进行讨论;

(2018年8月18日)  应坛友@wdgao 的要求,将原子的按键实验(实验3)进行改写,分享完整工程,供网友下载测试;

(2018年8月23日) 最新附件优化了消抖代码,并采取了自适应变频扫键措施,在原子战舰开发板上反复测试通过,稳定可靠;

(2018年8月26日) 受到坛友@leiyitan 所提供信息https://github.com/0x1abin/MultiButton的启发,借鉴加入了可选的状态机组件,可以非常方便地实现单击、双击、
长按、保持等功能;

(2018年8月27日) 将读键变成后台队列处理,确保最大限度地容忍主循环长时间才查询一次所带来的影响,CPU非常忙碌时也绝对不丢键。
同时,统筹优化了自适应变频扫键、智能节省CPU算力等措施代码;

(2018年8月29日) 调整程序结构,使强实时性的紧急按键可优先得到处理,从而兼具按键的强实时性要求以及适应超长(主循环执行一遍的时间长达1秒以上的)程序。
弱化三行功能,采用带参宏一次性定义了几乎所有的按键宏值,使得移植者无须为各类按键定义大量宏值,直接处理标准化格式的按键事件即可。
和网友@xiatianyun探讨后进一步加固了消抖代码,最大限度地确保按键的稳定读取;

(2018年8月30日) 代码进行了一些小的调整,进一步增强通用性、可读性及可移植性。定版本为2.2;

(2018年9月1日) 增加了大量注释,其中包括详细的运用、移植修改方法;

(2018年9月2日) 新写了一个4*4矩阵加4键直联混合共20个键的例子(放在153楼/154楼),供使用行列式矩阵等复杂键盘开发者参考;

***********************************************************************************************************************0901X317
支持连续按与不支持连续按的区别:

支持连续按:按下不松开则认为是连续有效。具体过程:就是检测相应按键,只要是在键按下的状态,就执行相应的操作,持续按下则持续不断地(多次)执行用户相应的操作。

不支持连续按:按下不松开则认为是一次有效的。具体过程:就是检测相应按键,只有按键在松开后才被认为是一次有效按键,每次按键只执行一次用户相应的操作。

本按键扫描程序可轻松支持这两种模式。

我们一般的程序需求常常是后者,即每次按键只执行一次用户相应的操作。
而这种所谓不支持连续按(按键一次只执行一次操作)实际上又可以有两种实现方式:
一种是只要按下就执行操作(反正一般按键你总是要松开的,我先执行了再说,这种表现出来的就是响应速度快,用户体验好)
另外一种处理方式就是严格等按键释放后才被认为是一次按键,也就是说你按下键时是没有反应的,等你松开按键后才执行动作。显然用户的体验是反应速度慢。
这两种模式本键盘扫描程序都支持。
事实上等待按键释放后才被认为是一次按键还可以派生出一种按键,那就是长按,只有按下不松开超出指定时间(如2秒以上)就被认为是一次长按事件成立。
长按成立后又引申出两种模式:即长按超过指定时间后还不释放,是执行一次操作还是只要再不放就连续执行操作。而连续执行操作又可分为是真正连续操作还是每间隔一个短时间才执行一次操作(最后这种比较典型的应用就是大家熟悉的电子表调整时间的按键,比如“+”键,按下不放超过时间后变成连续增加,而这个连续增加实际是间隔比如100ms增加一次的,如果真正无间隔地连续你就来不及看清楚和反应了)
所有这些本键盘扫描程序都可轻松支持。



但从上面的描述可知,用户对按键的具体要求是千差万别的,我不可能把所有这些都罗列在例程里(并且不同需求之间还存在相互冲突),大家根据具体应用需求进行按键规划后,只须修改Get_Key()函数相应的判断条件就可以了。








管理回复 编辑支持反对


我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 3 反对 0

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-15 22:36:25 | 显示全部楼层
本帖最后由 warship 于 2018-8-18 21:02 编辑

u16 KeyStable; //全局变量:存有稳定(消除抖动后)的当前键态
u16 KeyTime=0;  //存放按键持续时间长度

//这是网友转发的三行读键程序(实为两行)
u16 Trg=0;
u16 Cont=0;
void KeyRead(void)
{
        Trg=KeyStable & (KeyStable ^ Cont);
        Cont=KeyStable;
}

//宏定义(以下以战舰版的四个键为例定义):按键触发条件
#define KEY0_PRESSED                                 (Trg==0x0001)
#define KEY1_PRESSED                                 (Trg==0x0002)
#define KEY2_PRESSED                                 (Trg==0x0004)
#define WKUP_PRESSED                               (Trg==0x0008)
//宏定义:按键未释放值
#define KEY0_ON                                                 0x0001
#define KEY1_ON                                                 0x0002
#define KEY2_ON                                                 0x0004
#define WKUP_ON                                                 0x0008

/******************** 用户应用程序按键判断接口函数 *********************************/
//在主循环中调用,返回稳定的键值,用户程序直接处理规定的键值即可。

//可适应的按键类型如下(最新参考版本见一楼附件或42楼代码):
//普通:按下即有效,不用等按键释放
//短按:按下再松开后有效,可设置短按时间长度
//长按:按下超过规定的时间,超过后可连续输出,也可设置间隔一定时间输出一次键值
//组合:双键组合(其实多键组合也可同理实现)
/**********************************************************************************/
u8 Get_Key(void)
{
        u8 keyp=0;
        KeyRead(); //调用三行读键程序
        
        //按键判断,用户可根据需要随意添加(以下仅为示例)
        if((Cont==(KEY0_ON+WKUP_ON)) && KEY0_PRESSED) //KEY0+WKUP组合按键(先按下WKUP再按下KEY0)
                 return WKUP_PlusKEY0_PRES;
        if(Cont==KEY1_ON && KeyTime>=100)  //KEY1超过2秒(时间可任意设置)的长按键
                 return LONG_KEY1_PRES;
        if(KEY0_PRESSED) return KEY0_PRES;
        if(KEY1_PRESSED) return KEY1_PRES;
        if(KEY2_PRESSED) return KEY2_PRES;
        if(WKUP_PRESSED) return WKUP_PRES;
        
        return keyp;
}

//硬件按键编码(为应用三行读键程序而准备)
//以战舰版的四键为例(最大暂支持16键,相关变量定义为u32则可支持32键)
u16 GetHalKeyCode(void)
{
         u16 ktmp=0;
        if(!KEY0) ktmp|=1;
        if(!KEY1) ktmp|=1<<1;
        if(!KEY2) ktmp|=1<<2;
        if(WK_UP) ktmp|=1<<3;
        return ktmp;
}

我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-15 22:38:08 | 显示全部楼层
本帖最后由 warship 于 2018-8-16 06:39 编辑

以下是按键消除抖动及按键计时函数,
由SYSTICK中断服务程序调用。
//按键扫描函数:一般由Systick中断服务程序以20ms一次的时间节拍调用此函数
//该函数将影响全局变量:消除抖动后的稳定键态值KeyStable及时长KeyTime
void Key_Scan_Stick(void)
{
        volatile static u16 KeyValNew;
        static u16 NewKeyFlag=0;
        u16 KeyValTemp;

        
        KeyValTemp=GetHalKeyCode();  //扫描键盘,得到实时键值(合并),可存16个键值,按下相应位为1,松开为0
        KeyTime++;//按键时长累计(暂按旧键不变累计)
        if(KeyValTemp==KeyStable) //没有检测到新键态,直接返回
        {
                KeyValNew=KeyStable;  //确保正常情况下,KeyValNew存有当前稳定键态值
                NewKeyFlag=0;
                return ;
        }
        
        //以下为检测到新键态
        if(NewKeyFlag) //如果已经有新键检测标志,即现为第二次检测
        {
                NewKeyFlag=0; //复位新键检出标志
                if(KeyValTemp==KeyValNew) //第二次检测到同样的新键值,说明已经稳定
                        {
                                KeyStable=KeyValNew;  //更新键值
                                KeyTime=1; //新键值累计时长复位为1个时间单位
                                return;
                        }
                else   //第二次检测到不同的键值,说明是抖动
                {
                        KeyValNew=KeyStable;  //忽略掉这两次扫描
                        return;                        
                }
        }
        else //无新键检测标志,即第一次检测到新键
        {
                KeyValNew=KeyValTemp;
                NewKeyFlag=1;  //置新键检出标志
                return;
        }               
}
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-15 22:39:56 | 显示全部楼层
/* SysTick中断服务函数示例:事先配置为1ms中断一次*/
void SysTick_Handler(void)
{
        static int a = 0;
        if ( a++ > 19 )         //用变量累计是否达20ms
        {
          Key_Scan_Stick(); //这里调用按键扫描及消抖程序
        a = 0;
        }
}
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

3

主题

725

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1321
金钱
1321
注册时间
2018-8-14
在线时间
176 小时
发表于 2018-8-15 22:45:35 | 显示全部楼层
比一般的好了, 最少没需要求Delay, 缺点是去抖太省了,线短的应用应该可以。
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-15 22:47:19 | 显示全部楼层
edmund1234 发表于 2018-8-15 22:45
比一般的好了, 最少没需要求Delay, 缺点是去抖太省了,线短的应用应该可以。

什么意思?
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

3

主题

725

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1321
金钱
1321
注册时间
2018-8-14
在线时间
176 小时
发表于 2018-8-15 22:51:45 | 显示全部楼层

取样两次一样的就当去抖了, 在很多应用中是不可行的, 按键的线接得稍微长一些, 干扰大一些就会发现按键不灵敏
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-15 22:54:25 | 显示全部楼层
edmund1234 发表于 2018-8-15 22:51
取样两次一样的就当去抖了, 在很多应用中是不可行的, 按键的线接得稍微长一些, 干扰大一些就会发现按 ...

不应该呀,取样两次间隔20ms和延时20ms没有任何区别吧。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

3

主题

725

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1321
金钱
1321
注册时间
2018-8-14
在线时间
176 小时
发表于 2018-8-15 22:59:15 | 显示全部楼层
warship 发表于 2018-8-15 22:54
不应该呀,取样两次间隔20ms和延时20ms没有任何区别吧。

问题在两次取样
回复 支持 反对

使用道具 举报

  离线 

3

主题

725

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1321
金钱
1321
注册时间
2018-8-14
在线时间
176 小时
发表于 2018-8-15 23:01:21 | 显示全部楼层
warship 发表于 2018-8-15 22:54
不应该呀,取样两次间隔20ms和延时20ms没有任何区别吧。

问题在两次取样
回复 支持 反对

使用道具 举报

  离线 

27

主题

325

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1022
金钱
1022
注册时间
2018-4-13
在线时间
156 小时
发表于 2018-8-15 23:21:14 | 显示全部楼层
几天前也看到了这个三行按键检测程序代码,的确很精炼。不过和原子的按键教程其实是一个思路,不同的是三行代码这个程序更加简洁。
具体做法其实就是边沿检测:如果前后两次检测到的按键值相同,则异或值为0,也就是FALSE,此时按键可能处于按下也可能处于没有按下状态。如果前后两次检测到不同的按键值,则异或值不为0,是TRUE,此时按键可能从松开跳变到按下也可能从按下跳变到松开,此时就是检测到了边沿。
检测到边沿后如何知道是按下还是没有按下呢,就是用这个边沿状态&按下状态值或松开状态值,这样就检测到了按下或松开的跳变了。
短按检测就这样可以了,长按下如何检测呢?那就在检测到边沿状态后强制把存储上一次按键值的变量赋值为松开值,也就是让每次检测时都认为上次是松开状态,这样如果处于按下状态时(不处于跳变边沿),每次检测都会检测到边沿状态。
道理简单,但不简单的是代码只有三行作边沿检测,核心就是异或。
至于防抖处理,我个人有更好的措施,利用SysTick滴答定时器改造下就可以了。
设计一个静态的定时结构TimeStruct,里面存储两个变量:一个是计数累计值u32Time_ms,一个是等宽脉冲信号bPlus。
SysTick每次中断除了让u32Time_ms--外,让bPlus=!bPlus,这样,bPlus就是周期为2倍SysTick周期的信号了。利用这个bPlus的边沿作为其他定时器的计数时钟。
我这里的“其他定时器”不是系统定时器,而是自定义的各个计数器。
说到这里大家可能明白我怎么做了。就是检测bPlus边沿,这样就产生了边沿信号,用计计数器来累计边沿信号,这样计数器值就是开始定时到此时的时间了。
利用这个可以生成多个计数器,也就是可以有多个独立定时器,互不干扰。
这样防抖利用这个定时计数器,不用等待,每次描述到时检测定时时间是否到达即可。但要保证定时计数器能够被及时执行到,也就是while(1)执行时间不要超过一个SysTick周期即可,这个在定时要求不高的防抖处理中应该能够做到。
也可以在SysTick中断中来累计自定义定时器的计数值,不过这样需要每增加一个定时器就修改一次中断程序,比较啰嗦,删除定时器也需要同步删除中断里面的相应程序。
u32Time_ms的作用和教程一样处理,用于delay_ms()。

回复 支持 反对

使用道具 举报

  离线 

27

主题

325

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1022
金钱
1022
注册时间
2018-4-13
在线时间
156 小时
发表于 2018-8-15 23:46:54 | 显示全部楼层
楼主贴出了代码,组合按键看不明白。
回复 支持 反对

使用道具 举报

  离线 

27

主题

325

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1022
金钱
1022
注册时间
2018-4-13
在线时间
156 小时
发表于 2018-8-15 23:55:23 | 显示全部楼层
定时两次检测按键来防抖有一定的弊端,如果两次检测中间有抖动而刚好在检测前恢复了就检测不到中间的抖动了。
我的做法是使用定时器(自定义的定时器)来监测,每次扫描都直接读取按键值,如果值依然是1(键值)就继续定时,如果是0就复位定时器。只有连续按下超过定时值才认为按下了按键。
回复 支持 反对

使用道具 举报

  离线 

523

主题

9万

帖子

31

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
135867
金钱
135867
注册时间
2010-12-1
在线时间
1363 小时
发表于 2018-8-16 01:34:38 | 显示全部楼层
cool,谢谢分享。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-16 06:42:37 | 显示全部楼层
xiatianyun 发表于 2018-8-15 23:21
几天前也看到了这个三行按键检测程序代码,的确很精炼。不过和原子的按键教程其实是一个思路,不同的是三行 ...

思路可以借鉴,有空分享一下代码。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-16 06:51:59 | 显示全部楼层
本帖最后由 warship 于 2018-8-16 07:02 编辑
xiatianyun 发表于 2018-8-15 23:46
楼主贴出了代码,组合按键看不明白。

首先我定义了如下宏:
//宏定义(以下以战舰版的四个键为例定义):按键触发条件
#define KEY0_PRESSED                                 (Trg==0x0001)
#define KEY1_PRESSED                                 (Trg==0x0002)
#define KEY2_PRESSED                                 (Trg==0x0004)
#define WKUP_PRESSED                               (Trg==0x0008)
//宏定义:按键未释放值
#define KEY0_ON                                                 0x0001
#define KEY1_ON                                                 0x0002
#define KEY2_ON                                                 0x0004
#define WKUP_ON                                                 0x0008

组合按键KEY0+WKUP组合按键(先按下WKUP再按下KEY0)的判断原则是:
WKUP和KEY0处于按下状态,且检测到KEY0触发。
对应语句为 if((Cont==(KEY0_ON+WKUP_ON)) && KEY0_PRESSED)
假如想要先按下KEY0再按下WKUP也起作用,则判断语句为:
if((Cont==(KEY0_ON+WKUP_ON)) && WKUP_PRESSED)
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

6

主题

625

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1316
金钱
1316
注册时间
2017-8-2
在线时间
297 小时
发表于 2018-8-16 08:48:16 | 显示全部楼层
.......状态机?
猪猪熊呢?
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-16 08:53:30 来自手机 | 显示全部楼层
323232 发表于 2018-8-16 08:48
.......状态机?

不是状态机,是普通程序,只不过设计比较巧妙而已,其实听懂了很简单
回复 支持 反对

使用道具 举报

  离线 

80

主题

776

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1414
金钱
1414
注册时间
2014-3-7
在线时间
298 小时
发表于 2018-8-16 08:59:57 | 显示全部楼层
warship 发表于 2018-8-16 08:53
不是状态机,是普通程序,只不过设计比较巧妙而已,其实听懂了很简单

这个按键的写法优势是什么?
回复 支持 反对

使用道具 举报

  离线 

1

主题

33

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
250
金钱
250
注册时间
2015-10-13
在线时间
88 小时
发表于 2018-8-16 09:08:59 | 显示全部楼层
参考armfly 他们的做的很不错
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-16 10:05:15 来自手机 | 显示全部楼层
ssssssssssss 发表于 2018-8-16 08:59
这个按键的写法优势是什么?

不用再考虑松开,按下,连按,组合等逻辑判断细节,并且不会出现重复按键,代码实现结构清晰,增减按键定义很容易。
回复 支持 反对

使用道具 举报

  离线 

27

主题

325

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1022
金钱
1022
注册时间
2018-4-13
在线时间
156 小时
发表于 2018-8-16 10:16:41 | 显示全部楼层
今早研读了楼主的程序,组合按键的实现看明白了,谢谢分享!
不同的按键组合对应不同的键值,只处理键值。
我把我写的延时程序贴出来,给些意见。
[C] 纯文本查看 复制代码
// 延时器,配合SysTick使用。
static struct {
    u32 uTimer_ms;  // ms延时器,用于查询延时。
    bool bPlus_ms;  // ms脉冲信号,1msON,1msOFF.
}Timer;

// 脉冲型定时器结构
typedef struct {
    u32  uEt; // 定时计数当前值。
    bool bTemp; //脉冲暂存信号。
    bool bQ;    //定时时间到标识。
}TimerType;

这是两个结构,一个是静态结构变量Timer,是由SysTick中断处理的对象,一个是定时器结构类型TimerType,可以构造多个自定义定时器,这些定时器在程序中需要时定义,并由下面的定时器函数操作。
// 延时接通定时器
// 当bEnb为TRUE时开始定时,定时单位为1ms。
// 当bEnb为FALSE时复位定时器。
// 当定时到达后如果没有复位定时器则定时器当前计数值uEt保持不变。
bool TimeON(bool bEnb, u32 uPt, TimerType *timer);

回复 支持 反对

使用道具 举报

  离线 

27

主题

325

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1022
金钱
1022
注册时间
2018-4-13
在线时间
156 小时
发表于 2018-8-16 10:24:28 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-8-16 10:25 编辑

这是SysTick中断函数,SysTick可以设置成需要的定时,这里使用的是1ms。
[C] 纯文本查看 复制代码
// SysTick定时器中断
//每1ms中断一次,每次使bPlus_ms翻转一次,使uTimer_ms减1。
//所产生的脉冲周期为2ms,每1ms翻转一次。
void SysTick_Handler(void)
{    
    Timer.bPlus_ms = !Timer.bPlus_ms;
    if(Timer.uTimer_ms != 0U)
        Timer.uTimer_ms--;
}

delay_ms和delay_s是和教程一样的等待延时函数。
[C] 纯文本查看 复制代码
// ms级delay.
void delay_ms(u32 utime_ms)
{

    Timer.uTimer_ms = utime_ms;
    // 查询延时是否到,不到则继续查询。
    while( Timer.uTimer_ms != 0U )
        ;
}

// s级delay.
void delay_s(u32 utime_s)
{
    // 延时时间为utime_s倍的1000ms。
    for (int i = 0; i < utime_s; ++i)
    {
        delay_ms(1000U);
    }
}

这个是延时接通型定时器函数。
[C] 纯文本查看 复制代码
// 延时接通定时器
// 当bEnb为TRUE时开始定时,定时单位为1ms。
// 当bEnb为FALSE时复位定时器。
// 当定时到达后如果没有复位定时器则定时器当前计数值uEt保持不变。
bool TimeON(bool bEnb, u32 uPt, TimerType *timer)
{
    if(!bEnb){
        timer->uEt = 0U;
        timer->bTemp = FALSE;
        timer->bQ = FALSE;
        return FALSE;
    }
    else{
        //每次检测到边沿(每1ms翻转一次)就加1.        
        if((timer->uEt < uPt) && (Timer.bPlus_ms ^ timer->bTemp))
            timer->uEt++;
            
        timer->bTemp = Timer.bPlus_ms;

        if((timer->uEt >= uPt)){
            timer->bQ = TRUE;
            return TRUE;
        }
        else{
            timer->bQ = FALSE;
            return FALSE;
        }
    }
}

回复 支持 反对

使用道具 举报

  离线 

27

主题

325

帖子

1

精华

金牌会员

Rank: 6Rank: 6

积分
1022
金钱
1022
注册时间
2018-4-13
在线时间
156 小时
发表于 2018-8-16 10:29:02 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-8-16 10:40 编辑

下面是一key0键检测为例说明如何防抖。
[C] 纯文本查看 复制代码
//key0 扫描程序
//bSusKey==TRUE:连续按下可以扫描到连续多个值。
//bSusKey==FALSE:连续按下只扫描到一个按键值。
bool key0_Scan( bool bSusKey )
{
    static bool bsLastKey = FALSE;  //按键上次状态。
    //经过防抖处理后检测到按键压下。
    bool bKey;
    //防抖动检测定时器。
    static TimerType timer;
    //返回值。
    bool bRet = FALSE;

    assert_param(Is_BOOL(bSusKey));

    // 防抖处理。
    bKey = TimeON(KEY0_CODE, 150U, &timer);  //KEY0_CODE是宏,当读取到硬件有键输入时值为键值,否则是0。防抖时间150ms。
    bRet = bKey & (bKey ^ bsLastKey);  //沿检测到键按下.
    //如果是连续按键模式。
    if(bSusKey)
        bsLastKey = FALSE;  //当是连续模式时,把上次按键状态置为FALSE。
    //如果是单按键模式。
    else
        bsLastKey = bKey;


    return bRet;
}

经过TimeON()的防抖处理,只有连续按下超过150ms才能被检测到键按下,期间有抖动导致KEY0_CODE为0均会复位timer。
定时器的其他用法类似,比如需要延时200ms,可以采用等待延时delay_ms(200),也可以采用如下的方法:
[AppleScript] 纯文本查看 复制代码
// 定义定时器。
static TimerType Timer1;
while(1)
{
        TimeON(bT1_Enb, 200U, &Timer1);  //保证能够被循环执行。
       if(!Timer1.bQ)
            {
...................
             }
}


回复 支持 反对

使用道具 举报

  离线 

80

主题

776

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1414
金钱
1414
注册时间
2014-3-7
在线时间
298 小时
发表于 2018-8-16 11:05:42 | 显示全部楼层
本帖最后由 ssssssssssss 于 2018-8-16 11:06 编辑
xiatianyun 发表于 2018-8-16 10:29
下面是一key0键检测为例说明如何防抖。
[mw_shl_code=c,true]//key0 扫描程序
//bSusKey==TRUE:连续按下 ...

可否提供一份稳定,简单的按键程序
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-16 11:11:25 来自手机 | 显示全部楼层
xiatianyun 发表于 2018-8-16 10:29
下面是一key0键检测为例说明如何防抖。
[mw_shl_code=c,true]//key0 扫描程序
//bSusKey==TRUE:连续按下 ...

强大,看起来你想了不少办法。你说的两次检测消抖的弊端,你用连续150ms按下来确认,我觉得我的代码也可以借鉴,并且很容易实现,可以简单理解为150ms的长按,大概相当于我上面长按2秒参数为100,改为参数7或者8就行了。你觉得可行否?
回复 支持 反对

使用道具 举报

  离线 

18

主题

48

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
225
金钱
225
注册时间
2017-8-25
在线时间
38 小时
发表于 2018-8-16 17:18:33 | 显示全部楼层
warship 发表于 2018-8-15 22:36
u16 KeyStable; //全局变量:存有稳定(消除抖动后)的当前键态
u16 KeyTime=0;  //存放按键持续时间长度

...

我现在需要的是按键长按数值累加,像电子表调时间按加减,数值一直累加,直到放掉按键停止,自然也支持短按
回复 支持 反对

使用道具 举报

  离线 

18

主题

48

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
225
金钱
225
注册时间
2017-8-25
在线时间
38 小时
发表于 2018-8-16 17:18:48 | 显示全部楼层
warship 发表于 2018-8-15 22:39
/* SysTick中断服务函数示例:事先配置为1ms中断一次*/
void SysTick_Handler(void)
{

我现在需要的是按键长按数值累加,像电子表调时间按加减,数值一直累加,直到放掉按键停止,自然也支持短按
回复 支持 反对

使用道具 举报

  离线 

18

主题

48

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
225
金钱
225
注册时间
2017-8-25
在线时间
38 小时
发表于 2018-8-16 17:19:06 | 显示全部楼层
warship 发表于 2018-8-15 22:39
/* SysTick中断服务函数示例:事先配置为1ms中断一次*/
void SysTick_Handler(void)
{

我现在需要的是按键长按数值累加,像电子表调时间按加减,数值一直累加,直到放掉按键停止,自然也支持短按
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-16 17:51:43 来自手机 | 显示全部楼层
逍遥1 发表于 2018-8-16 17:19
我现在需要的是按键长按数值累加,像电子表调时间按加减,数值一直累加,直到放掉按键停止,自然也支持短 ...

这个需求是没有问题的。看我一楼引用的出处里面的内容,就专门描述你的这个电子表式的需求及实现
回复 支持 反对

使用道具 举报

  离线 

18

主题

48

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
225
金钱
225
注册时间
2017-8-25
在线时间
38 小时
发表于 2018-8-16 19:11:53 | 显示全部楼层
warship 发表于 2018-8-16 17:51
这个需求是没有问题的。看我一楼引用的出处里面的内容,就专门描述你的这个电子表式的需求及实现

我写好按长按,但是动作的是短按的程序,有时会是长按的程序,短按有时按三下才反应
回复 支持 反对

使用道具 举报

  离线 

18

主题

48

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
225
金钱
225
注册时间
2017-8-25
在线时间
38 小时
发表于 2018-8-16 19:14:04 | 显示全部楼层
逍遥1 发表于 2018-8-16 19:11
我写好按长按,但是动作的是短按的程序,有时会是长按的程序,短按有时按三下才反应

程序是状态机的结构
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-16 19:16:06 | 显示全部楼层
逍遥1 发表于 2018-8-16 19:14
程序是状态机的结构

无须状态机,按这个写没有问题的。也便于理解。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

18

主题

48

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
225
金钱
225
注册时间
2017-8-25
在线时间
38 小时
发表于 2018-8-16 19:29:38 | 显示全部楼层
warship 发表于 2018-8-16 19:16
无须状态机,按这个写没有问题的。也便于理解。

因为我这程序有点大,循环程序跑一遍有点长
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-16 23:43:14 来自手机 | 显示全部楼层
本帖最后由 warship 于 2018-8-19 10:04 编辑
逍遥1 发表于 2018-8-16 19:29
因为我这程序有点大,循环程序跑一遍有点长

你最好测试一下跑一遍循环需要多长时间,如果超过250ms,恐怕就只能用中断来处理按键了
回复 支持 反对

使用道具 举报

  离线 

5

主题

63

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1219
金钱
1219
注册时间
2015-2-4
在线时间
229 小时
发表于 2018-8-17 10:07:24 | 显示全部楼层
warship 发表于 2018-8-15 22:36
u16 KeyStable; //全局变量:存有稳定(消除抖动后)的当前键态
u16 KeyTime=0;  //存放按键持续时间长度

...

这里:
        if(GET_K0())  ktmp|=1;
        if(GET_K1())  ktmp|=1<<1;
        if(GET_K2())  ktmp|=1<<2;
        if(GET_KWK())  ktmp|=1<<3;
     GET_K0()、 GET_K1()从何而来,Get_Key()?
回复 支持 反对

使用道具 举报

  离线 

19

主题

561

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1276
金钱
1276
注册时间
2016-4-29
在线时间
201 小时
发表于 2018-8-17 13:59:46 | 显示全部楼层
不错,标记一下,按键扫描程序
回复 支持 反对

使用道具 举报

  离线 

80

主题

776

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1414
金钱
1414
注册时间
2014-3-7
在线时间
298 小时
发表于 2018-8-17 14:22:23 | 显示全部楼层
Snail_Feng 发表于 2018-8-16 09:08
参考armfly 他们的做的很不错

在哪里下载给个链接
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-17 14:53:22 | 显示全部楼层
wdgao 发表于 2018-8-17 10:07
这里:
        if(GET_K0())  ktmp|=1;
        if(GET_K1())  ktmp|=1

这个无须调用函数,就是原子的宏定义
#define GET_K0()        (!KEY0)
#define GET_K1()        (!KEY1)
#define GET_K2()        (!KEY2)
#define GET_KWK()        (WK_UP)   //注意WK_UP按键硬件上是高电平有效
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-17 14:56:53 | 显示全部楼层
#define KEY0          GPIO_ReadInputDataBit(KEY_MAIN_PORT,KEY0_GPIO_PIN)//读取按键0
#define KEY1          GPIO_ReadInputDataBit(KEY_MAIN_PORT,KEY1_GPIO_PIN)//读取按键1
#define KEY2          GPIO_ReadInputDataBit(KEY_MAIN_PORT,KEY2_GPIO_PIN)//读取按键2  ??
#define WK_UP         GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)   //读取按键3(WK_UP)
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-17 15:00:18 | 显示全部楼层
wdgao 发表于 2018-8-17 10:07
这里:
        if(GET_K0())  ktmp|=1;
        if(GET_K1())  ktmp|=1

综合上面的两组宏定义,可以直接写成这样:
        if(!GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) )  ktmp|=1;
        if(!GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) )  ktmp|=1<<1;
        if(!GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) )  ktmp|=1<<2;
        if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) )  ktmp|=1<<3;
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-18 11:40:58 | 显示全部楼层
本帖最后由 warship 于 2018-8-18 20:29 编辑

/******************** 用户应用程序按键判断接口函数 *********************************/
//在主循环中调用,返回稳定的键值,用户程序直接处理规定的键值即可。

//想添加什么样的键(类型或组合)就在本函数增加(下列代码仅为示例,包括注释掉的代码也是可用的)。
//可适应的按键类型如下:
//普通:按下即有效,不用等按键释放
//短按:按下再松开后有效,可设置短按时间长度
//长按:按下超过规定的时间,超过后可连续输出,也可设置间隔一定时间输出一次键值
//组合:双键组合(其实多键组合也可同理实现)
/**********************************************************************************/
u8 Get_Key(void)
{
        u8 keyp=0;
        static u16 OldKeyTime=0;
        static u16 OldKeyTrg=0;
        static u16 OldKeyCont=0;
        
        KeyRead(); //调用三行读键程序
        
        //以下是按键判断,用户可根据需要随意添加
        if((Cont==(KEY0_ON+WKUP_ON)) && KEY0_PRESSED) //KEY0+WKUP组合按键(先按下WKUP再按下KEY0)
                 keyp=WKUP_PLUSKEY0_PRES;
//        else if(Cont==KEY1_ON && KeyTime>=100)  //长按键KEY1超过2秒
//                 keyp=LONG_KEY1_PRES;
        else if(Cont==KEY1_ON && KeyTime>=100 && (KeyTime%20==0))  //KEY1超过2秒的长按键,每400ms返回一次键值(可用于常见的快速调整键)
                 keyp=LONG_KEY1_PRES;
        else if((OldKeyCont==KEY0_ON) && KEY_RELEASED && (OldKeyTime<40)) keyp=KEY0_PRES;  //短按(时间可设也可不设),松开有效
        else if((OldKeyCont==KEY1_ON) && KEY_RELEASED && (OldKeyTime<40)) keyp=KEY1_PRES;
        else if((OldKeyCont==KEY2_ON) && KEY_RELEASED && (OldKeyTime<40)) keyp=KEY2_PRES;
        else if((OldKeyCont==WKUP_ON) && KEY_RELEASED && (OldKeyTime<40)) keyp=WKUP_PRES;
//        else if(KEY0_PRESSED) keyp=KEY0_PRES;  //按下即有效
//        else if(KEY1_PRESSED) keyp=KEY1_PRES;
//        else if(KEY2_PRESSED) keyp=KEY2_PRES;
//        else if(WKUP_PRESSED) keyp=WKUP_PRES;
        
        OldKeyTime=KeyTime; //保存上一次读的触发值、按键值及按键时间
        OldKeyTrg=Trg;
  OldKeyCont=Cont;        
        return keyp;
}
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

62

主题

5144

帖子

0

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
6581
金钱
6581
注册时间
2012-11-26
在线时间
1503 小时
发表于 2018-8-18 11:45:02 | 显示全部楼层
还是状态机的思想
道不同,不相与为谋
回复 支持 反对

使用道具 举报

  离线 

5

主题

63

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1219
金钱
1219
注册时间
2015-2-4
在线时间
229 小时
发表于 2018-8-18 12:19:00 | 显示全部楼层
jermy_z 发表于 2018-8-18 11:45
还是状态机的思想

感谢楼主不断地更新、补充,不知楼主能否利用该按键实现思想将原子的按键实验改写一下,以便大家对比研究探讨。
回复 支持 反对

使用道具 举报

  离线 

5

主题

63

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1219
金钱
1219
注册时间
2015-2-4
在线时间
229 小时
发表于 2018-8-18 12:24:29 | 显示全部楼层
warship 发表于 2018-8-18 11:40
/******************** 用户应用程序按键判断接口函数 *********************************/
//在主循环中 ...

感谢楼主不断地更新、补充,不知楼主能否利用该按键实现思想将原子的按键实验改写一下,以便大家对比研究探讨。
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-18 13:18:58 | 显示全部楼层
jermy_z 发表于 2018-8-18 11:45
还是状态机的思想

这个不是状态机,只是巧妙利用异或操作检测变化沿
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

21

主题

273

帖子

0

精华

高级会员

Rank: 4

积分
742
金钱
742
注册时间
2017-4-1
在线时间
170 小时
发表于 2018-8-18 14:01:14 | 显示全部楼层
谢谢分享 mark
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-18 14:40:25 | 显示全部楼层
wdgao 发表于 2018-8-18 12:24
感谢楼主不断地更新、补充,不知楼主能否利用该按键实现思想将原子的按键实验改写一下,以便大家对比研究 ...

好的,马上发。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

  离线 

24

主题

1361

帖子

2

精华

金牌会员

Rank: 6Rank: 6

积分
2393
金钱
2393
注册时间
2018-5-11
在线时间
434 小时
 楼主| 发表于 2018-8-18 14:42:28 | 显示全部楼层
本帖最后由 warship 于 2018-8-18 14:51 编辑

已分享工程,在一楼,欢迎下载测试。
我的开源链接 https://github.com/ShuifaHe/STM32.git  请关注,点赞支持哦。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则




关闭

正点原子STM32/FPGA资料免费洗澡上一条 /1 下一条

正点原子公众号

QQ|联系我们|手机版|官方淘宝店|微信公众平台|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2018-12-18 03:49

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表
/* */