基于状态机的矩阵按键扫描法

按键抖动以及消抖

我们都知道,我们所用的机械按键,在按键按下时,并不会正常弹起,会存在一个抖动的现象。也就是说,我们只按一次按键,实际产生的按下次数确是多次的。因此,为了避免这种现象,我们通常采用按键消抖的措施。
按键消抖分为硬件消抖和软件消抖。在一般程序应用中,我们采用的都是软件消抖。

软件消抖 即用软件方法执行消抖,即在程序检测到按键按下时,执行一段延时程序,具体的延时视情况而定,通常为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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值