基于STM32的电子密码锁仿真与源码拆解:从Proteus到门禁系统
简介基于STM32的电子密码锁仿真与源码包面向嵌入式初学者、电子设计爱好者及课程设计人员以完整工程形式呈现密码锁从按键输入、密码校验到开锁控制的全流程适合作为单片机综合练习的参考模板。压缩包共277个文件约7.6MB主体为70个C源码与94个头文件涵盖HAL库定时器、串口、GPIO等外设驱动同时附带Proteus仿真工程、HEX烧录文件及Keil工程配置方便直接导入环境验证。已有559人次浏览学习具备一定参考热度。通过源码与仿真联动既能观察硬件时序与逻辑状态也能修改密码规则、扩展功能从而加深对STM32开发流程和电子密码锁方案的理解资料仅供学习交流使用请勿用于商业或非法用途。1. 为什么很多人在网上找“基于STM32的电子密码锁仿真源码”这项目到底能干什么如果你在搜索引擎里敲过这串关键词大概率是手头有一个课程设计、毕业设计或者新手入门练习要做电子密码锁但又不想从零画电路、写驱动、调时序。这套基于STM32的电子密码锁仿真与源码项目把整条链路都打包好了STM32F103的程序源码、Proteus仿真工程、以及配套的电路接线说明。它的价值在于你不需要实体开发板也能把按键输入、密码校验、LCD显示、开锁动作完整跑起来。适合三类人刚开始学STM32但手里没板子的学生被课程设计截止日期追着跑的开发者以及想快速把密码锁业务逻辑移植到自己项目里的嵌入式工程师。我拆完这套资源后的第一感受是它最大的亮点不是代码有多高深而是把怎么在仿真里让单片机真正工作起来的配置细节都铺好了。2. 整体架构与工程文件结构先把代码地图画出来2.1 硬件资源分配从引脚到外设的规划逻辑拿到工程第一件事不是看代码而是看引脚分配。这个项目的硬件拓扑很典型一个4x4矩阵键盘负责输入一块LCD1602负责显示状态和输入回显一个继电器或LED模拟电磁锁执行机构外加一个蜂鸣器做提示音。在STM32F103C8T6这颗芯片上引脚规划大致是矩阵键盘的行线接PA0-PA3列线接PA4-PA7LCD1602的数据线接PB0-PB7控制线RS、RW、EN分别接PB8、PB9、PB10。密码存储用到了EEPROM模拟或者外部AT24C02通过I2C协议挂接。这样一个资源分配方案把芯片的GPIO、定时器、I2C外设都用上了但彼此不冲突。你要注意的细节是矩阵键盘的八个引脚没有使用外部上拉电阻而是通过配置GPIO的输入模式为上拉输入来实现这在Proteus仿真里是可行的但换成实物板子时如果引脚本身没有内部上拉你就需要外加上拉电阻否则按键扫描会失灵。2.2 工程目录每个源文件对应什么职责展开源码包后你会看到一个结构清晰的STM32标准外设库工程。核心目录长这样Project/ ├─ User/ │ ├─ main.c // 主函数状态机入口 │ └─ stm32f10x_it.c // 中断服务函数 ├─ Hardware/ │ ├─ keypad.c // 矩阵键盘扫描驱动 │ ├─ keypad.h │ ├─ lcd1602.c // LCD1602显示驱动 │ ├─ lcd1602.h │ ├─ eeprom.c // 密码存储与读取 │ ├─ eeprom.h │ ├─ buzzer.c // 蜂鸣器控制 │ └─ buzzer.h ├─ Library/ │ └─ STM32F10x_StdPeriph_Driver/ // 标准外设库 ├─ Output/ │ └─ 密码锁.hex // 编译产物烧录到Proteus └─ Simulation/ └─ 密码锁仿真.pdsprj // Proteus仿真工程分离逻辑很清楚main.c里只放状态机的流转和业务规则具体的硬件操作全部下沉到Hardware目录。我挺欣赏这种分层的做法因为你在做二次开发时比如把LCD1602换成OLED或者把继电器换成步进电机只需要动Hardware层对应文件业务逻辑完全不用碰。这也是我建议你在自己的项目里保持的习惯驱动归驱动业务归业务别混在一起。2.3 主循环架构一个典型的前后台系统main.c里跑的是一个经典的前后台系统主循环是后台定时器中断是前台。主循环负责响应按键、刷新显示、执行状态切换定时器中断则用于按键消抖计时和长按检测。系统上电后首先从EEPROM读取密码如果读到的数据无效比如全FF就用默认密码123456覆写进去这是很多密码锁项目通用的初始化策略保证设备第一次使用就有一组可用密码。我单独把初始化的判断逻辑拎出来说是因为这里藏着一个新手容易踩的坑密码读取时如果只判断数据的开头字节可能因为EEPROM某一位损坏而读到错数据。合格的做法是在存储时同时写入一个校验标志位读取时先检查标志位是否匹配再决定是否使用这组密码。下面这段代码是项目里初始化读取的常见写法// eeprom.c 密码读取与校验 uint8_t EEPROM_ReadValid(uint8_t *pwdBuffer) { uint8_t flag I2C_ReadByte(EEPROM_ADDR_Flag); if (flag ! 0xA5) // 0xA5是写入时的有效标志 { // 标志不对说明从未写入过或数据损坏 return 0; } for (uint8_t i 0; i PASSWORD_LEN; i) { pwdBuffer[i] I2C_ReadByte(EEPROM_ADDR_PWD i); } return 1; }逻辑说明I2C_ReadByte是底层单字节读取函数EEPROM_ADDR_Flag是存储标志位的地址0xA5是代码约定的有效密码标识。先读标志再读密码这个顺序很重要标志位一旦不匹配就直接返回0避免把残留在EEPROM里的随机数据当成密码去校验。参数说明密码长度PASSWORD_LEN在这个工程里定义为6位你改成4位或8位时除了改这个宏定义还要同步调整EEPROM的地址偏移量否则后位的数据会读到下一个地址段去。3. 核心模块拆解从按键按下到锁舌动作的完整链路3.1 矩阵键盘扫描为什么不用中断而用轮询矩阵键盘的原理是把16个按键排成4行4列的网格通过给行线输出低电平、检测列线电平来定位按键。项目里用的是行扫描法每次只拉低一行再去读四根列线就能知道这一行里哪个键被按下循环四轮就能扫完整个键盘。为什么不用外部中断因为16个按键如果每个都占一个中断引脚那整个芯片的GPIO基本就耗尽了。用轮询加定时器消抖是成本和可靠性的折中。不过要注意轮询扫描的周期必须足够短否则按键有延迟感经验值是每2ms扫描一轮。下面是keypad.c里的扫描核心// keypad.c 4x4矩阵键盘扫描 uint8_t KeyScan(void) { uint8_t row, col; uint8_t keyValue 0xFF; // 默认无按键 GPIO_Write(GPIOA, 0x0F); // 低四位输出0高四位保持 for (row 0; row 4; row) { // 依次拉低当前行的引脚 GPIO_WriteBit(GPIOA, RowPin[row], Bit_RESET); for (col 0; col 4; col) { if (GPIO_ReadInputDataBit(GPIOA, ColPin[col]) Bit_RESET) { // 检测到该列被拉低说明(row,col)处按键按下 keyValue KeyMap[row][col]; // 等待按键释放避免一次按下多次触发 while (GPIO_ReadInputDataBit(GPIOA, ColPin[col]) Bit_RESET); break; } } GPIO_WriteBit(GPIOA, RowPin[row], Bit_SET); // 恢复高电平 if (keyValue ! 0xFF) break; } return keyValue; }逻辑说明GPIO_Write(GPIOA, 0x0F)把端口低四位写成0是为了让行线全部先输出低电平然后进入循环逐行操作。RowPin和ColPin是引脚映射表KeyMap是二维键值表比如KeyMap[0][1]就代表第0行第1列的按键字符。检测到按键后代码里的while等待释放是为了消除长按导致的连续触发但这也带来了一个问题按键必须完全松开后函数才会返回如果你要做一个长按功能这种写法就不够用了。参数说明扫描返回值0xFF表示没有按键事件。这个约定很关键主循环拿到0xFF时直接跳过处理逻辑不刷新任何状态。如果某个按键的键值恰好也是0xFF那这个键会变成永远按不了所以KeyMap表中不要定义值为0xFF的按键。3.2 密码校验与错误锁定规则都写在哪里密码校验是整个项目的业务核心。它的规则并不复杂用户输入6位密码逐个和存储的密码比较全部一致则开锁不一致则记录一次错误连续错误3次后锁定键盘30秒。这套规则写在一个独立的函数里方便main调用也方便你改逻辑。// main.c 密码校验逻辑 uint8_t Pwd_Check(uint8_t *inputPwd) { uint8_t stored[PASSWORD_LEN] {0}; uint8_t i; EEPROM_ReadValid(stored); // 从EEPROM读回正确密码 for (i 0; i PASSWORD_LEN; i) { if (inputPwd[i] ! stored[i]) { return 1; // 有一位不匹配即判定失败 } } return 0; }逻辑说明这里采用逐字节比较而不是用memcpy直接整块比对是因为每个字节的差异都需要一个明确的可观测点方便你在调试时通过断点定位是第几位密码出错。返回值用0表示成功、1表示失败这个约定整个工程里是统一的。和校验函数配套的还有一个错误计数器放在main.c的全局变量里。每校验失败一次就自增当计数等于3时状态机切换到LOCKED状态主循环里所有按键事件全部忽略同时启动一个软件定时器计时30秒后清除错误计数并切回待机状态。有一点必须提醒你错误计数在掉电后会丢失因为它是RAM里边的变量。如果要求掉电后仍然保留错误状态就得把它也写进EEPROM但这会带来Flash寿命问题所以一般课程设计不需要这么较真。3.3 修改密码的完整流程验证旧密码后双次输入新密码修改密码的功能是这个项目比普通开关锁Demo更有价值的部分。流程设计是用户先按修改键进入设置模式此时要求输入旧密码验证身份验证通过后才能输入新密码。新密码需要输入两遍两遍一致才写入EEPROM。写入EEPROM的代码需要注意地址分配不能把新密码覆盖到旧密码的地址之外。下面这端代码就是典型的写入逻辑// eeprom.c 写入新密码到EEPROM void EEPROM_WriteNewPwd(uint8_t *newPwd) { uint8_t i; I2C_WriteByte(EEPROM_ADDR_FLAG, 0xA5); // 先写有效标志 for (i 0; i PASSWORD_LEN; i) { I2C_WriteByte(EEPROM_ADDR_PWD i, newPwd[i]); } }逻辑说明先写标志位后写密码是因为一旦中途掉电标志位已经更新但密码没写完下次上电会读到一段残缺数据。有些严谨的做法会先写密码再写标志保证标志位是最后生效的。本项目的顺序虽然简单但如果要上真实产品我建议你把写标志放到最后一步。参数说明EEPROM_ADDR_PWD是密码存储区的起始地址密码长度为6时实际占用的是基址到基址5这6个地址。如果你要扩展密码到8位记得检查这片地址是否被其他数据占用。4. 环境搭建与仿真运行从源码到Proteus里看见锁弹开4.1 Keil工程配置别漏了生成HEX这一步拿到源码后第一步不是打开main.c而是先确认Keil工程能不能编译出HEX文件。打开工程后点击魔术棒图标进入Options for Target在Output标签页里勾选Create HEX File。这一步漏了后面在Proteus里加载的就是一个过期的hex文件甚至根本没有hex文件。工程配置里还需要注意芯片型号选择这个项目的目标芯片是STM32F103C8T6如果选成其他型号启动文件可能不匹配导致编译报错。另外标准外设库的版本要和启动文件的版本对应常见的组合是标准外设库V3.5配对应启动文件你在工程里看到startup_stm32f10x_md.s这个汇编文件时就说明用的是中等容量芯片的启动配置。还有一处经常让人翻车的设置在C/C标签页里的Define栏填了USE_STDPERIPH_DRIVER这是标准外设库使用驱动层时必须的宏定义。如果你编译时报一堆函数未定义先去看看宏定义是否被误删了。4.2 Proteus仿真搭建芯片晶振频率是个大坑打开Simulation目录下的密码锁仿真工程你会看到完整的电路。如果要从头搭步骤是放置STM32F103C8T6芯片、LCD1602、4x4键盘、LED和电阻。连接完线路后双击芯片弹窗在Program File栏里加载Output目录下生成的HEX文件然后注意晶振频率。代码里的SystemInit默认使用外部8MHz晶振并通过PLL倍频到72MHz所以Proteus里芯片属性面板的Clock Frequency必须填8MHz如果填了内部RC的默认8MHz而代码又配置了HSE仿真就会卡住。很多人在这一步栽跟头仿真运行了但LCD没有任何反应按键也没响应。原因基本就两个要么HEX没加载要么时钟频率和代码不匹配。检查方法很简单点击运行按钮观察芯片左下角的运行指示灯是否在闪烁如果不闪那基本就是时钟没有跑起来。4.3 运行验证把功能逐一过一遍仿真启动后第一屏LCD上会显示提示输入密码的字符比如INPUT PASSWORD。输入默认密码123456如果正确继电器吸合LED点亮LCD显示UNLOCK。按下修改密码键后屏幕提示输入旧密码验证通过输入两次新密码存储成功后屏幕显示SUCCESS。我建议你验证时做一个最容易被忽略的测试连续输错三次密码观察键盘是否进入锁定状态。这个测试能确认错误计数器的逻辑是否正确也能验证定时器的计时是否流畅。在Proteus里观察定时器计时注意看右下角的仿真时间如果程序用了阻塞式延时仿真时间会明显变慢这是一个值得优化的点可以试试。4.4 常见失败表现对应的处理手段如果你发现LCD显示的是乱码而不是英文字符先检查RS和EN两根控制线是否接反了。LCD1602的RS接PB8、EN接PB10这两根线在Proteus里很容易交叉连错。按键没有响应时用鼠标点击按键观察模拟LED是否跟着亮如果完全没反应十有八九是键盘的行列引脚对应关系与KeyMap表不一致对照keypad.h里RowPin和ColPin的定义逐项检查。5. 踩坑记录与排查指南仿真能跑但实物流转时会遇到的那些事5.1 现象Proteus里芯片一直是灰色点击运行没反应我在拆项目时第一次加载HEX后就遇到了这个状态。后来发现是芯片属性里的Program File路径指向了旧目录的HEX文件而那个文件已经编译过了。重新编译生成新的HEX再加载同时确认Clock Frequency为8MHz芯片就正常运行了。出现灰色的本质是芯片没有加载有效的程序数据所以优先检查HEX路径和文件修改时间。5.2 现象LCD显示全黑方块或者只有一行黑格LCD1602上电后如果初始化时序不对最常见的就是这种表现。项目里的lcd1602.c在初始化时有一段上电延时短于15ms就会导致内部控制器还没来得及复位。解决方法是把main函数里调用LCD_Init之前的延时加长到50ms这一步在Proteus里可能不明显但在实物上很关键因为实物上电瞬间电源会有个爬升过程芯片供电不稳时LCD更容易初始化失败。5.3 现象按键按下一次却触发了两次这是典型的消抖问题。项目代码里只有定时器消抖的标记如果你发现用鼠标在Proteus里点击时偶尔会出现双触发那是Proteus的模拟按键在电平翻转瞬间产生了多次跳变。解决方法是检查消抖标志是否配合了延时在KeyScan里加上10-20ms的软件延时或者用状态机的消抖逻辑替代简单的延时。这里有个细节判断标准如果只是在Proteus里偶发而在实物板上完全正常那就优先怀疑是Proteus的模拟时序问题不必过度改代码。5.4 现象修改密码后重启密码又变回默认值这说明密码根本没写进EEPROM或者写入了但读不出来。在Proteus里要注意EEPROM芯片是否真的连接了SDA和SCL引脚有一类典型翻车是在仿真中拖了24C02但两根I2C线没接到PB6和PB7上。在代码层面排查时重点看你使用的是硬件I2C还是模拟I2C如果是硬件I2C确认GPIO复用配置了AFIO时钟很多初学者会在RCC配置里漏掉AFIO时钟导致外设配置不完整。5.5 现象仿真运行非常缓慢按键延迟严重点击Proteus的运行按钮后仿真时间明显慢于真实时间每秒只能处理几次操作问题出在定时器中断频率或者主循环里的delay。标准外设库的SysTick配置通常设为1ms中断一次如果你在中断里做了太重的任务比如刷新LCD仿真速度就会断崖式下跌。处理方案是把LCD刷新工作放到主循环中断只做标志位置1。6. 二次开发把电子密码锁扩展成一个可用的门禁系统拿到源码后最大的收益不是跑通仿真而是知道怎么改造。我拆这个项目时想到的最直接的扩展是把继电器替换成真实的电磁锁驱动电路同时增加HC-SR501人体红外传感器实现检测到人靠近才启动输入界面的节能逻辑。具体改造路径可以从三个方向推进。第一个方向是输入方式的升级把4x4矩阵键盘换成触摸按键或者增加一个RC522的RFID读卡器模块让门禁支持刷卡开锁。RC522走SPI接口你需要在Hardware目录新增rc522.c和rc522.h然后在main.c的待机状态里轮询是否有合法卡片通过后将卡号与管理员卡号比对来决定是否开锁。这段逻辑乒乓结构很清晰完全复用现有的Pwd_Check判定流程。第二个方向是上位机联动通过串口把开锁记录发出去。STM32的USART1接USB转TTL模块每次校验成功或失败后用printf格式化输出时间戳和操作结果。串口接收中断可以设计成临时密码下发上位机发一串指令码单片机解析后存入EEPROM的临时密码区30分钟内有效超时自动清除。第三个方向是增加密码分段管理把6位密码拆成管理员密码和普通用户密码管理员可以增删普通用户。这只涉及密码存储区的地址规划把EEPROM的后半段划分成一个用户列表区每条记录包含用户ID、密码和启用标志。校验函数从比对单组密码改为遍历用户列表每找到一个匹配就返回对应用户的权限级别。改完这些功能后验证方法是把main.c的状态机再加一个分支AUTH_MODE下按不同键进入不同认证子流程。我这个人在拆这类项目时有一个习惯每次改完代码都强制自己走一遍完整的编译、加载HEX到仿真、输入密码、断电重启、再次输入的闭环流程确保改动没有破坏原有的核心链路。并不是每次都能一次通过有一次我改串口部分时不小心把GPIO初始化里的一个时钟使能漏掉了结果整个GPIOA都失效按键全部失灵排查了半个多小时才定位到是RCC_APB2PeriphClockCmd的参数不完整。从那以后我就每次改完都先看一眼外设时钟配置再往下走。希望这篇拆解能帮你把项目跑起来也让你在二次开发时少走几步冤枉路。本文还有配套的精品资源点击获取