矩阵键盘的“两情相悦”与“一厢情愿”

前言

日常设计中,不可避免的会使用到按键,像我们常见的 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的更多精彩内容哦!

如果你觉得对自己有帮助的话,给个赞,点个关注,点个在看,感谢前进的道路上有你的陪伴!

所有公众号文章资料源码已上传,关注公众号回复资料即可获取哦,欢迎加群一起炸起来!

矩阵键盘的“两情相悦”与“一厢情愿”

上一篇:451. 根据字符出现频率排序


下一篇:实现时钟按键显示