• 由一个按键程序引发的思考(上)


      说起按键程序,只要会单片机的肯定都很熟悉。一般开始学习单片机的时候,入门程序基本都是LED灯和按键。那么这个按键程序有什么特别的吗,还需要专门去思考吗?如果我刚开始学单片机的时候也会这么想,但是随着项目的积累,经验的增加,越来越觉得复杂的事情简单做,简单的事情复杂做,这句话很有哲理,越是看起来简单的事情,真正做好却很不容易。下面就抽丝剥茧的来慢慢分析下这个按键程序有什么特别之处。

      首先来看一下按键的硬件原理图。
    在这里插入图片描述

      这是一个很常见的按键硬件连接图,电源接一个上拉电阻,在接按键,按键的另一端接地,电阻和按键的连接点接到单片机的IO口。按键未按下的时候输出高电平,当按键按下的时候输出低电平。由于按键是机械开关,所以在按下的一瞬间可能会产生毛刺。按键的实际波形如下:
    在这里插入图片描述
      由于按键在按下或者弹起的瞬间会产生毛刺,所以在写程序判断的时候,需要加上10ms的延时。如果10ms后按键依然是低电平就认为按键有效,否则就认为出现了干扰,按键无效。

    在这里插入图片描述

    u8 key_scan(void)
    {
    	if(key==0)
    	{
    		delay_ms(10);
    		if(key==0)
    			return 1;
    		else
    			return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

      这种是很常见的方法,使用起来也最简单。看起来也没什么问题,现在将问题复杂化一下。假设按键被用户按下了一次,那么会有以下这些情况。

    在这里插入图片描述
      第一种情况是正常按键按下时间大概0.2秒左右,第二种情况是按键的时间非常短只有0.02秒,第三种情况是持续按下2秒。那么这三种情况都会被程序正常识别到。但是程序运行的结果可能会出现不同的效果。

      假如现在的程序实现的功能是,在主程序中循环检测按键,如果发现按键按下,就让LED灯的状态翻转一下。也就是按一下按键LED灯亮,再按一下按键LED灯灭。程序执行流程如下。

    在这里插入图片描述

    void main(void)
    {
    	char value = 0;
    	while(1)
    	{
    		value = key_scan();
    		if(value)
    			LED = !LED;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

      在主函数中一直循环检测按键,如果有按键按下,那么就翻转LED灯的状态。在实际测试过程中会发现按键有时候好像不怎么灵敏,当LED灯亮的时候,按一下按键,LED灯还是亮的。如果用示波器观察LED灯的波形,会发现按键按下一次时,LED的状态会翻转很多次。

    在这里插入图片描述
      按键按下的时候越长,LED翻转的次数越多。LED最后是亮还是灭就得靠运气了,通过按键很难控制一下灭,一下亮。那么为什么会出现这种情况。下面就根据波形分析一下程序执行的流程。
    在这里插入图片描述
      由于主程序干的事情很少,所以代码消耗的时间,主要是在按键检测的10ms中,也就是主程序基本10ms就能执行完一圈。假如按键按下的时间比较长,当按键程序检测完成之后,LED灯的状态也翻转了,此时按键依然没有弹起,那么程序会再次进入按键检测函数中。此时会再次检测到按键按下。这样LED灯在按键按下的这段时间中就会一直翻转。所以LED灯基本10ms翻转一次,由于人的视觉暂留时间时是20ms,翻转的时间小于20ms所以人眼看不到LED灯的闪烁。如果将按键的10ms延时改为100ms,那么在按键按下的时候,就会看到LED灯一直在闪烁。单次按键功能变成了连续按键的功能。

      那么如果想要这个程序实现正常的功能要怎么办呢?第一种方法是在主程序中加入延时,让程序执行一圈花费的时间更多一点。第二种方法是按键检测的延时时间加长一点。第三种方法是当发现按键按下时直接进入死循环中,直到按键弹起,才退出按键检测程序。

      前两种方法看着能解决问题,但是实际中不同的人按键的时间长短很难把握,有的人轻轻按一下,有的人使劲按半天。这样延时时间太长,感觉按键比较迟钝,延时时间太短,又会导致按一次按键,灯闪好几次的情况出现。这样同一个产品在不同客户的手里就有不同的客户体验,就会导致有的客户感觉产品的功能和设计的不符。那么最有效的解决方法只能是第三种,当按键按下后,等待按键弹起,然后在执行LED状态翻转的功能。

      那么按键检测的流程改为下面这种:
    在这里插入图片描述

    u8 key_scan(void)
    {
    	if(key==0)
    	{
    		delay_ms(10);
    		if(key==0)
    		{
    			while(key==0);
    			return 1;
    		}
    		else
    			return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

      这样当按键按下的时候,就一直在while循环中等待按键释放。LED灯的状态在按键弹起的时候才会翻转。

    在这里插入图片描述
      不论按键按的快还是慢,LED的状态在按键的时候每次只会改变一次。这样就比较符合设计的期望。

      但是这样又会引入一个新的问题,就是在按键按下的时候,程序就会一直停留在那里,其他的事情就不能干了。如果此时还有数码管动态显示函数,那么在按键按下的时候就数码管就会闪烁一下,如果按键按下时间很长,那么数码管就会熄灭。这样产品使用的时候体验就很不好。
    在这里插入图片描述

      那按键既不能在按下的时候让LED状态翻转,又不能在弹起的时候让LED翻转,难道就无解了吗?那这个LED灯如如何控制呢?要知按键如何判断?且听下回分解。

    由一个按键程序引发的思考(中)

  • 相关阅读:
    【Unity3D】Shader Graph节点
    [安洵杯 2019]easy_web-1
    Go 接口:Go中最强大的魔法,接口应用模式或惯例介绍
    【npm开发指南(1)】从npm包的开发,发布到引用
    利用nginx发布静态服务
    golang for循环append的数据重复
    linux-文件服务
    idea自动封装方法
    【Oracle】Oracle系列之十--Oracle正则表达式
    『GitHub项目圈选02』一款可实现视频自动翻译配音为其他语言的开源项目
  • 原文地址:https://blog.csdn.net/qq_20222919/article/details/127588792