按键抖动以及消抖
我们都知道,我们所用的机械按键,在按键按下时,并不会正常弹起,会存在一个抖动的现象。也就是说,我们只按一次按键,实际产生的按下次数确是多次的。因此,为了避免这种现象,我们通常采用按键消抖的措施。
按键消抖分为硬件消抖和软件消抖。在一般程序应用中,我们采用的都是软件消抖。
软件消抖 即用软件方法执行消抖,即在程序检测到按键按下时,执行一段延时程序,具体的延时视情况而定,通常为5~20 ms。
状态机概述
前面提到,延时的加入,虽然解决了按键抖动的问题,但是,对于我们初学者学习还好,但在产品开发过程中,程序都是模块化,我们不能让单片机系统始终处于延时状态下,在会造成系统的浪费。
有限状态机是一种概念性机器,它能采取某种操作来响应一个外部事件。具体采取的操作不仅能取决于接收到的事件,还能取决于各个事件的相对发生顺序。之所以能 做到这一点,是因为机器能跟踪一个内部状态,它会在收到事件后进行更新。为一个事件而响应的行动不仅取决于事件本身,还取决于机器的内部状态。另外,采取 的行动还会决定并更新机器的状态。这样一来,任何逻辑都可建模成一系列事件/状态组合。
[2] 状态机可归纳为4个要素,即现态、条件、动作、次态。这样的归纳,主要是出于对状态机的内在因果关系的考虑。“现态”和“条件”是因,“动作”和“次态”是果。详解如下:
①现态:是指当前所处的状态。
②条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
首先我们确定程序思路。
程序开始时,首先处于初始态(无按键按下),如果此时没有按键按下,则保持状态不变。若此时按键状态值为0,即有按键按下,但是否时按键或者抖动尚不清楚,则进入判断按键状态,如果是抖动,则返回初始态。如果确实有按键按下,列举出所有按键,进入按键确定状态。进入按键确定状态,如果检测到松手,则返回初始态,若只是抖动不是松手,则返回确定态。
//状态机函数头文件
#ifndef _KeyScan_H_
#define _KeyScan_H_
#include "STC15.h"
#define u8 unsigned char //宏定义
#define u16 unsigned int
#define KEYPORT P4 //键盘接入端口
#define Key_State0 0 //按键初始状态0
#define Key_State1 1 //判断按键是否按下
#define Key_State2 2 //确定按键按下
#define Key_State3 3 //松手检测状态
extern unsigned char KeyScan(void);
#endif
//状态机函数
#include <KeyScan.h>
u8 KeyValue; //键值
/* ***************************************************** */
// 函数名称:KeyScan(void)
// 函数功能:扫描按键
// 入口参数:无
// 出口参数:键值(KeyValue)
/* ***************************************************** */
u8 KeyScan(void)
{
static u8 KeyState = Key_State0; //定义按键为初始状态0
u8 Key_temp;
u8 KeyLine,KeyRank; //定义按键行列
KEYPORT = 0xf0;
KeyLine = KEYPORT;
KeyLine = KEYPORT&0xf0; //确定哪一行按下
KEYPORT = 0x0f;
KeyRank = KEYPORT;
KeyRank = KEYPORT&0x0f;
Key_temp = KeyLine|KeyRank; //确定按键位置
switch(KeyState)
{
case Key_State0: //按键初始状态
if(Key_temp!=0xff) //当按键按下,状态切换至判断态
KeyState = Key_State1;
break;
case(Key_State1):
if(Key_temp==0xff)
KeyState = Key_State0; //还处于抖动状态,回到初始态
else
{
switch(Key_temp) //当按键按下,列举出所有按键情况
{
case 0xee: KeyValue=0;break;
case 0xde: KeyValue=1;break;
case 0xbe: KeyValue=2;break;
case 0x7e: KeyValue=3;break;
case 0xed: KeyValue=4;break;
case 0xdd: KeyValue=5;break;
case 0xbd: KeyValue=6;break;
case 0x7d: KeyValue=7;break;
case 0xeb: KeyValue=8;break;
case 0xdb: KeyValue=9;break;
case 0xbb: KeyValue=10;break;
case 0x7b: KeyValue=11;break;
case 0xe7: KeyValue=12;break;
case 0xd7: KeyValue=13;break;
case 0xb7: KeyValue=14;break;
case 0x77: KeyValue=15;break;
}
KeyState = Key_State2 ; //状态切换至确定态
}
break;
case Key_State2: //状态二表示确定有按键按下
if(Key_temp==0xff)
KeyState = Key_State3; //若检测到松手,状态切换至松手检测态
break;
case Key_State3:
if(Key_temp==0xff)
KeyState = Key_State0; //若确实松手后,则切换至初始态
else
{
KeyState = Key_State2; //若只是抖动并不是松手,则返回确定态
}
break;
default: KeyState=Key_State0;break;
}
return KeyValue;
}
数码管显示函数:本例的数码管硬件电路是采用74HC595芯片设计的。
//头文件
#ifndef _DISPLAY_H_
#define _DISPLAY_H_
#include "STC15.h"
#define u16 unsigned int
#define u8 unsigned char
sbit smgSER = P5^0; // 595(14脚)SER 数据输入引脚
sbit RCK = P5^2; // 595(12脚)STCP 锁存时钟 1个上升沿所存一次数据
sbit SCK = P5^3; // 595(11脚)SHCP 移位时钟 8个时钟移入一个字节
extern unsigned char code SMG_Array[];
extern void Display(u8 ucVal);
extern void HC595_WrOneByte(unsigned char ucDat);
#endif
//程序
#include <Display.h>
#include <intrins.h>
/* ******************************************************************
* 函数名称:HC595_WrOneByte()
* 入口参数:待写入HC595的一个字节(ucDat)
* 出口参数:无
* 函数功能:向HC595中写入一个字节
****************************************************************** */
void HC595_WrOneByte(unsigned char ucDat)
{
unsigned char i = 0;
/* 通过8循环将8位数据一次移入74HC595 */
for(i=0;i<8;i++)
{
smgSER = (bit)(ucDat & 0x80);
SCK = 0;
ucDat <<= 1;
SCK = 1;
}
/* 数据并行输出(借助上升沿) */
RCK = 0;
_nop_();
_nop_();
RCK = 1;
}
/* ***************************************************** */
// 函数名称:Display()
// 函数功能:数码管刷新
// 入口参数:需显示的数值(ucVal)
// 出口参数:无
/* ***************************************************** */
void Display(unsigned char ucVal)
{
P6 = 0x00; //启动8位数码管的位选
if(ucVal == 16) //若键值是16即没有按键按下,则不显示
{
HC595_WrOneByte(SMG_Array[0]);
}
else //若有按下,显示对应键值
{
HC595_WrOneByte(SMG_Array[ucVal]);
}
}
//主函数及定时器
#include <KeyScan.h>
#include <Display.h>
#define u8 unsigned char //宏定义
#define u16 unsigned int
#define INT_TIME 10 //定时时间 单位为ms
#define MAIN_Fosc 11059200
unsigned char code SMG_Array[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,
0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; // 0 ~ F
u8 flag; //10ms中断标志
void GPIO_Init(void)
{
P4M1 = 0;
P4M0 = 0; //设置为准双向口
}
/* ******************************************************************
* 函数名称: Timer0_Init()
* 入口参数:无
* 出口参数:无
* 函数功能:初始化定时器 0
****************************************************************** */
void Timer0_Init(void)
{
TMOD = 0x01; // 设置定时器0工作在模式1下
TH0=(65536-(INT_TIME*MAIN_Fosc/12000))>>8; //定时初值为10ms
TL0=(65536-(INT_TIME*MAIN_Fosc/12000));
TR0 = 1; // 开定时器0
EA=1;
ET0=1;
}
/* ***************************************************** */
// 函数名称:main()
// 函数功能:矩阵按键扫描并用数码管显示键值
// 入口参数:无
// 出口参数:无
/* ***************************************************** */
void main()
{
static u8 KeyNum = 0;
Timer0_Init();
GPIO_Init();
while(1)
{
P4=0xff;
if(flag)
{
flag=0;
KeyNum=KeyScan();
Display(KeyNum);
}
}
}
void Timer0_ISR()interrupt 1
{
TH0=(65536-(INT_TIME*MAIN_Fosc/12000))>>8; //定时初值为10ms
TL0=(65536-(INT_TIME*MAIN_Fosc/12000));
flag=1;
}