当前位置: 首页 > news >正文

STC8单片机矩阵按键控制的功能实现

STC8 单片机矩阵按键控制:从原理到功能实现(附完整代码)

在单片机项目中,当需要多个按键(如 6 个以上)时,传统 “一对一 IO 口” 的独立按键方案会严重浪费 IO 资源。而矩阵按键通过 “行 / 列交叉扫描”,仅需N+M个 IO 口即可实现N×M个按键功能(如 4 行 4 列仅需 8 个 IO,实现 16 个按键),是高按键数量场景的最优选择。

一、矩阵按键核心原理(先搞懂再动手)

矩阵按键由 “行线” 和 “列线” 交叉组成,按键两端分别接在一条行线和一条列线上(如 4×4 矩阵:4 条行线 + 4 条列线,共 16 个交叉点,对应 16 个按键)。其检测核心是 **“逐行拉低 + 逐列检测”**:

  1. 行线控制:将某一行线拉低(输出低电平),其他行线拉高(输出高电平);

  2. 列线检测:读取所有列线的电平,若某一列线为低电平,说明 “当前拉低的行线” 与 “该列线” 交叉处的按键被按下;

  3. 循环扫描:依次拉低每一行,重复步骤 2,即可检测所有按键的按下状态。

以 4×4 矩阵为例,若 “行 2 拉低时,列 3 检测到低电平”,则对应按键为 “行 2 列 3”(需提前定义按键编号与行 / 列的对应关系)。

二、项目硬件清单与接线(4×4 矩阵为例)

STC8 单片机 IO 口充足,推荐选择 P1 口(8 个 IO)实现 4×4 矩阵按键(4 行 + 4 列),硬件成本极低,仅需按键、杜邦线和面包板:

硬件模块规格 / 型号作用说明
STC8 单片机STC8A8K64U(或同系列)主控,实现按键扫描与功能控制
矩阵按键4×4 薄膜按键(或独立按键搭建)输入设备,提供 16 个按键控制信号
面包板 + 杜邦线通用规格搭建电路,连接单片机与按键
上拉电阻(可选)10kΩ(4 个)若行 / 列线无内部上拉,需外接确保电平稳定

硬件接线图(关键!按此接线避免错误)

STC8 的 P1 口分为 “行线” 和 “列线”,建议将高 4 位设为行线(输出)低 4 位设为列线(输入),接线如下:

STC8 单片机引脚矩阵按键引脚角色备注
P34行 1(Row1)输出控制该行电平(拉低 / 拉高)
P35行 2(Row2)输出-
P40行 3(Row3)输出-
P41行 4(Row4)输出-
P03列 1(Col1)输入检测该列电平(判断按键是否按下)
P06列 2(Col2)输入-
P07列 3(Col3)输入-
P17列 4(Col4)输入-
GND矩阵按键 GND接地所有按键的公共端需接地

注意:STC8 的 IO 口可配置为 “准双向口”(默认),准双向口作输入时需先拉高(避免电平不确定),因此列线无需额外上拉电阻(软件拉高即可)。

三、软件核心实现(从扫描到功能)

矩阵按键的软件核心是 **“消抖 + 扫描 + 按键编码”**:消抖避免按键机械抖动导致误触发,扫描获取按键位置,编码将 “行 / 列坐标” 转为便于使用的按键编号(如 0-15)。以下代码基于 Keil C51 开发,兼容 STC8 全系列单片机。

1. 第一步:基础定义与消抖函数(避免误触发)

按键按下时会有 10-20ms 的机械抖动(电平反复跳变),需通过 “延时消抖” 或 “多次检测” 确保按键状态稳定。这里采用 “延时消抖”(简单易懂,适合新手)。

代码文件:MatrixKey.h(头文件)
#ifndef __MATRIX_KEYS__
#define __MATRIX_KEYS__#include "GPIO.h"#define MK_USE_DOWN 	1
#define MK_USE_UP	 	1
// 只是做声明,用户使用的时候,相应开关打开,函数同时一定要定义
// 按下的回调函数
void MK_on_keydown(u8 row, u8 col);
// 抬起的回调函数
void MK_on_keyup(u8 row, u8 col);// 初始化
void MK_init();// 扫描按键
void MK_scan();#endif
代码文件:MatrixKey.c(延时消抖函数)//在按键后面加个NOP10();就好
// 扫描按键
void MK_scan() {u8 r, c;for (r = 0; r < 4; r++) { // 检查行  r = 0~3NOP2(); // 可选的延时,可以不写row_out(r); // 设置行引脚for (c = 0; c < 4; c++) { // 列if (IS_KEY_UP(r, c) && col_in(c) == DOWN) { // 上一次抬起,当前按下,按下才有效SET_KEY_DOWN(r, c);  // 保存状态// printf("第 %d 行第 %d 列 按下\n", (int)(r+1), (int)(c+1));#if MK_USE_DOWNMK_on_keydown(r, c);#endif} else if (IS_KEY_DOWN(r, c) && col_in(c) == UP) { // 上一次按下,当前抬起,抬起才有效SET_KEY_UP(r, c);// printf("第 %d 行第 %d 列 抬起\n", (int)(r+1), (int)(c+1));#if MK_USE_UPMK_on_keyup(r, c);#endif}}	}
}

2. 第二步:矩阵按键扫描函数(核心算法)

扫描函数通过 “逐行拉低→逐列检测” 实现按键位置识别,步骤如下:

  1. 初始化行线(全部拉高)、列线(全部拉高,确保输入电平稳定);

  2. 逐行拉低当前行,其他行保持拉高;

  3. 检测所有列线,若某列电平为低,说明对应按键按下,进行消抖后返回按键编号;

  4. 若所有行扫描完成无低电平,返回 “无按键”(KEY_NONE)。

代码片段:MatrixKey.c(扫描函数实现)
#include "MatrixKeys.h"#define		COL1	P03  // 列引脚
#define		COL2	P06
#define		COL3	P07
#define		COL4	P17
#define		ROW1	P34	 // 行引脚
#define		ROW2	P35
#define		ROW3	P40
#define		ROW4	P41#define		DOWN	0
#define		UP		1u16 states = 0xffff;  // 每个按键都是1,都是抬起#define  IS_KEY_UP(r, c)   		((states >> (r*4+c) & 1) == 1)   // 取出第n位,判断是1
#define  IS_KEY_DOWN(r, c) 		((states >> (r*4+c) & 1) == 0)   // 取出第n位,判断是0
#define  SET_KEY_UP(r, c)   	(states |= (1 << (r*4+c)))    // 第n位置1
#define  SET_KEY_DOWN(r, c) 	(states &= ~(1 << (r*4+c)))  // 第n位置0void row_out(u8 r) {COL4 = COL3 = COL2 = COL1 = 1;ROW1 = r == 0 ? 0 : 1;ROW2 = r == 1 ? 0 : 1;ROW3 = r == 2 ? 0 : 1;ROW4 = r == 3 ? 0 : 1;
}u8 col_in(u8 c) {if (c == 0) return COL1; // 返回列引脚的电平的值if (c == 1) return COL2;if (c == 2) return COL3;if (c == 3) return COL4;return 0;
}// 初始化
void MK_init() {// P03 06 07P0_MODE_IO_PU(GPIO_Pin_3 | GPIO_Pin_6 | GPIO_Pin_7);// P17P1_MODE_IO_PU(GPIO_Pin_7);// P34 35P3_MODE_IO_PU(GPIO_Pin_4 | GPIO_Pin_5);// P40 41P4_MODE_IO_PU(GPIO_Pin_0 | GPIO_Pin_1);
}// 扫描按键
void MK_scan() {u8 r, c;for (r = 0; r < 4; r++) { // 检查行  r = 0~3NOP2(); // 可选的延时,可以不写row_out(r); // 设置行引脚for (c = 0; c < 4; c++) { // 列if (IS_KEY_UP(r, c) && col_in(c) == DOWN) { // 上一次抬起,当前按下,按下才有效SET_KEY_DOWN(r, c);  // 保存状态// printf("第 %d 行第 %d 列 按下\n", (int)(r+1), (int)(c+1));#if MK_USE_DOWNMK_on_keydown(r, c);#endif} else if (IS_KEY_DOWN(r, c) && col_in(c) == UP) { // 上一次按下,当前抬起,抬起才有效SET_KEY_UP(r, c);// printf("第 %d 行第 %d 列 抬起\n", (int)(r+1), (int)(c+1));#if MK_USE_UPMK_on_keyup(r, c);#endif}}	}
}

优化说明:代码中 “等待按键释放”(

while(COLx == 0)

)可避免长按导致的重复触发,若需要 “长按连发” 功能,可删除该句,并在主函数中通过定时判断长按时间。

3. 第三步:主函数实现(按键功能控制)

主函数通过循环调用扫描函数获取按键状态,再根据按键编号执行对应功能(这里以 “控制 LED 亮灭” 为例,实际可替换为 “调节屏幕显示、控制电机、设置参数” 等功能)。

代码文件:main.c(功能实现)
#include    "GPIO.h"
#include	"Delay.h"
#include 	"UART.h"	// 串口配置 UART_Configuration
#include 	"NVIC.h"	// 中断初始化NVIC_UART1_Init
#include 	"Switch.h"  // 引脚切换 UART1_SW_P30_P31#include "MatrixKeys.h"void GPIO_config() { GPIO_InitTypeDef info;// ===== UART1  P30  P31  准双向info.Mode = GPIO_PullUp; 				// 准双向info.Pin = GPIO_Pin_0 | GPIO_Pin_1;   	// 引脚GPIO_Inilize(GPIO_P3, &info);
}// 串口配置函数的定义
void UART_config(void) {// >>> 记得添加 NVIC.c, UART.c, UART_Isr.c <<<COMx_InitDefine		COMx_InitStructure;					//结构定义COMx_InitStructure.UART_Mode      = UART_8bit_BRTx;	//模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTxCOMx_InitStructure.UART_BRT_Use   = BRT_Timer1;			//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)COMx_InitStructure.UART_BaudRate  = 115200ul;			//波特率, 一般 110 ~ 115200COMx_InitStructure.UART_RxEnable  = ENABLE;				//接收允许,   ENABLE或DISABLECOMx_InitStructure.BaudRateDouble = DISABLE;			//波特率加倍, ENABLE或DISABLEUART_Configuration(UART1, &COMx_InitStructure);		//初始化串口1 UART1,UART2,UART3,UART4NVIC_UART1_Init(ENABLE,Priority_1);		//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3UART1_SW(UART1_SW_P30_P31);		// 引脚选择, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
}// 按下的回调函数
void MK_on_keydown(u8 r, u8 c) {
//	printf("第 %d 行第 %d 列 按下\n", (int)(r+1), (int)(c+1));if (r == 0 && c == 0) { // 从0开始计算printf("===========key1按下=========\n");} else if ((r+1) == 4 && (c+1) == 4) { // 从1开始printf("===========key16按下=========\n");}
}// 抬起的回调函数
void MK_on_keyup(u8 r, u8 c) {
//	printf("第 %d 行第 %d 列 抬起\n", (int)(r+1), (int)(c+1));if (r == 0 && c == 0) {printf("===========key1抬起=========\n");} else if ((r+1) == 4 && (c+1) == 4) {printf("===========key16抬起=========\n");}
}void main() {EA = 1; // 使能中断总开关GPIO_config(); // GPIO配置UART_config(); // 串口配置MK_init();  // 矩阵按键while (1){MK_scan();delay_ms(20);}}

四、常见问题排查(避坑指南)

  1. 所有按键无响应
  • 检查接线:行线 / 列线是否接反,GND 是否接好;

  • 检查 IO 口方向:行线是否设为输出,列线是否先拉高(准双向口输入需先拉高);

  • 测试延时函数:若延时过短,消抖不彻底,可适当增加消抖时间(如改为 30ms)。

  1. 按键误触发(按下 A 键触发 B 键)
  • 排查硬件短路:行线与列线是否有短路(面包板接触不良可能导致);

  • 优化扫描逻辑:确保 “扫描完一行后恢复为高电平”(避免相邻行干扰)。

  1. 按键按下后无释放(一直触发)
  • 检查 “等待按键释放” 代码:若删除了while(COLx == 0),需在主函数中增加 “按键释放检测”;

  • 检查按键机械故障:更换按键或清理按键触点(避免触点粘连)。

五、功能拓展(从基础到实用)

  1. 长按功能:在扫描函数中记录 “按键按下时间”,若按下时间超过 500ms,判定为长按,执行对应功能(如长按 “上键” 快速调节数值);

  2. 组合按键:检测多个按键同时按下(如 “KEY_0+KEY_1”),执行特殊功能(如系统复位);

  3. 中断扫描:将列线设为外部中断引脚,仅当列线电平变化时触发中断,再扫描行线,降低 CPU 占用率(适合多任务场景);

  4. 结合屏幕显示:将按键功能与前文的 ISP/OLED 屏幕结合,如按下按键切换显示界面、修改时间参数等。

http://www.lryc.cn/news/625049.html

相关文章:

  • 分治-归并-493.翻转对-力扣(LeetCode)
  • Flutter 自定义 Switch 切换组件完全指南
  • Python 面向对象三大特性详解(与 C++ 对比)
  • Android Handler 线程执行机制
  • flutter项目适配鸿蒙
  • 【展厅多媒体】互动地砖屏怎么提升展厅互动感的?
  • 2025年最新美区Apple ID共享账号免费分享(持续更新)
  • 数组学习2
  • Java面试题储备14: 使用aop实现全局日志打印
  • 【HTML】document api
  • Vue 3中watch的返回值:解锁监听的隐藏技巧
  • C++---有符号和无符号整数的位移操作
  • RabbitMQ:数据隔离
  • kafka 冲突解决 kafka安装
  • Unity进阶--C#补充知识点--【Unity跨平台的原理】Mono与IL2CPP
  • 探索性测试:灵活找Bug的“人肉探测仪”
  • MongoDB Windows 系统实战手册:从配置到数据处理入门
  • keil错误:Error: failed to execute ‘D:\Keil\C51\BIN\BIN\A51.EXE‘
  • 【智慧工地源码】智慧工地云平台系统,涵盖安全、质量、环境、人员和设备五大管理模块,实现实时监控、智能预警和数据分析。
  • PYTHON让繁琐的工作自动化-猜数字游戏
  • 从数据汇总到高级分析,SQL 查询进阶实战(下篇)—— 分组、子查询与窗口函数全攻略
  • 车e估牵头正式启动乘用车金融价值评估师编制
  • CoRL 2025|隐空间扩散世界模型LaDi-WM大幅提升机器人操作策略的成功率和跨场景泛化能力
  • 从「行走」到「思考」:机器人进化之路与感知—决策链路的工程化实践
  • 第4.3节:awk正则表达式详解-特殊字符
  • Pytest测试框架基础及进阶
  • 前端css学习笔记7:各种居中布局空白问题
  • Jenkins全链路教程——Jenkins调用Maven构建项目
  • IoT/透过oc_lwm2m和at源码,分析NB-IoT通信模组和主板MCU之间的通信过程
  • 【Jenkins】03 - 自动构建和docker构建