STM32 4×4矩阵按键超详细分析!

矩阵键盘的基本结构

  • 定义

  • 矩阵键盘,又称为行列式键盘,是用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——运行我们的程序!!!!

     

    作者:STM大善人

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 4×4矩阵按键超详细分析!

    发表回复