Extend module 01:Keyboard
目录
一、Keyboard
(1)资源介绍
🔅原理图
🔅扫描原理
(2)STM32CubeMX 软件配置
(3)代码编写
(4)实验现象
二、Keyboard接口函数封装
三、踩坑日记
(1)上拉电阻问题
一、Keyboard
(1)资源介绍
🔅原理图
蓝桥杯物联网竞赛实训平台提供了一个拓展接口 EXTEND MODULE,所有拓展模块均可直接安装在 Lora 终端上使用;

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

通过两张电路图连接可知,引脚资源配置情况为:
Keyboard | MCU |
---|---|
COLUMN_1 | PB3 |
COLUMN_2 | PA15 |
COLUMN_3 | PA0 |
ROW_1 | PA12 |
ROW_2 | PA11 |
🔅扫描原理
1️⃣行扫描:所有行线作为输入端口,列线作为输出端口。逐个拉低列线输出,检测对应行是否读到低电平,若有即可确定按键触发位置;
2️⃣列扫描:与行扫描相反。所有列线作为输入端口,行线作为输出端口。逐个拉低行线输出,检测对应列是否读到低电平,从而确定按键触发位置;
本文将通过列扫描方式使用矩阵键盘模块;
(2)STM32CubeMX 软件配置
🔅“工程建立、时钟树配置、Debug 串行线配置、代码生成配置” 在下文中有讲解,这里不再赘述❗️
【蓝桥杯——物联网设计与开发】Part1:GPIOhttps://blog.csdn.net/m0_63116406/article/details/144894900?spm=1001.2014.3001.55011️⃣点击引脚 PA12 和 PA11 → 选择 GPIO_Input 模式;
2️⃣点击"System Core" → "GPIO", 将 PA12 和 PA11 的 "GPIO Pull-up/Pull-down" 栏修改为 "Pull-up" ,即将 PA12 和 PA11 引脚初始化为带上拉电阻的输入引脚;

3️⃣点击引脚 PA0 、 PA15 和 PB3 → 选择 GPIO_Output 模式;
4️⃣点击"System Core" → "GPIO", 将 PA0 、 PA15 和 PB3 的"GPIO output level" 栏修改为 "High",即将 PA0 、 PA15 和 PB3 引脚初始化为高电平;

5️⃣配置 OLED;
6️⃣生成代码即可;
(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_val = 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_I2C1_Init();
/* USER CODE BEGIN 2 */
OLED_Init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
key_val = ' ';
/* 按键扫描 */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET); // 第一列
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0)
{
HAL_Delay(10); // 延时消抖
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0)
{
key_val = '1';
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0); // 抬手检测
}
}
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0)
{
HAL_Delay(10); // 延时消抖
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0)
{
key_val = '4';
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0); // 抬手检测
}
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET); // 第二列
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0)
{
HAL_Delay(10); // 延时消抖
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0)
{
key_val = '2';
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0); // 抬手检测
}
}
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0)
{
HAL_Delay(10); // 延时消抖
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0)
{
key_val = '5';
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0); // 抬手检测
}
}
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); // 第三列
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0)
{
HAL_Delay(10); // 延时消抖
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0)
{
key_val = '3';
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0); // 抬手检测
}
}
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0)
{
HAL_Delay(10); // 延时消抖
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0)
{
key_val = '6';
while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0); // 抬手检测
}
}
/* 键值显示 */
sprintf((char*)puc_oled, " Key value:%c ", key_val);
OLED_ShowString(0, 0, puc_oled, 16);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
(4)实验现象
按下按键后,OLED 屏幕上会显示对应键值。
二、Keyboard接口函数封装
🟡️Keyboard扫描函数
uint8_t key_read(void)
{
uint8_t key_val = 0;
/* 按键扫描 */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET); // 第一列
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0)
key_val = '1';
else if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0)
key_val = '4';
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET); // 第二列
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0)
key_val = '2';
else if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0)
key_val = '5';
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); // 第三列
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_12) == 0)
key_val = '3';
else if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_11) == 0)
key_val = '6';
return key_val;
}
🔴Keyboard接口函数调用实例
/* 按键处理任务函数 */
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, " Key value:%c ", key_down);
OLED_ShowString(0, 0, puc_oled, 16);
}
}
三、踩坑日记
(1)上拉电阻问题
- 做列扫描时,行对应的引脚需配置为带上拉电阻的输入模式;
- 做行扫描时,列对应的引脚需配置为带上拉电阻的输入模式;
🔅如果不带有上拉电阻,则在按键未按下的时候,输入引脚的电平处于不确定的状态,导致读入数据不稳定;而带有上拉电阻,可将引脚电平钳制为高电平,在按键按下时才变化为低电平;