#include<REGX52.H>#include"lcd1602.h"sbit DS1302_SCLK=P3^6;//看原理图ds1302模块的3个代表定义方便查看,防止模块冲突sbit DS1302_IO=P3^4;sbit DS1302_CE=P3^5;//单片机上电是默认1#defineDS1302_SECOND0x80//second 秒写入#defineDS1302_MINUTE0x82//minute 分写入#defineDS1302_HOUR0x84//hour 时写入#defineDS1302_DATE0x86//date 日写入#defineDS1302_MONTH0x88//month 月写入#defineDS1302_DAY0x8A//day 周写入#defineDS1302_YEAR0x8C//year 年写入#defineDS1302_WP0x8E//wp 保护写入charDS1302_Time[]={24,1,15,16,35,55,1};//一个数组给24年1月15号16点35分55秒,星期一voidDS1302_Init(void)//初始化打开时序准备相当于{DS1302_CE=0;DS1302_SCLK=0;}voidDS1302_WriteByte(unsignedcharCommand,Data){// DS1302_CE = 1;// DS1302_IO = Command&0x01; //清零前7位,取出第0位控制字,控制字是指令// DS1302_SCLK = 1;// DS1302_SCLK = 0;//// DS1302_IO = Command&0x02; //0000 0010,取出第1位控制字// DS1302_SCLK = 1;// DS1302_SCLK = 0;unsignedchari;DS1302_CE=1;for(i=0;i<8;i++){DS1302_IO=Command&(0x01<<i);DS1302_SCLK=1;DS1302_SCLK=0;}for(i=0;i<8;i++){DS1302_IO=Data&(0x01<<i);//这里是数据DS1302_SCLK=1;DS1302_SCLK=0;}DS1302_CE=0;//写入完成关闭时序CE}unsignedcharDS1302_ReadByte(unsignedcharCommand){unsignedchari,Data=0x00;DS1302_CE=1;Command|=0x01;//给第一位置1for(i=0;i<8;i++){DS1302_IO=Command&(0x01<<i);DS1302_SCLK=0;DS1302_SCLK=1;}for(i=0;i<8;i++){DS1302_SCLK=1;DS1302_SCLK=0;if(DS1302_IO){Data|=(0x01<<i);}}DS1302_IO=0;//建议放这里DS1302_CE=0;//读完完成关闭时序CEreturnData;// if(DS1302_IO){Data=Data|0x01;} //如果io口读出为1,数据获得变为0000 0001// DS1302_SCLK = 0;// DS1302_SCLK = 1;// if(DS1302_IO){Data=Data|0x02;} // 相当于把读取后com的量读给data// DS1302_SCLK = 0;// DS1302_SCLK = 1;}voidDS1302_SetTime(void)//写入时间{DS1302_WriteByte(DS1302_WP,0x00);//关闭写保护DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);DS1302_WriteByte(DS1302_WP,0x80);}voidDS1302_ReadTime(void)//读取时间{unsignedcharTemp;Temp=DS1302_ReadByte(DS1302_YEAR);DS1302_Time[0]=Temp/16*10+Temp%16;Temp=DS1302_ReadByte(DS1302_MONTH);DS1302_Time[1]=Temp/16*10+Temp%16;Temp=DS1302_ReadByte(DS1302_DATE);DS1302_Time[2]=Temp/16*10+Temp%16;Temp=DS1302_ReadByte(DS1302_HOUR);DS1302_Time[3]=Temp/16*10+Temp%16;Temp=DS1302_ReadByte(DS1302_MINUTE);DS1302_Time[4]=Temp/16*10+Temp%16;Temp=DS1302_ReadByte(DS1302_SECOND);DS1302_Time[5]=Temp/16*10+Temp%16;Temp=DS1302_ReadByte(DS1302_DAY);DS1302_Time[6]=Temp/16*10+Temp%16;}#ifndef__ds1302_H__#define__ds1302_H__externcharDS1302_Time[];//extern申明外部变量voidDS1302_Init(void);voidDS1302_WriteByte(unsignedcharCommand,Data);unsignedcharDS1302_ReadByte(unsignedcharCommand);voidDS1302_ReadTime(void);voidDS1302_SetTime(void);#endif1. 引言
DS1302 是一款常用的低功耗实时时钟芯片,能够独立完成年、月、日、时、分、秒及星期的计时功能,非常适合用于单片机系统中需要记录时间的场景。本文以 51 单片机(STC89C52)为例,详细讲解 DS1302 的驱动原理、读写时序、BCD 码转换以及 LCD1602 显示方案,并给出可直接运行的完整代码。
2. 硬件连接与原理
2.1 引脚说明
DS1302 采用三线 SPI 兼容接口,与单片机仅需 3 根线即可通信:
| 引脚 | 功能 | 连接说明 |
|---|---|---|
| CE(RST) | 片选使能 | 接 P3^5,高电平有效 |
| SCLK | 串行时钟 | 接 P3^6,上升沿写入 |
| I/O | 串行数据 | 接 P3^4,双向数据线 |
| VCC2 | 主电源 | 接 5V |
| VCC1 | 备用电池 | 接 3V 纽扣电池(可选) |
注意:单片机上电时 CE 默认输出 1,因此初始化函数中必须先将 CE 拉低,避免误触发。
2.2 通信时序
DS1302 的读写时序分为三个阶段:
- 写命令字节:在 CE 拉高后,先发送 8 位命令字(含寄存器地址和读写标志)。
- 写数据字节:紧接着发送 8 位数据,SCLK 上升沿写入。
- 读数据字节:发送命令字后,改为在 SCLK 下降沿读取 I/O 线上的数据。
3. 代码逐段讲解
3.1 引脚定义与寄存器地址
sbit DS1302_SCLK=P3^6;sbit DS1302_IO=P3^4;sbit DS1302_CE=P3^5;sbit是 51 单片机特有的位寻址关键字,用于定义单个引脚。- 寄存器地址采用
0x80起始的偶数地址,最低位0表示写操作,1表示读操作。
3.2 初始化函数
voidDS1302_Init(void){DS1302_CE=0;DS1302_SCLK=0;}将 CE 和 SCLK 都置低,确保芯片处于待机状态,防止上电瞬间误操作。
3.3 写字节函数
voidDS1302_WriteByte(unsignedcharCommand,Data){unsignedchari;DS1302_CE=1;for(i=0;i<8;i++){DS1302_IO=Command&(0x01<<i);DS1302_SCLK=1;DS1302_SCLK=0;}for(i=0;i<8;i++){DS1302_IO=Data&(0x01<<i);DS1302_SCLK=1;DS1302_SCLK=0;}DS1302_CE=0;}关键点:
- 先发送 8 位命令字,再发送 8 位数据。
- 每次写入前将 I/O 置为对应位,然后产生一个上升沿(SCLK 先高后低)完成锁存。
- 循环结束后拉低 CE,结束本次通信。
3.4 读字节函数
unsignedcharDS1302_ReadByte(unsignedcharCommand){unsignedchari,Data=0x00;DS1302_CE=1;Command|=0x01;// 最低位置 1,表示读操作for(i=0;i<8;i++){DS1302_IO=Command&(0x01<<i);DS1302_SCLK=0;DS1302_SCLK=1;}for(i=0;i<8;i++){DS1302_SCLK=1;DS1302_SCLK=0;if(DS1302_IO){Data|=(0x01<<i);}}DS1302_IO=0;DS1302_CE=0;returnData;}关键点:
- 命令字最低位置 1 后发送,告诉芯片接下来要读数据。
- 读数据阶段在 SCLK 下降沿采样 I/O 电平。
- 读取结束后将 I/O 置 0,释放总线。
3.5 BCD 码转换
DS1302 内部存储的是 BCD 码(二进制编码的十进制),而我们在程序中习惯使用十进制,因此需要转换:
| 操作 | 公式 | 示例(十进制 24) |
|---|---|---|
| 十进制 → BCD | (x/10)*16 + (x%10) | (24/10)*16 + 24%10 = 0x24 |
| BCD → 十进制 | (x/16)*10 + (x%16) | (0x24/16)*10 + 0x24%16 = 24 |
// 写入时:十进制转 BCDDS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);// 读取时:BCD 转十进制Temp=DS1302_ReadByte(DS1302_YEAR);DS1302_Time[0]=Temp/16*10+Temp%16;3.6 时间设置与读取
voidDS1302_SetTime(void){DS1302_WriteByte(DS1302_WP,0x00);// 关闭写保护DS1302_WriteByte(DS1302_YEAR,...);// ... 依次写入月、日、时、分、秒、周DS1302_WriteByte(DS1302_WP,0x80);// 重新开启写保护}- 写保护寄存器(
0x8E)默认开启,必须先写入0x00才能修改时间。 - 设置完成后重新写入
0x80防止意外修改。
4. 主程序与 LCD1602 显示
#include<REGX52.H>#include"lcd1602.h"#include"ds1302.h"voidmain(){DS1302_Init();DS1302_SetTime();// 首次上电写入初始时间LCD_Init();while(1){DS1302_ReadTime();LCD_ShowString(1,1,"20");LCD_ShowNum(1,3,DS1302_Time[0],2);// ... 依次显示月、日、时、分、秒}}5. 常见问题与调试建议
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 时间不更新 | 写保护未关闭 | 先写0x00到0x8E |
| 读回数据全 0 | CE 时序错误 | 确保 CE 拉高后再发命令 |
| 时间乱跳 | 电源干扰 | 加 0.1μF 去耦电容 |
| 备用电池失效 | 电池接反 | 检查 VCC1 极性 |
6. 总结
本文从硬件连接、通信时序、代码实现到调试方法,完整讲解了 DS1302 在 51 单片机上的驱动方案。核心要点可归纳为:
- 三线通信:CE 控制片选,SCLK 提供时钟,I/O 传输数据。
- BCD 码转换:写入时十进制转 BCD,读取时 BCD 转十进制。
- 写保护:修改时间前必须关闭写保护。
- 时序严谨:写数据用上升沿,读数据用下降沿。
掌握这些要点后,你可以轻松将 DS1302 集成到自己的项目中,实现万年历、定时器、数据记录等丰富功能。