基于51单片机和LCD1602的矩阵按键的全功能(按住、按下、松开、单击、双击、长按、重复)演示

系列文章目录


前言

【方法来源】江协科技编程技巧第二期

按住:一直按着
按下:按下瞬间
松开:松手瞬间
单击:按一次
双击:短时间内按两次
长按:按住按键超过一定的时间
重复:长按后每隔一段时间将重复的标志位置1

如果能检测多种按键的事件,可以实现更加多的功能。

本代码使用的是普中A2开发板。

【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】LCD1602、按键

本文代码为矩阵按键的全功能演示,下载链接中也有独立按键的全功能演示的工程文件。

效果查看/操作演示:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有工程文件下载链接。

一、效果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、原理分析

1、方法来源

不懂的建议看江协科技的视频。

[编程技巧] 第2期 全功能按键非阻塞式实现 按键单击 双击 长按

江协科技共享的PPT。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、我的移植

(1)我按照江协科技的方法,扩展到了矩阵按键的全功能非阻塞式的检测。

跟独立按键的检测类似,每次调用函数KeyTick后,都要检测每个按键是否已经按下。

在这里插入图片描述

如上图所示,如果想要检测S11是否已按下,可以设置P15=0,即拉低第三行的线,然后检测P11的电平,如果是低电平,则说明S11已按下,如果是高电平,则说明S11未按下。

要注意,每次检测前或检测后,都要释放所有引脚,即将P10~P17都设置为高电平。

3、移植出现的问题

/**
  * 函    数:按键检测驱动函数,在定时器中断函数中使用
  * 参    数:无
  * 返 回 值:无
  * 说    明:最后的else的目的是防止S[i]的初值在0~4之外导致检测不了单击、双击、长按、重复
  */
void Key_Tick(void)
{
	static unsigned char xdata Count,i;
	static unsigned char xdata NowState[16];
	static unsigned char xdata LastState[16];
	static unsigned char xdata S[16];
	static unsigned char xdata KeyTime[16];

	for(i=0;i<16;i++)
	{
		if(KeyTime[i]>0)
		{
			KeyTime[i]--;
		}
	}

	Count++;
	if(Count>=2)	//本案例中每隔20ms检测一次按键
	{
		Count=0;

		for(i=0;i<16;i++)
		{
			LastState[i]=NowState[i];
			NowState[i]=Key_GetState(i);
			
			if(NowState[i] == KEY_PRESSED)
			{
				Key_Flag[i] |= HOLD;
			}
			else
			{
				Key_Flag[i] &= ~HOLD;
			}
			
			if(NowState[i] == KEY_PRESSED && LastState[i] == KEY_UNPRESSED)
			{
				Key_Flag[i] |= DOWN;
			}
			
			if(NowState[i] == KEY_UNPRESSED && LastState[i] == KEY_PRESSED)
			{
				Key_Flag[i] |= UP;
			}
			
			if(S[i] == 0)
			{
				if(NowState[i] == KEY_PRESSED)
				{
					KeyTime[i]=KEY_TIME_LONG;
					S[i]=1;
				}
			}
			else if(S[i] == 1)
			{
				if(NowState[i] == KEY_UNPRESSED)
				{
					KeyTime[i]=KEY_TIME_DOUBLE;
					S[i]=2;
				}
				else if(KeyTime[i] == 0)
				{
					KeyTime[i]=KEY_TIME_REPEAT;
					Key_Flag[i] |= LONG;
					S[i]=4;
				}
			}
			else if(S[i]==2)
			{
				if(NowState[i] == KEY_PRESSED)
				{
					Key_Flag[i] |= DOUBLE;
					S[i]=3;
				}
				else if(KeyTime[i] == 0)
				{
					Key_Flag[i] |= SINGLE;
					S[i]=0;
				}
			}
			else if(S[i] == 3)
			{
				if(NowState[i] == KEY_UNPRESSED)
				{
					S[i]=0;
				}
			}
			else if(S[i]==4)
			{
				if(NowState[i] == KEY_UNPRESSED)
				{
					S[i]=0;
				}
				else if(KeyTime[i] == 0)
				{
					KeyTime[i]=KEY_TIME_REPEAT;
					Key_Flag[i] |= REPEAT;
					S[i]=4;
				}
			}
			else	//我加的
			{	//我加的
				S[i]=0;	//我加的
			}	//我加的
		}
	}
}

如上所示,由于是实现矩阵按键的全功能,每个数组的变量都是16个,所以我将变量存储在片外RAM区,即用xdata修饰,然后发现一个问题,按住、按下、松开是能检测的,单击、双击、长按、重复检测不到,经过思考,发现可能是这些变量的初值是不确定的,如果S[i]大于4,就没办法检测单击、双击、长按、重复了,所以后面加多一个else的情况,确保S[i]的范围是0~4的范围。

三、各模块代码

1、LCD1602

h文件

#ifndef __LCD1602_H__
#define __LCD1602_H__

void LCD_WriteCommand(unsigned char Command);
void LCD_WriteData(unsigned char Data);
void LCD_SetCursor(unsigned char Line,unsigned char Column);
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_MakeChar(void);
void LCD_Clear(void);

#endif

c文件

#include <REGX52.H>

//引脚定义
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//自定义字符,最多8个
unsigned char code CGRAMData[]={
0x07,0x05,0x07,0x00,0x00,0x00,0x00,0x00,	//0:摄氏度的圆圈
0x04,0x0C,0x04,0x04,0x0E,0x00,0x00,0x00,	//1:小1
0x0E,0x02,0x0E,0x08,0x0E,0x00,0x00,0x00,	//2:小2
0x00,0x0A,0x1F,0x1F,0x0E,0x04,0x00,0x00,	//3:小心形
0x0E,0x0A,0x0E,0x04,0x0E,0x15,0x0A,0x11,	//4:小人
0x00,0x0A,0x00,0x00,0x11,0x0E,0x00,0x00,	//5:小笑脸
0x1F,0x15,0x1F,0x15,0x1F,0x1F,0x1F,0x1F,	//6:小门
0x00,0x00,0x01,0x02,0x14,0x08,0x00,0x00,	//7:小勾
};

/**
  * 函    数:LCD1602私有延时函数,延时约100us
  * 参    数:无
  * 返 回 值:无
  */
void LCD_Delay100us(void)	//12T@11.0592MHz
{
	unsigned char i;
	i=44;
	while(--i);
}

/**
  * 函    数:LCD1602私有延时函数,延时2ms
  * 参    数:无
  * 返 回 值:无
  */
void LCD_Delay2ms(void)	//12T@11.0592MHz
{
	unsigned char i, j;
	i=4;
	j=146;
	do
	{
		while(--j);
	}while(--i);
}

/**
  * 函    数:LCD1602写指令
  * 参    数:Command 要写入的指令
  * 返 回 值:无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay100us();
	LCD_EN=0;
	LCD_Delay100us();
}

/**
  * 函    数:LCD1602写数据
  * 参    数:Data 要写入的数据
  * 返 回 值:无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay100us();
	LCD_EN=0;
	LCD_Delay100us();
}

/**
  * 函    数:LCD1602设置光标位置
  * 参    数:Line 行位置,范围:1~2
  * 参    数:Column 列位置,范围:1~16
  * 返 回 值:无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * 函    数:LCD1602初始化函数
  * 参    数:无
  * 返 回 值:无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);	//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0C);	//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);	//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);	//光标复位,清屏
	LCD_Delay2ms();	//清屏指令执行需要较长时间,需要较长的延时
}

/**
  * 函    数:在LCD1602指定位置上显示一个字符
  * 参    数:Line 行位置,范围:1~2
  * 参    数:Column 列位置,范围:1~16
  * 参    数:Char 要显示的字符
  * 返 回 值:无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * 函    数:在LCD1602指定位置开始显示所给字符串
  * 参    数:Line 起始行位置,范围:1~2
  * 参    数:Column 起始列位置,范围:1~16
  * 参    数:String 要显示的字符串
  * 返 回 值:无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * 函    数:指数函数/幂函数
  * 参    数:X 底
  * 参    数:Y 幂
  * 返 回 值:X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * 函    数:在LCD1602指定位置开始显示所给数字
  * 参    数:Line 起始行位置,范围:1~2
  * 参    数:Column 起始列位置,范围:1~16
  * 参    数:Number 要显示的数字,范围:0~65535
  * 参    数:Length 要显示数字的长度,范围:1~5
  * 返 回 值:无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * 函    数:在LCD1602指定位置开始以有符号十进制显示所给数字
  * 参    数:Line 起始行位置,范围:1~2
  * 参    数:Column 起始列位置,范围:1~16
  * 参    数:Number 要显示的数字,范围:-32768~32767
  * 参    数:Length 要显示数字的长度,范围:1~5
  * 返 回 值:无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * 函    数:在LCD1602指定位置开始以十六进制显示所给数字
  * 参    数:Line 起始行位置,范围:1~2
  * 参    数:Column 起始列位置,范围:1~16
  * 参    数:Number 要显示的数字,范围:0~0xFFFF
  * 参    数:Length 要显示数字的长度,范围:1~4
  * 返 回 值:无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * 函    数:在LCD1602指定位置开始以二进制显示所给数字
  * 参    数:Line 起始行位置,范围:1~2
  * 参    数:Column 起始列位置,范围:1~16
  * 参    数:Number 要显示的数字,范围:0~1111 1111 1111 1111
  * 参    数:Length 要显示数字的长度,范围:1~16
  * 返 回 值:无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

/**
  * 函    数:在CGRAM中写入8个自定义字符的64个字节,每个5*8点阵横向取模,使用每个字节的低5位
  * 参    数:无
  * 返 回 值:无
  */
void LCD_MakeChar(void)
{
	unsigned char i;
	LCD_WriteCommand(0x40);	//CGRAM起始地址为0x40
	for(i=0;i<64;i++)	//01XX XXXX,最多可以在CGRAM中写入64个数据,一个自定义字符对应8个数据
	{
		LCD_WriteData(CGRAMData[i]);
	}
}

/**
  * 函    数:LCD1602的光标复位,清屏
  * 参    数:无
  * 返 回 值:无
  */
void LCD_Clear(void)
{
	LCD_WriteCommand(0x01);
	LCD_Delay2ms();
}

2、矩阵按键

h文件

/*方法来源:B站江协科技的编程技巧(第二期)*/
#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__

//各按键对应数组的索引
#define S1	0
#define S2	1
#define S3	2
#define S4	3
#define S5	4
#define S6	5
#define S7	6
#define S8	7
#define S9	8
#define S10	9
#define S11	10
#define S12	11
#define S13	12
#define S14	13
#define S15	14
#define S16	15

//标志位掩码
#define HOLD	0x01	//按住
#define DOWN	0x02	//按下
#define UP		0x04	//松开
#define SINGLE	0x08	//单击
#define DOUBLE	0x10	//双击
#define LONG	0x20	//长按
#define REPEAT	0x40	//重复

void Key_Clear(void);
unsigned char Key(unsigned char n,unsigned char Flag);
void Key_Tick(void);

#endif

c文件

/*方法来源:B站江协科技的编程技巧(第二期)*/
#include <REGX52.H>
#include "MatrixKey.h"

#define KEY_PRESSED		1	//按键已按下
#define KEY_UNPRESSED	0	//按键未按下

//数值单位:定时器中断函数中相邻两次调用函数Key_Tick的时间间隔
#define KEY_TIME_DOUBLE		20	//双击判定的等待时长
#define KEY_TIME_LONG		100	//长按判定的等待时长
#define KEY_TIME_REPEAT		6	//长按后,重复标志位再次置1的时长
//本案例中,以上数值的单位是:10ms
//按住按键,从将Down标志位置1开始计时,经过1000ms将长按的标志位置1
//长按的标志位置1后,每隔60ms将重复标志位置1

//引脚配置
#define Port P1
sbit Row1=P1^7;
sbit Row2=P1^6;
sbit Row3=P1^5;
sbit Row4=P1^4;
sbit Column1=P1^3;
sbit Column2=P1^2;
sbit Column3=P1^1;
sbit Column4=P1^0;

/** 按键标志数组
  * 
  * 一个字节对应8位,分别为:B7 B6 B5 B4 B3 B2 B1 B0
  * 【B0】1:一直按住,0:未按下
  * 【B1】1:按下瞬间,0:不是按下瞬间
  * 【B2】1:松开瞬间,0:不是松开瞬间
  * 【B3】1:单击,0:不是单击
  * 【B4】1:双击,0:不是双击
  * 【B5】1:长按,0:不是长按
  * 【B6】1:重复,0:未重复
  * 【B7】保留位
  * 其中单击、双击、长按互斥(即这三个对应的标志位最多只能有一个是1)
  * 除B0外,其他标志位在读取后置0
  * xdata:变量保存在片外RAM
  */
unsigned char xdata Key_Flag[16];

/**
  * 函    数:获取按键状态(检测按键是否按下)
  * 参    数:n 按键索引,范围:0~15
  * 返 回 值:按下返回1,未按下返回0
  */
unsigned char Key_GetState(unsigned char n)
{
	if(n==S1)
	{
		Row1=0;
		if(Column1==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S2)
	{
		Row1=0;
		if(Column2==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S3)
	{
		Row1=0;
		if(Column3==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S4)
	{
		Row1=0;
		if(Column4==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S5)
	{
		Row2=0;
		if(Column1==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S6)
	{
		Row2=0;
		if(Column2==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S7)
	{
		Row2=0;
		if(Column3==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S8)
	{
		Row2=0;
		if(Column4==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S9)
	{
		Row3=0;
		if(Column1==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S10)
	{
		Row3=0;
		if(Column2==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S11)
	{
		Row3=0;
		if(Column3==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S12)
	{
		Row3=0;
		if(Column4==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S13)
	{
		Row4=0;
		if(Column1==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S14)
	{
		Row4=0;
		if(Column2==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S15)
	{
		Row4=0;
		if(Column3==0)
		{
			return KEY_PRESSED;
		}
	}
	else if(n==S16)
	{
		Row4=0;
		if(Column4==0)
		{
			return KEY_PRESSED;
		}
	}
	Port=0xFF;
	return KEY_UNPRESSED;
}

/**
  * 函    数:获取按键标志位的值
  * 参    数:n 按键索引,范围:0~15
  * 参    数:Flag 标志位掩码,用来获取标志的某一位的值为1还是0
  * 返 回 值:1或者0
  */
unsigned char Key(unsigned char n, unsigned char Flag)
{
	if(Key_Flag[n] & Flag)
	{
		if(Flag != HOLD)
		{
			Key_Flag[n] &= ~Flag;
		}
		return 1;
	}
	return 0;
}

/**
  * 函    数:按键检测驱动函数,在定时器中断函数中使用
  * 参    数:无
  * 返 回 值:无
  * 说    明:最后的else的目的是防止S[i]的初值在0~4之外导致检测不了单击、双击、长按、重复
  */
void Key_Tick(void)
{
	static unsigned char xdata Count,i;
	static unsigned char xdata NowState[16];
	static unsigned char xdata LastState[16];
	static unsigned char xdata S[16];
	static unsigned char xdata KeyTime[16];
	
	for(i=0;i<16;i++)
	{
		if(KeyTime[i]>0)
		{
			KeyTime[i]--;
		}
	}
	
	Count++;
	if(Count>=2)	//本案例中每隔20ms检测一次按键
	{
		Count=0;
		
		for(i=0;i<16;i++)
		{
			LastState[i]=NowState[i];
			NowState[i]=Key_GetState(i);
			
			if(NowState[i] == KEY_PRESSED)
			{
				Key_Flag[i] |= HOLD;
			}
			else
			{
				Key_Flag[i] &= ~HOLD;
			}
			
			if(NowState[i] == KEY_PRESSED && LastState[i] == KEY_UNPRESSED)
			{
				Key_Flag[i] |= DOWN;
			}
			
			if(NowState[i] == KEY_UNPRESSED && LastState[i] == KEY_PRESSED)
			{
				Key_Flag[i] |= UP;
			}
			
			if(S[i] == 0)
			{
				if(NowState[i] == KEY_PRESSED)
				{
					KeyTime[i]=KEY_TIME_LONG;
					S[i]=1;
				}
			}
			else if(S[i] == 1)
			{
				if(NowState[i] == KEY_UNPRESSED)
				{
					KeyTime[i]=KEY_TIME_DOUBLE;
					S[i]=2;
				}
				else if(KeyTime[i] == 0)
				{
					KeyTime[i]=KEY_TIME_REPEAT;
					Key_Flag[i] |= LONG;
					S[i]=4;
				}
			}
			else if(S[i]==2)
			{
				if(NowState[i] == KEY_PRESSED)
				{
					Key_Flag[i] |= DOUBLE;
					S[i]=3;
				}
				else if(KeyTime[i] == 0)
				{
					Key_Flag[i] |= SINGLE;
					S[i]=0;
				}
			}
			else if(S[i] == 3)
			{
				if(NowState[i] == KEY_UNPRESSED)
				{
					S[i]=0;
				}
			}
			else if(S[i]==4)
			{
				if(NowState[i] == KEY_UNPRESSED)
				{
					S[i]=0;
				}
				else if(KeyTime[i] == 0)
				{
					KeyTime[i]=KEY_TIME_REPEAT;
					Key_Flag[i] |= REPEAT;
					S[i]=4;
				}
			}
			else
			{
				S[i]=0;
			}
		}
	}
}

/**
  * 函    数:清空所有按键的所有标志位
  * 参    数:无
  * 返 回 值:无
  * 说    明:防止切换模式的时候受上一模式所按按键的影响
  */
void Key_Clear(void)
{
	unsigned char i;
	for(i=0;i<16;i++)
	{
		Key_Flag[i]=0;
	}
}

3、定时器0

h文件

#ifndef __TIMER0_H__
#define __TIMER0_H__

void Timer0_Init(void);

#endif

c文件

#include <REGX52.H>

/**
  * 函    数:定时器0初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer0_Init(void)
{	
	TMOD&=0xF0;	//设置定时器模式为16位不自动重装模式
	TMOD|=0x01;	//设置定时器模式为16位不自动重装模式
	TL0=0x66;	//设置定时初值,定时1ms,12T@11.0592MHz
	TH0=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHz
	TF0=0;		//清除TF0标志
	TR0=1;		//定时器0开始计时
	ET0=1;		//打开定时器0中断允许
	EA=1;		//打开总中断
	PT0=0;		//设置定时器0的优先级
}

/*定时器中断函数模板
void Timer0_Routine() interrupt 1	//定时器0中断函数
{
	static unsigned int T0Count;	//定义静态变量
	TL0=0x66;	//设置定时初值,定时1ms,12T@11.0592MHz
	TH0=0xFC;	//设置定时初值,定时1ms,12T@11.0592MHz
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
		
	}
}
*/

四、主函数

main.c

/*by甘腾胜@20250608
【效果查看/操作演示】B站搜索“甘腾胜”或“gantengsheng”查看
【单片机】STC89C52RC
【频率】12T@11.0592MHz
【外设】LCD1602、矩阵按键
【接线】
LCD1602模块:RS接P26,RW接P25,EN接P27,并口接P0(开发板上已接好)
矩阵按键:P17接第一行,P16接第二行,P15接第三行,P14接第四行,P13接第一列,P12接第二列,P11接第三列,P10接第四列(开发板上已接好)
【简单的原理分析】https://blog.csdn.net/gantengsheng/article/details/143581157
【注意】点阵屏旁边的跳线帽需要接右边两个排针
【操作说明】无
*/

#include <REGX52.H>		//51单片机头文件
#include "LCD1602.h"	//液晶显示屏
#include "MatrixKey.h"	//矩阵按键
#include "Timer0.h"		//定时器0

unsigned int Num1,Num2;

/**
  * 函    数:主函数(有且仅有一个)
  * 参    数:无
  * 返 回 值:无
  * 说    明:主函数是程序执行的起点,负责执行整个程序的主要逻辑‌
  */
void main()
{
	LCD_Init();	//LCD1602初始化
	Timer0_Init();	//定时器0初始化
	LCD_ShowNum(1,1,Num1,5);	//在LCD1602的1行1列开始用五位数显示变量Num1
	LCD_ShowNum(2,1,Num2,5);	//在LCD1602的2行1列开始用五位数显示变量Num2
	Key_Clear();	//进主循环之前先清空所有按键的标志位,防止读出错误的标志位
	while(1)
	{
		//S1
		if(Key(S1,DOWN))	//如果S1按下
		{
			Num1--;	//变量Num1减1
			LCD_ShowNum(1,1,Num1,5);
		}
		if(Key(S1,REPEAT))	//如果S1重复
		{
			Num1--;	//变量Num1减1
			LCD_ShowNum(1,1,Num1,5);
		}

		//S2
		if(Key(S2,UP))	//如果S2松开
		{
			Num1++;	//变量Num1加1
			LCD_ShowNum(1,1,Num1,5);
		}
		if(Key(S2,REPEAT))	//如果S2重复
		{
			Num1++;	//变量Num1加1
			LCD_ShowNum(1,1,Num1,5);
		}

		//S3
		if(Key(S3,SINGLE))	//如果S3单击
		{
			Num1-=10;	//变量Num1减10
			LCD_ShowNum(1,1,Num1,5);
		}
		if(Key(S3,DOUBLE))	//如果S3双击
		{
			Num1-=100;	//变量Num1减100
			LCD_ShowNum(1,1,Num1,5);
		}
		if(Key(S3,LONG))	//如果S3长按
		{
			Num1=0;	//变量Num1清零
			LCD_ShowNum(1,1,Num1,5);
		}

		//S4
		if(Key(S4,SINGLE))	//如果S4单击
		{
			Num1+=10;	//变量Num1加10
			LCD_ShowNum(1,1,Num1,5);
		}
		if(Key(S4,DOUBLE))	//如果S4双击
		{
			Num1+=100;	//变量Num1加100
			LCD_ShowNum(1,1,Num1,5);
		}
		if(Key(S4,LONG))	//如果S4长按
		{
			Num1=0;	//变量Num1清零
			LCD_ShowNum(1,1,Num1,5);
		}

		//组合键
		if(Key(S16,HOLD) && Key(S5,DOWN))	//按住S16的同时,按下S5
		{
			Num2-=1000;	//变量Num2减1000
			LCD_ShowNum(2,1,Num2,5);
		}
		if(Key(S16,HOLD) && Key(S6,UP))	//按住S16的同时,松开S6
		{
			Num2+=1000;	//变量Num2加1000
			LCD_ShowNum(2,1,Num2,5);
		}
	}
}

/**
  * 函    数:定时器0中断函数
  * 参    数:无
  * 返 回 值:无
 */
void Timer0_Routine() interrupt 1
{
	TL0=0x00;	//设置定时初值,定时10ms,12T@11.0592Hz
	TH0=0xDC;	//设置定时初值,定时10ms,12T@11.0592Hz
	Key_Tick();	//每隔10ms调用一次按键驱动函数Key_Tick
}

总结

我觉得江协科技的这个检测按键的方法非常巧妙,非常好用。只要硬件没问题,检测就不会出错。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值