学习笔记|矩阵按键控制原理|数值转化为键码|密码锁|STC32G单片机视频开发教程(冲哥)|第十四集:矩阵按键原理及实践

文章目录

  • 1.矩阵按键是什么
  • 2.矩阵按键的控制原理
  • 3.矩阵按键程序的编写
    • 将数值转化为键码
    • 完整代码:
      • demo.c:
      • key.c:
      • key.h:
  • 密码锁(简易版)
    • 需求分析:
  • 总结
  • 课后练习:

1.矩阵按键是什么

这个矩阵按键也是我们这个开发版上最后一个GPIO的一个应用,如果对IO回的输入跟输出还有什么问题的话,一定要回过头去看一下我们之前的程序理清楚思路。
之前的按键电路图:
学习笔记|矩阵按键控制原理|数值转化为键码|密码锁|STC32G单片机视频开发教程(冲哥)|第十四集:矩阵按键原理及实践_第1张图片
1个按键占用一个IO口的。
在按键数量较多时,为了减少I/O口的占用,将按键排列成矩阵排列的形式的按键阵列我们称位矩阵按键。

2.矩阵按键的控制原理

电路图:
学习笔记|矩阵按键控制原理|数值转化为键码|密码锁|STC32G单片机视频开发教程(冲哥)|第十四集:矩阵按键原理及实践_第2张图片

按键识别原理:端口默认为高电平,实时读取到引脚为低电平是表示按下。再次读取到高电平,表示松开。

第一步:现将P0.0-P0.3输出低电平,P0.6-P0.7输出高电平,如果有按键按下,按下的那一列的IO就会变成低电平,就可以判断出哪一列按下了。

第二步:现将P0.0-P0.3输出高电平,P0.6-P0.7输出低电平,如果有按键按下,按下的那一行的IO就会变成低电平,就可以判断出哪一行按下了。

第三步:行列组合一下就可以判断出是哪个按键按下了。

按键按下后导线导通,哪条线上有高电平,就会被拉低为低电平,从而检测出是哪条线路,二次检查,交叉节点就是有按键按下的按键位置。

3.矩阵按键程序的编写

先完成矩阵按键的功能编写。
复制9.TIM多任务为10.矩阵按键,用到P0端口,还是在之前的KEY模块基础上进行修改:
在key.h中定义:#define MateixKEY P0 //矩阵按键的引脚
定义函数MateixKEY_Read:
u8 MateixKEY_Read(void); //矩阵按键读取当前是哪一个按钮按下,返回值是按键序号
在key.c中实现函数MateixKEY_Read:
先增加函数头,并把实现思路复制过来作为编写依据,围绕这三步,编写矩阵按键的读取程序:
MateixKEY = 0XC0; //1100 0000 P0.6-P0.7输出高电平,
学习笔记|矩阵按键控制原理|数值转化为键码|密码锁|STC32G单片机视频开发教程(冲哥)|第十四集:矩阵按键原理及实践_第3张图片

增加延时函数MateixKEY_delay留反应时间:
MateixKEY_delay(); //留反应时间
MateixKEY_delay的实现,并添加函数头:

void MateixKEY_delay(void)
{
	u8 i;
	i = 60; //根据之前的毫秒延时函数,可以算出此处延时的时间
	while(--i);
}

第一步:假设P0.7按下, 则为0100 0000,如果想实现哪路按下哪位变成1,可以采用异或运算。
即:0100 0000 ^1100 0000 = 1000 0000
则有:keystate = MateixKEY^0XC0;

第二步:第二次扫描,高位输出低电平,低位输出高电平:MateixKEY = 0X0f; //0000 1111
保存按键状态,假设P0.0按下, 则为0000 1110^0000 1111 = 0000 0001,这里要采用|=,0000 0001 | 1000 0000 = 1000 0001 = 0x81
学习笔记|矩阵按键控制原理|数值转化为键码|密码锁|STC32G单片机视频开发教程(冲哥)|第十四集:矩阵按键原理及实践_第4张图片
keystate |= (MateixKEY^0X0f);是为了避免把之前的数值覆盖。
第三步:keystate中已经保存了行、列的状态,行列组合一下就可以判断出是哪个按键按下了。
printf(“%02x\r\n”,keystate); //强制变为2位,以16进制显示。
return keystate;
在demo.c中调用:
将10ms扫描按键的代码部分注释掉,只检测MateixKEY_Read,加入该函数,编译,运行。按动按键,串口打印对应的16进制数值。

将数值转化为键码

u8 key_val = 0; //表示按键的键码
这里采用switch关键词,直接有模板插入,编写switch函数:

	switch (keystate) //单选开关函数
    {
    	case 0x41:	key_val = 1;
    		break;
    	case 0x42:	key_val = 2;
    		break;
    	case 0x44:	key_val = 3;
    		break;
    	case 0x48:	key_val = 4;
    		break;
    	case 0x81:	key_val = 5;
    		break;
    	case 0x82:	key_val = 6;
    		break;
    	case 0x84:	key_val = 7;
    		break;
    	case 0x88:	key_val = 8;
    		break;
    	default:  	key_val = 0;
    		break;
    }

关闭数码管初始化显示,main函数中新建变量:u8 KEY_NUM = 0; //保存矩阵按键的键码
读取矩阵按键的键码保存在KEY_NUM中,并在数码管最后1位显示:

			KEY_NUM = MateixKEY_Read();
			SEG7 = KEY_NUM; 	//在数码管最后一位显示

编译下载,按动按键就可以判断是哪个按键按下了。

完整代码:

demo.c:

#include "COMM/stc.h"		//调用头文件
#include "COMM/usb.h"
#include "seg_led.h"
#include "key.h"			//调用头文件
#include "beep.h"
#include "tim0.h"



#define MAIN_Fosc 24000000UL	//定义主时钟

char *USER_DEVICEDESC = NULL;
char *USER_PRODUCTDESC = NULL;
char *USER_STCISPCMD = "@STCISP#";

bit TIM_10MS_Flag;		//10ms标志位

void sys_init();	//函数声明
void delay_ms(u16 ms);

void Timer0_Isr(void);


void main()					//程序开始运行的入口
{
	u8 KEY_NUM = 0; 		//保存矩阵按键的键码
	sys_init();				//USB功能+IO口初始化
	usb_init();				//usb库初始化
	Timer0_Init();

	EA = 1;					//CPU开放中断,打开总中断。


	//数码管初始化,显示0-7
//	SEG0 = 0;
//	SEG1 = 1;
//	SEG2 = 2;
//	SEG3 = 3;
//	SEG4 = 4;
//	SEG5 = 5;
//	SEG6 = 6;
//	SEG7 = 7;

	LED = 0x0f;	//赋初值,亮一半灭一半,可以写8位的变量.从7开始数到0

	while(1)		//死循环
	{
//		if( DeviceState != DEVSTATE_CONFIGURED ) 	//
//			continue;
		if( bUsbOutReady )
		{
			usb_OUT_done();
		}
		if(TIM_10MS_Flag == 1)   //将需要延时的代码部分放入
		{
			TIM_10MS_Flag = 0;		//TIM_10MS_Flag 变量清空置位
//			KEY_Deal();				//P3上所有端口都需要执行一遍
			BEEP_RUN();				//蜂鸣运行

//			if(KEY_ReadState(KEY1)== KEY_RESS)	//判断KEY1按钮是否为单击
//			{
//				BEEP_ON(2);							//蜂鸣20ms
//				LED0 = 0;
//			}
//			else if(KEY_ReadState(KEY1)== KEY_LONGPRESS) //判断KEY1按钮是否为长按
//			{
//				BEEP_ON(2);							//蜂鸣20ms
//				LED1 = 0;
//			}
//			else if(KEY_ReadState(KEY1)== KEY_RELAX)	//判断KEY1按钮是否为松开
//			{
//				LED = 0XFF;
//			}
			KEY_NUM = MateixKEY_Read();
			SEG7 = KEY_NUM; 	//在数码管最后一位显示
		}

	}
}

void sys_init()		//函数定义
{
    WTST = 0;  //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快
    EAXFR = 1; //扩展寄存器(XFR)访问使能
    CKCON = 0; //提高访问XRAM速度

	P0M1 = 0x00;   P0M0 = 0x00;   //设置为准双向口
    P1M1 = 0x00;   P1M0 = 0x00;   //设置为准双向口
    P2M1 = 0x00;   P2M0 = 0x00;   //设置为准双向口
    P3M1 = 0x00;   P3M0 = 0x00;   //设置为准双向口
    P4M1 = 0x00;   P4M0 = 0x00;   //设置为准双向口
    P5M1 = 0x00;   P5M0 = 0x00;   //设置为准双向口
    P6M1 = 0x00;   P6M0 = 0x00;   //设置为准双向口
    P7M1 = 0x00;   P7M0 = 0x00;   //设置为准双向口

    P3M0 = 0x00;
    P3M1 = 0x00;

    P3M0 &= ~0x03;
    P3M1 |= 0x03;

    //设置USB使用的时钟源
    IRC48MCR = 0x80;    //使能内部48M高速IRC
    while (!(IRC48MCR & 0x01));  //等待时钟稳定

    USBCLK = 0x00;	//使用CDC功能需要使用这两行,HID功能禁用这两行。
    USBCON = 0x90;
}


void delay_ms(u16 ms)	//unsigned int
{
	u16 i;
	do
	{
		i = MAIN_Fosc/6000;
		while(--i);
	}while(--ms);
}

void Timer0_Isr(void) interrupt 1 //1ms进来执行一次,无需其他延时,重复赋值
{
	static timecount = 0;

	SEG_LED_Show();		//数码管刷新

	timecount++;		//1ms+1
	if(timecount>=10)	//如果这个变量大于等于10,说明10ms到达
	{
		timecount = 0;
		TIM_10MS_Flag = 1;	//10ms到了
	}
}

key.c:

#include "key.h"			//调用头文件
u16 Count[8] = {0,0,0,0,0,0,0,0};	//按键的时间状态变量初始化8位
u8 LastState = 0;					//8位变量,b0=1 则表示key0上一次按下过


//========================================================================
// 函数名称:KEY_Deal
// 函数功能:按键状态的获取
// 入口参数:无
// 函数返回:无
// 当前版本: VER1.0
// 修改日期: 2023-1-1
// 当前作者:
// 其他备注:循环读取8个端口的状态,并将按下的时间赋值给Count数组,然后按下的状态赋值给变量LastState
//========================================================================
void KEY_Deal(void) 			//检查所有的按键状态,10ms执行一次
{
	u8 i = 0;
	for(i=0;i<8;i++)			//for循环变量 循环8次,i取值为0-7,代表P30-P37的状态查询
	{
		if(~KEY & (1< 0 )	//如果这个按键是按下过的,
			{
				LastState |= (1< 0)			//判断按键是按下的
	{
		if(Count[keynum] < 3)		//按下小于30ms,返回消抖状态
		{
			return KEY_FLCKER;
		}
		else if(Count[keynum] == 3)	//按正好等于30ms,返回单击状态
		{
			return KEY_RESS;
		}
		else if(Count[keynum] < 300 ) //按下小于3000ms,返回单击结束
		{
			return KEY_PRESSOVER;
		}
		else if(Count[keynum] == 300 ) //按下正好等于3000ms,返回长按
		{
			return KEY_LONGPRESS;
		}
		else					//长按结束
		{
			return KEY_LONGOVER;
		}
	}
	else						//按键已经松开了,返回KEY_RELAX状态
	{
		if(LastState &(1<

key.h:

#ifndef __KEY_H
#define __KEY_H

#include "COMM/stc.h"			//调用头文件
#include "COMM/usb.h"

//------------------------引脚定义------------------------//
#define KEY P3 			//定义一个按键 引脚选择P32-P36

#define KEY1 2			//按键1
#define KEY2 3			//按键2
#define KEY3 4			//按键3
#define KEY4 5			//按键4

#define MateixKEY P0    //矩阵按键的引脚


//------------------------变量声明------------------------//
//状态	功能
#define KEY_NOPRESS 0 		//按键未按下	0
#define KEY_FLCKER 1 		//消抖	1
#define KEY_RESS 2			//单击	2
#define KEY_PRESSOVER 3 	//单击结束	3
#define KEY_LONGPRESS 4 	//长按3s	4
#define KEY_LONGOVER 5 		//长按结束	5
#define KEY_RELAX 6 		//按键松开	6


//------------------------函数声明-----------------------//
void KEY_Deal(void);			//检查所有的按键状态
u8  KEY_ReadState(u8 keynum);	//读取指定按键的状态

u8 MateixKEY_Read(void);		//矩阵按键读取当前是哪一个按钮按下,返回值是按键序号

#endif

密码锁(简易版)

学习笔记|矩阵按键控制原理|数值转化为键码|密码锁|STC32G单片机视频开发教程(冲哥)|第十四集:矩阵按键原理及实践_第5张图片

由于KEY_NUM = MateixKEY_Read();执行后,会持续输出1,需要修改,3s内只输出1次即可,让它符合今天的主题。
先定义一个静态变量,这是一个很常见的用法,static u8 keystate_Last; //表示当前的按钮上一次的状态值
增加判断条件:

	if(keystate_Last != keystate)	//如果本次获取到的按键状态值和之前的不一样
	{
		keystate_Last = keystate;   //把本次的按键状态值写入进去

		switch (keystate) //单选开关函数
		{
			case 0x41:	key_val = 1;
				break;
			case 0x42:	key_val = 2;
				break;
			case 0x44:	key_val = 3;
				break;
			case 0x48:	key_val = 4;
				break;
			case 0x81:	key_val = 5;
				break;
			case 0x82:	key_val = 6;
				break;
			case 0x84:	key_val = 7;
				break;
			case 0x88:	key_val = 8;
				break;
			default:  	key_val = 0;
				break;
		}
		printf("%d\r\n",(int)key_val);  //强制转化为整形变量
	}

需求分析:

1.通过LED0模拟门锁状态,LED点亮表示门锁打开,熄灭表示门锁锁上;
增加横线显示值,SEG_Tab[22]=
默认显示横线:u8 Show_Tab[8] = {21,21,21,21,21,21,21,21};
新增变量:u8 KEY_Str = 0; //表示当前输入了几个密码
2.增加8位数码管,可以动态显示8位的密码,无密码时显示 “- - - - - - - -”;
3.通过矩阵按键可以输入1-8的数字表示密码,并依次显示在数码管上;
4.每输入一个数字,蜂鸣器响20ms表示有数字按下;
5.密码正确打开LED0,密码错误蜂鸣响2秒;
根据条件,修改demo.c中的main函数代码如下:

void main()					//程序开始运行的入口
{
	u8 KEY_NUM = 0; 		//保存矩阵按键的键码
	u8 KEY_Str = 0; 		//表示当前输入了几个密码位
	sys_init();				//USB功能+IO口初始化
	usb_init();				//usb库初始化
	Timer0_Init();

	EA = 1;					//CPU开放中断,打开总中断。


	//数码管初始化,显示0-7
//	SEG0 = 0;
//	SEG1 = 1;
//	SEG2 = 2;
//	SEG3 = 3;
//	SEG4 = 4;
//	SEG5 = 5;
//	SEG6 = 6;
//	SEG7 = 7;

	//LED = 0x0f;	//赋初值,亮一半灭一半,可以写8位的变量.从7开始数到0
	LED = 0xff;	//赋初值,密码锁应用初始状态熄灭所有LED
	while(1)		//死循环
	{
//		if( DeviceState != DEVSTATE_CONFIGURED ) 	//
//			continue;
		if( bUsbOutReady )
		{
			usb_OUT_done();
		}
		if(TIM_10MS_Flag == 1)   //将需要延时的代码部分放入
		{
			TIM_10MS_Flag = 0;		//TIM_10MS_Flag 变量清空置位
//			KEY_Deal();				//P3上所有端口都需要执行一遍
			BEEP_RUN();				//蜂鸣运行

//			if(KEY_ReadState(KEY1)== KEY_RESS)	//判断KEY1按钮是否为单击
//			{
//				BEEP_ON(2);							//蜂鸣20ms
//				LED0 = 0;
//			}
//			else if(KEY_ReadState(KEY1)== KEY_LONGPRESS) //判断KEY1按钮是否为长按
//			{
//				BEEP_ON(2);							//蜂鸣20ms
//				LED1 = 0;
//			}
//			else if(KEY_ReadState(KEY1)== KEY_RELAX)	//判断KEY1按钮是否为松开
//			{
//				LED = 0XFF;
//			}
			KEY_NUM = MateixKEY_Read();		//当前矩阵按键的键值
			//SEG7 = KEY_NUM; 	//在数码管最后一位显示
			if( KEY_NUM > 0)				//如果有按键按下
			{
				KEY_NUM = 0;						//键值先清空,清空按键
				BEEP_ON(2);							//蜂鸣20ms
				Show_Tab[KEY_Str] = KEY_NUM; 		//表示当前输入了几个密码 = KEY_NUM;		//将当前的按键状态保存到数组
				KEY_Str++;							//输入的密码位数+1

				if(KEY_Str ==8)		//如果密码已经等于8位,
				{
					if((Show_Tab[0]==1)&&(Show_Tab[1]==1)&&(Show_Tab[2]==1)&&(Show_Tab[3]==1)&&(Show_Tab[4]==1)&&(Show_Tab[5]==1)&&(Show_Tab[6]==1)&&(Show_Tab[7]==1))
					{
						LED0 = 0;			//如果密码正确,LED0点亮
					}
					else
					{
						BEEP_ON(200);	//密码错误,蜂鸣2s。单位是10ms,2000ms=2s
					}
				}
			}
		}

	}
}

编译下载,发现按键后均显示0:KEY_NUM = 0;位置有误,应该放在调用以后再置0.
重新测试,8位输入后不能自动清空,应该添加代码,到达长度后回到初始值显示横杠:SEG0 = SEG1 = SEG2 = SEG3 = SEG4 = SEG5 = SEG6 = SEG7 = 21;
重新测试,输入错误后,复位,再按键输入,显示有问题:KEY_Str ==8后忘记归0了。
经过修改,功能已正常实现。
完整代码请参考:《STC单片机原理-教学视频配套附件-20230731.zip

总结

1.了解矩阵按键的工作原理和代码编写的过程

课后练习:

给今天的门锁增加如下功能:
1.LED0(门锁)打开后,5秒后自动关闭;
2.增加门内的手动开门按钮,按下按钮门锁打开;
3.10秒内没有输入密码自动数码管熄灭省电;有按键按下时再显示。
4.用for去改写一下密码判断的地方。

你可能感兴趣的:(STC32,学习,stc,mcu,矩阵键盘,密码锁)