矩阵键盘中的一厢情愿与爱
前言
日常设计中,不可避免的会使用到按键,像我们常见的 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的更多精彩内容哦!
如果你觉得对自己有帮助的话,给个赞,点个关注,点个在看,感谢前进的道路上有你的陪伴!
推荐阅读
-
二叉树, 329. 矩阵中的最长递增路径, 备忘递归表填充, [73] [算法分析与设计] 516.
-
矩阵范式与图像识别:范式在图像识别中的重要性
-
49 使用 linux 内核源代码中的矩阵键盘驱动程序
。 -
读取 Numpy 矩阵中某一列的方法以及数组与矩阵相互转换的示例
-
矩阵键盘中的一厢情愿与爱
-
[姿势估计] 实践记录:使用 Dlib 和 mediapipe 进行人脸姿势估计 - 本文重点介绍方法 2):方法 1:基于深度学习的方法:。 基于深度学习的方法:基于深度学习的方法利用深度学习模型,如卷积神经网络(CNN)或递归神经网络(RNN),直接从人脸图像中学习姿势估计。这些方法能够学习更复杂的特征表征,并在大规模数据集上取得优异的性能。方法二:基于二维校准信息估计三维姿态信息(计算机视觉 PnP 问题)。 特征点定位:人脸姿态估计的第一步是通过特征点定位来检测和定位人脸的关键点,如眼睛、鼻子和嘴巴。这些关键点提供了人脸的局部结构信息,可用于后续的姿势估计。 旋转表示:常见的旋转表示方法包括欧拉角和旋转矩阵。欧拉角通过三个旋转角度(通常是俯仰、偏航和滚动)描述头部的旋转姿态。旋转矩阵是一个 3x3 矩阵,表示头部从一个坐标系到另一个坐标系的变换。 三维模型重建:根据特征点的定位结果,三维人脸模型可用于姿势估计。通过将人脸的二维图像映射到三维模型上,可以估算出人脸的旋转和平移信息。这就需要建立人脸的三维模型,然后通过优化方法将模型与特征点对齐,从而获得姿势估计结果。 特征点定位 特征点定位是用于检测人脸关键部位的五官基础部分,还有其他更多的特征点表示方法,大家可以参考我上一篇文章中介绍的特征点检测方案实践:人脸校正二次定位操作来解决人脸校正的问题,客户在检测关键点的代码上略有修改,坐标转换部分客户见上图 def get_face_info(image). img_copy = image.copy image.flags.writeable = False image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) results = face_detection.process(image) # 在图像上绘制人脸检测注释。 image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) box_info, facial = None, None if results.detections: for detection in results. for detection in results.detections: mp_drawing.Drawing.detection = 无 mp_drawing.draw_detection(image, detection) 面部 = detection.location_data.relative_keypoints 返回面部 在上述代码中,返回的数据是五官(6 个关键点的坐标),这是用 mediapipe 库实现的,下面我们可以尝试用另一个库:dlib 来实现。 使用 dlib 使用 Dlib 库在 Python 中实现人脸关键点检测的步骤如下: 确保已安装 Dlib 库,可使用以下命令: pip install dlib 导入必要的库: 加载 Dlib 的人脸检测器和关键点检测器模型: 读取图像并将其灰度化: 使用人脸检测器检测图像中的人脸: 对检测到的人脸进行遍历,并使用关键点检测器检测人脸关键点: 显示绘制了关键点的图像: 以下代码将参数 landmarks_part 添加到要返回的关键点坐标中。
-
Pytorch 中哈达玛乘积与矩阵乘积的区别
-
比较邻接矩阵与邻接表在图论中的应用与差异
-
如何轻松绘制深度学习中的邻接矩阵热力图:步骤详解与实际操作
-
邻接矩阵在图形处理中的应用与算法简介