文章目录
- 1.矩阵按键是什么
- 2.矩阵按键的控制原理
- 3.矩阵按键程序的编写
- 将数值转化为键码
- 完整代码:
- demo.c:
- key.c:
- key.h:
- 密码锁(简易版)
- 需求分析:
- 总结
- 课后练习:
1.矩阵按键是什么
这个矩阵按键也是我们这个开发版上最后一个GPIO的一个应用,如果对1O回的输入跟输出还有什么问题的话,
一定要回过头去看一下我们之前的程序理清楚思路。
之前的按键电路图:
1个按键占用一个IO口的。
在按键数量较多时,为了减少I/O口的占用,将按键排列成矩阵排列的形式的按键阵列我们称位矩阵按键。
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输出高电平,
增加延时函数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
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<<i) ) //如果持续按下,变量加1
{
if(Count[i] < 60000) // Count是u16类型,最大值小于65535,故增加限定条件。
Count[i] ++; //如果持续按下,这个变量加1
}
else //如果按键松开,变量清0
{
if(Count[i] > 0 ) //如果这个按键是按下过的,
{
LastState |= (1<<i); //这个变量相应的标志位置1,单独写对应位
}
else
{
LastState &= ~(1<<i); //仅操作这个变量相应的标志位清0
}
Count[i] = 0; //如果松开,计数变量清0
}
}
}
//========================================================================
// 函数名称:KEY_ReadState
// 函数功能:读取指定的按钮的状态
// 入口参数: @keynum; 按钮的端口号,例如P32的端口号是2,
// 函数返回:见KEY的返回值的各种状态,看其他备注
// 当前版本: VER1.0
// 修改日期: 2023-1-1
// 当前作者:
// 其他备注: 函数返回值如下:
状态 功能
//#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
//========================================================================
u8 KEY_ReadState(u8 keynum) //读取指定按键的状态,10ms执行1次
{
if(Count[keynum] > 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<<keynum)) //按键之前按下过,要判断上1s是不是高电平,如果上1s是低电平,说明是按键按下
//例如,要判断P32,P30,31,32,左移2位,
//按键已经松开了
{
return KEY_RELAX;
}
else //按键之前没有按下过,返回未按下
{
return KEY_NOPRESS;
}
}
}
//========================================================================
// 函数名称:MateixKEY_delay
// 函数功能:矩阵按键读取专用延时函数
// 入口参数:无
// 函数返回:无
// 当前版本: VER1.0
// 修改日期: 2023
// 当前作者:
// 其他备注:
//========================================================================
void MateixKEY_delay(void)
{
u8 i;
i = 60; //根据之前的ms延时的函数,可以算出此处延时的时间
while(--i);
}
//========================================================================
// 函数名称:MateixKEY_Read
// 函数功能:矩阵按键读取当前是哪一个按钮按下
// 入口参数:无
// 函数返回:0表示没有按键按下,1-8表示当前是第几个按键按下
// 当前版本: VER1.0
// 修改日期: 2023-1-1
// 当前作者:
// 其他备注:
//========================================================================
u8 MateixKEY_Read(void) //矩阵按键读取当前是哪一个按钮按下,返回值是按键序号
{
u8 keystate; //表示当前的按钮状态值
u8 key_val = 0; //表示按键的键码
// 第一步:现将P0.0-P0.3输出低电平,P0.6-P0.7输出高电平,如果有按键按下,按下的那一列的IO就会变成低电平,就可以判断出哪一列按下了。
MateixKEY = 0XC0; //1100 0000 P0.6-P0.7输出高电平,
MateixKEY_delay(); //留反应时间
keystate = (MateixKEY^0XC0); //保存按键状态,假设P0.7按下, 则为0100 0000,如果想实现变成哪路按下变成1,用异或
// 第二步:现将P0.0-P0.3输出高电平,P0.6-P0.7输出低电平,如果有按键按下,按下的那一行的IO就会变成低电平,就可以判断出哪一行按下了。
MateixKEY = 0X0f; //0000 1111 高位输出低电平,低位输出高电平
MateixKEY_delay(); //留反应时间
keystate |= (MateixKEY^0X0f); //保存按键状态,假设P0.0按下, 则为0000 1110^0000 1111 = 0000 0001,这里要采用|=,为了避免把之前的数值覆盖。
// 第三步:行列组合一下就可以判断出是哪个按键按下了。
// printf("%02x\r\n",keystate); //强制变为2位,以16进制显示。
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;
}
return key_val;
}
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
密码锁(简易版)
由于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去改写一下密码判断的地方。