目录
一、矩阵键盘
(1)资源介绍
🔅原理图
蓝桥杯物联网竞赛实训平台提供了一个拓展接口 CN2,所有拓展模块均可直接安装在 Lora 终端上使用;

2×3 矩阵键盘模块电路原理图如下所示:

通过两张电路图连接可知,引脚资源配置情况为:
Keyboard | MCU |
---|---|
COLUMN_1 | PB1 |
COLUMN_2 | PB0 |
COLUMN_3 | PA8 |
ROW_1 | PB6 |
ROW_2 | PB7 |
⚠️注意:PA8 引脚为 OLED 显示屏 I2C_SCL 信号,在同时使用 OLED 屏幕和 2×3 矩阵键盘模块时,在每次矩阵键盘扫描任务前、后,需要重置 PA8 到相应的工作模式和状态;或放弃使用 PA8 对应列(B3 按键、B6 按键),使用 2×3 矩阵键盘功能。
🔅扫描原理
1️⃣行扫描:将所有行线拉低(清 0),列线拉高(置 1)。当有按键触发时,对应列线拉低(清 0),即可得出此次按键触发所在列;
2️⃣列扫描:与行扫描相反。列扫描则是将所有列线拉低(清 0),行线拉高(置 1)。当有按键触发时,对应行线拉低(清 0),即可得出此次按键触发所在行;
本文将介绍列扫描方式使用矩阵键盘模块;
(2)STM32CubeMX 软件配置
🔅“工程建立、时钟树配置、Debug 串行线配置、代码生成配置” 在下文中有讲解,这里不再赘述❗️
1️⃣点击引脚 PB1 → 选择 GPIO_Output 模式;

2️⃣点击引脚 PB0 → 选择 GPIO_Output 模式;

3️⃣点击引脚 PB6 → 选择 GPIO_Input 模式;

4️⃣点击引脚 PB7 → 选择 GPIO_Input 模式;

5️⃣点击 "GPIO" → 点击引脚 PB7、PB7 → 分别将 "GPIO Pull-up/Pull-down" 栏修改为 "Pull-up",即将 PB7、PB7 引脚初始化为带上拉电阻;

6️⃣初始化 OLED;(配置步骤在下文中有讲解,这里不再赘述)

7️⃣生成代码即可;
(3)代码编写
🟢️main 函数
/* USER CODE BEGIN Includes */
#include "oled.h"
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
uint8_t puc_oled[17];
/* USER CODE END PV */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
uint8_t key_down, key_tmp; // 按键下降沿、按键当前值
static uint8_t key_old = 0; // 按键上一次值
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C3_Init();
/* USER CODE BEGIN 2 */
OLED_Init(); // 初始化 OLED
OLED_Clear(); // OLED 清屏
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); // 拉高 PB1
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); // 拉高 PB0
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* 按键检测 */
key_tmp = 0; // 当前值清零
// 第一列扫描
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6) == 0)
key_tmp = '1';
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == 0)
key_tmp = '4';
// 第二列扫描
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6) == 0)
key_tmp = '2';
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == 0)
key_tmp = '5';
// 三行代码
key_down = key_tmp & (key_tmp ^ key_old);
key_old = key_tmp;
// 下降沿逻辑处理
if(key_down)
{
sprintf((char *)puc_oled, "KeyValue: %c", key_down);
OLED_ShowString(0, 0, puc_oled, 16);
}
HAL_Delay(10);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
(4)实验现象
按下前两列按键,OLED 显示屏会显示对应的键值;

二、矩阵键盘接口函数封装
🟡️按键扫描函数
uint8_t key_read(void)
{
uint8_t key_value = 0;
// 第一列扫描
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6) == 0)
key_value = '1';
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == 0)
key_value = '4';
// 第二列扫描
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6) == 0)
key_value = '2';
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == 0)
key_value = '5';
return key_value;
}
🔴矩阵键盘接口函数调用实例
void task_keys(void)
{
uint8_t key_down, key_tmp;
static uint8_t key_old = 0;
// 10ms 调用一次
if(cnt_10ms < 10) return;
cnt_10ms = 0;
// 三行代码
key_tmp = key_read();
key_down = key_tmp & (key_tmp ^ key_old);
key_old = key_tmp;
// 下降沿逻辑处理
if(key_down)
{
OLED_Clear();
sprintf((char *)puc_oled, "KeyValue:%c", key_down);
OLED_ShowString(0, 0, puc_oled, 16);
}
}
三、踩坑日记
(1)上拉电阻问题
- 做列扫描时,行对应的引脚需配置为带上拉电阻的输入模式;
- 做行扫描时,列对应的引脚需配置为带上拉电阻的输入模式;
🔅如果不带有上拉电阻,则在按键未按下的时候,输入引脚的电平处于不确定的状态,导致读入数据不稳定;而带有上拉电阻,可将引脚电平钳制为高电平,在按键按下时才变化为低电平;