51单片机 proteus仿真 智能锁 (4x4矩阵键盘+数码管+外部中断代码详解)

可实现功能

本代码基于51单片机(12MHz晶振),可实现以下核心功能,所有功能均通过代码逻辑闭环实现,可直接编译运行(修正语法错误后):

  • 4x4矩阵键盘扫描:识别数字0~9、

  • 4位共阴数码管动态显示:支持在4个指定位置显示数字0~9,可根据程序状态显示输入的密码、验证结果,显示稳定无闪烁。

  • 密码输入与验证:支持分步输入4个数字密码(num1~num4),按下"-"键(按键11)触发验证,判断密码是否为预设的1234。

  • 验证结果反馈:密码正确时,数码管显示1234,同时控制P2_4引脚低电平(可驱动外部设备如LED点亮);密码错误时,数码管显示输入的错误密码,控制P2_5引脚低电平(可驱动错误提示设备)。

  • 外部中断重置:外部中断0(INT0,对应P3_2引脚)触发时,自动清空所有输入的密码、重置程序状态,回到初始输入界面,实现系统快速重置。

  • 软件延时控制:实现毫秒级延时,用于按键消抖、数码管显示防抖,保障程序运行稳定性。

本文针对51单片机(12MHz晶振)的4x4矩阵键盘、4位数码管显示、外部中断综合代码,逐行、逐函数拆解,讲解每一步操作的作用、底层原理,结合51单片机I/O口、中断、键盘扫描、数码管显示等核心知识点,让初学者能完全理解代码逻辑和运行机制。

一、头文件与全局变量定义(代码开头部分)

c 复制代码
#include <REGX51.H>

unsigned char num1 = 0;     
unsigned char num2 = 0;    
unsigned char num3 = 0;     
unsigned char num4 = 0;    

unsigned char state = 0;

unsigned char KeyNum;

unsigned char flag = 0;

1. 头文件说明(原理+作用)

  • #include <REGX51.H>:51单片机核心寄存器定义头文件,必须包含。该头文件已定义51单片机所有寄存器(如TCON、EA、EX0等)和I/O口(如P1、P2),无需手动定义,直接使用即可。

2. 全局变量说明(原理+作用)

全局变量的特点:整个程序(所有函数)都能访问和修改,用于存储程序运行过程中的核心数据,全部用unsigned char(无符号字符型,范围0~255),因存储的数字、状态无需负数,且数值不大。

  • num1~num4:存储通过矩阵键盘输入的4个数字(密码),分别对应第1~4个输入的数字。

  • state:状态标志位,用于控制程序的输入流程(0=输入num1、1=输入num2、2=输入num3、3=输入num4、4=等待验证),实现"分步输入"逻辑。

  • KeyNum:存储矩阵键盘扫描返回的按键值(1~16,0表示无按键按下),用于后续按键逻辑判断。

  • flag:验证结果标志位(0=未验证、1=验证通过、2=验证失败),用于控制数码管显示内容和外部设备状态。

二、软件延时函数(Delayms)

c 复制代码
void Delayms(unsigned int xms)
{
	unsigned char i,j;
	while(xms--)
	{
		i=2;             // 内层循环次数
		j=239;           // 内层循环计数
		do
		{
			while(--j);   // 内层循环:消耗CPU时间
		}while(--i);
	}
}

函数作用

实现"毫秒级延时",核心用于两个场景:1. 矩阵键盘消抖(避免按键接触不良导致的误触发);2. 数码管显示防抖(避免显示闪烁)。括号内的xms是延时时间(单位:ms)。

原理拆解(基于12MHz晶振)

51单片机晶振为12MHz时,1个机器周期=12个晶振周期,即12/(12×10^6) = 1μs(微秒),延时函数通过"循环消耗机器周期"实现延时。

  1. unsigned char i,j;:定义局部循环变量i、j,用于控制内层循环次数。

  2. while(xms--):外层循环,xms是传入的延时毫秒数,每循环一次,xms减1,直到xms=0,循环结束(比如xms=20,就循环20次,每次循环实现1ms延时,总延时20ms)。

  3. 内层循环(i=2; j=239; do{while(--j);}while(--i);):通过两次嵌套循环消耗CPU时间,经过计算,该循环组合刚好能实现约1ms的延时(12MHz晶振下,内层循环总机器周期≈1000μs=1ms)。

  4. while(--j);:j从239递减到0,每递减一次消耗1个机器周期,循环239次,配合外层i=2的循环,刚好凑够1ms的延时。

三、外部中断初始化函数(Exti_Init)

c 复制代码
void Exti_Init(void)
{
    // 配置TCON寄存器:INT0触发方式为低电平/下降沿(0x01=IT0=0,低电平触发)
    TCON |= 0x01;  
    // 配置TCON寄存器:INT1触发方式为低电平/下降沿(0x04=IT1=0,低电平触发)
    TCON |= 0x04;  
    
    // 开启总中断(EA=1:51单片机所有中断的总开关)
    EA  = 1;       
    // 开启外部中断0(EX0=1:允许INT0中断)
    EX0 = 1;       
    // 开启外部中断1(EX1=1:允许INT1中断)
    EX1 = 1;       
}

函数作用

初始化51单片机的外部中断0(INT0)和外部中断1(INT1),配置中断触发方式,开启中断允许,让外部中断能够正常响应(本文重点用到INT0,INT1仅配置未使用)。

关键知识点

51单片机外部中断:INT0对应P3_2引脚,INT1对应P3_3引脚;中断触发方式由TCON寄存器的IT0(INT0)、IT1(INT1)位控制(0=低电平触发,1=下降沿触发);中断需要开启"总中断(EA)"和"对应中断允许(EX0/EX1)"才能生效。

逐行拆解(原理+作用)

  1. TCON |= 0x01;:配置INT0的触发方式。TCON是定时器/计数器控制寄存器,低4位控制外部中断,0x01二进制为00000001,对应IT0位(TCON的bit0)设为0,即INT0采用"低电平触发"(P3_2引脚为低电平时,触发外部中断0)。

  2. TCON |= 0x04;:配置INT1的触发方式。0x04二进制为00000100,对应IT1位(TCON的bit2)设为0,即INT1采用"低电平触发"(P3_3引脚为低电平时,触发外部中断1),本文未使用INT1,仅做配置。

  3. EA = 1;:开启CPU总中断允许。EA是总中断开关,若EA=0,所有中断都被禁止,即使开启了EX0、EX1,中断也无法触发;EA=1,才能允许已开启的中断响应。

  4. EX0 = 1;:开启外部中断0允许。EX0是外部中断0的允许位,EX0=1,允许INT0触发中断;EX0=0,禁止INT0中断。

  5. EX1 = 1;:开启外部中断1允许。EX1是外部中断1的允许位,EX1=1,允许INT1触发中断;本文未使用INT1,仅做配置。

四、外部中断0服务函数(Int0_ISR)

c 复制代码
void Int0_ISR(void) interrupt 0
{
	num1 = 0;       
	num2 = 0;
    num3 = 0;
    num4 = 0;
	flag = 0;
    P2_4 = 1;
    P2_5 = 1;                 
	state = 0; 
}

函数作用

这是外部中断0(INT0)触发后,CPU自动执行的函数(中断服务函数),核心功能:重置系统状态------清空输入的4个数字、重置验证标志和状态标志、关闭外部设备(P2_4、P2_5置1),回到初始的"输入第1个数字"状态。

关键知识点

interrupt 0:51单片机中断优先级编号,外部中断0的中断编号是0(外部中断1是2,定时器0是1),必须写对,否则CPU无法识别中断来源,中断无法响应。

逐行拆解(原理+作用)

  1. num1 = 0; num2 = 0; num3 = 0; num4 = 0;:清空之前通过键盘输入的4个数字(num1~num4),避免残留旧数据。

  2. flag = 0;:重置验证结果标志位,回到"未验证"状态(flag=0时,不显示验证通过/失败的内容)。

  3. P2_4 = 1; P2_5 = 1;:控制P2口的4、5引脚置高电平,用于关闭外部设备(比如LED、继电器,高电平熄灭/断开,具体取决于外设接法)。

  4. state = 0;:重置状态标志位,回到"输入第1个操作数(num1)"的初始状态,让用户可以重新输入4个数字。

五、4x4矩阵键盘扫描函数(Key)

c 复制代码
//  ====================== 4x4矩阵键盘扫描函数 ======================
// 功能:扫描4x4矩阵键盘,返回按键值
// 返回值:按键编号(1-16),0表示无按键按下
// 键盘布局:
//   P1_7-P1_4为行线(输出),P1_3-P1_0为列线(输入)
//   按键映射:1-9为数字,
unsigned char Key()
{
	unsigned char Number=0;
	
	P1=0XFF;
	P1_7=0;
	if(P1_3==0){Delayms(20);while(P1_3==0);if(P1_3==1){Delayms(20);Number=1;}}
	if(P1_2==0){Delayms(20);while(P1_2==0);if(P1_2==1){Delayms(20);Number=2;}}
	if(P1_1==0){Delayms(20);while(P1_1==0);if(P1_1==1){Delayms(20);Number=3;}}
	if(P1_0==0){Delayms(20);while(P1_0==0);if(P1_0==1){Delayms(20);Number=4;}}
	
	P1=0XFF;
	P1_6=0;
	if(P1_3==0){Delayms(20);while(P1_3==0);if(P1_3==1){Delayms(20);Number=5;}}
	if(P1_2==0){Delayms(20);while(P1_2==0);if(P1_2==1){Delayms(20);Number=6;}}
	if(P1_1==0){Delayms(20);while(P1_1==0);if(P1_1==1){Delayms(20);Number=7;}}
	if(P1_0==0){Delayms(20);while(P1_0==0);if(P1_0==1){Delayms(20);Number=8;}}
	
	P1=0XFF;
	P1_5=0;
	if(P1_3==0){Delayms(20);while(P1_3==0);if(P1_3==1){Delayms(20);Number=9;}}
	if(P1_2==0){Delayms(20);while(P1_2==0);if(P1_2==1){Delayms(20);Number=10;}}
	if(P1_1==0){Delayms(20);while(P1_1==0);if(P1_1==1){Delayms(20);Number=11;}}
	if(P1_0==0){Delayms(20);while(P1_0==0);if(P1_0==1){Delayms(20);Number=12;}}
	
	P1=0XFF;
	P1_4=0;
	if(P1_3==0){Delayms(20);while(P1_3==0);if(P1_3==1){Delayms(20);Number=13;}}
	if(P1_2==0){Delayms(20);while(P1_2==0);if(P1_2==1){Delayms(20);Number=14;}}
	if(P1_1==0){Delayms(20);while(P1_1==0);if(P1_1==1){Delayms(20);Number=15;}}
	if(P1_0==0){Delayms(20);while(P1_0==0);if(P1_0==1){Delayms(20);Number=16;}}
	
	return Number;
}

函数作用

扫描4x4矩阵键盘的按键状态,识别按下的按键,返回对应的按键编号(1~16),无按键按下时返回0;核心是"逐行拉低、逐列检测",实现矩阵键盘的高效扫描,同时通过延时实现按键消抖。

关键知识点(矩阵键盘工作原理)

4x4矩阵键盘由4条行线和4条列线组成,按键跨接在行线和列线之间;扫描原理:逐行将行线拉低(输出低电平),然后检测列线的电平状态,若某条列线为低电平,说明该列与当前拉低的行线交叉处的按键被按下。

本文键盘接线:P1口复用------P1_7P1_4为行线(输出),P1_3P1_0为列线(输入);

逐部分拆解(原理+作用)

  1. unsigned char Number=0;:定义存储按键编号的变量,初始为0(无按键按下状态)。

  2. 扫描逻辑(以第一行为例,其余三行原理相同):

  • P1=0XFF;:先将P1口所有引脚置高电平(行线初始为高,避免误触发)。

  • P1_7=0;:将第一行(P1_7)拉低为低电平,其余行仍为高电平。

  • if(P1_3==0){...}:检测第一列(P1_3)电平,若为低电平,说明"第一行第一列"的按键(编号1)被按下。

  • Delayms(20);:按键消抖,因为机械按键按下时会有接触抖动(约10~20ms),延时20ms避开抖动期,避免误识别。

  • while(P1_3==0);:等待按键释放(按键按下时P1_3为低,释放后为高),避免按键长按导致多次触发。

  • if(P1_3==1){Delayms(20);Number=1;}:确认按键释放后,再延时20ms(二次消抖),将按键编号设为1,完成第一行第一列按键的识别。

  1. 其余三行扫描(P1_6=0、P1_5=0、P1_4=0):原理与第一行完全一致,分别扫描第二、三、四行,对应识别按键58、912、13~16。

  2. return Number;:返回按键编号,无按键按下时,Number始终为0,有按键按下时,返回对应编号(1~16)。

六、4位数码管显示函数(Display)

c 复制代码
// ====================== 两位数码管显示函数 ======================
// 功能:在指定位置显示数字,支持小数点
// 参数:
//   Location - 显示位置(1=十位,2=个位)
//   Digital  - 显示数字(0-9,10=E错误,11=熄灭)
//   Dot      - 小数点标志(1=点亮个位小数点,0=熄灭)
// 说明:使用共阴数码管,P0输出段码,P2控制位选
void Display(int Location,Digital)
{
	unsigned char Num[]={0X3F,0X06,0X5B,0X4F,0X66,0X6D,0X7D,0X07,0X7F,0X6F};
	switch(Location)
	{
		case 1:P2_0=0;P2_1=1; P2_2=1; P2_3=1; break;
		case 2:P2_0=1;P2_1=0; P2_2=1; P2_3=1; break;
        case 3:P2_0=1;P2_1=1; P2_2=0; P2_3=1; break;
        case 4:P2_0=1;P2_1=1; P2_2=1; P2_3=0; break;		
	}
	P0=Num[Digital];
	Delayms(1);
	P0=0X00;
}

函数作用

在4位共阴数码管的指定位置(14位)显示数字(09),通过P2口控制"位选"(选择哪一位数码管点亮),P0口输出"段码"(控制数码管显示哪个数字),实现4位数码管的动态显示。

关键知识点

  1. 共阴数码管原理:共阴数码管的阴极接地,阳极接高电平时,对应段点亮;段码是控制数码管8个段(a~g、dp)的电平组合,0X3F对应数字0,0X06对应数字1,以此类推。

  2. 动态显示原理:通过快速切换4位数码管的位选和段码,利用人眼的"视觉暂留"效应(约10ms),让4位数码管看起来同时点亮,避免显示闪烁。

逐行拆解(原理+作用)

  1. 函数参数说明:注释中写"两位数码管",实际代码支持4位(Location=1~4),参数int Location,Digital存在语法小问题(Digital未声明类型),应为void Display(unsigned char Location, unsigned char Digital)(后续讲解会补充修正)。

  2. unsigned char Num[]={0X3F,0X06,0X5B,0X4F,0X66,0X6D,0X7D,0X07,0X7F,0X6F};:共阴数码管段码数组,索引09对应数字09,段码值对应数码管的亮段组合(比如0X3F=00111111,对应数码管a~f段点亮,显示数字0)。

  3. switch(Location):位选控制,通过P2_0~P2_3引脚的电平组合,选择要点亮的数码管位置(低电平有效):

  • Location=1:P2_0=0,其余位(P2_1~P2_3)=1,点亮第1位数码管;

  • Location=2:P2_1=0,其余位=1,点亮第2位数码管;

  • Location=3:P2_2=0,其余位=1,点亮第3位数码管;

  • Location=4:P2_3=0,其余位=1,点亮第4位数码管。

  1. P0=Num[Digital];:将对应数字的段码写入P0口,数码管显示对应的数字(比如Digital=0,P0=0X3F,显示0)。

  2. Delayms(1);:延时1ms,让当前位置的数码管保持点亮状态,利用视觉暂留效应,避免显示闪烁。

  3. P0=0X00;:清空P0口段码,避免当前数码管的段码残留,导致下一位数码管显示异常(交叉干扰)。

七、主函数(main)------ 程序入口

c 复制代码
void main()
{
    P2_4 = 1;
    P2_5 = 1;  
    Exti_Init();
    
	while(1)  // 主循环:持续扫描按键和更新显示
	{
		KeyNum = Key();  // 读取键盘按键值
		
		// ====================== 按键处理逻辑 ======================
		if(KeyNum != 0)  // 有按键按下
		{
			// ====================== 状态0:输入第1操作数 num1 ======================
			 if(state == 0)
			{
				if(KeyNum >= 0 && KeyNum <= 9) num1 = KeyNum;
                state = 1 ; 

			}
			// ====================== 状态1:输入第2操作数 num2======================			
			else if(state == 1)
			{
				 if(KeyNum >= 1 && KeyNum <= 9) num2 = KeyNum;
                 state = 2;
            }  
          // ====================== 状态2:输入第3操作数 num3 ======================
            else if(state == 2)
            {
				 if(KeyNum >= 0 && KeyNum <= 9) num3 = KeyNum;
                 state = 3;
            }  
          // ====================== 状态3:输入第一个操作数 num4 ======================
			else if(state == 3)
			{
				 if(KeyNum >= 0 && KeyNum <= 9) num4 = KeyNum;
                 state = 4;
            }
			// ====================== 状态4:验证密码 ======================
           else if(state == 4)
            {
                if(KeyNum==11)
                {
                    if(num1==1 && num2==2 && num3==3 && num4==4)
                    {
                    flag = 1;
                    }
                    else 
                    {
                    flag = 2;
                    }
                }
                
            }
        }
                
        // ====================== 数码管显示逻辑 ======================
        if(flag == 1)
        {
            Display(1, 1);  // 第1位显示1
            Display(2, 2);  // 第2位显示2
            Display(3, 3);  // 第3位显示3
            Display(4, 4);  // 第4位显示4
            P2_4 = 0;
            P2_5 = 1;  
        }
        else if(flag == 2)
        {
            Display(1, num1%10);  // 第1位显示num1
            Display(2, num2%10);  // 第2位显示num2
            Display(3, num3%10);  // 第3位显示num3
            Display(4, num4%10);  // 第4位显示num4
            P2_4 = 1;
            P2_5 = 0;  
        }
    }
}

函数作用

主函数是程序的入口(51单片机程序从main函数开始执行),核心逻辑:初始化外部设备→进入死循环,持续扫描键盘、处理按键逻辑(分步输入4个数字)、根据验证结果控制数码管显示和外部设备状态,实现完整的"输入-验证-反馈"流程。

逐行拆解(原理+作用)

  1. P2_4 = 1; P2_5 = 1;:初始化P2_4、P2_5引脚为高电平,关闭外部设备(如LED、继电器),避免程序启动时外设误动作。

  2. Exti_Init();:调用外部中断初始化函数,开启外部中断0、1,配置触发方式,让中断能够正常响应。

  3. while(1):死循环(无限循环),程序会一直执行循环内的代码,实现持续的键盘扫描和显示更新(51单片机主函数必须有死循环,否则程序执行完会乱跑)。

  4. KeyNum = Key();:调用矩阵键盘扫描函数,读取当前按键值,存入KeyNum变量(0=无按键,1~16=对应按键)。

(一)按键处理逻辑(有按键按下时,KeyNum≠0)

核心是"状态机"逻辑,通过state变量控制输入流程,分步输入4个数字(num1~num4),最后验证密码:

  1. state == 0(输入num1):若按下的是数字键(0~9),将按键值赋值给num1,然后state设为1,进入"输入num2"状态;注意:代码中KeyNum >= 0 && KeyNum <=9,但键盘扫描返回的KeyNum最小为1,0无按键,此处可改为KeyNum >=1 && KeyNum <=9(避免逻辑冗余)。

  2. state == 1(输入num2):若按下的是数字键(1~9),将按键值赋值给num2,state设为2,进入"输入num3"状态。

  3. state == 2(输入num3):若按下的是数字键(1~9),将按键值赋值给num3,state设为3,进入"输入num4"状态。

  4. state == 3(输入num4):若按下的是数字键(1~9),将按键值赋值给num4,state设为4,进入"验证密码"状态。

  5. state == 4(验证密码):若按下的是"-"键(KeyNum=11),判断num1~num4是否等于1、2、3、4(预设密码):

  • 密码正确:flag设为1(验证通过);

  • 密码错误:flag设为2(验证失败)。

(二)数码管显示逻辑(根据flag值显示不同内容)
  1. flag == 1(验证通过):调用Display函数,4位数码管分别显示1、2、3、4(对应预设密码);同时P2_4=0、P2_5=1,开启对应外部设备(如LED点亮)。

  2. flag == 2(验证失败):4位数码管分别显示输入的num1~num4(提示用户输入错误);同时P2_4=1、P2_5=0,开启另一个外部设备(如错误提示LED)。

  3. flag == 0(未验证):未执行任何显示操作(实际可补充"显示当前输入的数字",让用户看到输入过程,后续可优化)。

八、整体运行流程(核心逻辑串联)

  1. 程序启动,执行main函数,初始化P2_4、P2_5为高电平(外设关闭),调用Exti_Init()开启外部中断。

  2. 进入死循环,持续调用Key()函数扫描键盘,读取按键值KeyNum。

  3. 有按键按下时,根据state状态分步输入num1~num4(state从0→1→2→3→4)。

  4. 输入完4个数字(state=4)后,按下"-"键(KeyNum=11),验证密码:

    • 密码正确(1234):flag=1,数码管显示1234,P2_4=0(开启外设1);
    • 密码错误:flag=2,数码管显示输入的4个数字,P2_5=0(开启外设2)。
  5. 若触发外部中断0(P3_2引脚为低电平),执行Int0_ISR(),清空num1~num4、flag、state,重置系统,回到初始输入状态。

  6. 无按键按下时,持续循环扫描键盘,数码管保持当前显示状态。

相关推荐
仰泳之鹅2 小时前
【51单片机】第一课:单片机简介与软件安装
单片机·嵌入式硬件·51单片机·1024程序员节
凌盛羽3 小时前
使用python绘图分析电池充电曲线
开发语言·python·stm32·单片机·fpga开发·51单片机
Tisfy4 小时前
LeetCode 2946.循环移位后的矩阵相似检查:模拟(左即是右)
算法·leetcode·矩阵·题解
DC升降压/LED驱动IC4 小时前
源芯片选型指南之 AP5193 DC-DC 宽电压 LED 降压恒流驱动器
stm32·单片机·嵌入式硬件·物联网·51单片机·proteus
蓝凌y4 小时前
51单片机之花样灯
c语言·单片机·嵌入式硬件·51单片机
蓝凌y5 小时前
51单片机点亮LED
单片机·嵌入式硬件·51单片机
蓝凌y6 小时前
51单片机之数码管0~9显示
单片机·嵌入式硬件·51单片机
穿条秋裤到处跑7 小时前
每日一道leetcode(2026.03.27):循环移位后的矩阵相似检查
算法·leetcode·矩阵
Cathy Bryant7 小时前
拓扑学-毛球定理
笔记·线性代数·算法·矩阵·拓扑学·高等数学