STM32F103C8T6新手避坑:用扫描法搞定4x4矩阵键盘,附完整代码和OLED显示
2026/6/14 16:15:50 网站建设 项目流程

STM32F103C8T6矩阵键盘实战:从扫描法到OLED显示的完整指南

第一次接触STM32的矩阵键盘驱动时,我被那些满是寄存器操作的代码吓到了。作为跟着江科大教程自学的电子爱好者,我花了整整两天时间才搞明白如何用更易懂的方式实现4x4矩阵键盘的扫描。这篇文章将分享我的实战经验,重点介绍扫描法的实现细节GPIO模式切换的时机,以及如何将按键值实时显示在OLED上。

1. 矩阵键盘工作原理与硬件连接

矩阵键盘本质上是通过行列交叉点来识别按键的装置。4x4键盘由4行4列共16个按键组成,每个按键连接特定的行线和列线。当按键按下时,对应的行线和列线会导通。

硬件连接要点

  • 行线(ROW1-ROW4)连接到STM32的4个GPIO,配置为输入模式
  • 列线(COL1-COL4)连接到另外4个GPIO,配置为输出模式
  • 每个按键两端分别连接行线和列线

提示:建议使用宏定义来管理行列引脚,方便后期修改硬件连接

2. 扫描法实现详解

2.1 扫描法核心逻辑

扫描法的精髓在于动态切换GPIO的输入输出模式来定位按键位置。整个过程分为两个阶段:

  1. 行扫描阶段

    • 行引脚配置为下拉输入(默认低电平)
    • 列引脚配置为推挽输出并置高
    • 检测哪一行出现高电平
  2. 列扫描阶段

    • 切换行引脚为推挽输出并置高
    • 列引脚改为下拉输入
    • 检测哪一列出现高电平
// 行扫描阶段配置示例 void Key_Init_RowInput(void) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD; // 下拉输入 GPIO_InitStruct.GPIO_Pin = ROW_PINS; // 行引脚宏定义 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(KEY_PORT, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStruct.GPIO_Pin = COL_PINS; // 列引脚宏定义 GPIO_Init(KEY_PORT, &GPIO_InitStruct); GPIO_SetBits(KEY_PORT, COL_PINS); // 列输出高电平 }

2.2 消抖处理与按键识别

机械按键存在抖动现象,需要在检测到按键后加入适当的延时:

if(ROW1_INPUT == 1 || ROW2_INPUT == 1 || ROW3_INPUT == 1 || ROW4_INPUT == 1) { Delay_ms(10); // 消抖延时 if(ROW1_INPUT == 1) { // 切换到列扫描模式 Key_Init_ColInput(); Delay_ms(5); // 检测列引脚状态 if(COL1_INPUT == 1) key_value = 1; else if(COL2_INPUT == 1) key_value = 2; // ...其他列检测 } // ...其他行检测 }

3. 代码优化与可移植性设计

3.1 使用宏定义提高可读性

通过宏定义封装引脚配置和读取操作:

// key.h 文件中的宏定义 #define ROW_PINS (GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14) #define COL_PINS (GPIO_Pin_10 | GPIO_Pin_2 | GPIO_Pin_1 | GPIO_Pin_0) #define ROW1_INPUT GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) #define ROW2_INPUT GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) // ...其他行输入宏定义 #define COL1_INPUT GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) #define COL2_INPUT GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2) // ...其他列输入宏定义

3.2 状态机实现非阻塞扫描

为了避免while循环阻塞系统,可以使用状态机实现非阻塞扫描:

typedef enum { SCAN_ROW, SCAN_COL, DEBOUNCE, KEY_RELEASE } KeyScanState; KeyScanState key_state = SCAN_ROW; void Key_Scan_NonBlocking(void) { static uint8_t current_row = 0; switch(key_state) { case SCAN_ROW: // 行扫描逻辑 if(检测到按键) { key_state = DEBOUNCE; debounce_timer = 10; // 10ms消抖 } break; case DEBOUNCE: if(--debounce_timer == 0) { key_state = SCAN_COL; } break; // ...其他状态处理 } }

4. OLED显示集成与项目实战

4.1 OLED初始化与显示函数

将按键值显示到OLED上需要先初始化OLED模块:

// main.c 中的初始化 OLED_Init(); OLED_ShowString(1, 1, "Key Value:"); while(1) { Key_Scan(); if(key_pressed) { OLED_ShowNum(1, 10, key_value, 2); key_pressed = 0; // 清除按键标志 } }

4.2 完整项目流程

  1. 硬件初始化

    • 配置系统时钟
    • 初始化GPIO用于矩阵键盘
    • 初始化OLED显示
  2. 主循环逻辑

    • 调用键盘扫描函数
    • 检测按键并更新显示
    • 处理其他任务(如需要)
// 完整的主函数示例 int main(void) { // 硬件初始化 SystemInit(); Key_Init(); OLED_Init(); // 显示初始内容 OLED_ShowString(1, 1, "Matrix Key Demo"); OLED_ShowString(2, 1, "Press any key:"); while(1) { uint8_t key = Key_GetValue(); if(key != 0) { OLED_ShowNum(2, 15, key, 2); } // 可以添加其他任务... } }

5. 常见问题与调试技巧

5.1 扫描法不工作的排查步骤

  1. 检查硬件连接

    • 确认行列引脚连接正确
    • 检查上拉/下拉电阻配置
  2. 验证GPIO模式

    • 确保输入输出模式切换正确
    • 检查引脚时钟是否使能
  3. 调试技巧

    • 使用LED指示扫描过程
    • 通过串口打印调试信息

5.2 中断法实现的问题分析

原始文章中提到的中断法不工作可能有以下原因:

  • 中断优先级配置不当
  • 中断标志未正确清除
  • 电平检测时机不对
  • 硬件连接存在冲突

对于初学者,建议先从扫描法入手,掌握基本原理后再尝试中断法实现。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询