目录
[3.0 独立按键原理图](#3.0 独立按键原理图)
[3.1 项目示例1:独立按键控制LED亮灭](#3.1 项目示例1:独立按键控制LED亮灭)
[✅ 效果:按下按键1,LED点亮;松开按键1,LED熄灭](#✅ 效果:按下按键1,LED点亮;松开按键1,LED熄灭)
[3.2 项目示例2:独立按键控制LED状态](#3.2 项目示例2:独立按键控制LED状态)
[✅ 效果:按一次按键1,LED 状态切换一次](#✅ 效果:按一次按键1,LED 状态切换一次)
[3.3 项目示例3:独立按键控制LED显示二进制](#3.3 项目示例3:独立按键控制LED显示二进制)
[✅ 效果:按一次按键1, 松开后, 8 个 LED 灯会逐次多点亮 1 个;](#✅ 效果:按一次按键1, 松开后, 8 个 LED 灯会逐次多点亮 1 个;)
[3.4 项目示例4: 独立按键控制LED移位](#3.4 项目示例4: 独立按键控制LED移位)
[✅ 效果:按下按键1LED循环右移,按下按键2LED循环左移](#✅ 效果:按下按键1LED循环右移,按下按键2LED循环左移)
[3.5 模块化--独立按键](#3.5 模块化--独立按键)
[3.5.1 查询式延时阻塞消抖](#3.5.1 查询式延时阻塞消抖)
[✅ key.c](#✅ key.c)
[✅ key.h](#✅ key.h)
[3.5.2 中断式无延时消抖](#3.5.2 中断式无延时消抖)
[✅ key.c](#✅ key.c)
[✅ key.h](#✅ key.h)
3.0 独立按键原理图

51 单片机引脚电平规则 :未外接电路时,I/O 口默认为 高电平 1 ,按键按下接地则变为 低电平 0。
3.1 项目示例1:独立按键控制LED亮灭
✅ 效果: 按下按键1,LED点亮;松开按键1,LED熄灭
cpp
#include <REGX52.H> //52单片机寄存器定义头文件
void main()
{
while(1) //循环检测按键状态
{
if(P3_1==0) {P2_0=0;} //P3_1按键按下,点亮P2_0的LED
else {P2_0=1;} //按键松开,熄灭P2_0的LED
}
}
3.2 项目示例2: 独立按键控制LED状态
✅ 效果: 按一次按键1,LED 状态切换一次
cpp
#include <REGX52.H> // 引入52系列单片机寄存器定义头文件,定义了P0-P3/I/O口、延时相关寄存器等所有硬件地址
// 毫秒级延时函数 函数名:Delay,形参xms=需要延时的毫秒数 晶振频率标注:@12MHz(标准51单片机常用晶振)
void Delay(unsigned int xms)
{
unsigned char i, j; // 定义两个无符号字符型局部变量,用于延时循环计数
while(xms) // 外层循环:实现 传入的xms 毫秒延时,xms减到0时延时结束
{
i = 2; // 固定赋值,是12MHz晶振下,实现1ms精准延时的核心参数
j = 239; // 固定赋值,配合i的数值,组成1ms延时的内层循环
do
{
while (--j); // j自减循环,空指令消耗单片机时钟周期,实现短延时
} while (--i); // i自减,当i=0时,完成1ms延时
xms--; // 每完成1ms延时,总延时毫秒数减1
}
}
// 主函数:程序的唯一入口,51单片机所有代码从main()开始执行
void main()
{
while(1) // 死循环:让{}内的按键检测逻辑 无限循环执行,单片机持续检测按键状态,永不停止
{
if(P3_1 == 0) // 检测P3_1引脚电平:如果等于0 → 代表P3_1引脚的按键被按下
{
Delay(20); // 按键消抖第一步:延时20ms,跳过按键按下瞬间的机械抖动(电平跳变)阶段
while(P3_1 == 0); // 松手检测:如果按键一直按住,程序会卡在这里,直到按键松开(P3_1=1)
Delay(20); // 按键消抖第二步:延时20ms,跳过按键松开瞬间的机械抖动阶段
P2_0=~P2_0; // 核心功能:对P2_0引脚的电平进行【按位取反】,实现LED亮/灭状态翻转
}
}
}
按键消抖的完整逻辑:
检测到按键按下(P3_1=0) → 第一步延时20ms → 松手检测 → 第二步延时
3.3 项目示例3: 独立按键控制LED显示二进制
✅ 效果: 按一次按键1 → 松开后, 8 个 LED 灯会逐次多点亮 1 个 ;
cpp
#include <REGX52.H> // 引入52系列单片机寄存器定义头文件,定义P0-P3口/寄存器等硬件地址
// 毫秒级延时函数,形参xms=延时毫秒数,晶振频率@12MHz(标准51单片机常用晶振),实现精准xms毫秒延时
void Delay(unsigned int xms)
{
unsigned char i, j; // 定义uchar型局部变量,用于延时循环消耗时钟周期
while(xms) // 外层循环:控制总延时毫秒数,每执行一次内层循环=1ms,xms自减到0结束
{
i = 2;
j = 239;
do
{
while (--j); // 内层双层空循环,精准消耗1ms的时钟周期
} while (--i);
xms--; // 完成1ms延时,总延时数-1
}
}
void main()
{
// 51单片机上电后,所有I/O口默认输出高电平,等价于执行 P2 = 0xFF; 此时8个LED全灭
unsigned char LEDstate = 0; // 定义uchar型LED状态变量,初始值0,用于控制LED点亮状态
while(1) // 死循环,持续检测按键,永不停止
{
if(P3_1==0) // 检测P3_1引脚按键是否按下(按键按下→引脚接地→电平为0)
{
Delay(20); // 按键消抖:延时20ms跳过按下的机械抖动期
while(P3_1==0); // 松手检测:按住按键则卡在此行,松开按键才继续执行
Delay(20); // 按键消抖:延时20ms跳过松开的机械抖动期
LEDstate++; // LED状态变量自增1,改变点亮的LED组合
P2 = ~LEDstate; // 核心逻辑:状态值按位取反后赋值给P2口,低电平点亮LED
}
}
}
3.4 项目示例4: 独立按键控制LED移位
✅ 效果:按下按键1LED循环右移,按下按键2LED循环左移
- 上电默认点亮 P2_0****这 1 个 LED 灯;
- 按下 P3_1 按键**→ 松开后,LED 灯** 向右循环移位**(P20→P21→P22→...→P27→回到 P20);**
- 按下 P3_0 按键**→ 松开后,LED 灯** 向左循环移位**(P20→P27→P26→...→P21→回到 P20);**
cpp
#include <REGX52.H> // 引入52单片机寄存器定义头文件,定义P0/P1/P2/P3口等所有硬件寄存器地址
// 毫秒级延时函数 @12MHz晶振 标准写法,形参xms为需要延时的毫秒数,实现精准延时
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms) // 外层循环:控制总延时毫秒数,每执行一次内层循环=精准1ms延时
{
i = 2;
j = 239;
do
{
while (--j); // 内层双层空循环,消耗单片机时钟周期,实现1ms延时
} while (--i);
xms--; // 完成1ms延时,总延时节数减1
}
}
unsigned char LEDNum; // 定义全局变量,存放LED点亮的位序号;全局变量默认初始化赋值为0,无需手动写=0
// 放在主函数外的优势:全局作用域,整个程序都能调用,比局部变量更适合做状态记录
void main()
{
P2 = ~(0x01); // 上电初始化:默认点亮P2口的第一个LED灯(P2_0),程序上电就亮灯
while(1) // 死循环,持续循环检测P3_1和P3_0两个按键,永不停止
{
// 按键1(P3_1引脚)按下:实现 LED灯 向右循环移位点亮 (P2_0→P2_1→P2_2→...→P2_7→P2_0)
if(P3_1==0)
{
Delay(20); // 按键消抖:延时20ms跳过按下的机械抖动,过滤虚假电平
while(P3_1==0); // 松手检测:按键按住不放则卡在此行,松开按键才继续执行
Delay(20); // 按键消抖:延时20ms跳过松开的机械抖动,确保是真实按键动作
LEDNum++; // LED位序号+1,实现向右移位
if(LEDNum>=8) // 临界值判断:因为P2口只有8个LED(P2_0~P2_7),序号到7为止
{
LEDNum=0; // 溢出后归零,实现【循环移位】,不会乱码
}
P2 = ~(0x01<<LEDNum); // 核心移位逻辑:控制指定序号的LED点亮
}
// 按键2(P3_0引脚)按下:实现 LED灯 向左循环移位点亮 (P2_0→P2_7→P2_6→...→P2_1→P2_0)
if(P3_0==0)
{
Delay(20); // 按键消抖
while(P3_0==0); // 松手检测
Delay(20); // 按键消抖
if(LEDNum==0) // 临界值判断:如果当前是第一个灯(P2_0)
{
LEDNum=7; // 直接跳转到最后一个灯(P2_7),实现向左循环
}else
{
LEDNum--; // 不是第一个灯,序号-1,实现向左移位
}
P2 = ~(0x01<<LEDNum); // 核心移位逻辑:控制指定序号的LED点亮
}
}
}
✔ 知识点 1 : << 是 C 语言的 左移运算符 ,作用是把一个数的 二进制位向左移动指定的位数,低位补 0,高位舍弃。

3.5 模块化--独立按键
3.5.1 查询式延时阻塞消抖

✅ key.c
cpp
#include <REGX52.H> // 51单片机寄存器定义头文件,必须包含
#include "Delay.h" // 包含延时函数头文件,本代码核心依赖延时实现消抖
/**
* @brief 独立按键扫描函数 - 带延时消抖+松手检测
* @param 无
* @retval 按键键码,返回0表示无按键按下;1=K1,2=K2,3=K3,4=K4
* @note 查询式阻塞写法,入门经典款;按键为低电平有效,按住按键会阻塞程序,松手触发一次有效按键
*/
unsigned char Key()
{
unsigned char KeyNumber=0; // 定义按键键码变量,初始化为0(默认无按键)
// 检测K1按键 P3_1引脚,按下则执行消抖+松手检测
if(P3_1==0)
{
Delay(20); // 延时20ms,跳过按键按下时的机械抖动阶段
while(P3_1==0); // 松手检测:死循环等待,直到按键松开(引脚恢复高电平)
Delay(20); // 延时20ms,跳过按键松开时的机械抖动阶段
KeyNumber=1; // 确认按键有效,赋值键码1
}
// 检测K2按键 P3_0引脚,逻辑同上
if(P3_0==0)
{
Delay(20);
while(P3_0==0);
Delay(20);
KeyNumber=2;
}
// 检测K3按键 P3_2引脚,逻辑同上
if(P3_2==0)
{
Delay(20);
while(P3_2==0);
Delay(20);
KeyNumber=3;
}
// 检测K4按键 P3_3引脚,逻辑同上
if(P3_3==0)
{
Delay(20);
while(P3_3==0);
Delay(20);
KeyNumber=4;
}
return KeyNumber; // 返回最终的按键键码,无按键则返回初始值0
}
✅ key.h
cpp
#ifndef __KEY_H__
#define __KEY_H__
unsigned char Key();
#endif
3.5.2 中断式无延时消抖
✅ key.c
cpp
#include <REGX52.H>
#include "Delay.h" // 包含延时头文件(本文件暂未用到,预留兼容)
unsigned char Key_KeyNumber; // 全局变量:存储最终【有效按键键码】,0表示无按键按下
/**
* @brief 按键键码读取接口函数(给主函数调用)
* @param 无
* @retval 返回值:0表示无按键按下,1~4分别对应K1~K4按键
* @note 核心特性:读取一次后会立即清零键码变量,防止主函数重复读取同一个按键值
*/
unsigned char Key(void)
{
unsigned char Temp=0; // 定义临时变量,缓存当前有效按键值
Temp=Key_KeyNumber; // 将全局变量中存储的有效键码赋值给临时变量
Key_KeyNumber=0; // 清零全局按键变量,避免按键值被重复读取
return Temp; // 返回按键键码给主函数
}
/**
* @brief 按键实时状态读取函数(底层函数,仅读取硬件电平,无任何处理)
* @param 无
* @retval 返回值:0表示无按键按下,1~4分别对应K1~K4按键
* @note 无消抖、无松手检测,按下瞬间直接返回键码;按键为低电平有效,对应独立按键硬件接线
*/
unsigned char Key_GetState()
{
unsigned char KeyNumber=0; // 定义变量,存储实时检测到的按键状态
if(P3_1 == 0){KeyNumber=1;} // P3_1引脚为低电平 → K1按键按下,返回键码1
if(P3_0 == 0){KeyNumber=2;} // P3_0引脚为低电平 → K2按键按下,返回键码2
if(P3_2 == 0){KeyNumber=3;} // P3_2引脚为低电平 → K3按键按下,返回键码3
if(P3_3 == 0){KeyNumber=4;} // P3_3引脚为低电平 → K4按键按下,返回键码4
return KeyNumber; // 返回实时按键状态
}
/**
* @brief 按键驱动核心函数(必须在定时器中断中调用,推荐20ms调用一次)
* @param 无
* @retval 无
* @note 核心功能:实现【硬件消抖】+【松手检测】,输出唯一有效的按键键码,是整个按键驱动的核心
*/
void Key_Loop(void)
{
// 定义静态局部变量,存储按键的上一次状态和当前状态
// static特性:只初始化一次,函数执行完后值永久保留,不会清零,用于状态对比
static unsigned char NowState,LastState;
LastState = NowState; // 【状态更新】把当前状态赋值为上一次状态
NowState = Key_GetState(); // 【状态更新】读取最新的实时按键状态,作为当前状态
// ===== 核心按键判定逻辑:松手检测+硬件消抖 二合一 =====
// 判定条件:上一次状态是【按键按下】,本次状态是【按键松开】
// 原理:按键按下时的抖动会让电平反复变化,只有稳定按下时LastState才会固定为对应键码
// 只有松手瞬间才会触发该条件,完美避开按下/松开的抖动,实现无延时消抖
if(LastState==1 && NowState==0){Key_KeyNumber=1;}
if(LastState==2 && NowState==0){Key_KeyNumber=2;}
if(LastState==3 && NowState==0){Key_KeyNumber=3;}
if(LastState==4 && NowState==0){Key_KeyNumber=4;}
}
✅ key.h
cpp
#ifndef __KEY_H__
#define __KEY_H__
unsigned char Key(void);
void Key_Loop(void);
#endif
示例见13.6项目示例2