
可实现功能
本代码基于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(微秒),延时函数通过"循环消耗机器周期"实现延时。
-
unsigned char i,j;:定义局部循环变量i、j,用于控制内层循环次数。 -
while(xms--):外层循环,xms是传入的延时毫秒数,每循环一次,xms减1,直到xms=0,循环结束(比如xms=20,就循环20次,每次循环实现1ms延时,总延时20ms)。 -
内层循环(
i=2; j=239; do{while(--j);}while(--i);):通过两次嵌套循环消耗CPU时间,经过计算,该循环组合刚好能实现约1ms的延时(12MHz晶振下,内层循环总机器周期≈1000μs=1ms)。 -
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)"才能生效。
逐行拆解(原理+作用)
-
TCON |= 0x01;:配置INT0的触发方式。TCON是定时器/计数器控制寄存器,低4位控制外部中断,0x01二进制为00000001,对应IT0位(TCON的bit0)设为0,即INT0采用"低电平触发"(P3_2引脚为低电平时,触发外部中断0)。 -
TCON |= 0x04;:配置INT1的触发方式。0x04二进制为00000100,对应IT1位(TCON的bit2)设为0,即INT1采用"低电平触发"(P3_3引脚为低电平时,触发外部中断1),本文未使用INT1,仅做配置。 -
EA = 1;:开启CPU总中断允许。EA是总中断开关,若EA=0,所有中断都被禁止,即使开启了EX0、EX1,中断也无法触发;EA=1,才能允许已开启的中断响应。 -
EX0 = 1;:开启外部中断0允许。EX0是外部中断0的允许位,EX0=1,允许INT0触发中断;EX0=0,禁止INT0中断。 -
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无法识别中断来源,中断无法响应。
逐行拆解(原理+作用)
-
num1 = 0; num2 = 0; num3 = 0; num4 = 0;:清空之前通过键盘输入的4个数字(num1~num4),避免残留旧数据。 -
flag = 0;:重置验证结果标志位,回到"未验证"状态(flag=0时,不显示验证通过/失败的内容)。 -
P2_4 = 1; P2_5 = 1;:控制P2口的4、5引脚置高电平,用于关闭外部设备(比如LED、继电器,高电平熄灭/断开,具体取决于外设接法)。 -
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为列线(输入);
逐部分拆解(原理+作用)
-
unsigned char Number=0;:定义存储按键编号的变量,初始为0(无按键按下状态)。 -
扫描逻辑(以第一行为例,其余三行原理相同):
-
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,完成第一行第一列按键的识别。
-
其余三行扫描(P1_6=0、P1_5=0、P1_4=0):原理与第一行完全一致,分别扫描第二、三、四行,对应识别按键58、912、13~16。
-
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位数码管的动态显示。
关键知识点
-
共阴数码管原理:共阴数码管的阴极接地,阳极接高电平时,对应段点亮;段码是控制数码管8个段(a~g、dp)的电平组合,0X3F对应数字0,0X06对应数字1,以此类推。
-
动态显示原理:通过快速切换4位数码管的位选和段码,利用人眼的"视觉暂留"效应(约10ms),让4位数码管看起来同时点亮,避免显示闪烁。
逐行拆解(原理+作用)
-
函数参数说明:注释中写"两位数码管",实际代码支持4位(Location=1~4),参数
int Location,Digital存在语法小问题(Digital未声明类型),应为void Display(unsigned char Location, unsigned char Digital)(后续讲解会补充修正)。 -
unsigned char Num[]={0X3F,0X06,0X5B,0X4F,0X66,0X6D,0X7D,0X07,0X7F,0X6F};:共阴数码管段码数组,索引09对应数字09,段码值对应数码管的亮段组合(比如0X3F=00111111,对应数码管a~f段点亮,显示数字0)。 -
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位数码管。
-
P0=Num[Digital];:将对应数字的段码写入P0口,数码管显示对应的数字(比如Digital=0,P0=0X3F,显示0)。 -
Delayms(1);:延时1ms,让当前位置的数码管保持点亮状态,利用视觉暂留效应,避免显示闪烁。 -
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个数字)、根据验证结果控制数码管显示和外部设备状态,实现完整的"输入-验证-反馈"流程。
逐行拆解(原理+作用)
-
P2_4 = 1; P2_5 = 1;:初始化P2_4、P2_5引脚为高电平,关闭外部设备(如LED、继电器),避免程序启动时外设误动作。 -
Exti_Init();:调用外部中断初始化函数,开启外部中断0、1,配置触发方式,让中断能够正常响应。 -
while(1):死循环(无限循环),程序会一直执行循环内的代码,实现持续的键盘扫描和显示更新(51单片机主函数必须有死循环,否则程序执行完会乱跑)。 -
KeyNum = Key();:调用矩阵键盘扫描函数,读取当前按键值,存入KeyNum变量(0=无按键,1~16=对应按键)。
(一)按键处理逻辑(有按键按下时,KeyNum≠0)
核心是"状态机"逻辑,通过state变量控制输入流程,分步输入4个数字(num1~num4),最后验证密码:
-
state == 0(输入num1):若按下的是数字键(0~9),将按键值赋值给num1,然后state设为1,进入"输入num2"状态;注意:代码中KeyNum >= 0 && KeyNum <=9,但键盘扫描返回的KeyNum最小为1,0无按键,此处可改为KeyNum >=1 && KeyNum <=9(避免逻辑冗余)。 -
state == 1(输入num2):若按下的是数字键(1~9),将按键值赋值给num2,state设为2,进入"输入num3"状态。 -
state == 2(输入num3):若按下的是数字键(1~9),将按键值赋值给num3,state设为3,进入"输入num4"状态。 -
state == 3(输入num4):若按下的是数字键(1~9),将按键值赋值给num4,state设为4,进入"验证密码"状态。 -
state == 4(验证密码):若按下的是"-"键(KeyNum=11),判断num1~num4是否等于1、2、3、4(预设密码):
-
密码正确:flag设为1(验证通过);
-
密码错误:flag设为2(验证失败)。
(二)数码管显示逻辑(根据flag值显示不同内容)
-
flag == 1(验证通过):调用Display函数,4位数码管分别显示1、2、3、4(对应预设密码);同时P2_4=0、P2_5=1,开启对应外部设备(如LED点亮)。 -
flag == 2(验证失败):4位数码管分别显示输入的num1~num4(提示用户输入错误);同时P2_4=1、P2_5=0,开启另一个外部设备(如错误提示LED)。 -
flag == 0(未验证):未执行任何显示操作(实际可补充"显示当前输入的数字",让用户看到输入过程,后续可优化)。
八、整体运行流程(核心逻辑串联)
-
程序启动,执行main函数,初始化P2_4、P2_5为高电平(外设关闭),调用Exti_Init()开启外部中断。
-
进入死循环,持续调用Key()函数扫描键盘,读取按键值KeyNum。
-
有按键按下时,根据state状态分步输入num1~num4(state从0→1→2→3→4)。
-
输入完4个数字(state=4)后,按下"-"键(KeyNum=11),验证密码:
-
- 密码正确(1234):flag=1,数码管显示1234,P2_4=0(开启外设1);
-
- 密码错误:flag=2,数码管显示输入的4个数字,P2_5=0(开启外设2)。
-
若触发外部中断0(P3_2引脚为低电平),执行Int0_ISR(),清空num1~num4、flag、state,重置系统,回到初始输入状态。
-
无按键按下时,持续循环扫描键盘,数码管保持当前显示状态。