4*4矩阵按键(STM32)超!超!超!详细分析

矩阵键盘的基本结构

  • 定义

  • 矩阵键盘,又称为行列式键盘,是用4条I/O线作为行线,4条I/O线作为列线组成的键盘。在行线和列线的每一个交叉点上设置一个按键,因此键盘中按键的个数是4×4个。

  • 优势

  • 这种行列式键盘结构能够有效地提高单片机系统中I/O口的利用率,节约单片机的资源。(8引脚控制16按键

硬件连接

  • 行线与列线:在硬件上,4条行线连接到微控制器的输出引脚,而4条列线连接到微控制器的输入引脚。
  • 按键位置:每个按键位于行线和列线的交叉点上。

GPIO初始化

行线

通常设置为输出模式,用于向列线发送扫描信号。

扫描信号

行线的主要功能是向列线发送扫描信号。当某一行线被设置为低电平时,与之相交的列线会被检测(若相对应的按键被按下,列线被检测为低电平)以判断是否有按键被按下。

确定按键行

通过依次将每一行线设置为低电平(可以逐行扫描键盘)。当检测到列线上有低电平时,可以确定被按下的按键位于当前选中的行上。若列线上无低电平,那么将此行线设置为高电平,下一行设置为低电平,进行新一轮按键检测。

列线

设置为输入模式,用于读取按键状态。

读取按键状态

列线的主要功能是读取按键状态。(STM32就靠列线来确认按键状态)当某一行线被选中(设置为低电平)时,微控制器会读取所有列线的电平状态,以判断是否有按键被按下。

注意事项

在实际应用中,还需要考虑去抖处理、中断处理等因素,以确保按键检测的准确性和稳定性。

代码详解

初始化

void KEY_4x4_Init(void)     //键盘IO口配置及初始化
{
 	GPIO_InitTypeDef GPIO_InitStructure;       	
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);      
	//使用GPIOA的0,1,2,3引脚为行 R1~R4对应矩形按钮的5,6,7,8引脚
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;  
   //行  0123
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;     //使用推挽输出
    GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_SetBits(GPIOA,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3);   
//令GPIO的0,1,2,3引脚输出为1
	/********************************************************************/
	/********************************************************************/
	/********************************************************************/
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;         
//使用GPIOA的4,5,6,7引脚为列  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;        //使用上拉输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_SetBits(GPIOA, GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7);	
//令GPIO的4,5,6,7引脚输出为1	
}

这里比较重要的是,我们需要将0-7的I/O口全部拉成高电平!我们后面检测的是低电平,所以你不设置,全部都是低电平。直接让自己的按键默认全部按下(成为一个导线(手动滑稽一下))。

按键检测

首先讲一讲我们都部分参数,方便接下来的理解。

u8 anxian = 0;  //anxian这个变量为1时,证明有按钮正在被按下
*key = 1;   
//输出key的值为1传递到主函数,当值为x时,则说明按钮Sx被按下,在变量前增加*,为c语言中的指针操作,使用指针进行地址传递,在子函数中修改的值在主函数中可以利用地址读取,而不需要利用子函数返回值,然后主函数在增加一个变量进行接收。

开始吧!

void KEY_Scan(u8 *key) 
{	 	
	GPIO_Write(GPIOA,0x00fe); 
	if((GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0))   
	{
		Delay_ms(10);  
		
		if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)   
		{
			anxian = 1;    
			*key = 1;   
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4));   
				//当按钮处于被按下的状态的时候,程序一直卡在循环读取按钮的状态,避免多按钮同时按下时读取错误
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==0)
		{
			anxian = 1;
			*key = 2;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5));
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0)
		{
			anxian = 1;
			*key = 3;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6));
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0)
		{
	    	anxian = 1;
			*key = 4;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7));
		}
		else  //如果第一行四列中没有按钮被按下
		{
			anxian = 0;
			GPIO_Write(GPIOA,0x00ff);
		}
	}//第一行判断完成,这是我们判断第二行
	GPIO_Write(GPIOA,0x00fd);    //第二行         
	if((GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0))
	{
		Delay_ms(10);
		if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)
		{
			anxian = 1;
			*key = 5;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4));
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==0)
		{
	     	anxian = 1;
			*key = 6;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5));
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0)
		{
	    	anxian = 1;
			*key = 7;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6));
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0)
		{	 
	    	anxian = 1;
			*key = 8;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7));
		}
		else 
		{
			anxian = 0;
			GPIO_Write(GPIOA,0x00ff);
		}
	}
	GPIO_Write(GPIOA,0x00fb);//第三行
	if((GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0))
	{
		Delay_ms(10);
		if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)
		{
			anxian = 1;
			*key = 9;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4));
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==0)
		{
	     	anxian = 1;
			*key = 10;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5));
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0)
		{
	    	anxian = 1;
			*key = 11;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6));
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0)
		{
	    	anxian = 1;
			*key = 12;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7));
		}
		else 
		{
			anxian = 0;
			GPIO_Write(GPIOA,0x00ff);
		}
	}
	GPIO_Write(GPIOA,0x00f7);//第四行
	if((GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0))
	{
		Delay_ms(10);
		if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)
		{
			anxian = 1;
			*key = 13;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4));
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==0)
		{
	     	anxian = 1;
			*key = 14;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5));
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0)
		{
	    	anxian = 1;
			*key = 15;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6));
		}
		else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0)
		{
	    	anxian = 1;
			*key = 16;
			while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7));
		}
		else 
		{
			anxian = 0;
			GPIO_Write(GPIOA,0x00ff);
		}
	}
	
}

我们拉出来讲一个(第一行)。

GPIO_Write(GPIOA,0x00fe);(0000 0000 1111 1110)

根据我们打开串口的位置可知——>低四位为行,所以1110可得将第一行的电位设置为0,即低电平,高四位为列,所以11111可得前四列全部保持高电平,等待我们按下按键。

这里属于炫技了,其实你可以手都复位GPIO_Pin_0使其为低电平。其他不改变!

if((GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0)||(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0)) 

判断四列中是否有是否为电平拉低(||或语句)

Delay_ms(10);  

进行延时,模拟真实案件情况:消除按键抖动 

if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4)==0)  

//如果第一列处于低电平则表示第一列被按下
        {
            anxian = 1;   
            *key = 1;  
            while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_4));   
当按钮处于被按下的状态的时候,程序一直卡在循环读取按钮的状态,避免多按钮同时按下时读取错误(必须有这个否则代码运行顺序会混乱)
        }

else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)==0)
        {
            anxian = 1;
            *key = 2;
            while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5));
        }
        else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0)
        {
            anxian = 1;
            *key = 3;
            while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6));
        }
        else if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7)==0)
        {
            anxian = 1;
            *key = 4;
            while(!GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_7));
        }

到这里都是一列一列的判断,掌握一个就行
        else  //如果第一行四列中没有按钮被按下
        {
            anxian = 0;
            GPIO_Write(GPIOA,0x00ff);

(0000 0000 1111 1111)

再次全部拉高,按键程序中断!
        }
    }

第一行判断完成,判断第二行

主函数使用

KEY_Scan (&key);
		if(FLAG == 1)    
		{
			FLAG = 0;
			if(key==1)  
			{
				//运行我需要的代码。
			}	

在主函数时因为Key是一个指针所以在调用时使用&key

当按键被按下时:FLAG置1——判断按键FLAG为1进入if函数内部——再将FLAG置零用于下次判断——如果我们接受到key指针的返回值为1——运行我们的程序!!!!

 

  • 17
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用STM32ZET6实现4x4矩阵按键的代码: 首先,需要定义按键矩阵的行列引脚: ```c #define ROW1_GPIO_Port GPIOA #define ROW1_Pin GPIO_PIN_0 #define ROW2_GPIO_Port GPIOA #define ROW2_Pin GPIO_PIN_1 #define ROW3_GPIO_Port GPIOA #define ROW3_Pin GPIO_PIN_2 #define ROW4_GPIO_Port GPIOA #define ROW4_Pin GPIO_PIN_3 #define COL1_GPIO_Port GPIOA #define COL1_Pin GPIO_PIN_4 #define COL2_GPIO_Port GPIOA #define COL2_Pin GPIO_PIN_5 #define COL3_GPIO_Port GPIOA #define COL3_Pin GPIO_PIN_6 #define COL4_GPIO_Port GPIOA #define COL4_Pin GPIO_PIN_7 ``` 然后,需要定义按键矩阵的键值: ```c uint8_t key_map[4][4] = { {1, 2, 3, 10}, {4, 5, 6, 11}, {7, 8, 9, 12}, {15, 0, 14, 13} }; ``` 接着,需要初始化引脚: ```c void GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pins : ROW1_Pin ROW2_Pin ROW3_Pin ROW4_Pin */ GPIO_InitStruct.Pin = ROW1_Pin|ROW2_Pin|ROW3_Pin|ROW4_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /*Configure GPIO pins : COL1_Pin COL2_Pin COL3_Pin COL4_Pin */ GPIO_InitStruct.Pin = COL1_Pin|COL2_Pin|COL3_Pin|COL4_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } ``` 然后,需要编写扫描按键的函数: ```c uint8_t scan_key(void) { uint8_t row, col; for (col = 0; col < 4; col++) { HAL_GPIO_WritePin(COL1_GPIO_Port, COL1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(COL2_GPIO_Port, COL2_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(COL3_GPIO_Port, COL3_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(COL4_GPIO_Port, COL4_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(COL1_GPIO_Port, COL1_Pin + col, GPIO_PIN_RESET); for (row = 0; row < 4; row++) { if (!HAL_GPIO_ReadPin(ROW1_GPIO_Port, ROW1_Pin + row)) { return key_map[row][col]; } } } return 0; } ``` 最后,在主函数中可以调用扫描按键的函数: ```c int main(void) { // ... while (1) { uint8_t key = scan_key(); if (key != 0) { // 处理按下的键 } HAL_Delay(50); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值