定时器扫描矩阵键盘简易代码(短按/长按)


前言

  本次编程实验以IAP15F2K61S2为单片机主控芯片,其编程使用与STC15F2K60S2完全相同,头文件为STC15F2K60S2.H。若用于51系列单片机,以reg52.h为头文件,则需将后文中timer0.c中void Timer0_Init(void)函数中的AUXR &= 0x7F;一行删除。程序中可能涉及的定时器初始化程序和LED亮灭程序和数码管显示程序,读者可根据自身所用单片机原理图和手册自行修改。


一、使用定时器扫描矩阵键盘按键的原理

1.基本原理

  为了检测按键按下和松开,必须记录按键当前状态和上一次状态。如果当前状态为高电平,上一次状态为低电平,则检测到按键松手;如果当前状态为低电平,上一次为高电平,则检测到按键按下。由于短按按下一次只需返回一次按键值,因此只需检测按键松开动作,即上一次状态低电平,当前状态高电平。

2.消抖原理

  按键按下和松开时,其连接的IO口电平并非瞬间拉低,而是会有一小段时间的抖动:

按键抖动
  考虑到按键按下和松开抖动时间较短,按下时间通常大于50ms。定时器每隔25ms扫描一次按键状态,假设扫描时恰好处于按键按下时的抖动过程,检测到低电平,那么经过25ms此时按键已经稳定处于按下状态,必然检测到低电平,从而上一次状态和现状态均为低电平;如果检测到高电平,则25ms后,上一次状态为高电平,当前状态为低电平,再经过25ms后,上一次状态和现状态均为低电平。当按键松开时,假设恰好检测到抖动过程的高电平,则上一次状态为低电平,当前状态为高电平,符合松手条件,返回按键值;如果检测到抖动过程的低电平,则上一次状态和当前状态均为低电平,经过25ms后,此时按键稳定处于松开状态,必然检测到高电平,则上一次状态为低电平,当前状态为高电平,符合松手条件,返回按键值。

3.矩阵键盘原理图

  开发板上矩阵键盘原理图如下图所示,后文代码中关于矩阵键盘行和列IO口的定义和操作皆根据原理图进行,读者可自行修改。代码中对按键值的定义为:从左至右,从上至下,从1递增到16。
矩阵键盘原理图

二、编程实现

1.矩阵键盘函数

  kbd.h

#ifndef __KBD_H
#define __KBD_H

#include <STC15F2K60S2.H>

//矩阵键盘四列IO口重命名,根据原理图修改
sbit colu1=P4^4;
sbit colu2=P4^2;
sbit colu3=P3^5;
sbit colu4=P3^4;

unsigned char kbd_state();   //矩阵键盘按键状态读取
unsigned char kbd_send(unsigned char mode);  //矩阵键盘最终按键值返回


#endif

  kbd.c
  mode用于选择短按长按模式。mode为0 时,通过检测是否满足上一次状态为低电平(kbdlast不为0)且当前状态为高电平(kbdnow为0)决定是否返回按键值;mode为1时,通过检测是否满足上一次状态为低电平(kbdlast不为0)且当前状态为低电平(kbdnow不为0,且kbdlast=kbdnow)决定是否返回按键值。

#include "kbd.h"

typedef unsigned char u8;
unsigned char column=0;  //column为矩阵键盘列对应IO口的加权和


/*
  *  @brief    矩阵键盘按键状态读取函数
  *  @param    
  *  @reval    key:矩阵键盘按键状态
  *  @note:    按行扫描
*/

unsigned char kbd_state()
{
	unsigned char key=0,i=0;
	
	for(i=0;i<4;i++)
	{
		colu1=colu2=colu3=colu4=1;   //将矩阵键盘四列电平拉高
		P3=P3&0xf0|(~(0x1<<i));				//按行扫描,依次将第一行、第二行、第三行和第四行电平拉低,而后只需对列进行低电平查找
		column=(u8)colu1+(u8)colu2*2+(u8)colu3*4+(u8)colu4*8;   //得到行电平拉低后的四列电平加权总和,第1列权1,第2列权2,以此类推,注意应将colu强制转换为unsigned char型
		switch (column)     //查找低电平所在的列,一旦找到则key不为0,返回key值,结束此函数
		{
			case 0x0e:key=1+4*i;break;  //如果低电平在第一列,从第四列到第一列电平依次为:1 1 1 0
			case 0x0d:key=2+4*i;break;  //如果低电平在第二列,从第四列到第一列电平依次为:1 1 0 1
			case 0x0b:key=3+4*i;break;  //如果低电平在第三列,从第四列到第一列电平依次为:1 0 1 1
			case 0x07:key=4+4*i;break;  //如果低电平在第四列,从第四列到第一列电平依次为:0 1 1 1		
		}
		if(key!=0)  //一旦key不为0,返回key,函数结束
			return key;
	}
	return 0; //若无按键按下,返回0
}

/*
  *  @brief     矩阵键盘最终按键值返回函数
  *  @param     mode:长短按选择标志位,1长按0短按
	*  @reval     kbdlast:矩阵键盘最终按键值
  *  @note:    
*/
unsigned char kbd_send(unsigned char mode)
{
	static u8 kbdlast=0,kbdnow=0,i=1;
	
	kbdlast=kbdnow;   //不断刷新矩阵键盘上一次状态和当前状态
	kbdnow=kbd_state();	
	for(i=1;i<17;i++)  //遍历1-16
	{
		if(kbdlast==i&&kbdnow==(i*mode))  //如果上一次状态按键值和当前状态按键值满足条件,则返回上一次状态按键值,函数结束
			return kbdlast;
	}
	
	return 0;  //如果上一次状态按键值和当前状态按键值不满足条件,则返回0
}

2.定时器0函数

  timer0.h

#ifndef __TIMER0_H
#define __TIMER0_H

#include <STC15F2K60S2.H>

void Timer0_Init(void);		//1毫秒@11.0592MHz

#endif

  timer0.c

#include "timer0.h"

void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	AUXR &= 0x7F;			//定时器时钟12T模式
	TMOD &= 0xF0;			//设置定时器模式
	TMOD |= 0x01;			//设置定时器模式
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	
	ET0 = 1;				//使能定时器0中断	
	EA=1;						//使能总中断
}

3.main.c

  定时器定时1毫秒基础时间(方便扩展其他时间要求的功能函数),每过25毫秒检测一次矩阵键盘按键,如果按键按下并松开,则返回相应的按键值(只监测按键松手,因此按下一次只能返回一次按键值);如果连续按下2秒,则切换为长按模式,每过500毫秒检测一次矩阵键盘按键,并返回按键值(监测按键持续按下,每过500毫秒返回一次按键值)。
  实际上,短按扫描是一直在运行的。在短按模式起返回按键和检测按下时间是否超过2s作用;在长按模式起检测按键是否松手作用。如果在长按模式期间不进行短按扫描,那么一旦进入长按模式就无法返回短按模式。

#include <STC15F2K60S2.H>
#include "kbd.h"
#include "timer0.h"
#include <intrins.h>

typedef unsigned char u8;

#define outputp0(y,x)  P0=x,P2&=0x1f,P2|=y,P2&=0x1f;  //P2高三位用于74HC138译码器选择锁存器,P0用于发送数据

u8 kbdnum=0,kbdtemp=0;  //按键值;按键缓存值
int shownum=0;  //数码管显示值
unsigned char code Seg_Table[]={   //数码管段码表,共阳
0xc0,
0xf9,
0xa4,
0xb0,
0x99,
0x92,
0x82,
0xf8,
0x80,
0x90,
0xff  //8段全灭
};

void Delay2ms(void)	//2ms@11.0592MHz
{
	unsigned char data i, j;

	_nop_();
	_nop_();
	i = 22;
	j = 128;
	do
	{
		while (--j);
	} while (--i);
}

/*
  *  @brief     1位数码管显示函数
  *  @param     pos:位选;dat:显示数字;dot:小数点选择位,1有0无
  *  @reval      
  *  @note:    
*/
void showbit(unsigned char pos,dat,dot)
{
	outputp0(0xc0,0x01<<pos);    //位选
	outputp0(0xe0,Seg_Table[10]);   //段选,这两行起消隐作用
	
	outputp0(0xc0,0x01<<pos);     //位选
	outputp0(0xe0,Seg_Table[dat]+0x80*dot);	  //段选
	Delay2ms();   //延时
}
void test()  //测试程序
{
	if(kbdtemp!=0)
	{
		kbdnum=kbdtemp;   //通过缓存量向keynum赋值,防止按下按键松开后立刻被清零
		shownum+=kbdnum;
		shownum%=10000;
		kbdnum=0;       //清零,防止一次按下多次执行
		kbdtemp=0;
	}
	showbit(0,shownum/1000%10,0);   //数码管显示
	showbit(1,shownum/100%10,0);
	showbit(2,shownum/10%10,0);
	showbit(3,shownum%10,0);
		
}
void main()
{
	Timer0_Init();
	while(1)
	{
		test();
	}
}

void Timer0_Isr(void) interrupt 1
{
	static u8 kbd_longflag=0;      //矩阵键盘长按标志位,1长按0短按
	static int kbd_short_t=0,kbd_t=0,kbd_long_t=0;
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	
	kbd_short_t++;          //矩阵键盘按键短按时间计数
	if(kbd_short_t>=(500*kbd_longflag+25*(!kbd_longflag)))   //短按每25ms检测一次按键
	{
		if(!(colu1&colu2&colu3&colu4))     //长按检测,每25ms检测矩阵键盘按键是否仍在按下,如果是
		{
			kbd_t++;						//矩阵键盘按键按下时间计数(以25ms为单位)
			if(kbd_t>=80)       //如果持续按下2s
			{
				kbd_t=80;         //防止溢出
				kbd_longflag=1;           //切换为长按模式
			}
		}
		else if(colu1&colu2&colu3&colu4)		//长按检测,每25ms检测按键是否仍在按下,如果不是
		{
			kbd_t=0;							//清零按键按下时间计数
			kbd_longflag=0;								//切换为短按模式
		}
		
		kbd_short_t=0;             //清零短按时间计数
		if(kbd_longflag==0)         //如果是短按模式
			kbdtemp=kbd_send(0);  //按键以短按模式检测
	}
	
	kbd_long_t++;           //按键长按时间计数
	if(kbd_long_t>=500)     //长按时每500ms检测一次按键
	{
		kbd_long_t=0;						  //清零按键长按时间计数
		if(kbd_longflag==1)         //如果是长按模式
			kbdtemp=kbd_send(1);  //按键以长按模式检测
	}
}

三、测试程序现象

  单片机上电数码管显示0000,短按按下矩阵键盘按键,数码管显示值在原基础上加上按下的按键值;长按按下时,按下2秒时开始在原基础上加上按键值,而后每隔500毫秒增加一次值。


总结

  通过上述程序,可以实现定时器扫描矩阵键盘以提高主程序执行效率。通过类比,也可以实现定时器扫描独立按键和数码管,详情见定时器扫描按键(短按/长按)定时器扫描数码管(含滚动显示)
  此外,由于定时器每1ms进入一次中断,对于具有IIC和One-Wire通信等具有严格时序要求的项目来说,容易使通信被干扰,因此不建议使用定时器扫描的方式。当然,通过一些方法也可以实现二者兼容并存,在DS1302可调时钟(按键短按/长按)中提供了其中一种方法。

  有任何问题和补充,欢迎评论区交流。

  • 28
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值