news 2026/11/6 2:36:23

51单片机简易密码锁:矩阵键盘、LCD1602与AT24C02实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
51单片机简易密码锁:矩阵键盘、LCD1602与AT24C02实现

简介:《51单片机简易密码锁》是一份面向单片机课程设计、电子密码锁入门实践与选题参考的中文文档,围绕AT89C52最小系统、矩阵键盘、1602LCD显示、电源与报警电路展开,给出密码设置、修改、显示和错误报警的整体设计思路。压缩包仅含1个doc文件,大小约1.72MB,内容以课程设计说明书形式组织,便于读者了解硬件模块划分、C语言软件流程及系统可靠性、安全性等分析要点。已有445人学习,适合需要快速搭建简易密码锁方案、梳理报告结构与功能模块的读者参考,也可用于对照理解矩阵输入、LCD显示和单片机控制之间的配合关系。

1. 一个两块钱的单片机和一把锁:51单片机简易密码锁到底在做什么

一件很具体的小事:桌面上摆着一个铁皮柜,钥匙老丢,有人想让它认密码不认钥匙。做法是拿一颗经典 8051 内核的单片机(AT89C52 这类引脚兼容型号),配 4×4 矩阵键盘、LCD1602 字符屏、一片 AT24C02 掉电存储器和一路继电器,把「按键输入 → 密码比对 → 驱动电磁锁」这条链路跑通,整套物料成本三十块上下。

51单片机简易密码锁解决的问题就这么朴素。适合两类人:刚学完 C 语言和数字电路、第一次想把代码烧进真实电路的新手;要带课程设计又不想陷进 RTOS 和一堆库函数的指导者。难点从来不在写代码,而在电平、时序和干扰——这三样东西仿真软件永远不会教你,也正是一块能开锁的板子最难做出来的原因。

2. 硬件怎么搭:矩阵键盘、LCD1602 和电磁锁的引脚分配

动手之前先把一件事说清楚:这个项目能不能一次成功,八成取决于硬件这一层有没有埋雷。软件写错了改一行重烧,硬件接错了轻则查一整晚,重则烧片子。我一般会先画一张引脚分配表贴在桌上,再拿万用表量一遍,确认 5V 和地没有短,才开始插线。

2.1 元器件清单与一张必须先画出来的引脚表

先说选型理由。51 内核单片机虽然老,但它的 IO 电平是 5V,能直接带动 LCD1602 和继电器模块,不用电平转换;引脚功能直观,P0 是开漏,P1/P2/P3 内部有弱上拉;片内没有硬件 I2C,但用两个 IO 口软件模拟完全够用;开发环境简单,一个串口就能下载。缺点也明显:RAM 只有 128 到 256 字节,存不下复杂逻辑;没有真正的加密引擎,密码存储只能做混淆不能做保护;IO 驱动能力弱,任何功率负载都得加三极管。

清单如下,数量按一台整机算:

类别器件数量说明
主控AT89C52 及同引脚兼容型号111.0592MHz 或 12MHz 晶振均可
时钟复位11.0592MHz 晶振、22pF 瓷片电容、10μF 电解、10kΩ 电阻各 2/2/1/1晶振靠近芯片,复位电容正极接 VCC
输入4×4 薄膜矩阵键盘116 键,行 4 线列 4 线
显示LCD1602 字符屏1带背光,5V 供电
显示配件10kΩ 电位器、10kΩ 排阻1/1电位器调对比度,排阻给 P0 上拉
存储AT24C0212Kbit,可存 256 字节,够存密码和设置
存储配件4.7kΩ 电阻2I2C 的 SCL/SDA 上拉,不可省
驱动5V 继电器模块或 S8050+1N4007+1kΩ1 套光耦模块更省事,分立方案更看得懂原理
电源5V/2A 适配器、12V/1A 适配器各 1锁用 12V,主控用 5V,必须分开

引脚分配建议这样定,定了就别中途改,改了代码和接线会一起乱:

功能单片机引脚方向备注
键盘行线 ROW1~ROW4P1.4~P1.7输出低电平有效,逐行拉低
键盘列线 COL1~COL4P1.0~P1.3输入读回低电平说明该键按下
LCD1602 数据 D0~D7P0.0~P0.7输出P0 开漏,必须外接 10k 排阻上拉
LCD1602 RS/RW/EP2.5 / P2.6 / P2.7输出RW 不用检测忙就直接接地
AT24C02 SCL/SDAP2.0 / P2.1双向各接 4.7k 上拉到 5V
继电器控制P2.2输出高电平吸合,具体看模块触发极性
门磁或防拆开关P3.2输入可选,做防撬检测时用

提示:P0 口当普通输出用时一定要加排阻,否则 LCD1602 收到的电平是虚的,会出现「能显示但字符发虚、偶尔乱码」这种特别难查的现象。

2.2 最小系统、矩阵键盘与 LCD1602 的接法

最小系统三件套:晶振、复位、电源。晶振和两颗 22pF 电容尽量贴着芯片的 XTAL1/XTAL2 放,走线短。复位电路是 10μF 电容正极接 VCC、负极接 RST,同时 RST 经 10kΩ 电阻接地,这样上电瞬间 RST 是高电平,实现上电复位。如果板子上有手动复位按钮,并联在电容两端即可。

矩阵键盘的接法是把 4 根行线接到 P1.4~P1.7,4 根列线接到 P1.0~P1.3。扫描原理是:每次只让一根行线输出低电平,其余行线输出高电平,然后读 4 根列线;如果某一列读到低电平,就说明「这一行这一列」交叉处的按键被按下。16 个键对应 0~9、确认、删除、修改、上锁等,键值在代码里用一张表映射。

LCD1602 接 P0 做数据口,RS 接 P2.5 控制「写指令还是写数据」,RW 接 P2.6 控制读写方向,E 接 P2.7 做使能。第三脚 V0 接 10kΩ 电位器的中间抽头,电位器两端分别接 5V 和地,用来调对比度。背光的 A/K 两端串一个 100Ω 限流电阻接 5V 和地。如果板上空间紧张,把 RW 直接接地,程序里只写不读,用固定延时替代忙标志检测,能省一根线和一个判断。

上电时序这块一定要按顺序来:先给 5V,等 40ms 以上让 LCD 内部复位完成,再连续发三次 0x38(8 位数据、两行显示、5×7 点阵),然后发 0x0C 开显示关光标、0x06 设置写入后光标右移、0x01 清屏。这个顺序错一步,屏幕就可能只亮一排黑方块,看着像坏了,其实只是没初始化对。

2.3 锁的驱动:继电器、三极管和那颗不能省的续流二极管

单片机的 IO 口拉电流只有几百微安,灌电流也就十几毫安,直接驱动继电器线圈是绝对带不动的,必须加一级三极管或者用现成的光耦继电器模块。分立方案是:S8050 的基极串 1kΩ 电阻接 P2.2,发射极接地,集电极接继电器线圈的一端,线圈另一端接 5V。基极电阻不能省,省了会从 IO 口抽走大电流。

线圈两端必须反并联一颗 1N4007,阴极朝 5V,阳极朝集电极。原因是继电器线圈是感性负载,三极管关断瞬间线圈会产生几十伏的反向电动势,不泄放就会击穿三极管,甚至顺着电源线把单片机一起带走。这颗二极管是血泪经验,不是可选项。

电磁锁分两类,选错了会出安全问题:通电开锁型(fail-safe)断电时是开着的,适合消防通道;断电开锁型(fail-secure)断电时是锁死的,适合储物柜。做密码锁用后者更符合直觉,但要接受「停电就打不开」这个事实,重要的柜子得配机械应急钥匙。

电源部分是我见过翻车最多的地方。单片机用 5V/2A 适配器,锁用 12V/1A 适配器,两路电源的负极必须接到一起共地,否则继电器控制信号没有参考电平,会出现「继电器偶尔吸合偶尔不吸合」的玄学故障。在 5V 电源入口并一颗 1000μF 电解电容和一颗 0.1μF 瓷片电容,前者扛继电器吸合的浪涌,后者滤高频噪声。如果继电器模块自带光耦隔离,单片机的地和锁的地可以分开,抗干扰更好。

3. 软件骨架:用定时器中断把按键扫描和开锁时序拆开

硬件搭好之后,软件最容易犯的错是把所有事情塞进一个 while(1) 里,用一堆 delay 函数凑时序。这样写出来的程序,按键响应迟钝、屏幕刷新闪烁、开锁时间不准,改一处崩一片。正确的做法是先用定时器中断建立一个统一时基,所有跟时间有关的动作都挂在这个时基上。

3.1 用 2ms 定时器中断做时基:按键消抖和长按判定的统一入口

定时器 0 工作在模式 1(16 位定时),晶振 11.0592MHz 时机器周期是 1.085μs,要产生 2ms 中断,初值算下来是 65536 - 1843 = 63693,也就是 TH0 = 0xF8、TL0 = 0xCD 左右。这个数值不用死记,用公式反推一遍就有数了。

中断服务函数里只做三件事:重装初值、给一个全局的 tick 变量加一、把按键扫描的标志位置一。中断里绝对不能放延时、不能刷屏、不能读写 EEPROM,否则主循环会被拖慢,串口通信也会丢数据。我见过有人在中断里调 LCD 写函数,结果屏幕每隔几秒花一次,查了两天才发现是中断超时。

有了 2ms 的节拍,消抖就好办了:每 10 个 tick(20ms)采样一次键盘,连续两次采样值相同才认为是稳定状态,从按下到确认大约 40ms,手指感觉不到延迟,但足以滤掉绝大多数抖动。长按判定也一样,同一个键连续保持 100 个 tick(2000ms)就算长按,用来做「长按修改键进入改密模式」这类操作。

/* 定时器0初始化:11.0592MHz 下产生 2ms 中断 */ void Timer0Init(void) { TMOD &= 0xF0; /* 清掉定时器0的配置位 */ TMOD |= 0x01; /* 定时器0,模式1,16位 */ TH0 = 0xF8; /* 2ms 初值高字节 */ TL0 = 0xCD; /* 2ms 初值低字节 */ ET0 = 1; /* 开定时器0中断 */ EA = 1; /* 开总中断 */ TR0 = 1; /* 启动定时器0 */ } /* 中断服务:只做计数和置标志,不做耗时操作 */ void Timer0Isr(void) interrupt 1 { TH0 = 0xF8; TL0 = 0xCD; g_tick2ms++; /* 全局节拍,主循环按需取模使用 */ if (g_tick2ms % 10 == 0) g_keyScanFlag = 1; /* 每 20ms 通知主循环扫一次键 */ }

逻辑说明:中断里只累加计数、置标志,把真正的按键处理留给主循环。参数上,0xF8CD这个初值是按 11.0592MHz 算的,如果你手头是 12MHz 晶振,同样 2ms 对应的初值是 0xF8F0 附近,两者差一点点,长时间跑会累积误差,所以晶振频率和初值必须配对。g_tick2ms用unsigned int,65536 个 tick 是 131 秒,做超时计时够用;要更长的计时就再开一个变量在主循环里累加。

3.2 矩阵键盘扫描法:行扫列读与三种无效键的处理

矩阵键盘扫描的核心是「逐行拉低、读列判断」。4 行 4 列,共 16 个键,扫描一轮需要 4 次端口操作。关键细节有三个:每次换行之后要等一小段时间让电平稳定,一般几十微秒;读列之前要先把列线置高,因为 51 的 IO 是准双向口,读之前必须先写 1;扫描到按键之后要记录键值并等待松手,否则一次按下会被识别成很多次。

无效键有三种情况要单独处理。第一种是「多键同时按下」,两行都有列线被拉低,这时候要么取第一个扫到的,要么直接丢弃,我一般丢弃,因为真实使用中不会同时按两个键。第二种是「按键卡住不放」,需要松手检测,否则程序会一直认为有键输入。第三种是「扫描期间按键刚好松开」,这是竞态,解决办法是扫描完立刻再读一次确认。

/* 扫描一次矩阵键盘,返回 0~15 的键号,无键按下返回 0xFF */ unsigned char KeyScanOnce(void) { unsigned char row, col, key = 0xFF; for (row = 0; row < 4; row++) { P1 = ~(0x10 << row); /* 只把第 row 行拉低,其余高,低4位写1 */ DelayUs(50); /* 等电平稳定 */ col = P1 & 0x0F; /* 读低4位列线 */ if (col != 0x0F) { /* 有列被拉低说明有键 */ if (col == 0x0E) key = row * 4 + 0; else if (col == 0x0D) key = row * 4 + 1; else if (col == 0x0B) key = row * 4 + 2; else if (col == 0x07) key = row * 4 + 3; else key = 0xFF; /* 多键同时按下,丢弃 */ break; } } P1 = 0xFF; /* 恢复全高,避免影响其他电路 */ return key; }

逻辑说明:~(0x10 << row)这个表达式是整段代码的核心。0x10 << row在第 row 行为 1,取反之后那一行为 0、其余为 1,低 4 位全是 1,正好满足「输出低电平的那一行 + 输入列线先写 1」的要求。DelayUs(50)是经验值,太短电平没稳,太长影响扫描速度。返回 0xFF 表示本轮无有效键,主循环拿到 0xFF 就跳过,不更新状态。这套扫描配合 20ms 采样周期和连续两次一致的判定,能滤掉 99% 的抖动和串键。

3.3 密码比对、状态机与开锁时序

密码锁的本质是一个有限状态机,状态不多,但转移条件必须写死。我一般分五个状态:上锁态 LOCKED、输入态 INPUT、校验态 CHECK、开锁态 OPEN、报警锁定态 ALARM。上电默认 LOCKED,屏幕显示「INPUT PASSWORD」;每按一个数字键进入 INPUT,屏幕对应位置显示星号;按确认键进入 CHECK,比对密码;正确进入 OPEN,驱动继电器吸合 3 秒后自动回到 LOCKED;错误则计数加一,累计三次进入 ALARM,锁定 60 秒并置蜂鸣器。

密码不存明文是个好习惯,哪怕只是做简易项目。最省事的做法是把 4 位密码逐字节异或一个固定值再写进 AT24C02,读出来再异或回来。这挡不住真正想破解的人,但能挡住「拿编程器读出 EEPROM 直接看到 1234」这种最低级的窥探。首地址再存一个魔数 0x5A 作为「已初始化」标志,读出来不等于这个值就写默认密码,避免新片子全是 0xFF 导致永远对不上。

开锁时序有两个细节。第一,继电器吸合之后要立刻把屏幕更新为「OPEN」,别等延时时再刷,否则用户会觉得卡。第二,开锁 3 秒后自动上锁,这个 3 秒由 tick 计数实现,不能用 delay,不然期间按什么键都没反应。开锁期间如果检测到门磁仍然闭合(门没开),可以提前结束开锁,省电也省线圈寿命。

4. 代码落地:四个模块的可编译 C 代码与参数说明

到这一步,硬件和思路都有了,缺的是能烧进去跑起来的代码。我习惯把工程拆成四个文件:按键与显示、存储与校验、主逻辑、公共延时。这里按调用顺序给三段关键代码,每一段都能单独编译验证。

4.1 键盘扫描与 LCD1602 驱动:先让屏幕亮起来

调试顺序一定是先显示后按键。屏幕不亮,你连按键值对不对都看不见。LCD1602 的驱动就四个函数:写指令、写数据、初始化、指定位置显示字符。

#include <reg52.h> sbit LCD_RS = P2^5; sbit LCD_RW = P2^6; sbit LCD_EN = P2^7; #define LCD_DATA P0 /* 写指令:RS=0,RW=0,数据送到 P0,E 打一个高脉冲 */ void LcdWriteCmd(unsigned char cmd) { LCD_RS = 0; LCD_RW = 0; LCD_DATA = cmd; DelayMs(1); LCD_EN = 1; DelayMs(1); LCD_EN = 0; DelayMs(2); /* 普通指令执行约 40us,留足余量 */ } /* 写数据:RS=1,其余同上 */ void LcdWriteData(unsigned char dat) { LCD_RS = 1; LCD_RW = 0; LCD_DATA = dat; DelayMs(1); LCD_EN = 1; DelayMs(1); LCD_EN = 0; DelayMs(2); } /* 初始化:上电延时 + 三次 0x38 + 开显示 + 清屏 */ void LcdInit(void) { DelayMs(50); /* 上电必须等 40ms 以上 */ LcdWriteCmd(0x38); LcdWriteCmd(0x38); LcdWriteCmd(0x38); /* 连发三次确保进入 8 位模式 */ LcdWriteCmd(0x0C); /* 开显示,关光标,不闪烁 */ LcdWriteCmd(0x06); /* 写入后光标右移 */ LcdWriteCmd(0x01); /* 清屏 */ DelayMs(5); }

逻辑说明:LCD_EN的高脉冲宽度要大于 450ns,DelayMs(1)是保守写法,实际几十微秒就够。三次 0x38 是 LCD1602 上电复位的固定流程,少一次都可能停在 4 位模式,表现是字符显示成乱码。0x0C里的低两位分别控制光标和闪烁,密码输入时不希望光标闪,所以是 0。清屏指令 0x01 执行时间较长(约 1.64ms),后面必须跟延时,否则紧接着写数据会丢。

按键部分把 3.2 节的KeyScanOnce再包一层消抖:主循环每 20ms 调用一次,连续两次返回同一个非 0xFF 键值才算有效,然后等松手。这套逻辑写出来不到 40 行,但决定了整个交互手感。

4.2 AT24C02 读写与密码校验:掉电不丢密码的最小实现

AT24C02 走 I2C,51 没有硬件 I2C,得用两个 IO 口模拟起始、停止、应答和字节收发。时序的关键是延时,标准模式 100kHz 下每半个时钟周期约 5μs,用DelayUs(5)就能满足,慢一点没关系,快了反而容易失败。

sbit I2C_SCL = P2^0; sbit I2C_SDA = P2^1; /* 起始信号:SCL 高时 SDA 由高变低 */ void I2cStart(void) { I2C_SDA = 1; I2C_SCL = 1; DelayUs(5); I2C_SDA = 0; DelayUs(5); I2C_SCL = 0; } /* 停止信号:SCL 高时 SDA 由低变高 */ void I2cStop(void) { I2C_SDA = 0; I2C_SCL = 1; DelayUs(5); I2C_SDA = 1; DelayUs(5); } /* 写一个字节,返回 0 表示从机应答,1 表示无应答 */ unsigned char I2cWriteByte(unsigned char dat) { unsigned char i, ack; for (i = 0; i < 8; i++) { I2C_SDA = (dat & 0x80) ? 1 : 0; dat <<= 1; DelayUs(5); I2C_SCL = 1; DelayUs(5); I2C_SCL = 0; } I2C_SDA = 1; /* 释放 SDA,准备读应答 */ DelayUs(5); I2C_SCL = 1; DelayUs(5); ack = I2C_SDA; /* 0 为应答 */ I2C_SCL = 0; return ack; }

逻辑说明:I2C 的每个字节都是高位先发,第 9 个时钟周期从机把 SDA 拉低表示应答。I2C_SDA = 1在读应答之前必须先释放总线,否则主机会一直把 SDA 按在地上,从机应答读不到。写完一个字节之后,AT24C02 需要 5ms 内部写周期,这期间它不应答任何指令,所以每次写操作之后必须DelayMs(6),这是新手最容易漏的一步,漏了就会出「第一次写成功、第二次写丢」的怪事。

存密码的布局我一般这样定:地址 0x00 存魔数 0x5A,地址 0x01 存密码长度,地址 0x02 到 0x05 存四位密码的异或值。上电读出来先看魔数,不等于 0x5A 就写默认密码「1234」并置魔数。

4.3 主循环与开锁动作:把状态机串起来

主循环里不写任何长延时,所有等待都靠 tick 计数。结构是:扫键、按状态分发、到时间做状态转移、刷新显示。

#define ST_LOCKED 0 #define ST_INPUT 1 #define ST_OPEN 2 #define ST_ALARM 3 unsigned char g_state = ST_LOCKED; unsigned char g_input[6], g_len = 0; unsigned char g_errCnt = 0; unsigned int g_openTick = 0, g_alarmTick = 0; void main(void) { unsigned char key; Timer0Init(); LcdInit(); PwdLoad(); /* 从 AT24C02 读密码,无魔数则写默认 */ LcdShowStr(0, 0, "INPUT PASSWORD"); while (1) { if (g_keyScanFlag) { g_keyScanFlag = 0; key = KeyGet(); /* 内部完成 20ms 消抖和松手检测 */ if (key != 0xFF) KeyHandle(key); /* 按当前状态分发处理 */ } StateMachine(); /* 处理超时、自动上锁、报警解除 */ if (g_dispDirty) { /* 只在内容变化时刷屏,避免闪烁 */ g_dispDirty = 0; DispRefresh(); } } }

逻辑说明:KeyGet()内部维护「上次键值」和「稳定计数」,两次采样一致才返回,返回一次之后要等松手才允许下一次,这样按住不放不会连发。StateMachine()里判断g_state == ST_OPEN时比较当前 tick 和g_openTick,差值超过 3 秒对应的 tick 数(1500 个 2ms)就回到 LOCKED 并拉低继电器。g_dispDirty是个小技巧,密码锁的显示内容变化很少,只在按键、状态切换时置位,能显著减少 LCD 写操作,屏幕不闪,寿命也长。

开锁动作本身只有三行:拉高继电器引脚、记录开锁时刻、把状态改成 OPEN。真正的功夫在异常分支:开锁期间再按确认键要不要立即上锁?错误三次之后锁定期间按键是不是完全无响应?这些边界必须在写代码之前想清楚,不然现场演示时按两下就死机。

5. 排查与避坑:密码锁不工作时先看这 5 条

硬件和代码都齐了,板子还是不听话,这时候别急着改程序,先把下面几条按顺序过一遍。这些都是我实际踩过的坑,每一条都值得单独记在本子上。

5.1 显示与按键类:屏幕黑块、按键串键

现象一:上电后 LCD1602 只显示一排黑方块,写什么字符都没反应。原因有三种:初始化延时不够(少于 40ms)、对比度电位器没调到合适位置、RW 引脚悬空导致 LCD 一直处于忙状态。解决:在LcdInit第一行加DelayMs(50),0x38 连发三次;拿螺丝刀慢慢调电位器,调到字符清晰、背景不黑为止;RW 不用就老老实实接地,别让它悬着。

现象二:按一个键,屏幕出来两三个数字,或者手靠近键盘就乱跳。原因是没做消抖,或者扫描速度太快,或者列线上拉电阻缺失。51 的 P1 口内部上拉很弱,线长一点就容易被干扰。解决:把扫描周期固定到 20ms、连续两次一致才确认、确认后等松手;列线各并一颗 10kΩ 上拉到 5V;键盘排线尽量短,远离继电器和电源线。

现象三:某个键永远按不出来,其他键正常。多半是那一行或那一列的焊点虚了,或者排线里断了一根。解决:用万用表蜂鸣档量键盘排线两端通断,再量单片机引脚到排线座的通断,逐个排除。这种问题代码改一万遍也没用。

5.2 电源与驱动类:一开锁就复位、锁关不断

现象四:继电器一吸合,屏幕立刻花掉,程序从头开始跑。这是电源被拉垮了。原因:锁和单片机共用一路 5V,继电器吸合瞬间浪涌把电压拉到 4V 以下,单片机欠压复位;或者续流二极管漏装,反电动势顺着电源线打回来。解决:主控和锁分开供电、单点共地;线圈两端加 1N4007;5V 入口并 1000μF 电解;继电器模块的控制线远离晶振和复位线。

现象五:断电之后锁还吸着,继电器摸上去发烫。原因可能是用了通电开锁型的锁却按断电开锁的逻辑写程序,也可能是三极管关断不彻底。解决:先确认锁的类型,通电开锁的锁断电才弹开,程序逻辑要反过来;三极管基极加一颗 10kΩ 下拉电阻到地,确保 IO 高阻时可靠关断;继电器模块如果是低电平触发,程序里的电平要取反。

5.3 存储与逻辑类:密码莫名变成 255

现象六:断电重上电,密码恢复成默认值,或者怎么输都不对。原因:AT24C02 写完没等 5ms 写周期,数据没真正落盘;或者首地址没写魔数,程序把全 0xFF 的空片子当成了有效密码。解决:每次写操作后DelayMs(6);地址 0x00 固定写 0x5A,读出不等就重写默认密码;密码按字节异或后再存,读出来先异或还原。

现象七:改完密码当时能用,重启就失效。多半是只改了 RAM 里的变量没写 EEPROM,或者写的时候地址算错了。解决:改密流程固定成「校验旧密码 → 写新密码到 EEPROM → 回读比对 → 更新 RAM」,回读比对这一步不能省,它是唯一能确认写成功的手段。

注意:调试期可以把当前状态、按键值、错误次数实时显示在 LCD 第二行,比接串口打印快得多,拔掉电源就看不见,也不会留痕迹。

6. 从能用到敢用:虚位密码、防拆检测和掉电保护

一套能开锁的装置和一套敢装在门上的装置,差的是几个不起眼的设计。虚位密码是最容易加、效果最直观的一个:用户可以在真实密码前后随便加数字,只要末尾 4 位对就开锁,旁边有人看着也猜不出哪几位是真的。

/* 虚位密码:输入串长度不短于 6,且末尾 4 位等于真实密码即通过 */ unsigned char CheckVirtual(const unsigned char *buf, unsigned char len) { if (len < 6 || len > 16) return 0; /* 太短没意义,太长缓冲区放不下 */ return (buf[len-4] == g_pwd[0] && buf[len-3] == g_pwd[1] && buf[len-2] == g_pwd[2] && buf[len-1] == g_pwd[3]); }

这段代码把输入缓冲区开到 16 字节,按确认键时只比对末尾四位,其余位置随便填。缓冲区长度这个参数要跟着 RAM 走,51 的 RAM 只有一百多字节,开太大别的地方就不够用了。

防拆检测用 P3.2 接一个常闭的微动开关,装在锁体内部。正常关门时开关闭合,P3.2 读到低电平;外壳被撬开开关断开,P3.2 变高,程序立刻进入 ALARM 状态并锁定键盘。这个功能的价值不在于报警声有多大,而在于让「拆开面板直接短接继电器」这条路走不通。

掉电保护是个容易被忽略的细节。密码本身存在 AT24C02 里不怕断电,怕的是开锁过程中断电导致继电器状态不确定。做法是在主循环里检测一个「开锁中」标志,上电初始化时先无条件把继电器拉到关闭状态,再读 EEPROM 判断是否处于异常断电,如果是就要求在 30 秒内重新输入密码才能再次开锁。多这几行代码,能避免「停电再来电,门自己开了一整晚」这种事故。

最后说个我自己的习惯:每次改完硬件,先不接锁,用万用表量三样东西——5V 对地有没有短路、单片机 RST 脚上电瞬间是不是高电平然后变低、继电器控制脚在待机时是不是确定的低电平。这三样都对了,再插锁、再烧程序。这个顺序帮我省下的芯片,比省下的时间多得多。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/6 2:36:22

ROSGPT实战:用大模型把自然语言指令变成ROS机器人动作

把一句大白话变成机器人真正能执行的动作&#xff0c;这事在ROS圈子里被念叨了很多年。早年我们用语音识别加规则模板&#xff0c;后来用意图分类加槽位填充&#xff0c;再往后是行为树编排&#xff0c;每一代都能跑&#xff0c;但每一代都脆——换个说法、多一个修饰词、少一个…

作者头像 李华
网站建设 2026/11/6 2:36:21

S7-1200 PLC恒压供水系统设计与PID控制实践

1. 项目背景与核心需求在工业自动化领域&#xff0c;恒压供水系统是典型的闭环控制应用场景。这个"一拖三"项目指的是用一台S7-1200 PLC同时控制三台水泵的协同工作&#xff0c;实现管网压力的精确稳定控制。相比传统单泵系统&#xff0c;这种方案能显著降低设备启停…

作者头像 李华
网站建设 2026/11/6 2:36:10

Web安全逻辑漏洞挖掘:业务流程、权限边界与并发时序测试

漏洞挖掘这件事&#xff0c;入门时最容易走偏&#xff1a;一上来就找工具、背Payload&#xff0c;结果面对一个真实业务系统时不知道从哪下手。我带着几个刚入门的朋友做过几轮授权范围内的演练&#xff0c;发现真正拉开差距的不是工具用得多花&#xff0c;而是对业务流程的理解…

作者头像 李华
网站建设 2026/11/6 2:36:02

C语言顺序表实现与优化全解析

1. 顺序表基础概念解析顺序表是C语言中最基础也是最常用的线性表存储结构之一。它采用一组地址连续的存储单元依次存储数据元素&#xff0c;通过物理位置上的相邻关系来体现逻辑上的线性关系。这种结构特别适合C语言的数组特性&#xff0c;因此在教学和实际开发中都是数据结构入…

作者头像 李华
网站建设 2026/11/6 2:35:30

Metasploit本地渗透测试实战指南

1. 项目背景与目标最近在安全测试领域&#xff0c;我发现很多初学者对渗透测试工具的实际操作存在认知偏差。特别是对于Metasploit Framework这样的经典工具&#xff0c;不少人觉得它"神秘莫测"。其实通过本地环境模拟&#xff0c;我们可以安全地学习其核心功能。这次…

作者头像 李华
网站建设 2026/11/6 2:34:15

26天打卡习惯养成:从行为设计到神经机制

1. 打卡机制的本质与价值连续打卡26天这个行为背后&#xff0c;隐藏着一套完整的习惯养成方法论。从行为心理学角度看&#xff0c;打卡本质上是一种"承诺机制"&#xff08;Commitment Device&#xff09;&#xff0c;通过外在形式强化内在动机。我曾在团队管理中推行…

作者头像 李华