51单片机学习笔记3-独立按键

目录

[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循环左移

  1. 上电默认点亮 P2_0****这 1 个 LED 灯;
  2. 按下 P3_1 按键**→ 松开后,LED 灯** 向右循环移位**(P20→P21→P22→...→P27→回到 P20);**
  3. 按下 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

相关推荐
麦托团子2 小时前
51单片机学习笔记1-基础知识碎碎念
51单片机
恶魔泡泡糖21 小时前
51单片机直流电机
单片机·嵌入式硬件·51单片机
想放学的刺客1 天前
单片机嵌入式试题(第20期)通信协议深度解析与系统调试实战
stm32·单片机·嵌入式硬件·物联网·51单片机
恶魔泡泡糖2 天前
51单片机点阵屏应用
单片机·嵌入式硬件·51单片机
恶魔泡泡糖2 天前
51单片机I-O扩展1
c语言·嵌入式硬件·51单片机
想放学的刺客3 天前
单片机嵌入式试题(第19期)嵌入式系统故障诊断与固件升级设计
c语言·stm32·嵌入式硬件·物联网·51单片机
小美单片机4 天前
External model DLL ”ADC083XDLL“ not found_proteus仿真报错解决方法
c语言·单片机·51单片机·proteus·课程设计·课设
萧技电创EIIA4 天前
基于stc12单片机的双轴舵机太阳能追光系统设计与实现
单片机·51单片机·硬件工程·pcb工艺
恶魔泡泡糖4 天前
51单片机I-O扩展2
单片机·嵌入式硬件·51单片机