1.电路图分析
当跳线帽接 3、2时为独立键盘 S7 S6 S5 S4 当跳线帽接2、1时为矩阵键盘
2.独立按键
当跳线帽接3、2时,一旦S7 S6 S5 S4这四个按键某个按键被按下,相应的I/O口就是接地状态,为低电平。
写单片机时按键按下需要消抖,之前是使用delay延时程序消抖,但是不建议在程序中使用delay,我们可以使用定时器延时来完成消抖。
之前写独立按键原理:
检测按键按下—>延时消抖—>再次检测按键是否还在按下状态—>确认按键按下—>操作—>检测按键是否松开—>延时消抖—>再次检测按键是否松开—>确定按键松开—>操作
但是对于多个按键来说,这样就显得繁琐了,所以在这里介绍一种简单一些的方法。
我们使用定时器定时2ms,然后每次中断扫描一下按键,将扫描值左移至一个变量中,每移动一次就判断当前连续的8次按键状态是不是全1或者全0,如果是全1则判断为弹起,如果是全0则判断为按下,如果是0和1交错,就认为是抖动,不做任何判定。
利用这种方法就可以避免通过延时消抖占用单片机执行时间,而是转化成了一种按键状态判定而非按键过程判定,我们只对当前按键的连续16ms的状态进行判断,而不再关心它在这16ms内都做了什么事情。
sys.h
#ifndef __SYS_H__
#define __SYS_H__
//头文件包含
#include <STC15F2K60S2.H>
#include <intrins.h>
//管脚声明
sbit s4 = P3^3;
sbit s5 = P3^2;
sbit s6 = P3^1;
sbit s7 = P3^0;
//变量类型声明
typedef unsigned int uint;
typedef unsigned int u16;
typedef unsigned char uchar;
typedef unsigned char u8;
//外部变量声明
//函数声明
void ALL_Init();
void Operate_Delay(unsigned int ms);
void Timer0Init(void);
//key.c
void Key_Scan();
void Key_Drive();
void Key_Action(u8 dat);
void Led_Iullme(u8 Led_dat);
#endif
key.c
#include "sys.h"
u8 KeySta[4] = {1,1,1,1};//当前按键状态
u8 KeyBackup[4] = {1,1,1,1};//按键状态备份,保存前一次的按键值
u8 Keycode[4] = {1,2,3,4};
u8 Led_dat = 0xff;
//独立键盘扫描函数 在定时器中调用 定时1ms
void Key_Scan()
{
u8 i;
static u8 KeyBuff[] = {0xff,0xff,0xff,0xff};
KeyBuff[0] = (KeyBuff[0] << 1)|s4;
KeyBuff[1] = (KeyBuff[1] << 1)|s5;
KeyBuff[2] = (KeyBuff[2] << 1)|s6;
KeyBuff[3] = (KeyBuff[3] << 1)|s7;
for(i = 0; i<4; i++)
{
if(KeyBuff[i] == 0xff)
{
KeySta[i] = 1; //连续扫描8次都是1,16ms内都是弹起状态,按键已松开
}else if(KeyBuff[i] == 0x00)
{
KeySta[i] = 0; //连续扫描8次都是0,16ms内都是按下状态,按键已按下
}else
{
//其他状态键值不稳定,不作处理
}
}
}
//按键处理
void Key_Drive()
{
u8 i;
for(i = 0; i < 4; i++)
{
if(KeyBackup[i] != KeySta[i])
{
if(KeyBackup[i] != 0)
{
Key_Action(Keycode[i]);
}
KeyBackup[i] = KeySta[i];
}
}
}
void Key_Action(u8 dat)
{
switch(dat)
{
case 1: Led_dat = 0xfe; break;
case 2: Led_dat = 0xfd; break;
case 3: Led_dat = 0xfb; break;
case 4: Led_dat = 0xf7; break;
default:break;
}
Led_Iullme(Led_dat);
}
led.c
#include "sys.h"
void Led_Iullme(u8 Led_dat)
{
P2 = (P2&0X1F)|0X80;
P0 = Led_dat;
}
main.c
#include "sys.h"
void main()
{
ALL_Init();
Timer0Init();
while(1)
{
Key_Drive();
}
}
3.矩阵按键
将开发板上面的J5跳线帽接到KBD端,也就是J5的1和2相接。
这里注意:在开发板上,根据转接板原理图,WR端接到单片机的P42管脚,RD端接到单片机的P44管脚。
我们知道,按键按下通常会保持100ms以上,如果在按键扫描中断中,每次让矩阵按键的一个KeyOut输出低电平其他三个输出高电平,判断当前所有Keyln的状态,下次中断时再让下一个KeyOut输出低电平,其他三个输出高电平.再次判断所有KeyIn,通过快速的中断不停地循环进行判断,就可以最终确定哪个按键按下了。
这个原理是不是跟数码管动态扫描有点类似?数码管在动态赋值,而按键在动态读取状态。至于扫描间隔时间和消抖时间,因为现在有4个KeyOut输出,要中断4次才能完成一次全部按键的扫描,显然2ms中断时间较长,这里我们使用1ms。
sys.h
#ifndef __SYS_H__
#define __SYS_H__
/***头文件包含***/
#include <STC15F2K60S2.H>
#include <intrins.h>
/***管脚声明***/
sbit KEY_IN_4 = P3^4;
sbit KEY_IN_3 = P3^5;
sbit KEY_IN_2 = P4^2;
sbit KEY_IN_1 = P4^4;
/***变量类型声明***/
typedef unsigned int uint;
typedef unsigned int u16;
typedef unsigned char uchar;
typedef unsigned char u8;
/***外部变量声明***/
/***函数声明***/
//sys.c
void ALL_Init();
void Operate_Delay(unsigned int ms);
void Time0_Init();
//keyboard.c
void Key_Scan(void);
void Key_Drive();
void Key_Action(u8 Keycode);
//led.c
void paomaled(u8 num);
#endif
keyboard.c
/*******************************************************************************
* 文件名:keyboard.c
* 描 述:
* 作 者:guyao
* 版本号:v1.0
* 日 期: 2021年1月5日
* 备 注:矩阵键盘核心程序
*
*******************************************************************************/
#include "sys.h"
u8 KeySta[4][4] = {{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}};//当前按键状态缓存
u8 Key_backup[4][4] = {{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}};//按键状态备份,保存前一次的按键值
u8 Keycode[4][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};//键值
/**
*@brief 按键扫描函数 在定时器中断程序里调用 定时器间隔1ms
*@param[in] none
*@return none
**/
void Key_Scan(void)
{
u8 i;
static u8 keyout = 0;//矩阵按键的输出索引
static u8 keybuff[4][4] = {{0xff,0xff,0xff,0xff,},
{0xff,0xff,0xff,0xff,},
{0xff,0xff,0xff,0xff,},
{0xff,0xff,0xff,0xff,}};//矩阵按键扫描缓冲区
keybuff[keyout][0] = (keybuff[keyout][0] << 1) | KEY_IN_1;
keybuff[keyout][1] = (keybuff[keyout][1] << 1) | KEY_IN_2;
keybuff[keyout][2] = (keybuff[keyout][2] << 1) | KEY_IN_3;
keybuff[keyout][3] = (keybuff[keyout][3] << 1) | KEY_IN_4; //将每一行的4个按键值移入缓冲区
//消抖后更新按键状态
for(i=0;i<4;i++)
{
if((keybuff[keyout][i]&0x0f) == 0x00)
{ //一个按键需要等待4ms才能被扫描到一次 需要扫描4次才能确定是否按键被按下
KeySta[keyout][i] = 0; //连续4次扫描值都是0,即4*4ms内都是按下状态,认为按键已平稳按下
}
else if((keybuff[keyout][i]&0x0f) == 0x0f)
{
KeySta[keyout][i] = 1; //连续4次扫描值都是1,即4*4ms内都是松开状态,认为按键已稳定弹起
}
}
//执行下一次的扫描输出
keyout++;
keyout = keyout&0x03; //索引加到4就归0
//根据索引 轮流拉低P3的P3^0 P3^1 P3^2 P3^3 IO口
P3 = _crol_(0xfe,keyout)&0xff;
}
/**
*@brief 按键处理函数 检测按下的是哪个按键 计算出键值 需要在主循环中调用
*@param[in] none
*@return 返回键值
**/
void Key_Drive()
{
u8 i,j;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
{ /* 键值 */
if(Key_backup[i][j] != KeySta[i][j]) /*1 2 3 4 */
{ /*5 6 7 8 */
if(Key_backup != 0) /*9 10 11 12*/
{ /*13 14 15 16*/
Key_Action(Keycode[i][j]);
}
Key_backup[i][j] = KeySta[i][j];//更新前一次的键值
}
}
}
}
/**
*@brief 矩阵按键动作函数 矩阵键盘动作函数 根据键值执行相应程序
*@param[in] 键值 keycode
*@return none
**/
void Key_Action(u8 Keycode)
{
paomaled(Keycode);
}
led.c
#include "sys.h"
void paomaled(u8 num)
{
P2 = (P2&0x1f)|0x80;
if(num == 0)
{
P0 = 0xff;
}else
{
P0 = _crol_(0xfe,num-1);
}
}
main.c
#include "sys.h"
void main()
{
ALL_Init();
Time0_Init();
while(1)
{
Key_Drive();
}
}