news 2026/4/23 14:00:00

51单片机_按键检测

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
51单片机_按键检测

51单片机_按键检测

一、独立按键介绍

轻触按键相当于是一种电子开关

按下时开关接通,松开时开关断开,实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开

由于机械点的弹性作用,按键开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开。因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定,一般为5ms到10ms。

按键稳定闭合时间的长短由操作人员的按键动作决定,一般为零点几秒至数秒。按键抖动会引起按键被误读多次。为了确保CPU对按键的依次闭合仅作一次处理,必须及时进行消抖。

按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更加简单,通常采用软件消抖。

软件消抖一般来说一个简单的按键消抖就是先读取按键的状态,如果得到按键按下之后,延时10ms,再次读取按键状态,如果按键还是按下状态,那么说明按键已经按下。其中的延时10ms就是软件消抖处理。

常用的软件去抖动方法:

  1. 先设置IO口为高电平,由于开发板IO有上拉电阻,默认IO为高电平;
  2. 读取IO口电平确认是否有按键按下;
  3. 如果有IO电平为低电平后,延时几个毫秒;
  4. 再读取该IO电平,如果仍为低电平,说明按键按下;
  5. 执行相应按键的程序;

二、独立按键检测原理

独立按键电路构成是由各个按键的一个管脚连接在一起接地,按键其它引脚分别接到单片机IO口。

单片机的IO口既可作为输出也可作为输入使用,当检测按键时用的是它的输入功能,独立按键的一端接地,另一端与单片机的某个IO口相连,开始时先给IO口赋一高电平,然后让单片机不断检测该IO口是否变为低电平,当按键闭合时,相当于该IO口通过按键与地相连,变成低电平,程序一旦检测到IO口变为低电平则说明按键被按下,然后执行相应的指令。

三、独立按键应用实践

1)实现按键控制LED功能

/* by 01130.hk - online tools website : 01130.hk/zh/json2get.html */ #include <REGX52.H> #include "Delay.h" /** * 主函数 */ void main() { while(1) { if(P3_1 == 0) // 如果K1按键按下,则为低电平 { Delay(20); // 延时消抖 while(P3_1 == 0); // 松手检测 Delay(20); // 延时消抖 P2_0 = ~P2_0; // LED1 取反 } } }

这段代码依旧存在不灵敏的情况,下面是改进:

/* by 01130.hk - online tools website : 01130.hk/zh/json2get.html */ #include <REGX52.H> #include "Delay.h" void main() { while(1) { if(P3_1 == 0) // 检测按键是否按下 { Delay(10); // 延时消抖 if(P3_1 == 0) // 再次确认按键确实按下 { P2_0 = ~P2_0; // 执行LED取反 // 等待按键释放,避免连按 while(P3_1 == 0); // 等待按键松开 } } } }

2)按键控制LED灯二进制显示

#include <REGX52.H> #include "Delay.h" void main() { unsigned char LEDNum=0; while(1) { if(P3_1==0) //如果K1按键按下 { Delay(10); //延时消抖 if(P3_1==0) { LEDNum++; //变量自增 P2=~LEDNum; //变量取反输出给LED while(P3_1==0); //松手检测 } } } } // 实现原理: // +000 LEDNum 0000 0000 取反 —> 1111 1111 // +001 LEDNum 0000 0001 取反 —> 1111 1110 // +002 LEDNum 0000 0010 取反 —> 1111 1101 // +003 LEDNum 0000 0011 取反 —> 1111 1100 // ··· // +255 LEDNum 1111 1111 取反 —> 0000 0000 // +256 溢出 -> LEDNum 0000 0000 重新开始计数

3)按键实现LED左右移动

#include <REGX52.H> #include "Delay.h" unsigned char LEDNum; void main() { P2=~0x01; //上电默认LED1点亮 0000 0001 取反 -> 1111 1110 while(1) { if(P3_0==0) //如果K1按键按下,右移 { Delay(20); while(P3_0==0); Delay(20); LEDNum++; //LEDNum自增 if(LEDNum>=8) //限制LEDNum自增范围 LEDNum=0; P2=~(0x01<<LEDNum); //LED的第LEDNum位点亮 } if(P3_1==0) //如果K2按键按下,左移 { Delay(20); while(P3_1==0); Delay(20); if(LEDNum==0) //LEDNum减到0后变为7 LEDNum=7; else //LEDNum未减到0,自减 LEDNum--; P2=~(0x01<<LEDNum); //LED的第LEDNum位点亮 } } }

P2=~(0x01<<LEDNum);实现原理如下:

LEDNum0x01 << LEDNum (二进制)取反后 (二进制)效果说明
00000 00011111 1110LED0亮,其他灭
10000 00101111 1101LED1亮,其他灭
20000 01001111 1011LED2亮,其他灭
30000 10001111 0111LED3亮,其他灭
40001 00001110 1111LED4亮,其他灭
50010 00001101 1111LED5亮,其他灭
60100 00001011 1111LED6亮,其他灭
71000 00000111 1111LED7亮,其他灭

四、矩阵按键

独立键盘与单片机连接时,每一个按键都需要单片机的一个I/O口。若某单片机系统需要较多按键,如果用独立按键便会占用过多的I/O口资源。

当用到多个按键时,为了减少I/O口引脚,引入了矩阵按键。比如4*4矩阵键盘。

对于4*4矩阵键盘,开发板上通常将16个按键排成4行4列。第一行将每个按键的一端连接在一起构成行线,第一列将每个按键的另一端连接在一起构成列线,这样便一共有4行4列共8根线。将这8根线连接到单片机的8个I/O口上,通过程序扫描键盘可以检测16个键。

无论是独立键盘还是矩阵键盘,单片机检测其是否被按下的依据都是一样的,即检测与该键对应的I/O口是否为低电平。独立键盘有一端固定为低电平而矩阵键盘两端都与单片机I/O口相连,在检测时需编程通过单片机I/O口送出低电平。检测方法最常用的是行列扫描和线翻转法。

  • 行列扫描法检测时,先送一列为低电平,其余几列全为高电平,此时确定列数;然后立即轮流检测一次各行是否有低电平,若检测到某一行为低电平,此时确定了行数,便可以确认当前被按下的键是哪一行哪一列的。用同样方法轮流送各列一次低电平,再轮流检测一次各行是否变为低电平,这样即可检测完所有的按键,当有按键被按下时便可判断出按下的键是哪一个键。
  • 线翻转法检测时,就是使所有行线为低电平时,检测所有列线是否有低电平,如果有,就记录列线值;然后再翻转,使所有列线都为低电平,检测所有行线的值。由于有按键按下,行线的值也会有变化,记录行线的值,从而就可以检测到全部按键。

矩阵按键也需要按键消抖。

以第一列代码作为示例,简单介绍下逐列扫描的检测原理:

  • P1=0xFF;把所有按键端口置为高电平,也就是关闭所有按键
  • P1_3=0;将第一列按键置为低电平,其他按键依旧为高电平(关闭状态)
  • 使用If判断检测行按键,如果某一行为低电平,就返回KeyNumber编号

其他3列按键的检测原理同上

五、矩阵按键应用实践

1)实现LCD1602显示按键编号

main.c

#include <REGX52.H> #include "Delay.h" //包含Delay头文件 #include "LCD1602.h" //包含LCD1602头文件 #include "MatrixKey.h" //包含矩阵键盘头文件 unsigned char KeyNum; void main() { LCD_Init(); //LCD初始化 LCD_ShowString(1,1,"MatrixKey:"); //LCD显示字符串 while(1) { KeyNum=MatrixKey(); //获取矩阵键盘键码 if(KeyNum) //如果有按键按下 { LCD_ShowNum(2,1,KeyNum,2); //LCD显示键码 } } }

Delay.c

void Delay(unsigned int xms) { unsigned char i, j; while(xms--) { i = 2; j = 239; do { while (--j); } while (--i); } }

Delay.h

#ifndef __DELAY_H__ #define __DELAY_H__ void Delay(unsigned int xms); #endif

LCD1602.c

#include <REGX52.H> //引脚配置: sbit LCD_RS=P2^6; sbit LCD_RW=P2^5; sbit LCD_EN=P2^7; #define LCD_DataPort P0 //函数定义: /** * @brief LCD1602延时函数,12MHz调用可延时1ms * @param 无 * @retval 无 */ void LCD_Delay() { unsigned char i, j; i = 2; j = 239; do { while (--j); } while (--i); } /** * @brief LCD1602写命令 * @param Command 要写入的命令 * @retval 无 */ void LCD_WriteCommand(unsigned char Command) { LCD_RS=0; LCD_RW=0; LCD_DataPort=Command; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602写数据 * @param Data 要写入的数据 * @retval 无 */ void LCD_WriteData(unsigned char Data) { LCD_RS=1; LCD_RW=0; LCD_DataPort=Data; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602设置光标位置 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @retval 无 */ void LCD_SetCursor(unsigned char Line,unsigned char Column) { if(Line==1) { LCD_WriteCommand(0x80|(Column-1)); } else if(Line==2) { LCD_WriteCommand(0x80|(Column-1+0x40)); } } /** * @brief LCD1602初始化函数 * @param 无 * @retval 无 */ void LCD_Init() { LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵 LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关 LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动 LCD_WriteCommand(0x01);//光标复位,清屏 } /** * @brief 在LCD1602指定位置上显示一个字符 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @param Char 要显示的字符 * @retval 无 */ void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char) { LCD_SetCursor(Line,Column); LCD_WriteData(Char); } /** * @brief 在LCD1602指定位置开始显示所给字符串 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串 * @retval 无 */ void LCD_ShowString(unsigned char Line,unsigned char Column,char *String) { unsigned char i; LCD_SetCursor(Line,Column); for(i=0;String[i]!='\0';i++) { LCD_WriteData(String[i]); } } /** * @brief 返回值=X的Y次方 */ int LCD_Pow(int X,int Y) { unsigned char i; int Result=1; for(i=0;i<Y;i++) { Result*=X; } return Result; } /** * @brief 在LCD1602指定位置开始显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~65535 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以有符号十进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:-32768~32767 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length) { unsigned char i; unsigned int Number1; LCD_SetCursor(Line,Column); if(Number>=0) { LCD_WriteData('+'); Number1=Number; } else { LCD_WriteData('-'); Number1=-Number; } for(i=Length;i>0;i--) { LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以十六进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~0xFFFF * @param Length 要显示数字的长度,范围:1~4 * @retval 无 */ void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i,SingleNumber; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { SingleNumber=Number/LCD_Pow(16,i-1)%16; if(SingleNumber<10) { LCD_WriteData(SingleNumber+'0'); } else { LCD_WriteData(SingleNumber-10+'A'); } } } /** * @brief 在LCD1602指定位置开始以二进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~1111 1111 1111 1111 * @param Length 要显示数字的长度,范围:1~16 * @retval 无 */ void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0'); } }

LCD1602.h

#ifndef __LCD1602_H__ #define __LCD1602_H__ //用户调用函数: void LCD_Init(); void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char); void LCD_ShowString(unsigned char Line,unsigned char Column,char *String); void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length); void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); #endif

MatrixKey.c

#include <REGX52.H> #include "Delay.h" /** * @brief 矩阵键盘读取按键键码 * @param 无 * @retval KeyNumber 按下按键的键码值 如果按键按下不放,程序会停留在此函数,松手的一瞬间,返回按键键码,没有按键按下时,返回0 */ unsigned char MatrixKey() { unsigned char KeyNumber=0; P1=0xFF; // 1111 1111 全部置高电平,没有按键按下 P1_3=0; // 检测第一列 if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;} if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;} if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;} if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;} P1=0xFF; // 1111 1111 全部置高电平,没有按键按下 P1_2=0; // 检测第二列 if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;} if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;} if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;} if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;} P1=0xFF; // 1111 1111 全部置高电平,没有按键按下 P1_1=0; // 检测第三列 if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;} if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;} if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;} if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;} P1=0xFF; // 1111 1111 全部置高电平,没有按键按下 P1_0=0; // 检测第四列 if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;} if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;} if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;} if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;} return KeyNumber; }

MatrixKey.h

#ifndef __MATRIXKEY_H__ #define __MATRIXKEY_H__ unsigned char MatrixKey(); #endif

2)使用矩阵键盘实现密码锁功能

只需要修改main.c ,其余代码同上

#include <REGX52.H> #include "Delay.h" #include "LCD1602.h" #include "MatrixKey.h" unsigned char KeyNum; unsigned int Password,Count; void main() { LCD_Init(); LCD_ShowString(1,1,"Password:"); while(1) { KeyNum=MatrixKey(); if(KeyNum) { if(KeyNum<=10) //如果S1~S10按键按下,输入密码 { if(Count<4) //如果输入次数小于4 { Password*=10; //密码左移一位 Password+=KeyNum%10; //获取一位密码 Count++; //计次加一 } LCD_ShowNum(2,1,Password,4); //更新显示 } if(KeyNum==11) //如果S11按键按下,确认 { if(Password==2345) //如果密码等于正确密码 { LCD_ShowString(1,14,"OK "); //显示OK Password=0; //密码清零 Count=0; //计次清零 LCD_ShowNum(2,1,Password,4); //更新显示 } else //否则 { LCD_ShowString(1,14,"ERR"); //显示ERR Password=0; //密码清零 Count=0; //计次清零 LCD_ShowNum(2,1,Password,4); //更新显示 } } if(KeyNum==12) //如果S12按键按下,取消 { Password=0; //密码清零 Count=0; //计次清零 LCD_ShowNum(2,1,Password,4); //更新显示 } } } }

Password*=10; //密码左移一位

Password+=KeyNum%10; //获取一位密码

图表:按键序列 5 → 6 → 3 → 8 的处理过程

按键运算前 PasswordPassword × 10KeyNum % 10运算后 Password解释
500 × 10 = 05 % 10 = 55第一个数字直接存入
655 × 10 = 506 % 10 = 6565移到十位,6放在个位
35656 × 10 = 5603 % 10 = 356356变为560,3放在个位
8563563 × 10 = 56308 % 10 = 85638563变为5630,8放在个位
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 9:16:24

Llama Factory联邦学习:分布式数据下的隐私保护微调

Llama Factory联邦学习&#xff1a;分布式数据下的隐私保护微调 为什么需要联邦学习&#xff1f; 在医疗领域&#xff0c;各分院积累了大量有价值的患者数据&#xff0c;但受限于隐私法规&#xff08;如HIPAA、GDPR&#xff09;&#xff0c;这些数据无法集中共享。传统集中式训…

作者头像 李华
网站建设 2026/4/22 21:22:08

基于springboot的医院综合管理系统实现与设计

摘 要 伴随着我国社会的发展&#xff0c;人民生活质量日益提高。于是对医院综合管理进行规范而严格是十分有必要的&#xff0c;所以许许多多的信息管理系统应运而生。此时单靠人力应对这些事务就显得有些力不从心了。所以本论文将设计一套医院综合管理系统&#xff0c;帮助医院…

作者头像 李华
网站建设 2026/4/23 10:29:02

面对一个完全不熟悉的系统,你如何测试?

当面对一个完全不熟悉的系统时&#xff0c;如何确保测试的顺利进行&#xff1f; 需要先花一些时间来了解系统&#xff0c;阅读相关的文档、用户手册或者询问其他人对系统的了解&#xff0c;这样就能对系统的功能、架构和主要组件有个大致的了解。 接下来&#xff0c;仔细研究…

作者头像 李华
网站建设 2026/4/23 13:00:13

在做自动化测试之前你必须要知道的事

做测试好几年了&#xff0c;真正学习和实践自动化测试一年&#xff0c;自我感觉这一个年中收获许多。 我们更普遍的认识把“自动化测试”看做“ 基于产品或项目UI层的自动化测试”。 UI层的自动化测试&#xff0c;这个大家应该再熟悉不过了&#xff0c;大部分测试人员的大部分…

作者头像 李华
网站建设 2026/4/23 12:52:02

导师严选2026 AI论文网站TOP10:继续教育必备测评与推荐

导师严选2026 AI论文网站TOP10&#xff1a;继续教育必备测评与推荐 2026年学术AI写作工具测评&#xff1a;精准匹配继续教育需求 在人工智能技术快速发展的背景下&#xff0c;AI论文网站已成为高校师生、研究人员以及持续学习者的重要辅助工具。然而&#xff0c;面对市场上琳琅…

作者头像 李华
网站建设 2026/4/23 8:21:30

救命神器 9款一键生成论文工具测评:本科生毕业论文必备神器

救命神器 9款一键生成论文工具测评&#xff1a;本科生毕业论文必备神器 2026年学术写作工具测评&#xff1a;为何值得一看&#xff1f; 随着高校教育对论文质量要求的不断提升&#xff0c;越来越多本科生在撰写毕业论文时面临时间紧、任务重、格式复杂等多重压力。面对这些挑战…

作者头像 李华