嵌入式学习---矩阵按键

1.介绍


矩阵按键的基本原理是通过行和列的交叉点来确定按下的键位。下面我将简要介绍矩阵按键的工作原理和特点:

        1.1 工作原理

  • 矩阵按键由多行和多列的导电线路组成,形成一个网格状的布局。
  • 每个按键对应网格中的一个交叉点,当按键被按下时,该交叉点的行和列就会形成闭合电路,从而触发信号。

        1.2 特点

  • 节省IO:如果是普通的独立按键,一个按键需要一根线,即一个IO口;而矩阵按键一根线上同时接了多个按键,这样就节省了IO口。在需要较多按键,而IO资源较为紧张的场景下,非常适合采用矩阵按键。
  • 灵活性:矩阵按键可以通过软件编程来定义每个按键的功能,增加了设计的灵活性。

        1.3 应用场景

  • 键盘:包括计算机键盘、笔记本电脑键盘等。
  • 电子乐器:如电子琴、合成器等。
  • 遥控器:许多现代遥控器采用矩阵按键设计。
  • 工业控制面板:用于操作机器或设备的控制面板。

        1.4 实现方式

       通过扫描行和列的电信号变化,确定被按下的按键位置。

       通常有行列扫描(逐行逐列扫描)和行列反转扫描(线反法)两种方法。其中行列反转扫描法的效率较高,但其实现思路也比行列扫描法复杂一些。

        1.5 注意事项

  • 设计时需要考虑按键布局和扫描算法,以确保所有按键都能被准确识别。
  • 矩阵按键可能需要更复杂的软件支持,以处理按键扫描和信号处理。


2.思路及代码

        2.1行列扫描

简介:就是一行一行或一列一列地去扫描,两者是一样的。

详细思路:

  • 首先按键按下的判断依据是:比如A端是低,B端是高,那么检测到B端从高到低的变化,就可以判定按键被按下了。如果两端电压一致,那么就无法检测。为了方便叙述,我把一端高、一端低的状态称为“激活”,两端电压一致的状态称为“失效”。
  • 在此检测原理上,我们先假设从高到低的变化代表按键按下,采用一行一行扫描的方法。我们一次只“激活”一列,每次只检测该列的四个行的电平,其他列“失效”,比如两端给高电平。等该列的四个行检测完,把该列“失效”,再“激活”下一列检测四个行。

小结:在以上假设中,要检测哪一行,就把四列中的一列的线拉低,其他行线列线全部拉高,以此检测行线从1到0的变化,确定是哪一行被按下。

这里给出示例图方便理解,和下面的代码并不具有对应关系。

 

代码实现:

#define  KEY_MATRIX_PORT  GPIOC
//KEY_ROW1  读第一行电平,每行只有一个键生效了,其他列的键是失效状态
//因此可以像判断独立按键那样去判断是哪个按键被按下了
#define  KEY_ROW1  GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12)
#define  KEY_ROW2  GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_13)
#define  KEY_ROW3  GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)
#define  KEY_ROW4  GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15)

//逐行逐列扫描法  激活的那一列都是一端低一端高,只读取每一行从高到低的变化即可判断
uint8_t Key_Matrix_Scan(void)
{
    uint8_t col = 0,key_value = 0,pressed = 0;
    for(col = 0;col < 4;col++)
    {
         GPIO_ResetBits(KEY_MATRIX_PORT,1 << col);//循环拉低激活1~4列,1 << col利用了引脚正好是0、1、2、3,如果不是,就需要改代码,如何改也分很多情况,就不细说了,这个也不难
         //下面的和判断独立按键方法一样
         if(KEY_ROW1 ==0 || KEY_ROW2 ==0 || KEY_ROW3 == 0 || KEY_ROW4 == 0)
         {
                delay_ms(30);
                if(KEY_ROW1 == 0)//如果是第一行被按下
                {
                    key_value = col+1; //根据矩阵键盘的设计,找规律,得到键值的计算公式
                    pressed = 1;       //已经按下标志                     
                }
                else if(KEY_ROW2 == 0 )
                {
                    key_value = col+5;
                    pressed = 1;    
                }
                else if(KEY_ROW3 == 0 )
                {
                    key_value = col+9;
                    pressed = 1;                            
                }
                else if(KEY_ROW4 == 0 )
                {
                    key_value = col+13;
                    pressed = 1;    
                }
            }
            GPIO_SetBits(KEY_MATRIX_PORT,1 << col);
            if(pressed)               //如果已经按下,即已经检测到,结束扫描
                 break;
      }
    return key_value;
}


 2.2 行列反转扫描

简介:比如先扫描行,确定是哪一行的按键被按下,再扫描列,确定是哪一列的按键被按下,把二者的行列信息组合到一起,就能得到一个坐标,确定是哪个按键被按下。先扫描列,再扫描行,也是同样的道理。

详细思路:比如我们打算先扫描列,再扫描行。扫描列,也就是让列有一个从1到0或从0到1的电平变化。扫描行也是这个道理。

  • 我们把所有行线拉高设置为输出列线拉低设置为输入,当按键按下,列线与行线接通,列线电平被行线输出的1拉高至1,我们只需要读取列线从0到1的电平变化即可知道是哪一列被按下。
  • 同样的,我们把所有行线依然拉高但设置为输入,列线依然拉低但设置为输出(行列反转),当按键按下,行线与列线接通,行线电平被列线输出的0拉低至0,我们只需要读取行线从1到0的电平变化即可知道是哪一行被按下。

注意:以上所说的行线拉高,列线拉低只是一种情况,根据电路设计不同,可能是行低列高,并且可以先检测列,再检测行,因此有多种情况,这里只描述了其中一种情况,其他情况可以根据这个再做分析,本文不再赘述。



代码实现:

//ROW行 COL列
#define  KEY_ROW_COL_PORT   GPIOE
#define  KEY_ROW_ALL_PIN1   GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15										
#define  KEY_COL_ALL_PIN1   GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11
#define  KEY_ALL     GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 \
													  | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15

//选定引脚,配置为输出模式
void  Key_Output_Set(uint16_t OUT_PINS)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin =  OUT_PINS;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(KEY_ROW_COL_PORT, &GPIO_InitStructure);
}
//选定引脚,配置为输入模式(读取电平变化)
void  Key_Input_Set(uint16_t IN_PINS)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = IN_PINS;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
	GPIO_Init(KEY_ROW_COL_PORT, &GPIO_InitStructure);
}
//执行一次确定行或列,两次就可以确定行列
uint8_t Key_GPIO_RW(uint16_t IO_Read,uint16_t IO_Write)
{
	 uint16_t final_data = 0,Read_Data = 0,Write_Data = 0;
	 Key_Output_Set(KEY_ALL);  //8个引脚全置1

//判断选定的输出引脚是否为行线12~15号引脚,行线固定输出1
	 if(IO_Write == 0xf000)  
	     GPIO_SetBits(GPIOE,KEY_ROW_ALL_PIN1);
//判断选定的输出引脚是否为列线8~11号引脚,列线固定输出0
	 else if(IO_Write == 0x0f00)
			 GPIO_ResetBits(GPIOE,KEY_COL_ALL_PIN1);

	 Write_Data = GPIO_ReadOutputData(GPIOE);//读端口16个脚的输出电平
	 Write_Data &= IO_Write;     //获取4个输出引脚的电平状态
	 
	 Key_Input_Set(IO_Read);     //选定引脚设置为输入
	 Read_Data = GPIO_ReadInputData(GPIOE);//读端口16个脚的输入电平
	 Read_Data &=  IO_Read;      //获取4个输入引脚的电平状态
	 
//将输入输出引脚状态合并,得到8个脚的电平状态,右移8位方便处理
	 final_data = (uint8_t)((Read_Data | Write_Data) >> 8);
	 return  final_data;
}

uint8_t Key_Row_Col_Scan(void)
{
	uint8_t key = 0,key_value = 0;
	key = Key_GPIO_RW(KEY_COL_ALL_PIN1,KEY_ROW_ALL_PIN1);//列输入,行输出,先检测列
	if(key != 0xf0)
	{
		delay_ms(5);
		key = Key_GPIO_RW(KEY_COL_ALL_PIN1,KEY_ROW_ALL_PIN1);
		if(key != 0xf0)
		{
			switch(key)
			{//高位行1111低位列1000  即1111 1000,第1列由低变高,说明第1列被按下
             //这个还是要看自己具体接线,以及自己认为的第一列是在哪边
				case 0xf8: key_value = 1;break;
				case 0xf4: key_value = 2;break;
				case 0xf2: key_value = 3;break;
				case 0xf1: key_value = 4;break;
			 }
			key = Key_GPIO_RW(KEY_ROW_ALL_PIN1,KEY_COL_ALL_PIN1);
			switch(key)
			{//反转,0111 0000和上面一样的道理,公式自己总结,不同的设计公式不同,找规律就行了
			  case 0x70: key_value = key_value;break;
			  case 0xb0: key_value = key_value + 4;break;
			  case 0xd0: key_value = key_value + 8;break;
			  case 0xe0: key_value = key_value + 12;break;
			 }
		 }
	 }
	return  key_value;
}

行列反转法可能有点绕,还是要自己仔细分析以下。

最好选用序号连续的引脚,这样方便我们对数据进行处理。

矩阵按键到此也结束了。不知道小伙伴们学会没有(:

有什么意见和建议,大家可以留言评论!!!

 本人水平有限,有错误之处欢迎大家指出,本人一定虚心接受改正。

  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嵌入式系统中,矩阵键盘和数码管通常是通过GPIO口进行控制。下面是一个简单的程序示例,可以实现矩阵键盘控制数码管显示0-F。 首先,需要在程序中定义矩阵键盘的引脚和数码管的引脚,以及矩阵键盘的键值和数码管的数字对应关系。例如,假设我们使用4x4的矩阵键盘和共阴数码管,可以定义如下: ``` #define KEY_PORT GPIOA #define KEY_PIN GPIO_Pin_0 #define DIG_PORT GPIOB #define DIG_PIN_0 GPIO_Pin_0 #define DIG_PIN_1 GPIO_Pin_1 #define DIG_PIN_2 GPIO_Pin_2 #define DIG_PIN_3 GPIO_Pin_3 const uint8_t key_map[16] = { 0x01, 0x02, 0x03, 0x0A, 0x04, 0x05, 0x06, 0x0B, 0x07, 0x08, 0x09, 0x0C, 0x0E, 0x00, 0x0F, 0x0D }; const uint8_t dig_map[16] = { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x00, 0x3F, 0x5E }; ``` 接下来,可以编写初始化函数,配置GPIO口为输入和输出模式,并设置中断触发方式。例如,可以使用下面的代码初始化矩阵键盘和数码管: ``` void init_keypad(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin = KEY_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(KEY_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = DIG_PIN_0 | DIG_PIN_1 | DIG_PIN_2 | DIG_PIN_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DIG_PORT, &GPIO_InitStructure); } ``` 然后,可以编写一个轮询函数,检测矩阵键盘的按键状态,并根据按键值更新数码管的显示。例如,可以使用下面的代码实现: ``` void update_display(void) { static uint8_t cur_dig = 0; static uint8_t prev_key = 0; uint8_t key = 0; // 检测矩阵键盘的按键状态 GPIO_ResetBits(KEY_PORT, KEY_PIN); for (uint8_t i = 0; i < 4; i++) { GPIO_SetBits(DIG_PORT, 1 << i); if (GPIO_ReadInputDataBit(KEY_PORT, KEY_PIN) == RESET) { key = key_map[i * 4 + cur_dig]; break; } GPIO_ResetBits(DIG_PORT, 1 << i); } GPIO_SetBits(KEY_PORT, KEY_PIN); // 更新数码管的显示 if (key != prev_key) { uint8_t dig_val = dig_map[key]; GPIO_ResetBits(DIG_PORT, DIG_PIN_0 | DIG_PIN_1 | DIG_PIN_2 | DIG_PIN_3); GPIO_SetBits(DIG_PORT, dig_val << 8); prev_key = key; } // 切换下一个数码管 cur_dig = (cur_dig + 1) & 0x03; GPIO_SetBits(DIG_PORT, 1 << cur_dig); } ``` 最后,在主函数中循环调用轮询函数即可实现矩阵键盘控制数码管显示。例如,可以使用下面的代码实现: ``` int main(void) { init_keypad(); while (1) { update_display(); delay_ms(10); } } ``` 其中,`delay_ms`是一个自定义的延时函数,可以根据实际情况进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值