从单片机基础到程序框架(全集 2019pdf版).pdf - 第406页
第九十二节: 独立按键的四大要素(自锁,消抖,非阻塞 ,清 零式滤波) 。 【92.1 独立按键的硬件电路简介。 】 上图 92.1.1 独立 按键电路 按键 有两种 驱动 方式 ,一种 是独 立按 键,一 种是 矩阵 按键。 1 个独 立按键 要占 用 1 个 IO 口, IO 口 不能 共用。而矩 阵按键的 IO 口是 分时片选复用 的,用少 量的 IO 口就可以驱 动翻倍级别的 按键数量。比 如,用 8 个 IO 口只能驱动 8 …

{
Su8Lock=0; //关闭声音后,及时解锁,为下一次触发做准备
BeepClose(); //关闭声音,此处封装成函数,为了今后代码的移植性。
}
}
}
}

第九十二节: 独立按键的四大要素(自锁,消抖,非阻塞,清零式滤波)。
【92.1 独立按键的硬件电路简介。】
上图 92.1.1 独立按键电路
按键有两种驱动方式,一种是独立按键,一种是矩阵按键。1 个独立按键要占用 1 个 IO 口,IO 口不能
共用。而矩阵按键的 IO 口是分时片选复用的,用少量的 IO 口就可以驱动翻倍级别的按键数量。比如,用 8
个 IO 口只能驱动 8 个独立按键,但是却可以驱动 16 个矩阵按键(4x4)。因此,按键少的时候就用独立按键,
按键多的时候就用矩阵按键。这两种按键的驱动本质是一样的,都是靠识别输入信号的下降沿(或上升沿)
来识别按键的触发。
独立按键的硬件原理基础,如上图,P2.2 这个 IO 口,在按键 K1 没有被按下的时候,P2.2 口因为单片
机内部自带上拉电阻把电平拉高,此时 P2.2 口是高电平的输入状态。当按键 K1 被按下的时候,按键 K1 左
右像一根导线连接到电源的负极(GND),直接把原来 P2.2 口的电平拉低,此时 P2.2 口变成了低电平的输入
状态。编写按键驱动程序,就是要识别这个电平从高到低的过程,这个过程也叫下降沿。多说一句,51 单片
机的 P1,P2,P3 口是内部自带上拉电阻的,而 P0 口是内部没有上拉电阻的,需要外接上拉电阻。除此之外,
很多单片机内部其实都没有上拉电阻的,因此,建议大家在做独立按键电路的时候,养成一个习惯,凡是按
键输入状态都外接上拉电阻。
识别按键的下降沿触发有四大要素:自锁,消抖,非阻塞,清零式滤波。
“自锁”,按键一旦进入到低电平,就要“自锁”起来,避免不断触发按键,只有当按键被松开变成高
电平的时候,才及时“解锁”为下一次触发做准备。
“消抖”,按键是一个机械触点器件,在接触的瞬间必然存在微观上的机械抖动,反馈到电平的瞬间就
是“高,低,高,低...”这种不稳定的电平状态是一种干扰,但是,按键一旦按下去稳定了之后,这种状
态就消失,电平就一直保持稳定的低电平。消抖的本质就是滤波,要把这种接触的瞬间抖动过滤掉,避免按
键的“一按多触发”。
“非阻塞”,在处理消抖的时候,必须用到延时,如果此时用阻塞的 delay 延时就会影响其它任务的运
行效率,因此,用非阻塞的定时延时更加有优越性。
“清零式滤波”,在消抖的时候,有两种境界,第一种境界是判断两次电平的状态,中间插入“固定的
时间”延时,这种方法前后一共判断了两次,第一次是识别到低电平就进入延时的状态,第二次是延时后再
确认一次是否继续是低电平的状态,这种方法的不足是,“固定的时间”全凭经验值,但是不同的按键它们
的抖动时间长度是不同的,除此之外,前后才判断了两次,在软件的抗干扰能力上也弱了很多,“密码等级”
不够高。第二种境界就是“清零式滤波”,“清零式滤波”非常巧妙,抗扰能力超强,它能自动过滤不同按键
的“抖动时间”,然后再进入一个“稳定时间”的“N 次识别判断”,更加巧妙的是,在“抖动时间”和“稳
定时间”两者时间内,只要发现一次是高电平的干扰,就马上自动清零计时器,重新开始计时。“稳定时间”
一般取 20ms 到 30ms 之间,而“抖动时间”是隐藏的,在代码上并没有直接描写出来,但是却无形地融入了
代码之中,只有慢慢体会才能发现它的存在。
具体的代码如下,实现的功能是按一次 K1 或者 K2 按键,就触发一次蜂鸣器鸣叫。

#include "REG52.H"
#define KEY_VOICE_TIME 50 //按键触发后发出的声音长度
#define KEY_FILTER_TIME 25 //按键滤波的“稳定时间”25ms
void T0_time();
void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;
void BeepOpen(void);
void BeepClose(void);
void VoiceScan(void);
void KeyScan(void); //按键识别的驱动函数,放在定时中断里
void KeyTask(void); //按键任务函数,放在主函数内
sbit P3_4=P3^4;
sbit KEY_INPUT1=P2^2; //K1 按键识别的输入口。
sbit KEY_INPUT2=P2^1; //K2 按键识别的输入口。
volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;
volatile unsigned char vGu8KeySec=0; //按键的触发序号,全局变量意味着是其它函数的接口。
void main()
{
SystemInitial();
Delay(10000);
PeripheralInitial();
while(1)
{
KeyTask(); //按键任务函数
}
}
void T0_time() interrupt 1
{
VoiceScan();
KeyScan(); //按键识别的驱动函数
TH0=0xfc;
TL0=0x66;