1 矩阵键盘(2x3)模块
行/列 | 引脚 |
---|---|
COL 1 | PB1 |
COL 2 | PB0 |
COL 3 | PA8 |
ROW 1 | PB6 |
ROW 2 | PB7 |
注意:PA8
引脚为 OLED 显示屏 I2C SCL信号,在同时使用 OLED 屏幕和 2x3 矩阵键盘模块时,在每次矩阵键盘扫描任务前、后,需要重置 PA8
到相应的工作模式和状态;或放弃使用 PA8
对应列(B3/B6按键),使用 2x2 矩阵键盘功能 。
2 矩阵按键工作原理
行 | 引脚 | 列 | 引脚 |
---|---|---|---|
1 | P3.0 | 1 | P3.4 |
2 | P3.1 | 2 | P3.5 |
3 | P3.2 | 3 | P3.6 |
4 | P3.3 | 4 | P3.7 |
行列扫描:
- P3.0~P3.3行引脚全部拉高,P3.4~P3.7列引脚全部拉低
- 读取行引脚电平,若某行有引脚按下,则该行对应的引脚会被与之相连的列引脚拉低
- P3.4~P3.7列引脚全部拉高,P3.0~P3.3行引脚全部拉低
- 读取列引脚电平,若某列有引脚按下,则该列对应的引脚会被与之相连的行引脚拉低
uint8_t key_scanf(void)
{
uint8_t col = 0, row = 0;
P3 = 0x0f;
if(P3 != 0x0f)
{
delay_ms(10);
if(P30 == 0) row = 1;
if(P31 == 0) row = 2;
if(P32 == 0) row = 3;
if(P33 == 0) row = 4;
}
P3 = 0xf0;
if(P3 != 0xf0)
{
delay_ms(10)
if(P34 == 0) col = 1;
if(P35 == 0) col = 2;
if(P36 == 0) col = 3;
if(P37 == 0) col = 4;
}
return row ? (row - 1) * 4 + col : 0;
}
逐行/列扫描:
以逐行扫描为例(逐列同理)
- 将第1行引脚拉低,其余引脚全部拉高
- 读取除第1行引脚外的其他引脚电平,当第1行有按键按下时,相应的列引脚会被拉低
- 按此方法继续扫描第2~n行,即可确定所有状态
uint8_t key_scanf(void)
{
uint8_t col = 0, row = 0;
for (int i = 0; i < 4; i++)
{
P3 = 0xff & ~(1 << i);
if ( P3 != (0xff & ~(1 << i)) )
{
delay_ms(10);
if(P34 == 0) col = 1;
if(P35 == 0) col = 2;
if(P36 == 0) col = 3;
if(P37 == 0) col = 4;
if(col) row = i + 1;
}
}
return row ? (row - 1) * 4 + col : 0;
}
3 矩阵按键HAL库编程
相对51,STM32引脚配置就灵活多了,以逐行扫描为例:
- 列引脚配置为上拉输入,行引脚初始全部推挽输出高电平(也可以列引脚下拉输入,行引脚推挽输出低电平)
- 第一行引脚输出低电平,其余行引脚输出高电平,当第一行有按键按下时,读取列引脚电平,若第x列引脚读取到低电平,说明第一行第x列按键被按下。
- 继续扫描第2~n行,即可确定所有状态
但因为PA8(col3)引脚与oled冲突,因此采用逐列扫描方法,弃用第三列按键避免使用PA8引脚:
- 行引脚配置为上拉输入,列引脚初始全部推挽输出高电平
- 第一列引脚输出低电平,其余列引脚输出高电平,之后检测方法与逐行扫描同理,不多赘述。
3.1 初始化
// 列引脚
#define COL1_KEY_PORT GPIOB
#define COL1_KEY_PIN GPIO_PIN_1
#define COL2_KEY_PORT GPIOB
#define COL2_KEY_PIN GPIO_PIN_0
#define COL3_KEY_PORT GPIOA // PA8与oled冲突
#define COL3_KEY_PIN GPIO_PIN_8
// 行引脚
#define ROW1_KEY_PORT GPIOB
#define ROW1_KEY_PIN GPIO_PIN_6
#define ROW2_KEY_PORT GPIOB
#define ROW2_KEY_PIN GPIO_PIN_7
void key_gpio_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
// 行引脚上拉输入
GPIO_InitStruct.Pin = ROW1_KEY_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(ROW1_KEY_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ROW2_KEY_PIN;
HAL_GPIO_Init(ROW2_KEY_PORT, &GPIO_InitStruct);
// 列引脚推挽输出高电平
GPIO_InitStruct.Pin = COL1_KEY_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(COL1_KEY_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = COL2_KEY_PIN;
HAL_GPIO_Init(COL2_KEY_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(COL1_KEY_PORT, COL1_KEY_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(COL2_KEY_PORT, COL2_KEY_PIN, GPIO_PIN_SET);
}
3.2 列引脚电平切换函数
static void keyboard_col(uint8_t col)
{
HAL_GPIO_WritePin(COL1_KEY_PORT, COL1_KEY_PIN, col == 1 ? GPIO_PIN_RESET : GPIO_PIN_SET);
HAL_GPIO_WritePin(COL2_KEY_PORT, COL2_KEY_PIN, col == 2 ? GPIO_PIN_RESET : GPIO_PIN_SET);
}
3.3 矩阵按键扫描函数
void keyboard_scan(void)
{
key.value = No_Press;
// 独立按键USER
if (HAL_GPIO_ReadPin(USER_KEY_PORT, USER_KEY_PIN) == GPIO_PIN_RESET)
key.value = USER;
// 矩阵按键B1~B6
keyboard_col(1);
if (HAL_GPIO_ReadPin(ROW1_KEY_PORT, ROW1_KEY_PIN) == GPIO_PIN_RESET)
key.value = B1;
else if (HAL_GPIO_ReadPin(ROW2_KEY_PORT, ROW2_KEY_PIN) == GPIO_PIN_RESET)
key.value = B4;
keyboard_col(2);
if (HAL_GPIO_ReadPin(ROW1_KEY_PORT, ROW1_KEY_PIN) == GPIO_PIN_RESET)
key.value = B2;
else if (HAL_GPIO_ReadPin(ROW2_KEY_PORT, ROW2_KEY_PIN) == GPIO_PIN_RESET)
key.value = B5;
}
关于Key与OLED冲突解决方法:
- 不扫描第三列
- 扫描第三列前切换PA8为推挽输出模式,扫描结束后切换回复用开漏输出模式(硬件IIC),如果是软件IIC则不用切换,都是推挽输出(若按键扫描函数放中断,需要用中断锁保护OLED的IIC传输不得被打断)
END