ARTICLE DETAIL

资讯详情

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

51单片机密码锁课设:Proteus仿真与24C02掉电存储设计解析

51单片机密码锁课设:Proteus仿真与24C02掉电存储设计解析 简介这是一份面向电子信息、计算机等专业本科生的单片机课程设计密码锁项目聚焦如何用单片机配合PROTEUS仿真实现安全门禁设备既适合正在准备课程设计的在校生也适合希望夯实嵌入式基础的初学者。压缩包共20个文件包含C语言源代码、配套头文件、汇编启动文件、HEX烧录文件、PROTEUS仿真工程DSN及编译调试中间件整体仅108KB其中HEX烧录文件和DSN仿真工程可直接复用结构紧凑便于按模块定位关键设计。项目以8051系列单片机为核心覆盖键盘输入、密码校验、24C02 EEPROM掉电存储、显示与报警等完整功能链同时演示了错误处理、多次锁定等安全策略通过仿真可直接观察运行时行为省去实物搭建成本。目前已有2239人学习下载。借助这套资料读者能系统掌握硬件电路布局、状态机制、I2C通信以及程序编译烧录流程对课程设计答辩和后续嵌入式开发都有较强的参考价值。1. 单片机密码锁课设仿真先行别让板子阻碍你理解程序拿到“单片机课程设计——密码锁PROTEUS仿真代码”压缩包时多数人的第一反应是打开 mms.DSN 点运行看到屏幕上有数码管跳数字就觉得课设完了。可答辩老师随口问一句“密码存在哪掉电会不会丢”很多人就答不上来。这个项目真正值得拆解的是 51 单片机最小系统、矩阵键盘、数码管动态扫描、24C02 掉电存储这几部分如何在一个 mms.c 工程里串起来。用 Proteus 仿真而不是急着焊板子优势在于能随时看存储器内容、改逻辑、反复复位把边界情况测透。下面按工程文件结构、核心代码、仿真调试、掉电验证四条线展开。2. mms.uv2 与 mms.hex 之间密码锁工程文件到底由什么构成2.1 密码锁系统架构与文件对照密码锁是一个典型的前后台系统前台是不断刷新的数码管、不断扫描的键盘后台是被按键事件触发的密码比较与开锁动作。压缩包里的文件不是摆设每个后缀都对应一个环节。文件作用说明mms.Uv2Keil 工程文件双击打开即可编译包含源文件列表和编译选项mms.c主程序按键处理、状态切换、主循环key.h键盘头文件键值定义、按键扫描函数声明xsh.h显示头文件数码管段码、位选控制、刷新函数24c02.hEEPROM 驱动头文件I2C 启动、停止、读写字节函数STARTUP.A518051 启动代码清零 RAM、初始化堆栈mms.hex编译产物Proteus 加载到单片机 Flash 的文件mms.DSNProteus 原理图仿真工程包含电路连接和元件参数核心主循环一般写成下面这种非阻塞结构而不是用大量 delay 把 CPU 耗死// mms.c 主循环输入密码、刷新显示 #include REG52.H #include key.h #include xsh.h #include 24c02.h unsigned char pwd_buf[6]; unsigned char pwd_cnt 0; void main(void) { unsigned char key; xsh_init(); // 初始化 I/O 和显示缓冲区 while (1) { key key_scan(); // 非阻塞扫键无键按下返回 KEY_NONE if (key ! KEY_NONE) { if (pwd_cnt 6) { pwd_buf[pwd_cnt] key; xsh_display_char(key); // 数码管显示当前按键 } } xsh_refresh(); // 动态刷新每次调用只点亮一位 } }key_scan()返回KEY_NONE时说明没有按键事件主循环继续跑xsh_refresh()。xsh_refresh()如果设计成每位显示 1ms6 位数码管一轮也就 6ms视觉上不会闪烁。pwd_cnt是输入计数达到 6 位后进入校验流程这部分通常放在按键事件里而不是主循环里做判断避免重复比较。2.2 STARTUP.A51 启动文件对复位和存储器配置的影响STARTUP.A51 是 Keil 为 8051 自动生成的汇编启动代码主要完成两件事把内部 RAM 清零、给 C 语言运行环境设置好堆栈指针。很多课程设计里这个文件被当成“工程自动带的垃圾文件”其实它决定了一个常见 bug上电后变量初值是否稳定。在 Keil 中打开 mms.Uv2工程窗口展开 Target能看到 STARTUP.A51。默认配置下IDATALEN是 80H表示内部间接寻址 RAM 清零长度XDATALEN是 0表示不使用外部 RAM。如果你在 mms.c 里定义了大数组并让编译器放到 xdata 段却没有把XDATALEN改成实际长度外部 RAM 就不会被启动代码清零程序读到的可能是随机值。调试时想观察这一过程可以在 STARTUP.A51 的?C_START标号处打断点然后在 Keil 里单步执行。你会看到编译产物 mms.hex 并不是从 main 开始而是先跑这一小段汇编再跳进void main(void)。这也解释了为什么硬件复位后密码锁必须处于一个确定状态按键缓冲区、错误计数、开锁标志都不能依赖上电随机值。2.3 24C02 选型理由与 I2C 硬件连接密码要掉电不丢常见的方案有 24C02、24C04、AT88SC0104 这类 I2C EEPROM。24C02 容量 256 字节对 6 位密码加标志位绰绰有余而且 Proteus 元件库里直接搜索24C02就能找到SDA、SCL 两根线在仿真里也最容易观察。硬件连接上有三个细节容易被忽略。第一SDA 和 SCL 必须接上拉电阻常用 4.7kΩ 到 5V否则 I2C 通信不稳定第二24C02 的 A0、A1、A2 决定 I2C 地址接地时器件地址是 0xA0写和 0xA1读第三WP 写保护引脚接 GND否则运行中会写不进密码。在 Proteus 原理图里常见错误是从元件库拖出来 24C02 却忘了给 SDA、SCL 加上拉导致仿真能跑但密码永远存不进去。3. 键盘扫描、密码校验与 EEPROM 读写实现3.1 矩阵键盘行扫描与消抖实现key.h 里最核心的是键值定义。因为 0 也是有效密码字符KEY_NONE必须用一个不会出现在键盘上的值比如0xFF而不是 0。矩阵键盘扫描常用“行拉低、读列”的方式下面这段是 4×4 键盘的常见实现// key.c4×4 矩阵键盘行扫描 #define KEY_NONE 0xFF unsigned char code key_map[4][4] { {1, 2, 3, 10}, {4, 5, 6, 11}, {7, 8, 9, 12}, {14, 0, 15, 13} }; unsigned char key_scan(void) { unsigned char row, col, val; for (row 0; row 4; row) { P1 ~(1 row); // 将第 row 行拉低其余行高 val P1 0xF0; // 读高四位列线 if (val ! 0xF0) { delay_ms(10); // 延时消抖 val P1 0xF0; if (val ! 0xF0) { col (~val) 4; return key_map[row][col]; } } } return KEY_NONE; }P1 ~(1 row)的最低目标是每次只拉低一行。例如 row0 时 P1.0 输出 0其余行都是 1 或高阻输入此时读高四位的组合就能知道是哪一列被按下。delay_ms(10)是给机械触点抖动时间仿真里可能不明显但实际焊板子时必须保留。这段代码有两个边界点。一是 return 的键值可能包含 0所以调用处必须用key ! KEY_NONE判断而不是直接if (key)二是按键不松开会持续触发实际工程里应增加“等待释放”或“边沿触发”逻辑。我一般会在key_map查表后加一个while ((P1 0xF0) ! 0xF0);让一次按下只算一次输入。3.2 密码比较进入锁定状态的逻辑设计密码校验不能只做“相等就开锁”还需要考虑输入长度、错误次数、锁定时间。常见做法是在 mms.c 里维护两个变量pwd_cnt统计已输入位数error_times统计连续错误次数。// 密码校验与锁定 unsigned char code default_pwd[6] {6,5,4,3,2,1}; unsigned char error_times 0; unsigned char lock_flag 0; unsigned char lock_seconds 0; void check_password(void) { unsigned char i; if (pwd_cnt ! 6) return; // 位数不足不允许比较 for (i 0; i 6; i) { if (pwd_buf[i] ! default_pwd[i]) { error_times; if (error_times 3) { lock_flag 1; // 三次错误进入锁定 lock_seconds 30; // 锁定 30 秒 } pwd_cnt 0; // 清空输入缓冲区 return; } } lock_flag 0; error_times 0; unlock_flag 1; // 开锁成功 pwd_cnt 0; }这里要留意default_pwd用code关键字存放在程序存储区仿真和烧录后都能直接访问。但课设要求更高一点的做法是从 24C02 读取密码这样用户才能修改密码。判断错误时先自增error_times再判断是否达到 3 次注意在锁定期间键盘扫描要跳过密码录入否则用户可以一边锁着一边往里塞密码造成状态混乱。3.3 通过 24c02.h 读写密码的 I2C 细节24C02 的时序核心是 START、STOP、ACK 和单字节写页写。24c02.h 里至少需要提供i2c_start、i2c_stop、i2c_write_byte、eeprom_write_byte这几个函数。// 24c02.h 中的关键操作 sbit SCL P2^1; sbit SDA P2^0; void i2c_start(void) { SDA 1; SCL 1; SDA 0; SCL 0; } void i2c_stop(void) { SDA 0; SCL 1; SDA 1; } void eeprom_write_byte(unsigned char addr, unsigned char dat) { i2c_start(); i2c_write_byte(0xA0); // 器件地址 0xA0写操作 i2c_write_byte(addr); // 目标地址 i2c_write_byte(dat); // 数据 i2c_stop(); delay_ms(5); // 等待内部写周期 }i2c_start要求 SDA 在 SCL 高电平时拉低i2c_stop则是在 SCL 高电平时让 SDA 拉高。0xA0是 24C02 的写地址最后一位 0 表示写入读操作对应0xA1。写周期 5ms 不能省否则连续写入时 24C02 可能没写完下一次通信就失败。把这段代码放进 mms.c 后程序的运行顺序应该是上电先读 24C02 地址 0x00看有没有初始化标志如果没有就把default_pwd写到 EEPROM再置一个标志位。之后每次校验都从 EEPROM 读而不是直接比较 code 区常量这才算真正用到了 24C02。4. 在 Proteus 8 Professional 中加载 hex 并跑通仿真4.1 从 mms.DSN 开始联调加载 hex 与时钟设置打开 mms.DSN 后检查单片机属性是第一步。双击 AT89C51 元件在 Program File 一栏选择编译生成的 mms.hexClock Frequency 设置成 12MHz。这个 12MHz 不是随便写的它直接影响定时器初值和串口波特率计算比如后面要用 9600 波特率调试12MHz 计算出的 TH1 初值就是 0xFD。没有现成 mms.DSN 时可以自己从 Proteus 元件库搭建按下面顺序放置和接线AT89C51、24C02、4×4 键盘、两位共阴数码管、4.7kΩ 上拉电阻、10μF 电容和 10kΩ 复位电阻以及 12MHz 晶振和两个 20pF 电容。搜索关键词可以用AT89C51、24C02、KEYPAD-SMALL、7SEG-MPX4-CC、RESPACK-8。元件放好后先检查电源和数据总线连线再逐个修改属性。元件关键参数连接说明AT89C51Program File 选 mms.hexClock 12MHzP1 接矩阵键盘P0 接数码管段码P2.0/P2.1 接 24C0224C02A0、A1、A2 接地SCL 接 P2.1SDA 接 P2.0上拉 4.7kΩ晶振电路12MHz负载电容 20pF接 XTAL1/XTAL2复位电路10μF 电容 10kΩ 电阻RST 接高电平复位加载 hex 后按 F12 开始仿真。如果按下数字键数码管没反应先停止仿真检查代码里数码管段码是否和 Proteus 中元件引脚一致。常见的是共阴共阳选错导致段码全反。4.2 仿真排错表与“不跑”时的检查顺序仿真跑不动时不要急着改代码按现象排查效率更高。现象可能原因处理方式点击运行后单片机没反应晶振频率没设置或 EA 引脚悬空设置 12MHzEA 接 VCC键盘输入无显示P1 口行列接反对照 key_scan 的行拉低顺序调整键盘方向数码管亮度不均或雪花位选刷新太慢或没有共地缩短 xsh_refresh 中的位选延时密码掉电后改变24C02 WP 引脚接高电平WP 接地写周期后延时至少 5ms编译通过但仿真一直复位看门狗定时器被意外启动或 RST 悬空Reset 引脚接标准复位电路检查顺序我一般固定为先看单片机有没有加载 hex再看晶振频率和复位脚电平然后用 Proteus 的电压探针量 24C02 的 SCL、SDA 引脚看有没有方波。如果 SCL 一直低说明 I2C 通信没起来多半是代码里的 sbit 引脚定义与原理图不一致。4.3 用虚拟串口辅助观察键值比数码管显示更直接的调试方式是给工程加一个 UART 输出把 key_scan 返回的键值发到 Proteus 的 VIRTUAL TERMINAL。这样可以看到每次按键对应的数值避免数码管显示异常时无法判断是扫描问题还是显示问题。// UART 初始化与发送一个字节 void uart_init(void) { TMOD 0x20; // 定时器1工作在方式2 TH1 0xFD; // 12MHz 下 9600 波特率初值 SCON 0x50; // 方式1允许接收 TR1 1; } void uart_send(unsigned char ch) { SBUF ch; while (!TI); TI 0; }调用时在 key_scan 返回有效键值后加一句uart_send(key_val 0);注意key_val如果大于 9会输出冒号、分号这些符号这时需要自己写一个uart_send_hex把数值拆成 ASCII 码。这个技巧对课上验收很有用因为你不用盯着数码管猜数字直接在虚拟终端里能看到完整输入序列出错时一眼就能判断是扫描错位还是校验逻辑错。5. 验证掉电保存与状态切换用虚拟示波器确认 I2C 时序密码锁验收时最容易被问到的就是“你怎么证明密码真的写进 24C02 了”。在 Proteus 里暂停仿真不等于掉电必须停止仿真或断开电源再重新加载工程。操作方法是先输入密码把default_pwd改成一个新值并写入 EEPROM停止仿真后重新运行再输入同样密码看能否开锁。能开锁说明数据确实保存在 24C02 里如果密码又变回默认值说明写操作没完成或 WP 引脚被拉高了。抓 I2C 时序可以用 Proteus 的虚拟仪器。从虚拟仪器栏选择 I2C Debugger把 SDA、SCL 分别接到单片机和 24C02 的通信线上然后在 mms.c 的eeprom_write_byte函数里设置断点。运行到断点后单步执行I2C Debugger 会显示 START、STOP、ACK 和数据字节排查时重点看写入地址后有没有收到 ACK。如果没出现 ACK基本可以确认器件地址不对或者 SDA、SCL 信号没到芯片。进阶技巧是把密码修改功能加进去同时避免写入期间影响按键响应。我一般会在程序里定义一个pwd_update_flag在按键事件里把新密码写入 24C02 地址 0x00 到 0x05再把地址 0x06 写为 0xAA 作为初始化标志。上电后先读 0x06如果值不是 0xAA说明 EEPROM 是空的此时写默认密码和标志位否则直接读取 0x00 到 0x05 作为当前密码。这样既保证了首次上电能进入可用状态也让用户能通过特定按键组合修改密码答辩时可以现场展示完整流程。还需要注意 24C02 的写周期是 5ms如果每次按键都直接写 EEPROM数码管动态扫描会被阻塞。常见做法是把键值和状态存下来在定时器中断里延后执行 EEPROM 写入或者减少写次数只在确认新密码后写一次。对于 STM32F103C8T6 这类项目思路完全一样只是 I2C 外设和 GPIO 初始化方式不同核心仍然是一个键值队列加一个 EEPROM 状态机。稳妥的做法是写一个eeprom_write_async函数用毫秒级标志位代替死等把写周期从主循环里剥离开。本文还有配套的精品资源点击获取
返回列表