十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

51单片机电子密码锁系统设计:矩阵键盘与EEPROM存储详解

51单片机电子密码锁系统设计:矩阵键盘与EEPROM存储详解 简介面向电子类专业课程设计与毕业设计的电子密码锁仿真资源包基于51单片机完成软硬件设计适合学习矩阵键盘、LCD显示、密码校验与报警逻辑等知识点的读者参考。包内共18个文件以C语言源程序、Proteus仿真工程DSN/PDS工程和Keil工程文件为主同时包含HEX烧录文件、备份文件及编译过程生成的列表文件整体体积仅148KB。已有2336人学习下载。资源提供了完整可运行的电子密码锁设计与仿真方案使用者可对照源程序与仿真原理图理解单片机如何读取键盘输入、比较密码并控制开锁输出也可直接打开工程查看电路连接或借助HEX文件在Proteus中运行验证作为课程设计或毕业设计模板进行二次开发。1. 单片机课程设计里绕不开的电子密码锁系统设计电子密码锁是单片机课程设计里出现频率最高的题目之一因为它把输入、存储、显示、输出四件事全部串了起来。真动手做会发现键盘抖动、密码掉电丢失、开锁后状态乱跳每个问题都能卡住半天。这套系统设计真正的骨架是四件事51单片机负责状态调度矩阵键盘负责采集输入EEPROM承担密码掉电保存继电器与电磁锁完成最终动作。下面从选型讲到Proteus仿真再到防护加固把一条能直接复现的路径讲完。适合正在做课设或毕设的读者也适合想把手上那块51开发板变成能开门的小系统的开发者。2. 硬件选型与接线51单片机、矩阵键盘和LCD1602怎么搭2.1 主控选型STC89C52比AT89S52更适合课程设计STC89C52和AT89S52都是8051内核实验室里STC的板子更多内部8KB Flash、512B RAM支持ISP串口下载一条USB转TTL线就能烧程序不用额外买烧录器。密码锁程序编译出来一般两三KBFlash和RAM都不是瓶颈真正影响选择的是下载是否方便、资料是否好找。STC还有个优势内部看门狗第5章的防护逻辑会用到。这里要提醒一个常见误选有人为了省IO选AT89C2051这种20脚芯片但P0口和P2口被砍掉后LCD1602和继电器就得和键盘挤同一个P1口扫描和显示互相干扰排错成本反而更高。密码锁题目原则上选40脚封装的89C52P0、P1、P2三口分开干三件事逻辑清晰后续扩展继电器和蜂鸣器也不冲突。2.2 矩阵键盘扫描4x4按键只用P1一个口键盘按行列接法接到P1口4根行线接P1.0-P1.34根列线接P1.4-P1.7。扫描思路是分时复用每次只拉低一列读行线电平就能确定哪个键被按下。对应按键扫描函数是这样#include reg52.h #define KEY_PORT P1 unsigned char KeyScan(void) { unsigned char row, i; unsigned char code col_mask[4] {0xEF, 0xDF, 0xBF, 0x7F}; unsigned char code key_tab[4][4] { {1,2,3,A}, {4,5,6,B}, {7,8,9,C}, {*,0,#,D} }; for (i 0; i 4; i) { KEY_PORT col_mask[i]; // 依次把一列拉低 row KEY_PORT 0x0F; // 读四根行线状态 if (row ! 0x0F) { // 出现低电平说明有键按下 delay_ms(10); // 软件消抖过滤机械抖动 row KEY_PORT 0x0F; if (row ! 0x0F) { while ((KEY_PORT 0x0F) ! 0x0F); // 等待释放防连按 if (row 0x0E) return key_tab[0][i]; else if (row 0x0D) return key_tab[1][i]; else if (row 0x0B) return key_tab[2][i]; else if (row 0x07) return key_tab[3][i]; } } } return 0xFF; // 无键按下返回无效值 }这段代码把消抖、判定、防连按放在一个函数里完成。col_mask依次将P1.4到P1.7之一置低其余三列保持高电平如果行线上有低电平就说明该列与某行的交点被按下。返回值直接是按键字符后续密码比对时比较ASCII码即可。delay_ms(10)过滤掉机械按键按下瞬间的抖动如果省掉一次物理按下可能被识别成多次输入。扫描函数的阻塞特性要留意while循环会一直等按键释放才返回。密码锁这种交互节奏慢的场景阻塞反而省心主循环不会收到重复键值。如果以后要增加长按功能就得改成非阻塞扫描每10ms读一次只记录电平边沿变化。引脚分配如下这套接法对应第4章的Proteus仿真连线信号51引脚方向说明行线R0-R3P1.0-P1.3输入键盘行状态读取列线C0-C3P1.4-P1.7输出逐列拉低扫描LCD1602数据D0-D7P0双向8位并行数据LCD1602 RSP2.5输出寄存器选择LCD1602 RWP2.6输出读写选择LCD1602 ENP2.7输出使能信号继电器控制P2.1输出高电平吸合开锁蜂鸣器P2.0输出低电平触发2.3 LCD1602的P0上拉与继电器驱动电路LCD1602支持8位和4位两种并行模式。密码锁系统里数据口P0已经占满直接用8位模式最省事4位模式要先发高4位再发低4位代码多一层打包逻辑还容易在时序上出错。不论仿真还是实物都要记住P0口是开漏输出必须外接上拉电阻才能输出可靠的高电平。实际做的时候我一般用10kΩ排阻。注意P0口必须外接10kΩ上拉电阻Proteus里不加仿真也能跑实物上会把LCD打出一片乱码。驱动端的电流缺口更大。单片机IO口输出电流只有几百微安而常见5V继电器线圈吸合需要30-80mA直接驱动带不动。常见做法是三极管放大把P2.1接基极发射极接地集电极接继电器线圈线圈并联续流二极管吸收断电反电动势。控制逻辑代码很简单sbit RELAY P2^1; RELAY 1; // 三极管导通继电器吸合 delay_ms(3000); // 保持3秒 RELAY 0; // 释放锁体复位开锁保持时间不是固定3秒。普通电磁锁1-3秒就可以电控锁带闭锁机构的需要5秒以上。把时间抽成宏定义放在程序开头实物调试时按锁体规格改一处就行。继电器触点选型也要注意交流220V的锁具用10A/250VAC规格直流低压锁用5A/30VDC的继电器足够别把耐压选小了。3. 密码存储与校验EEPROM掉电保存和键盘输入怎么配合3.1 密码为什么存AT24C02而不是写死在Flash把密码固定写死在程序里用户改密码就得重新烧录固件这不符合电子密码锁的基本需求。AT24C02是2Kbit的EEPROMI2C接口掉电不丢数据允许反复擦写对密码锁这种偶尔改一次密码的使用频率绰绰有余。器件地址是0xA0A2/A1/A0全接地时这个地址头在写代码时要背下来。I2C时序里起始和停止条件要严格按协议生成sbit SCL P3^6; sbit SDA P3^7; void I2C_Start(void) { SDA 1; SCL 1; delay_us(5); // 保证两条线稳定在高电平 SDA 0; // SCL为高时SDA拉低产生起始信号 delay_us(5); SCL 0; } void I2C_Stop(void) { SDA 0; SCL 1; delay_us(5); SDA 1; // SCL为高时SDA拉高产生停止信号 delay_us(5); }主节点的延时决定I2C速率标准模式100kHz下SCL半个周期约5微秒STC89C52在12MHz晶振下用delay_us(5)正合适。如果换用支持400kHz快速模式的器件延时可以缩到2微秒。初学最容易在应答信号上翻车。AT24C02每收到一个字节后会把SDA拉低作为应答主机在第9个时钟周期必须释放SDA总线否则应答被破坏。等待应答的函数要单独提出来bit I2C_WaitAck(void) { unsigned char t 0; SDA 1; // 释放SDA让从器件能拉低 delay_us(2); SCL 1; delay_us(2); while (SDA) { // 一直高电平说明从器件不应答 if (t 200) { SCL 0; return 1; // 应答超时 } } SCL 0; return 0; // 应答正常 }返回1时多半是器件地址错误、SDA/SCL接反或者缺少上拉电阻。发送字节和读取字节的底层函数也要自己写void I2C_SendByte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { SCL 0; if (dat 0x80) SDA 1; else SDA 0; dat 1; SCL 1; // SCL高电平期间SDA数据必须保持稳定 } SCL 0; } unsigned char I2C_ReadByte(void) { unsigned char i, dat 0; SDA 1; // 释放总线准备接收 for (i 0; i 8; i) { SCL 1; dat 1; if (SDA) dat | 0x01; SCL 0; } return dat; }3.2 EEPROM地址分配与断电保存初始化密码数据在EEPROM里的分布要有固定规划不然改密码时新旧数据容易混。我常用的分配方式EEPROM地址作用说明0x00初始化标志0xA5表示已写入过密码0x01-0x066位密码以ASCII码存储上电先读标志字节。如果不是0xA5就按默认密码123456写入再补写标志是0xA5说明EEPROM里已有用户密码把这6个字节搬到内存数组。对应初始化函数unsigned char pwd[6]; void LoadPassword(void) { unsigned char flag, i; unsigned char code default_pwd[6] {1,2,3,4,5,6}; flag AT24C02_ReadByte(0x00); if (flag ! 0xA5) { for (i 0; i 6; i) { AT24C02_WriteByte(0x01 i, default_pwd[i]); } AT24C02_WriteByte(0x00, 0xA5); for (i 0; i 6; i) pwd[i] default_pwd[i]; } else { for (i 0; i 6; i) { pwd[i] AT24C02_ReadByte(0x01 i); } } }逐字节读回6位密码在课程设计里完全够用。AT24C02支持页读页写一次操作能搬多个字节但密码锁的读写频率低追求页模式只会让代码可读性变差。函数里的标志判断是这套系统设计里的关键防护不加这个标志每次上电都会用默认密码覆盖用户改过的密码。注意AT24C02每次写入后有约5ms的写周期期间不响应器件地址延时要留足10ms再发起下一次操作。3.3 按键输入缓冲与密码比对逻辑输入环节不要逐位实时匹配那样会打乱显示和后续的退格逻辑。先维护一个输入缓冲数组和计数器数字键填充缓冲LCD上显示“*”而不是明文防止旁边人记下密码。按满6位自动进入校验另外给个“#”确认键用户按得慢时不用等满6位unsigned char input_buf[6]; unsigned char input_cnt 0; void HandleKey(unsigned char key) { if (key 0 key 9) { // 只处理数字键 if (input_cnt 6) { input_buf[input_cnt] key; LCD_ShowChar(*); } if (input_cnt 6) { VerifyPassword(); // 输入满6位自动校验 } } else if (key *) { // 退格删除最后一位 if (input_cnt 0) { input_cnt--; LCD_Backspace(); } } else if (key #) { // 确认立即校验 VerifyPassword(); } }比对函数不用strcmp原因是密码定长6字节写成定长比较逻辑更直接也不会因字符串结束符提前返回unsigned char VerifyPassword(void) { unsigned char i; bit match 1; for (i 0; i 6; i) { if (input_buf[i] ! pwd[i]) { match 0; break; } } if (match) { RELAY 1; // 开锁 delay_ms(3000); RELAY 0; LCD_ShowString(OPEN); err_cnt 0; } else { LCD_ShowString(ERROR); err_cnt; // 错误次数累计供锁定逻辑使用 } input_cnt 0; // 无论成功还是失败都清空缓冲 return match ? 1 : 0; }成功或失败后清空input_cnt是必须的。如果不清空用户输错后继续按键新旧输入会拼在一起出现“按两三位就自动校验”的诡异现象。err_cnt这个变量目前只负责累加第5章会接入锁定状态机。4. 系统状态设计与Proteus仿真把键盘、显示和开锁动作串成闭环4.1 状态机待机、输入、校验、锁定四态直接在主循环堆if-else也能跑但加需求时很容易变成补丁摞补丁比如后面要加“连续输错锁定30秒”状态一多就乱了。更稳的写法是把系统抽象成几个状态用状态机管理typedef enum { STANDBY, // 待机等待首个按键 INPUT, // 输入密码中 VERIFY, // 校验中 LOCKOUT // 锁定禁止输入 } SysState; SysState state STANDBY; unsigned char err_cnt 0; unsigned char lock_timer 0; void SystemTick(void) { unsigned char key; key KeyScan(); switch (state) { case STANDBY: if (key 0 key 9) { // 首个数字键进入输入态 input_cnt 0; state INPUT; } break; case INPUT: if (key ! 0xFF) HandleKey(key); // 正常收集按键 break; case VERIFY: break; case LOCKOUT: if (lock_timer 0) { lock_timer--; // 定时器每1秒触发一次更新 } else { err_cnt 0; state STANDBY; } break; } }状态迁移表如下把触发条件和动作集中放一起联调时对着表查问题比读代码更直观当前状态触发条件迁移到动作STANDBY按下数字键INPUT清空输入缓冲LCD提示输入密码INPUT输入满6位或按“#”VERIFY读取当前密码准备比对VERIFY密码匹配STANDBY继电器吸合显示OPEN错误计数清零VERIFY密码不匹配INPUT显示ERROR错误计数加1INPUT连续错3次LOCKOUT锁定30秒蜂鸣器间歇报警LOCKOUT状态里主循环忽略所有按键这才是锁定的实质。如果只在输入判断里加一个计数锁定期间按键还是会被接受密码已经被人试出去了系统却没有拦截。51单片机资源有限状态枚举只占几个字节switch跑一遍的开销比if-else链还低在12MHz晶振下没有任何实时性压力。4.2 Proteus仿真搭建与两个高频坑Proteus是验证这套系统最快的路径不用焊接就能同时观察键盘扫描和LCD时序。把AT89C52、KEYPAD-SMALLCALC、LM016L、AT24C02、RELAY和LED拖到画布按第2章的引脚表连线。晶振设12MHz把Keil生成的hex文件加载到AT89C52的Program File属性里点运行就能看到效果。仿真里排在前两位的坑都跟外设配置有关。第一个是LCD不显示LM016L的RS、RW、EN三个控制脚接错任意一根数据都进不了寄存器。调试时先固定RW为低电平只做写操作能在LCD上显示固定字符串再继续。第二个是AT24C02读出全是0xFF检查SCL和SDA有没有上拉电阻在Proteus的属性面板把两条线设为10k上拉不然总线上电平不确定器件永远不应答。仿真的模型响应和真实器件有差异AT24C02的写周期在Proteus里常常表现为“好像不用等”这会导致在仿真里正常、烧到板子上就丢密码。无论仿真现象如何WriteByte后保留10ms延时让实物和仿真行为保持一致这是教材里不太会提的细节。4.3 实物调试顺序先模块后联调拿实物直接跑完整程序出了问题要同时排查五六个模块效率很低。分四步走烧一个只点亮LCD的程序确认显示字符正确单独测矩阵键盘按下的键值用数码管或串口打印出来单独验证EEPROM写一个字节再读回来和原值对比最后才把键盘、存储、继电器拼到一起跑完整的密码校验逻辑。每一步先在Proteus里仿真通过再上实物多数时间耗在排线上模块本身的问题反而少。调试期间最好留一路串口输出把状态机的跳转日志发到上位机。51的P3.0、P3.1是串口引脚和AT24C02用的P3.6、P3.7不冲突可以同时挂着用调试完再关掉。5. 51单片机电子密码锁加固锁定、改密码和初始化验证5.1 连续输错锁定30秒用看门狗兜底光有校验逻辑门禁场景根本不够用。错误计数err_cnt在VerifyPassword里累计满3次进入LOCKOUT状态锁定期间按键全部忽略蜂鸣器每秒响一次提醒。锁定时间先写30秒实际部署可以扩展成阶梯式第一次30秒第二次60秒第三次5分钟自动试密成本会快速上升。STC89C52内部看门狗也要开。程序一旦跑飞系统会在1.6秒内复位对挂在门上的设备死机等于开不了门WDTRST 0x1E; // 使能看门狗 WDTRST 0xE1;WDTRST是STC89C52的看门狗复位寄存器原版reg52.h里没有定义用Keil编译前先声明sfr WDTRST 0xA6;。主循环里每1000ms喂一次狗。注意开锁那3秒的delay_ms(3000)会阻塞主循环超过喂狗间隔系统会被看门狗复位看到的现象就是“一开门锁就重启”。要把开锁计时改成非阻塞用定时器中断计数主循环只检查状态标记既照顾了看门狗又不阻塞键盘扫描。5.2 修改密码原密码确认与新密码双次输入改密码的逻辑不能是“输入新密码直接写”。至少要分两步先验原密码再收新密码新密码还要输入两次确认一致。两次不一致就回到原密码校验不写EEPROM。真实使用场景里新用户输错一次的概率不低这个确认流程是体验兜底。新密码确认一致后再整体写入避免反复擦写EEPROM同时把内存里的pwd数组同步更新防止重启后又用旧密码。5.3 初始化引脚忘记密码后的最后手段电子密码锁最尴尬的故障就是忘记密码。预留一个初始化策略比返厂靠谱。在P3.2接一个测试按钮按下时把EEPROM的0x00标志改写重启后LoadPassword会认为从未初始化重新写入默认密码。日常固件验证时短接一下再上电恢复到出厂状态整个过程不涉及烧录器。另外把初始化按钮放在外壳内侧平时摸不到需要恢复出厂设置时打开外壳按一下就行。本文还有配套的精品资源点击获取
返回列表