简介:基于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-PA7,LCD1602的数据线接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的参数不完整。从那以后我就每次改完都先看一眼外设时钟配置,再往下走。希望这篇拆解能帮你把项目跑起来,也让你在二次开发时少走几步冤枉路。
本文还有配套的精品资源,点击获取