单片机矩阵键盘扫描驱动程序与电路分析
2023-01-05 来源:zhihu
以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;
}
}
- 英飞凌推出新型高性能微控制器AURIX™ TC4Dx
- 兆易创新推出GD32G5系列Cortex®-M33内核高性能MCU,全面激发工业应用创新活力
- 调试器连接MCU不稳定怎么办?
- MCU上电不启动的可能原因分析
- 兆易创新MCU新品重磅揭幕,以多元产品和方案深度解锁工业应用场景
- 国内首款!全国产自主可控高性能车规级MCU DF30芯片发布
- GD32MCU最小系统构成条件
- GD32MCU如何实现掉电数据保存?
- 【GD32 MCU 移植教程】9、从 STM32F10x 系列移植到 GD32F30x 系列
- 【GD32 MCU 移植教程】8、从 STM32F4xx 系列移植到 GD32F4xx 系列