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;
}
行列反转法可能有点绕,还是要自己仔细分析以下。
最好选用序号连续的引脚,这样方便我们对数据进行处理。
矩阵按键到此也结束了。不知道小伙伴们学会没有(:
有什么意见和建议,大家可以留言评论!!!
本人水平有限,有错误之处欢迎大家指出,本人一定虚心接受改正。