以4X4键盘为例,首先按照下图制作电路。
然后将HOR1-HOR4连接到单片机的输入引脚上去;LON1-LON4连接到单片机的开漏输出引脚上去,注意这4个引脚必须设置为开漏模式!
程序上首先将LON1所连接的IO输出低电平其余3个IO输出高电平,同时检测HOR1-HOR4的电平来获取K1-K4的按键状态;然后将LON2所连接的IO输出低电平其余3个IO输出高电平,同时检测HOR1-HOR4的电平来获取K5-K8的按键状态;依次类推。
但是这个电路是有BUG的,比如同时按下K1、K5和K6,当LON1为低电平的时HOR1检测到是低电平没有问题;因为K2没有被按下所以我们希望HOR2是高电平,但是由于K1、K5、K6同时按下电流从VCC通过R2再通过K6再通过K5再通过K1流到LON1,所以实际上HOR2也是低电平这时候程序就认为K2被按下了导致出错。
解决这个问题很简单只需要在合适的位置加一个二极管,利用其单向导电性阻挡电流跨列流动就行了。
好了,接下来是单片机代码时间:
#ifndef __Key_matrix_H
#define __Key_matrix_H
#include "gpio.h"
#include "gpio_bool.h"
/*务必把这4个输出IO设置为上拉输入*/
#define KEY_HOR1 PAin(7)
#define KEY_HOR2 PAin(6)
#define KEY_HOR3 PAin(5)
#define KEY_HOR4 PAin(4)
/*务必把这4个输出IO设置为开漏*/
#define KEY_LON1 PBout(0)
#define KEY_LON2 PCout(5)
#define KEY_LON3 PCout(4)
#define KEY_LON4 PCout(3)
#define KEY_PRESS_TIME 20//消抖常数
#define KEY_LONG_PRESS_TIME 3000//单个按键长按阈值3s
/*通过读取(只读)这三个变量即可获得按键的单按、长按和组合键信息*/
extern volatile uint16_t Key_Phy_Num;
extern volatile uint8_t Key_Pulse_Num;
extern volatile uint16_t Key_LP_Num;
typedef enum
{
KPL_DISABLE=0,
KPL_ENABLE
}K_L_P;//按键的长按状态
typedef struct
{
K_L_P KEY_LONG_PRESS;
uint16_t KeyOpenCount;
uint8_t KOC_EN;
uint16_t KeyCloseCount;
uint8_t KCC_EN;
}Key_Para;
exter Key_Par Key_1,Key_2,Key_3,Key_4,Key_5,Key_6,Key_7,Key_8,Key_9,Key_10,Key_11,Key_12,Key_13,Key_14,Key_15,Key_16;
void Clear_Key_Pulse_Num(void);//当读取完Key_Pulse_Num后调用
void KeyCount_Run(void);//在1ms滴答里调用
void Key_Scan(void);//大循环或者滴答里边都行
#endif
复制代码
#include "Key_matrix.h"
Key_Par Key_1,Key_2,Key_3,Key_4,Key_5,Key_6,Key_7,Key_8,Key_9,Key_10,Key_11,Key_12,Key_13,Key_14,Key_15,Key_16;
volatile uint16_t Key_Phy_Num=0; //Key_Phy_Num每一个bit代表一个按键的状态
volatile uint8_t Key_Pulse_Num=0;//当某一个按键从按下到弹起的过程中(非长按)始终只有该按键被操作,则Key_Pulse_Num被修改为该键的序号
volatile uint16_t Key_LP_Num=0; //Key_LP_Num每一个bit代表一个按键的长按状态
uint8_t KeyCom=0;//组合键是否出现
static void Key_Num_Read(Key_Para* Key,uint16_t KPN,uint8_t Pulse,uint8_t Key_Hor)
{
if(Key_Hor == 0)
{
Key->KOC_EN=0;//按键按下立即清除(松开)计数
if(Key->KeyCloseCount > KEY_PRESS_TIME)
{
/*消抖方法为检测到按键被(持续)按下超过20ms*/
Key_Phy_Num|=KPN;//消抖完毕后记录被按下的按键的键值
if(Key->KeyCloseCount > KEY_LONG_PRESS_TIME)
{
/*检测到按键被(持续)按下超过3秒*/
Key->KEY_LONG_PRESS=KPL_ENABLE;
Key_LP_Num|=KPN;
Key->KCC_EN=0;
}
else
{
/*时间不够启动计数*/
Key->KCC_EN=1;
}
}
else
{
/*时间不够启动计数*/
Key->KCC_EN=1;
}
}
else
{
Key->KCC_EN=0;//按键松开立即清除(按下)计数
if(Key->KeyOpenCount > KEY_PRESS_TIME)
{
if((Key_Phy_Num==KPN)&&(KeyCom==0)&&(Key->KEY_LONG_PRESS!=KPL_ENABLE))
{
//按键被按下过&&非长按&&不是在组合键周期,该按键释放时发出生命周期为直到被读取或者直到有新按键被按下的脉冲
Key_Pulse_Num=Pulse;
}
//清除该位
Key_Phy_Num&=(~KPN);
Key_LP_Num&=(~KPN);
/*检测到(持续)松开20ms*/
Key->KEY_LONG_PRESS=KPL_DISABLE;
Key->KOC_EN=0;
}
else
{
Key->KOC_EN=1;
}
}
}
/********************************************************/
static void Key_Count(Key_Para *Key)
{
if(Key->KOC_EN==0)
{
Key->KeyOpenCount=0;
}
else if(Key->KeyOpenCount>=50000)
{
Key->KeyOpenCount=50000;
}
else
{
Key->KeyOpenCount++;
}
if(Key->KCC_EN==0)
{
Key->KeyCloseCount=0;
}
else if(Key->KeyCloseCount>=50000)
{
Key->KeyCloseCount=50000;
}
else
{
Key->KeyCloseCount++;
}
}
/********************************************************/
void Clear_Key_Pulse_Num(void)
{
Key_Pulse_Num=0;
}
/********************************************************/
void KeyCount_Run(void)
{
Key_Count(&Key_1);
Key_Count(&Key_2);
Key_Count(&Key_3);
Key_Count(&Key_4);
Key_Count(&Key_5);
Key_Count(&Key_6);
Key_Count(&Key_7);
Key_Count(&Key_8);
Key_Count(&Key_9);
Key_Count(&Key_10);
Key_Count(&Key_11);
Key_Count(&Key_12);
Key_Count(&Key_13);
Key_Count(&Key_14);
Key_Count(&Key_15);
Key_Count(&Key_16);
}
/********************************************************/
static void Recognition_KeyCombination(void)
{
uint8_t i=0,j=0;
uint16_t Data=0;
Data=Key_Phy_Num;
for(i=0;i<16;i++)
{
if(Data&0x8000)
{
j++;
}
Data<<=1;
}
/*发现多个bit为1,那指定多个按键按下了*/
if(j>1)
{
KeyCom=1;
}
/*一切归于平静,又是一个因果循环*/
if(Key_Phy_Num==0x0)
{
KeyCom=0;
}
}
/********************************************************/
void Key_Scan(void)
{
static uint8_t ScanCount=0;
Recognition_KeyCombination();
switch(ScanCount)
{
case 0:
{
KEY_LON1=0;KEY_LON2=1;KEY_LON3=1;KEY_LON4=1;
Key_Num_Read(&Key_1,(uint16_t)0x0001 ,1,KEY_HOR1);
Key_Num_Read(&Key_2,(uint16_t)0x0001<<1,2,KEY_HOR2);
Key_Num_Read(&Key_3,(uint16_t)0x0001<<2,3,KEY_HOR3);
Key_Num_Read(&Key_4,(uint16_t)0x0001<<3,4,KEY_HOR4);
KEY_LON1=1;KEY_LON2=0;KEY_LON3=1;KEY_LON4=1;
ScanCount++;
}break;
case 1:
{
KEY_LON1=1;KEY_LON2=0;KEY_LON3=1;KEY_LON4=1;
Key_Num_Read(&Key_5,(uint16_t)0x0001<<4,5,KEY_HOR1);
Key_Num_Read(&Key_6,(uint16_t)0x0001<<5,6,KEY_HOR2);
Key_Num_Read(&Key_7,(uint16_t)0x0001<<6,7,KEY_HOR3);
Key_Num_Read(&Key_8,(uint16_t)0x0001<<7,8,KEY_HOR4);
KEY_LON1=1;KEY_LON2=1;KEY_LON3=0;KEY_LON4=1;
ScanCount++;
}break;
case 2:
{
KEY_LON1=1;KEY_LON2=1;KEY_LON3=0;KEY_LON4=1;
Key_Num_Read(&Key_9 ,(uint16_t)0x0001<<8 , 9,KEY_HOR1);
Key_Num_Read(&Key_10,(uint16_t)0x0001<<9 ,10,KEY_HOR2);
Key_Num_Read(&Key_11,(uint16_t)0x0001<<10,11,KEY_HOR3);
Key_Num_Read(&Key_12,(uint16_t)0x0001<<11,12,KEY_HOR4);
KEY_LON1=1;KEY_LON2=1;KEY_LON3=1;KEY_LON4=0;
ScanCount++;
}break;
case 3:
{
KEY_LON1=1;KEY_LON2=1;KEY_LON3=1;KEY_LON4=0;
Key_Num_Read(&Key_13,(uint16_t)0x0001<<12,13,KEY_HOR1);
Key_Num_Read(&Key_14,(uint16_t)0x0001<<13,14,KEY_HOR2);
Key_Num_Read(&Key_15,(uint16_t)0x0001<<14,15,KEY_HOR3);
Key_Num_Read(&Key_16,(uint16_t)0x0001<<15,16,KEY_HOR4);
KEY_LON1=0;KEY_LON2=1;KEY_LON3=1;KEY_LON4=1;
ScanCount=0;
}break;
default:
{
ScanCount=0;
}break;
}
}
上一篇:8051单片机快速入门--我的第一盏灯
下一篇:at89c51 8个LED 如何循环亮灭?
推荐阅读最新更新时间:2024-11-06 01:33
设计资源 培训 开发板 精华推荐
- LT1086CM 低压差稳压器与基准的典型应用
- DC1237A-B,使用 LTC3527EUD-1 双路 800mA/400mA、1.2MHz/2.2MHz 同步升压 DC/DC 转换器且具有输出断开连接的演示板
- Si5318-EVB,基于 Si5318 SONET/SDH 精密时钟乘法器的评估板
- 60W,交流转直流单输出笔记本电源
- 使用 Analog Devices 的 LTC1430CS8 的参考设计
- 典型应用图:TDF8599C I2C 总线控制的立体声 D 类放大器 136W/8ohm 具有完整诊断功能的 I2C 总线模式下的双 BTL 主设备、单 BTL 从设备
- 用于 TRIAC Crowbar 的 TL431B 可编程精密基准的典型应用
- MCP16301 高压输入集成开关降压稳压器电路(12V 输入、2V 输出、600 mA)下的典型应用
- 用于电池充电指示器的 NCP301LSN18T1 1.8V 电压检测器的典型应用
- 使用 ROHM Semiconductor 的 BD45392 的参考设计