前言
日常设计中,不可避免的会使用到按键,像我们常见的 POS 机、计算器等设备用到的按键是非常多的,如果采用普通的 1 个 IO 1 个按键的设计方法,显然对单片机资源来说是非常浪费的,所以采用类似矩阵的设计思路,能够大大减少 MCU IO 的使用,也是我们所说的矩阵键盘。
应用场景
一、矩阵键盘的两种扫描方式
矩阵键盘常用的有以下两种扫描方式,相比较之下,小飞哥更推荐第二种扫描方式,第二种扫描方式能够更有效地避免错误按键,本次分享的也是第二种扫描方式的代码。
-
逐行扫描:高四位输出低电平来对矩阵键盘逐行扫描,当低四位接受数据不全为一,表示有按键按下,然后通过接收到的数据是哪一位为 0 来判断哪个按键被按下,可以简单理解为,“一厢情愿型”;
-
行列扫描:高四位全部输出低电平,低四位输出高电平。当接受到的数据,低四位不全为高电平时,说明有按键按下。然后通过接收到的值判断是哪一列有按键按下,然后再反过来高四位输出高电平,第四位低电平,然后通过高四位接收到的值判断哪一行按键按下,可以简单理解为,“两情相悦型”;
二、“两情相悦型”按键扫描方式详解
上面图片是 4*4 矩阵键盘电路设计,ROW1-ROW4 为键盘你的行,COL1-COL4 为键盘的列,8 个 IO 共 16 个按键,我们以 K1 按下为例,分析具体的工作流程:
-
1、 先把行切换为上拉输入模式
-
2、把列切换为输出模式,输出低电平
-
3、获取当前行输入状态
即:ROW1 线上此时为低电平,按下之前 ROW1-ROW4 为:1111,即 0x0F,按下之后,ROW1 与 COL1 连通,ROW1-ROW4 的值变为:1110,即 0x0e,此时获取到按键所在行;
-
4、把行切换为输出状态,输出低电平
-
5、把列切换为上拉输入模式
-
6、获取当前列输入状态
即:COL1 线上此时为低电平,按下之前 COL1-COL4 为:1111,即 0x0F,按下之后,ROW1 与 COL1 连通,COL1-COL4 的值变为:1110,即 0x0e,此时获取到按键所在列;
- 7、两次行列状态获取完成之后,即确定了按键所在行列,得到按键编码:0xee,值并不是唯一的 0xee,具体跟硬件连接有关系。
硬件连接
调试用的键盘为这种非常简单的,某宝几毛钱,邮费都不够的说~
MCU | 键盘 |
---|---|
PF0 | ROW1 |
PF1 | ROW2 |
PF2 | ROW3 |
PF3 | ROW4 |
PG0 | COL1 |
PG1 | COL2 |
PG2 | COL3 |
PG3 | COL4 |
软件实现
1、cubemx配置键盘IO
行IO配置为输出模式,输出低电平
列IO配置为输入模式,需要注意的是,配置为输入上拉模式
配置完成的IO状态为:
时钟配置、串口等配置见:HAL库使用章节
2、代码编写
根据上一小节描述,按键按下之后是对应一个编码,根据上面的描述,我们可以计算出一个码表,也即按键真值表:
const uint8_t KeyTrueLable[] =
{
0xEE, 0xDE, 0xBE, 0x7E,
0xED, 0xDD, 0xBD, 0x7D,
0xEB, 0xDB, 0xBB, 0x7B,
0xE7, 0xD7, 0xB7, 0x77};
真值表是硬件IO状态的一种表达,对用户来说并不是需要的,需要攻城狮转换为用户需要的信息,也即是,建立一个与之对应的用户信息表:
const uint8_t KeyUserDefined[] =
{
1, 2, 3, 12,
4, 5, 6, 13,
7, 8, 9, 14,
10, 0, 11, 15};
根据上面原理介绍,我们需要封装4个函数,行输入、行输出、列输入、列输出:
///===============================================================
/// 行输入
///===============================================================
void KeyPad_SetROW_IN(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOF_CLK_ENABLE();
//ROW0-ROW3设置成上拉输入
GPIO_InitStruct.Pin = Key_ROW1_Pin | Key_ROW2_Pin | Key_ROW3_Pin | Key_ROW4_Pin; //KEY0-KEY3
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
}
///===============================================================
/// 行输出
///===============================================================
void KeyPad_SetROW_Out(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOF_CLK_ENABLE();
/*Configure GPIO pins : PFPin PFPin PFPin PFPin */
GPIO_InitStruct.Pin = Key_ROW1_Pin | Key_ROW2_Pin | Key_ROW3_Pin | Key_ROW4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOF, Key_ROW1_Pin | Key_ROW2_Pin | Key_ROW3_Pin | Key_ROW4_Pin, GPIO_PIN_RESET);
}
///===============================================================
///列输入
///===============================================================
void KeyPad_SetCol_IN(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOG_CLK_ENABLE();
//C0-3设置成上拉输入
/*Configure GPIO pins : PCPin PCPin PCPin PCPin */
GPIO_InitStruct.Pin = Key_COL1_Pin | Key_COL2_Pin | Key_COL3_Pin | Key_COL4_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
}
///===============================================================
///列输出
///===============================================================
void KeyPad_SetCol_Out(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = Key_COL1_Pin | Key_COL2_Pin | Key_COL3_Pin | Key_COL4_Pin; //KEY0-KEY3
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOG, Key_COL1_Pin | Key_COL2_Pin | Key_COL3_Pin | Key_COL4_Pin, GPIO_PIN_RESET);
}
然后编写我们的按键真值获取函数:
///===============================================================
//按键扫描程序
//先C低读R状态,再R低读C状态,判断是那个按键按下
//
///===============================================================
uint8_t KeyPad_KEY_Scan(void)
{
uint8_t keycode, keycode_temp;
//C低读ROW
KeyPad_SetCol_Out();
keycode = KEYPADROW & 0x0f;
//R低读COL
KeyPad_SetROW_Out();
KeyPad_SetCol_IN();
keycode_temp = KEYPADCOL;
keycode |= (keycode_temp << 4 & 0xf0);
KeyPad_SetROW_IN();
return keycode;
}
前面说过,直接获取到的是按键IO的状态组合,需要转换为实际需要的信息。封装一下函数:
///===============================================================
//键值转换用户定义键值
///===============================================================
uint8_t KeyPad_KeyTrueLableGet(uint8_t key)
{
uint8_t i = 0, R_key_user = 0xE0;
for (i = 0; i < D_Key_Count; i++)
{
if (key == KeyTrueLable[i])
{
R_key_user = KeyUserDefined[i];
}
}
return R_key_user;
///===============================================================
//得到键值
///===============================================================
uint8_t KeyPad_GetKeyValue(void)
{
uint8_t KeyValue = 0;
// HAL_Delay(10);
if (KeyPad_KEY_Scan() == D_NO_KEY_CODE) //没有按键/按键抬起
{
if (keypad_status.KeyPress == 1)
{
keypad_status.KeyValue_New = 0;
keypad_status.KeyValue_Old = 0;
keypad_status.KeyPress = 0;
}
return 0xF0;
}
HAL_Delay(10);
KeyValue = KeyPad_KEY_Scan();
if (KeyValue == D_NO_KEY_CODE)
{
if (keypad_status.KeyPress == 1)
{
//解决按键“0” 的问题
keypad_status.KeyValue_New = 0xF2;
keypad_status.KeyValue_Old = 0xF2;
keypad_status.KeyPress = 0;
}
return 0xF0;
}
KeyValue = KeyPad_KeyTrueLableGet(KeyValue);
keypad_status.KeyPress = 1;
//判断是否是第一次按下
keypad_status.KeyValue_New = KeyValue;
if (keypad_status.KeyValue_New == keypad_status.KeyValue_Old)
{
return 0xF1;
}
keypad_status.KeyValue_Old = KeyValue;
printf("\r\nkeycode is:%0x\r\n", KeyValue);
return KeyValue;
}
在keypad.h中定义:
#ifndef __KEYPAD_H
#define __KEYPAD_H
#include "main.h"
#include "stm32f4xx.h"
#define KEYPADROW GPIOF->IDR
#define KEYPADCOL GPIOG->IDR
#define D_Key_Count 16
#define D_ERR_KEY_CODE 0xFF
#define D_NO_KEY_CODE 0xFF
///===============================================================
typedef struct keypad
{
uint8_t KeyStatus;
uint8_t KeyValue_New;
uint8_t KeyValue_Old;
uint8_t KeyPress;
}Keypad_para;
uint8_t
KeyPad_KEY_Scan(void);
uint8_t KeyPad_GetKeyValue(void);
#endif
测试结果
本次的介绍就到这里啦,后面有更精彩的内容,欢迎大家持续关注嵌入式实验基地,来这里还可以学习HAL库+cubemx的更多精彩内容哦!
如果你觉得对自己有帮助的话,给个赞,点个关注,点个在看,感谢前进的道路上有你的陪伴!
所有公众号文章资料源码已上传,关注公众号回复资料即可获取哦,欢迎加群一起炸起来!