☰
STM32电子密码锁仿真设计:状态机、EEPROM存储与Keil+Proteus联调
2026/10/20 19:54:52 网站建设 项目流程

简介:这是一套基于STM32F103C6设计的电子密码锁仿真与源码工程,面向嵌入式初学者、STM32开发爱好者及课程设计用户。资源包含完整的HAL库驱动源码、外设配置和应用层逻辑,可帮助读者理解密码输入、校验、显示及开锁控制等典型设计流程,也便于在此基础上扩展按键、LCD或EEPROM等功能。压缩包共277个文件,以h头文件与c源文件为主体,另有o目标文件、axf调试输出及uvprojx工程配置等,整体约7.6MB,目录结构清晰,适合对照学习与二次开发。目前已有559人学习下载,资料偏重实践,比较适合想通过完整工程快速入门STM32外设编程和状态机设计的用户。

1. 需求切入:为什么选用 STM32 做电子密码锁仿真,而不是直接上实物

某实验室要把机械锁换成电子密码锁,需求列出来其实只有三条:密码能改、输错三次要锁定、掉电不能丢密码。最终交付的是一套基于 STM 设计的电子密码锁仿真加源码,仿真在先、代码在后,不急着铺 PCB,先把按键、存储、校验、驱动这一整套逻辑在仿真环境里完整跑通。选 STM32 而不是 51 单片机,是因为密码存储要碰 I2C 和 EEPROM,错误计数、锁定计时、状态切换这些业务逻辑放在 51 上写会非常挤,而且调试手段也受限。STM32F103 系列仿真模型成熟,Keil 里可以直接单步调试,外设映射清晰,很适合把逻辑设计和硬件设计拆开做。这套方案适合两类人:准备交电子系统课设或毕设的学生,以及产品还没有定型、想先把核心流程在仿真里验证完再考虑硬件方案的工程师。

2. 总体架构设计:硬件选型、外设映射与状态机规划

2.1 主控选型与外设引脚映射

主控选择 STM32F103C8T6,而不是同系列的低容量型号,主要看三件事:一是硬件 I2C 外设完整,直接接 AT24C02,不需要软件模拟时序;二是 64KB Flash、20KB SRAM,跑密码状态机绰绰有余,还给后续加指纹模块或者蓝牙模块留了空间;三是 Proteus 和 Keil MDK 对这颗芯片的支持都很完整,仿真模型不用自己建,直接放置就能用。

外设引脚映射如下,这套分配在后续代码里保持一致:

功能模块引脚分配配置说明
4×4 矩阵键盘行线 PB0~PB3,列线 PB4~PB7行线推挽输出,列线浮空输入
LCD1602 显示数据线 PA0~PA7,RS=PC0,RW=PC1,EN=PC28 位并行模式
AT24C02 密码存储PB6=SCL,PB7=SDA硬件 I2C1,两根线各接 4.7kΩ 上拉
电磁锁继电器驱动PA8高电平导通,继电器线圈并联续流二极管
有源蜂鸣器PA9NPN 三极管放大驱动
状态指示 LEDPC13低电平点亮,用于锁定/开锁可视化

引脚分配的原则是让矩阵键盘和 LCD 各占一组完整端口,这样操作 GPIO 端口寄存器时不需要做位屏蔽,代码写起来直接、不容易出错。I2C 引脚固定挂在 PB6/PB7 上,这是 STM32F103 的硬件映射,不能随意改。实物开发时经常有人把 PB6/PB7 复用到别的功能上导致 I2C 拉不起来,仿真阶段虽然不会遇到这个问题,但按这个映射表去做实物能少踩一个坑。

2.2 四层模块划分与数据流向

工程代码按职责拆成四层,每一层只做自己范围内的事,遇到问题也知道去哪层找原因。

BSP 驱动层负责 GPIO、I2C、定时器和看门狗的初始化,只提供 Init 和极少数读写接口。应用层封装具体外设:矩阵键盘扫描、LCD1602 显示、AT24C02 读写、蜂鸣器与继电器控制。业务层实现密码校验状态机,包括错误次数累计、锁定倒计时、修改密码事务。主循环只做三件事:轮询按键、喂狗、把按键事件交给状态机处理。

数据流向是一条单行道:矩阵键盘产生按键事件,状态机根据当前状态决定是写 EEPROM、操作继电器还是忽略按键,LCD 只负责把结果呈现出来。所有模块之间不跨层调用,比如 LCD 显示函数不能直接读按键缓冲区,那样会把逻辑搅成一团。实际做下来,这种分层最大的收益是测试方便:把按键事件模拟成函数调用,不碰屏幕不碰存储,就能把状态机跑一遍。

2.3 系统状态机:六状态设计与转移边界

系统运行生命周期由六个状态覆盖,每个状态的行为边界非常明确:

  • STATE_INIT 上电初始化:从 EEPROM 读取密码长度、密码内容和上次的错误次数。如果读出来是 0xFF 或长度越界,说明 EEPROM 是空白的,此时写入默认密码 123456,确保系统第一次上电就有可用密码。
  • STATE_IDLE 待机:屏幕显示待机界面,等待第一个数字键。
  • STATE_INPUT 输入:每按一个数字键追加进输入缓冲,界面统一用 * 回显,不显示明文。
  • STATE_VERIFY 校验:按下 # 键触发,把输入缓冲与 EEPROM 中存储的密码逐字节比对。
  • STATE_OPEN 开锁:继电器吸合 5 秒后自动断开,回到待机。
  • STATE_LOCKED 锁定:连续输错三次进入,锁定 30 秒,期间蜂鸣器鸣叫,任何按键都无效。

状态转移关系可以收敛成一张表:

当前状态触发事件动作下一状态
INIT初始化完成读取 EEPROM 并显示待机画面IDLE
IDLE按下数字键清空输入缓冲,写入第一位INPUT
INPUT按下数字键且缓冲未满追加输入位,显示一个 *INPUT
INPUT按下 # 且长度符合要求发起校验VERIFY
VERIFY校验通过继电器吸合,错误次数清零OPEN
VERIFY校验失败且错误次数不足 3 次提示剩余可尝试次数IDLE
VERIFY校验失败且累计达到 3 次锁定系统,蜂鸣器报警LOCKED
LOCKED30 秒倒计时结束清空错误计数,恢复待机IDLE

这里有两个容易被漏掉的转移边界。第一个是 INPUT 状态下按 * 键,应该在当前输入缓冲内删除最后一位,用作输错时的后悔药,不进其他状态。第二个是 VERIFY 状态执行校验期间,不允许任何按键打断,要等校验函数返回后再恢复按键扫描,否则处理到一半被新按键塞进来,输入缓冲就乱了。Locked 状态下还要注意,不要用阻塞延时做 30 秒倒计时,否则看门狗会一直在临界边缘试探,我用 SysTick 做全局 tick 计数,锁定后每毫秒递减,到零自动回 IDLE。

3. 核心实现细节:键盘扫描、EEPROM 存取与校验状态机代码

3.1 矩阵键盘扫描与双次消抖

矩阵键盘扫描的基本思路是逐行拉低、读列线电平。代码里先用一张映射表把行列位置翻译成实际键值,再按行扫描:

// 键值映射表:4 行 × 4 列 static const uint8_t key_map[4][4] = { {'1', '2', '3', 'A'}, // 第一行 {'4', '5', '6', 'B'}, // 第二行 {'7', '8', '9', 'C'}, // 第三行 {'*', '0', '#', 'D'} // 第四行,* 退格,# 确认 }; // 行线 PB0~PB3,列线 PB4~PB7 static const uint16_t row_pin[4] = {GPIO_Pin_0, GPIO_Pin_1, GPIO_Pin_2, GPIO_Pin_3}; static const uint16_t col_pin[4] = {GPIO_Pin_4, GPIO_Pin_5, GPIO_Pin_6, GPIO_Pin_7}; uint8_t Key_Scan(void) { uint8_t row, col; for (row = 0; row < 4; row++) { GPIO_Write(GPIOB, 0xFF); // 所有引脚先置高 GPIO_ResetBits(GPIOB, row_pin[row]); // 当前行拉低 Delay_us(50); // 等待电平稳定 for (col = 0; col < 4; col++) { if (GPIO_ReadInputDataBit(GPIOB, col_pin[col]) == 0) { // 列线被拉低,说明当前行列交叉处有按键按下 while (GPIO_ReadInputDataBit(GPIOB, col_pin[col]) == 0); return key_map[row][col]; } } } return 0xFF; // 无按键 }

这段代码里有两个细节:一是每轮扫描先把整个 GPIOB 写 0xFF,再单独拉低当前行,防止上一行残留电平影响判断;二是检测到按键后自旋等待松开,避免一次按键被重复返回多次。自旋等待在仿真阶段没有问题,但如果后面要做低功耗休眠,需要改成非阻塞方式:检测到按下就记录并返回键值,主循环下一轮再检测松开状态。

单纯扫描还不够,机械按键按下瞬间有 5~15ms 的抖动窗口,Proteus 仿真模型里也带这个特性,所以必须加消抖:

uint8_t Key_Get(void) { uint8_t k1 = Key_Scan(); if (k1 == 0xFF) return 0xFF; Delay_ms(20); // 抖动窗口 5~15ms,延时 20ms 足够覆盖 uint8_t k2 = Key_Scan(); if (k1 == k2) return k1; // 两次扫描结果一致,判定为有效按键 return 0xFF; // 结果不一致,按抖动处理丢弃 }

消抖的延时长短不是拍脑袋定的,实测大多数轻触按键的抖动集中在 10ms 以内,取 20ms 是留了余量。仿真环境运行速度比实物慢,35 毫秒以上的延时会让按键响应显得迟钝,所以 20ms 是一个兼顾稳定性和响应速度的值。真正容易翻车的地方是,如果在消抖延时期间主循环去做了别的耗时操作,按键状态就丢了一半,所以 Key_Get 应该被看成一次完整的原子操作。

3.2 EEPROM 密码存储:地址规划与读写封装

AT24C02 通过硬件 I2C1 挂载,密码、长度、错误次数和锁定标志都放在里面。先规划地址,避免后续逻辑越界:

地址内容说明
0x00密码长度写入时固定为 6
0x01~0x06密码内容6 字节 ASCII 码
0x10连续错误次数掉电不清零
0x11锁定标志记录是否处于锁定状态

读一个字节的标准 I2C 时序如下,使用硬件 I2C 外设而不是软件模拟,是因为硬件时序由芯片内部状态机保证,不会被按键中断打断:

#define EEPROM_WR_ADDR 0xA0 #define EEPROM_RD_ADDR 0xA1 uint8_t EEPROM_ReadByte(uint8_t addr) { uint8_t val = 0; // 起始信号 + 写地址 I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, EEPROM_WR_ADDR, I2C_Direction_Transmitter); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 发送内存地址 I2C_SendData(I2C1, addr); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 重复起始信号 + 切到读模式 I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, EEPROM_RD_ADDR, I2C_Direction_Receiver); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); // 读取一个字节 while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); val = I2C_ReceiveData(I2C1); I2C_GenerateSTOP(I2C1, ENABLE); return val; }

代码里每一步都在轮询 I2C 事件标志,这是硬件 I2C 的标准用法。如果仿真中程序卡在某个 while 循环里出不来,大概率是地址发送后 AT24C02 没回 ACK,需要在前面加一个读状态寄存器的调试函数确认器件在线。另一个我常犯的错是写完一字节后立刻读,AT24C02 有 5ms 左右的写周期,写完马上访问会读到旧数据,行业内管这叫黑匣子问题,所以写操作后必须加延时或查询 ACK 状态。

密码写入比读取多一步回读校验:

uint8_t Password_Save(uint8_t *pw, uint8_t len) { uint8_t i; EEPROM_WriteByte(PW_LEN_ADDR, len); Delay_ms(10); // 等待 AT24C02 内部写周期完成 for (i = 0; i < len; i++) { EEPROM_WriteByte(PW_DATA_ADDR + i, pw[i]); Delay_ms(10); // 每个字节之间留足够写周期 } // 回读校验:写入和读出不一致直接返回失败 for (i = 0; i < len; i++) { if (EEPROM_ReadByte(PW_DATA_ADDR + i) != pw[i]) return 0; } return 1; }

回读校验这步不是多余动作。仿真环境里时序不太严格可能稳定,但实物上电源跌落、总线干扰都可能导致写失败。如果不回读,界面提示“保存成功”但实际存的是错值,掉电重启后才发现密码不对,那时候已经没有后悔药了。

3.3 密码校验与连续错误锁定

密码校验函数把输入缓冲和 EEPROM 里的密码逐字节比对,长度不一致直接返回失败,不做多余的比较:

uint8_t Password_Verify(void) { uint8_t stored_len, match = 1, i; stored_len = EEPROM_ReadByte(PW_LEN_ADDR); if (stored_len != input_len) return 0; for (i = 0; i < stored_len; i++) { if (EEPROM_ReadByte(PW_DATA_ADDR + i) != input_buf[i]) { match = 0; break; } } return match; }

校验之后的动作分为三路:通过则继电器吸合并清零错误计数;失败且次数小于 3 则提示剩余次数;失败且达到 3 次则进入 LOCKED 状态。错误次数本身也存进 EEPROM,这样即使断电重启,锁定状态依然保留,用户不能靠重启绕过锁定。

void StateMachine_Process(uint8_t key) { switch (lock_state) { case STATE_INPUT: if (key >= '0' && key <= '9') { if (input_len < 6) { input_buf[input_len++] = key; LCD_ShowChar('*'); // 只回显星号 } } else if (key == '#') { lock_state = STATE_VERIFY; Verify_And_Act(); } else if (key == '*') { if (input_len > 0) { input_len--; // 删除输入缓冲最后一位 LCD_Backspace(); } } break; case STATE_LOCKED: // 锁定期间不处理任何按键,倒计时结束后由 tick 机制复位 break; default: break; } }

状态机的执行入口在主循环里统一调用,输入缓冲、输入长度都定义为全局变量,这样各个状态之间共享数据不需要传递指针。这种写法在裸机工程里最常见,也最容易被评审老师看懂。如果将来要加多用户密码,把 input_buf 换成二维数组,再加一个用户索引就能扩展。

3.4 修改密码流程与操作反馈

修改密码的入口放在待机界面按 B 键触发。流程分三步:先要求输入旧密码,验证通过后才能录入新密码;新密码需要输入两遍,两遍一致才写入 EEPROM;写入成功后给出一秒的提示音和显示反馈。

void Change_Password_Start(void) { // 修改密码前强制验证旧密码,防止误操作或旁路 if (Verify_OldPassword()) { input_cnt = 0; state = STATE_INPUT_NEW; // 进入新密码录入 LCD_Clear(); LCD_ShowString("NEW PIN: "); } else { error_cnt++; // 旧密码错误同样计入错误次数 LCD_ShowString("OLD PIN ERR"); } }

新密码两遍录入的逻辑不复杂,但有一个细节:第一次录入完按 # 确认后,清空缓冲进入第二次录入,第二遍确认时直接比较两遍缓冲。如果两遍不一致,重置输入状态并提示重新开始,而不是把第一遍缓存保留,这样能避免用户记忆混乱。

这个流程里最容易踩的坑是把修改密码做成可以无条件触发。如果任何人在待机界面按 B 都能改密码,密码锁就等于形同虚设,所以旧密码验证是硬门槛,而且验证失败也计入错误次数。

3.5 看门狗与主循环的耦合设计

电子密码锁长期处于待机状态,程序跑飞了没人知道,所以独立看门狗是必须的。看门狗初始化配置如下:

void IWDG_Init(void) { IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); IWDG_SetPrescaler(IWDG_Prescaler_64); // 64 分频 IWDG_SetReload(0xFFF); // 重载值 4095 IWDG_ReloadCounter(); IWDG_Enable(); }

喂狗的位置比看门狗本身的配置更关键。如果只在按键事件或状态切换时喂狗,用户输入密码期间停顿超过一秒,看门狗就会复位。通常的做法是把喂狗放在主循环的最开头,每轮循环都执行一次,不依赖任何业务分支:

while (1) { IWDG_ReloadCounter(); // 无条件喂狗,放在主循环入口 uint8_t key = Key_Get(); if (key != 0xFF) StateMachine_Process(key); LCD_Refresh(); }

开发调试阶段有个实用技巧:先把看门狗整体禁用,等所有功能都调通后再打开,避免调试暂停时反复触发复位干扰思路。仿真和实物的运行速度不一样,Proteus 里模拟 I2C 和 LCD 的耗时明显变长,主循环周期比实物大好几倍,喂狗间隔要留足余量,我就是这么应对的。

4. 仿真部署与常见问题排查:Keil 与 Proteus 联调中的五个坑

4.1 仿真前的工程配置与文件清单

开始联调前,先把三件事核对掉:第一,Keil 的 Device 选择要和 Proteus 原理图里放的芯片型号一致,型号不同启动文件配置的寄存器地址对不上,代码会在 SystemInit 阶段跑飞;第二,Proteus 的晶振参数要和代码里 RCC 配置匹配,STM32F103 外部晶振通常填 8MHz;第三,仿真运行速度优先调成低比例,开了 LCD 刷新和蜂鸣器之后 Proteus 会明显变慢,按键响应看起来像死机,其实只是仿真速度跟不上。

这套工程的代码文件可以按下面的结构组织,每个文件职责单一:

文件职责
main.c主循环、外设初始化、喂狗
key.c矩阵键盘扫描与消抖
lcd1602.cLCD1602 显示驱动
at24c02.cAT24C02 I2C 读写
state_machine.c密码校验、错误锁定、修改密码状态机
stm32f10x_it.c中断服务函数与 tick 计时

4.2 问题一:仿真启动后程序卡在 SystemInit

现象:Proteus 启动仿真,程序指针停在 SystemInit 或 HardFault_Handler,Keil 软件仿真却一切正常。

原因:这一类现象十有八九是外部晶振没震起来。Proteus 里的晶振模型需要设置匹配的频率,如果代码用 HSE 而原理图晶振参数没配好,RCC 等待外部时钟就绪的循环会一直转不出去,看起来就是卡死。

解决:先用内部时钟 HSI 把程序跑通,确认逻辑无误后再切回外部晶振。RCC 配置要加上超时保护,等待外部时钟的标志位循环加一个计数上限,超时后自动切换回内部时钟,这样即使晶振异常程序也能继续运行,不会变成黑匣子。

4.3 问题二:按键按一次,输入框出现两个字符

现象:按下数字键,LCD 上一下子多出两个 *,输入长度直接跳过一位。

原因:矩阵键盘扫描里用了等待松开的自旋,但 Proteus 按键模型按下时存在抖动,Key_Get 的双次消抖没有覆盖到释放阶段,或者主循环轮询频率太高,一次按下被拆成两个有效事件。

解决:把按键事件的生成条件改成“边沿触发”,也就是只有在按键状态从按下变成松开的那一轮才返回键值。实现方式很直接:全局记录上一次扫描值,本次扫描值不同且本次为松开状态时,才把缓存里的键值交给状态机,其余情况一律返回 0xFF。

4.4 问题三:修改密码后重启仿真,新密码丢失

现象:修改密码成功,关闭仿真重新打开,用新密码无法开锁,旧密码反而有效。

原因:Proteus 里 AT24C02 模型的存储内容在仿真会话之间不一定保留,尤其是直接关闭工程的情况。另外,如果写入时序没留够 AT24C02 的写周期,最后一个字节写入不完整,重启后读取到的密码长度或内容异常,系统回退到默认状态。

解决:两个办法叠加。第一,上电初始化时增加默认密码填充逻辑,读到的长度不是 6 或内容校验失败时,重写默认密码 123456 并清零错误计数,保证每次仿真起始状态确定。第二,修改密码的写入流程必须带回读校验,Password_Save 返回失败时界面直接提示写入错误,不给用户造成保存成功的假象。

// 上电时修复非法 EEPROM 状态 void Storage_CheckAndInit(void) { uint8_t len = EEPROM_ReadByte(PW_LEN_ADDR); if (len != 6) // 非法长度说明存储区损坏或从未写入 { const uint8_t default_pw[6] = {'1','2','3','4','5','6'}; Password_Save(default_pw, 6); EEPROM_WriteByte(PW_ERR_ADDR, 0); EEPROM_WriteByte(PW_LOCK_ADDR, 0); } }

4.5 问题四:开启看门狗后系统在仿真中反复复位

现象:代码在 Keil 里单步正常,挂到 Proteus 后每隔几秒屏幕重新初始化,像是系统不断重启。

原因:Proteus 模拟速度比真实芯片慢得多,LCD 忙等待、EEPROM 写延时和蜂鸣器操作把单次主循环的时间拉长,喂狗周期超过了看门狗超时值。仿真模式下用户输入停顿一秒以上也会触发这个问题。

解决:把喂狗从业务分支挪到主循环入口,保证每轮都执行。然后按仿真实际耗时把看门狗超时从 1 秒放宽到 2 秒,64 分频下把重载值从 0xFFF 改成更大值。更稳妥的做法是开发阶段先禁用看门狗,全部功能验收通过后最后再打开,用最短时间把风险面收敛。

4.6 问题五:LCD 显示乱码或第二行黑影

现象:LCD 前几个字符正常,往后越显示越乱,或者第二行出现一排黑色方块,屏幕整体发花。

原因:LCD1602 初始化时序不满足。常见的做法是用固定延时代替忙标志查询,仿真环境运行速度变化时,固定延时不够就会丢数据。另一个原因是 EN 引脚脉冲宽度不够,LCD 模块的时钟边沿采不到数据。

解决:把写命令和写数据的函数统一改成忙标志查询方式:先读 BF 标志,为 1 就继续等待,为 0 再写下一个字节。同时核对 Proteus 里 LCD1602 模型的时序参数,把 EN 高电平脉冲宽度保持在 1ms 以上。这个修改在实物和仿真上都适用,是 LCD 驱动的标准写法。

5. 验收与答辩:测试用例矩阵与评审演示脚本

5.1 功能测试用例与验收标准

验收不能只看“能开锁”,要按功能点逐条过。下面这份测试矩阵是我实际用过的版本,直接照着执行就可以:

用例编号操作步骤预期结果
T01上电,不输入任何按键屏幕显示待机画面,无乱码无异常
T02输入正确 6 位密码后按 #继电器吸合,LED 点亮,5 秒后自动断开
T03连续输入错误密码两次每次提示剩余次数,第三次直接锁定 30 秒
T04锁定期间按任意键屏幕保持锁定提示,按键不响应
T05输入过程中按 *删除输入缓冲最后一位,星号同步减少
T06按 B 修改密码,两次输入一致提示保存成功,断电重启后新密码生效
T07在 LOCKED 状态下断电再上电系统仍然处于锁定状态,不能通过重启绕过

5.2 答辩演示脚本

演示顺序建议按风险从低到高排:第一步先演示正常开锁,这是主功能,稳定得分;第二步演示错误次数锁定和倒计时恢复,展示状态机边界处理能力;第三步演示修改密码和断电重启,证明存储设计经得起检验;第四步展示代码文件结构和模块分层,让评审看到工程能力而不只是功能实现。整个过程控制在五分钟以内,不要在一次演示里反复试错,评委最怕看到的就是现场按了五次密码都开不了锁。

5.3 评审老师最常追问的三个问题

“为什么用硬件 I2C 而不用软件模拟?”可以答:硬件 I2C 不占 CPU 轮询时间,AT24C02 是总线上唯一的从设备,不存在总线抢占问题,硬件时序由外设状态机保证,比软件模拟稳定。

“密码明文存在 EEPROM 里,安全性怎么保证?”可以答:当前方案重功能验证,扩展上可以在密码写入前做异或混淆,或者只存密码的散列值,这两种改法都不影响状态机结构。

“锁定 30 秒期间断电再上电,锁定会不会被绕过?”可以答:错误次数和锁定标志都写在 EEPROM,掉电不清零,重新上电后初始化流程会读取锁定标志,停留在 LOCKED 状态直到倒计时结束。

我还记得第一次做类似项目时,就是没做存储状态的自检,答辩现场演示修改密码后重启直接变回出厂密码,当时对着键盘反复按,场面很尴尬。那之后我每次交付仿真工程,都强制自己走一遍“修改密码 → 回读校验 → 断电重启 → 再验证”的完整链路,确认存储状态在掉电前后一致才敢说完成。这套基于 STM32 的电子密码锁仿真和源码,整体就是按“状态机正确、存储可靠、边界可验证”这三条标准组织起来的,希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询