
简介这是一套基于51单片机的DS1302万年历Proteus仿真项目面向嵌入式初学者、电子竞赛参赛者及单片机课程设计人群用于学习实时时钟芯片的驱动原理与仿真验证方法。压缩包共20个文件容量仅92KB包含Keil工程源文件C/H、Proteus原理图DSN、Hex烧录文件、编译过程文件uv2/opt/m51/lst以及工程备份可直接打开工程同时查看电路与代码也便于二次修改调试。DS1302通过三线串行接口与51单片机通信程序涵盖芯片初始化、时间读取与设置、LCD1602显示、闰年判断和大小月天数处理完整呈现万年历功能实现链路关键代码注释清晰便于对照理解寄存器读写时序。借助Proteus虚拟仿真无需实体硬件即可观察时钟走时与显示效果大幅降低实验门槛。目前已有1291人学习下载适合快速入门DS1302驱动或作为单片机综合实训的参考工程。1. 从 Proteus 工程包认识 DS1302 万年历仿真的完整链路这个压缩包不是一段只能跑通的演示代码而是一条完整的「芯片时序 → C 语言驱动 → LCD1602 显示 → Proteus 仿真」链路。解压之后你拿到的是一份 Keil 工程加一份 Proteus 原理图Keil 侧管逻辑Proteus 侧管电路hex 文件已经编译好直接加载到 51 单片机模型里就能看到秒、分、时、日期、星期在 LCD1602 上滚动刷新。对于正在做 51 单片机课程设计、准备电子设计竞赛或者单纯想把 DS1302 用明白的工程师这个包比零散看数据手册要直观得多。DS1302 是 DALLAS 的串行实时时钟芯片自带时钟晶振电路和涓流充电能力51 单片机只需三根 IO 线就能完成全部读写。万年历显示的难点从来不在算法而在时序控制和寄存器寻址——写错一个命令字读回来的时间就是乱的。下面按「协议 → 代码 → 仿真 → 进阶」的顺序把这个工程拆透每一步都给到能直接复现的细节。2. DS1302 三线串口时序与寄存器寻址2.1 命令字节怎么拼读 0x81 与写 0x80 的区别DS1302 不使用标准 SPI 或 I²C它定义了自己的三线接口CE片选、SCLK时钟、I/O数据。每次操作第一步都是发送一个 8 位命令字节决定后续访问哪个寄存器、是读还是写。命令字节的位结构如下位序号bit7bit6bit5~1bit0含义逻辑 1 固定1 访问时钟日历 / 0 访问 RAM寄存器地址1 读 / 0 写读秒寄存器的命令是 0x81二进制是 1000 0001bit0 的 1 表示读操作写秒寄存器用 0x80。这个差异是新手最容易犯的错地址写对了方向位搞反读出来全是 0xFF。// 向 DS1302 写入一个字节 void DS1302_WriteByte(unsigned char addr, unsigned char dat) { unsigned char i; DS1302_CE 0; DS1302_SCLK 0; DS1302_CE 1; // 拉高 CE启动数据传送 for (i 0; i 8; i) { DS1302_IO addr 0x01; // 先发最低位LSB first DS1302_SCLK 1; // 上升沿 DS1302 采样数据 DS1302_SCLK 0; addr 1; } for (i 0; i 8; i) { DS1302_IO dat 0x01; DS1302_SCLK 1; DS1302_SCLK 0; dat 1; } DS1302_CE 0; // 拉低 CE结束传送 }命令字节和数据的发送都遵循 LSB first即最低位先走。地址段每移位一次SCLK 产生一个上升沿DS1302 在 SCLK 上升沿锁存 I/O 线上的电平。写完命令字节后紧接着写数据字节两个 8 位之间不需要额外的延迟但 CE 必须在整个过程中保持高电平。2.2 寄存器映射与 BCD 码格式DS1302 的时钟日历寄存器共 7 个地址从 0x80 到 0x8C每个寄存器存储的数据都是 BCD 码。所谓 BCD 码就是用一个字节的高 4 位存十位、低 4 位存个位例如 23 秒存储为 0x23而不是 0x17。寄存器名写命令读命令数据范围说明秒0x800x8100~59bit7 为 CH 时钟暂停位分0x820x8300~59无特殊位时0x840x8501~12 / 00~23bit7 为 12/24 小时制选择日0x860x8701~31无特殊位月0x880x8901~12无特殊位星期0x8A0x8B01~071 表示周日年0x8C0x8D00~99存储两位年份DS1302 内部硬件电路会自动处理月份天数和闰年逻辑所以万年历的核心计算在芯片内部51 单片机只需要把 BCD 码转成十进制送去 LCD 显示即可。这也意味着代码里不需要自己写闰年判断很多人看项目时以为万年历算法在 C 代码里实际上它固化的 DS1302 内部逻辑中。2.2.1 秒寄存器 CH 位与小时寄存器 12/24 小时制秒寄存器 0x80 的 bit7 是 CH 位Clock Halt上电默认值是 0时钟正常运行。如果这个位被写成 1振荡器停振秒值不再变化——这是仿真时常见现象LCD 显示时间但秒数不动。小时寄存器 0x84 的 bit7 是 12/24 小时制选择置 1 为 12 小时制bit5 表示 AM/PM置 0 为 24 小时制00~23 表示全天。写入小时之前一定要先把 bit7 清 0。// 初始化 DS1302关闭写保护设置为 24 小时制并校准时间 void DS1302_Init(void) { DS1302_WriteByte(0x8E, 0x00); // 0x8E 是写保护寄存器写 0 解除保护 DS1302_WriteByte(0x84, 0x16); // 小时设置为 16 点24 小时制 DS1302_WriteByte(0x80, 0x00); // 秒清 0同时保证 CH 位为 0 DS1302_WriteByte(0x8E, 0x80); // 重新使能写保护 }写保护寄存器 0x8E 的 bit7 是 WP 位上电默认值就是 1。WP 为 1 时所有寄存器的写操作全部无效所以每次写时间前必须先写 0写完后再恢复。包里的源码也沿用了这个标准流程初始化顺序是先解除保护、再写时间、最后恢复保护。2.3 读写时序的代码骨架与参数前面展示了写字节的流程读字节逻辑对称但数据采样点不同写操作在 SCLK 上升沿锁存读操作在 SCLK 下降沿输出数据单片机应在 SCLK 为低电平期间读取 I/O 线上的值。// 从 DS1302 读取一个字节addr 为读命令 unsigned char DS1302_ReadByte(unsigned char addr) { unsigned char i, dat 0; DS1302_CE 0; DS1302_SCLK 0; DS1302_CE 1; for (i 0; i 8; i) { DS1302_IO addr 0x01; DS1302_SCLK 1; DS1302_SCLK 0; addr 1; } DS1302_IO 1; // IO 线切换为输入模式准备读数据 for (i 0; i 8; i) { dat 1; if (DS1302_IO) { dat | 0x80; } DS1302_SCLK 1; DS1302_SCLK 0; // 下降沿后数据稳定此时采样 } DS1302_CE 0; return dat; }关键点在第 12 行的DS1302_IO 1。51 单片机的 IO 是准双向口输出 1 相当于释放总线外部器件才能把电平拉低这个步骤漏掉的话读回来的数据恒为 0x00。读数据时先移位再采样因为 DS1302 在 SCLK 下降沿更新数据单片机在 SCLK 为低时读取才能保证采到位。3. 工程源码拆解与 Keil 编译要点3.1 压缩包内的文件分工解压后看到十几个文件先分清哪些是源码、哪些是编译中间件、哪些是仿真配置避免在错误文件上浪费时间。文件类型作用DS1302.c / DS1302.h源码时钟芯片驱动核心业务逻辑LCD1602.h源码LCD 显示驱动头文件包含引脚定义DS1302.Uv2Keil 工程文件Keil 4 工程双击可直接打开DS1302.hex编译产物烧录文件Proteus 直接加载DS1302.DSNProteus 原理图完整仿真电路含 DS1302 和 LCD1602其余 .lst/.m51/.obj/.plg/.opt中间文件Keil 编译过程生成可删除要注意的是 LCD1602 只给了 .h 头文件而没有 LCD1602.c。说明 LCD 驱动函数集成在了 DS1302.c 里或者以其他文件名混编。搜索整个工程目录里所有 .c 文件确认 LCD 初始化、写命令、写数据这些函数的具体位置再决定改动哪个文件。3.2 核心读写函数解析DS1302.c 里最重要的函数除了 WriteByte 和 ReadByte就是时间设置与读取的封装。下面的代码是标准写法包里源码的实现大同小异// 批量设置时间参数分别为年、月、日、星期、时、分、秒 void DS1302_SetTime(unsigned char year, unsigned char month, unsigned char day, unsigned char week, unsigned char hour, unsigned char minute, unsigned char second) { DS1302_WriteByte(0x8E, 0x00); // 解除写保护 DS1302_WriteByte(0x8C, year); // 年BCD 码直接传入 DS1302_WriteByte(0x88, month); // 月 DS1302_WriteByte(0x86, day); // 日 DS1302_WriteByte(0x8A, week); // 星期 DS1302_WriteByte(0x84, hour); // 时 DS1302_WriteByte(0x82, minute); // 分 DS1302_WriteByte(0x80, second); // 秒含 CH 位 DS1302_WriteByte(0x8E, 0x80); // 恢复写保护 }写时间时传入的 year、month 等参数必须是 BCD 码格式。如果按键调整的时间是十进制需要先做十进制转 BCD转换bcd ((dec / 10) 4) | (dec % 10)。读取时间则要做反向转换dec (bcd 4) * 10 (bcd 0x0F)。包里源码在显示前一定做了这层转换否则 LCD 上会显示十六进制样式的数字。3.3 Keil 工程编译与 hex 生成Uv2 后缀的工程文件对应 Keil 4 版本。如果本机装的是 Keil 5双击会出现版本不兼容的报错正确做法不是在 Keil 5 里硬开而是用菜单 Project → New µVision Project 重新新建工程把 DS1302.c 添加进去再在 Options for Target → Output 选项卡勾选 Create HEX File重新编译生成新的 hex 文件。编译完成后注意查看 Build Output 窗口的提示DS1302 - 0 Error(s), 0 Warning(s)表示编译通过。如果有 Warning 且涉及sbit定义需要检查头文件里的引脚声明是否与 Proteus 原理图一致。工程默认的引脚映射大概率是sbit DS1302_CE P3^4; // 片选 sbit DS1302_IO P3^5; // 数据线 sbit DS1302_SCLK P3^6; // 时钟线如果 Proteus 原理图里 DS1302 的引脚接的不是 P3.4/P3.5/P3.6就必须同步修改这里。引脚不匹配的直接后果是 Proteus 仿真时 LCD 上无任何时间显示或者数据全为 0xFF。这是整个项目里最值得优先排查的接线一致性。4. Proteus 仿真模型搭建与调试4.1 DSN 布局与元件连接打开工程目录下的 DS1302.DSNProteus 会直接进入原理图编辑界面。主要元件包括 51 单片机AT89C51 或 AT89C52、DS1302 芯片、LCD1602 液晶、以及必要的上拉电阻。DS1302 的 X1 和 X2 引脚之间接了一个 32.768 kHz 晶振两个引脚还分别对地接了 6 pF 负载电容——这是时钟准确运行的标准接法。LCD1602 的数据口 D0~D7 接 P0 口因为 P0 口是开漏输出所以原理图上有一排 10k 上拉电阻排阻接到 VCC。RS 接 P2.6、RW 接 P2.5、EN 接 P2.7。这套接线是 51 单片机驱动 LCD1602 的常见方案包里的 LCD1602.h 引脚宏定义与之对应。4.2 仿真运行与时间校准点击 Proteus 左下角的运行按钮LCD1602 上应该显示类似2025-01-15 Wed和14:23:45两行信息。如果秒数正常跳动说明 DS1302 的振荡器在工作如果时间卡住不动优先查看秒寄存器的 CH 位是否被意外置 1以及晶振频率是不是 32.768 kHz。Protues 仿真是基于真实时间的DS1302 模型的秒跳变与实际时间流速一致不需要加速处理。要校准演示时间可以在运行状态下双击 DS1302 元件在弹窗里直接修改秒、分、时等寄存器值也可以在代码里调用DS1302_SetTime函数在初始化时写入指定时间。后一种方式更接近真实硬件行为调好之后重新编译 hex再在 Proteus 中重新加载。4.3 仿真中容易踩的坑DS1302 元件在 Proteus 中默认不带备用电池掉电后时间不保持这不是 bug 而是模型配置。要在原理图里加一颗 3V 纽扣电池接到 VCC2 引脚并确保 VCC1 接主电源 5VDS1302 会自动切换到较高电压的一侧供电。故障现象可能原因排查手段LCD 无显示但有背光LCD1602 对比度引脚接法不当检查 VL 引脚对地电位常见 10k 电位器LCD 显示乱码数据位接反或 P0 上拉缺失核对 D0~D7 接线补上拉电阻时间不走秒寄存器 CH 位为 1初始化时显式写 0x80 寄存器时间乱跳SCLK 频率过快检查延时函数是否被优化掉读回全 FFIO 线未正确切换输入模式确认读字节前DS1302_IO 1仿真和真实硬件有一个容易被忽略的差异Proteus 的 IO 模型速度远快于真实芯片如果写字节的 SCLK 翻转之间完全没有延时模型也能跑但烧到真实 51 单片机上可能因为 IO 翻转跟不上 DS1302 的最小时序要求而失败。建议保留_nop_()或空循环作为基础延时保证 SCLK 高电平和低电平持续时间都在 1 µs 以上。5. 写保护、burst 模式与掉电保持的仿真验证5.1 写保护 0x8E 实验写保护寄存器 0x8E 只有 bit7 有效其余位读回为 0。做一个小实验来验证它的行为在 Proteus 运行状态下用代码向 0x8E 写入 0x80然后再向秒寄存器 0x80 写入 0x30观察秒是否变化。如果秒保持不变说明写保护生效。这个实验能直观理解为什么每次初始化都要先执行DS1302_WriteByte(0x8E, 0x00)。// 写保护验证 DS1302_WriteByte(0x8E, 0x80); // 使能写保护 DS1302_WriteByte(0x80, 0x30); // 尝试写秒实际不会生效5.2 读取时间推荐 burst 模式单字节读需要发送 7 次命令字节效率低且每次都要拉高拉低 CE。DS1302 提供 burst 模式向命令字节 0xBF 发送后连续读取 8 个字节即可一次性拿到年、月、日、星期、时、分、秒等全部数据。// burst 方式读取完整时间 void DS1302_ReadTime(unsigned char *time_buf) // time_buf 长度至少为 7 { unsigned char i; unsigned char addr 0xBF; // 时钟日历 burst 读命令 DS1302_CE 0; DS1302_SCLK 0; DS1302_CE 1; for (i 0; i 8; i) { DS1302_IO addr 0x01; DS1302_SCLK 1; DS1302_SCLK 0; addr 1; } DS1302_IO 1; for (i 0; i 8; i) { time_buf[i] 0; // 内层逐个位读取 for (unsigned char j 0; j 8; j) { time_buf[i] 1; if (DS1302_IO) { time_buf[i] | 0x80; } DS1302_SCLK 1; DS1302_SCLK 0; } } DS1302_CE 0; }读出的 time_buf[0] 为秒、time_buf[1] 为分依此类推到 time_buf[6] 为年。注意 burst 模式下的寄存器顺序与单字节寻址顺序一致但命令位不再逐寄存器控制而是一次性连续输出。写 burst 模式的命令是 0xBE同样一次写 8 个字节方便校时场景。5.3 掉电保持与 RAM 区验证DS1302 除了时钟日历还有 31 字节的静态 RAM地址 0xC0~0xFD。这些 RAM 在 VCC2 接入电池后主电源掉电时数据不丢失适合保存校准参数。仿真中验证方法是运行后向 0xC0 写一个特征值 0x5A停止仿真并重新启动再读 0xC0如果返回 0x5A说明 RAM 内容在主电源复位后依然保留。这个特性对于需要断电保存配置的 51 单片机项目很实用比如万年历的闹钟时间、时间补偿值等都可以放在 DS1302 RAM 里不需要额外接 EEPROM。但要记住 RAM 操作同样受写保护控制访问 RAM 的命令字节 bit6 为 0与时钟日历的寻址域天然区分。如果你在 Proteus 里发现 RAM 读写异常最可能的原因是命令字节的 bit6 写错——把 0xC0 写成了 0x80 这类信号等于访问了不同的功能域数据自然对不上。把命令字逐 bit 打印出来核对这类问题很快就能定位。本文还有配套的精品资源点击获取