DS1302实时时钟芯片 - 从入门到精通
适合嵌入式小白的完整学习指南
基于蓝桥杯STC15F2K60S2单片机
版本:V1.0 | 日期:2026-02-08
目录
1. 芯片简介
1.1 DS1302是什么?
DS1302是一个实时时钟芯片(RTC),就像一个独立的电子手表:
能记录:秒、分、时、日、月、年、星期
自动处理闰年(到2100年)
断电后靠备用电池继续走时
12/24小时模式可选
额外31字节RAM可存数据
1.2 核心参数
| 参数 | 说明 |
|---|---|
| 工作电压 | 2.0V ~ 5.5V |
| 功耗 | 2.0V时 < 300nA |
| 接口类型 | 3线串行(CE、I/O、SCLK) |
| 晶振频率 | 32.768kHz |
| 封装 | 8脚DIP或SO |
1.3 为什么用DS1302?
- 接口简单:只需3根线,比IIC/SPI更省引脚
- 功耗极低:备用电池能用好几年
- 价格便宜:几块钱一片
- 蓝桥杯必考:几乎每届省赛都会涉及
2. 硬件连接
2.1 引脚定义
DS1302芯片(俯视图)
┌─────┐
VCC2 │1 8│ VCC1(备用电源,接纽扣电池+)
X1 │2 7│ SCLK(时钟线)
X2 │3 6│ I/O (数据线)
GND │4 5│ CE (片选/使能)
└─────┘
2.2 蓝桥杯标准接法(你的电路板)
sbit SCK = P1^7; // 时钟线 SCLK
sbit SDA = P2^3; // 数据线 I/O
sbit RST = P1^3; // 片选线 CE(也叫RST/CE)
实际连接:
单片机 DS1302
------- --------
P1^7 → SCLK (7脚)
P2^3 ↔ I/O (6脚) ⚠️双向!需要能输入输出
P1^3 → CE (5脚)
GND → GND (4脚)
5V → VCC2 (1脚)
电池+ → VCC1 (8脚) 可选,没有断电会丢失时间
2.3 晶振连接
X1(2脚) ━━━[32.768kHz晶振]━━━ X2(3脚)
注意:晶振紧贴芯片,走线越短越好,否则影响时钟精度!
3. 工作原理
3.1 通信时序(最重要!)
DS1302采用同步串行通信,类似SPI但更简单:
写入时序
┌─────────────────┐
CE │ └── 必须先拉高!
┌──┐ ┌──┐ ┌──┐
SCLK ──┘ └──┘ └──┘ └── 时钟打节拍
┌────┬────┬────
I/O ────┤ D0 │ D1 │ D2 ... 数据在SCLK上升沿被锁存
关键点:
- CE先拉高 = 开始通信
- 数据从bit 0开始(LSB优先)
- SCLK上升沿锁存数据
- CE拉低 = 结束通信
读取时序
┌─────────────────┐
CE │ └──
┌──┐ ┌──┐ ┌──┐
SCLK ──┘ └──┘ └──┘ └──
────┬────┬────
I/O 读<── D0 │ D1 │ D2 ... 数据在SCLK下降沿输出
关键点:
- 读取时先发命令,再读8位数据
- SCLK下降沿时读取I/O上的数据
3.2 命令字节格式
每次读写都要先发送一个命令字节:
Bit: 7 6 5 4 3 2 1 0
┌────┬────┬─────────────────┬────┬────┐
│ 1 │RAM │ 地址A4-A0 │ RD │ WR │
│固定│/CK │ │ / │ / │
└────┴────┴─────────────────┴────┴────┘
Bit 7: 必须是1
Bit 6: 0=时钟/日历寄存器, 1=RAM
Bit 5-1: 寄存器地址
Bit 0: 0=写, 1=读
举例:
0x80 = 1000 0000B → 写秒寄存器
0x81 = 1000 0001B → 读秒寄存器
0x84 = 1000 0100B → 写时寄存器
0x85 = 1000 0101B → 读时寄存器
3.3 寄存器地址表
| 寄存器 | 读地址 | 写地址 | 数据范围 | BCD格式 |
|---|---|---|---|---|
| 秒 | 0x81 | 0x80 | 00-59 | 是 |
| 分 | 0x83 | 0x82 | 00-59 | 是 |
| 时 | 0x85 | 0x84 | 0-23 (24h) 1-12 (12h) |
是 |
| 日 | 0x87 | 0x86 | 1-31 | 是 |
| 月 | 0x89 | 0x88 | 1-12 | 是 |
| 星期 | 0x8B | 0x8A | 1-7 | 是 |
| 年 | 0x8D | 0x8C | 00-99 | 是 |
| 写保护 | 0x8F | 0x8E | - | - |
重要标志位:
- 秒寄存器bit7 (CH位):时钟停止标志
CH=1→ 时钟停止CH=0→ 时钟运行
- 写保护寄存器bit7 (WP位):写保护
WP=1→ 禁止写入WP=0→ 允许写入
3.4 BCD码详解(重点!)
DS1302使用BCD(Binary-Coded Decimal)码存储时间:
什么是BCD码?
十进制23 → 普通二进制 00010111 (0x17)
十进制23 → BCD码 00100011 (0x23)
││││││││
││└┴┴┴└─→ 个位:3
└┴──────→ 十位:2
转换公式
// 十进制 → BCD码
BCD = (十位 * 16) + 个位
= (数值/10 * 16) + (数值%10)
例:23 → (2*16) + 3 = 0x23
// BCD码 → 十进制
十进制 = (高4位 * 10) + 低4位
= (BCD/16 * 10) + (BCD%16)
例:0x23 → (2*10) + 3 = 23
你的代码中的转换
// 写入时:十进制转BCD
Write_Ds1302_Byte(0x84, ucRtc[i]/10*16 + ucRtc[i]%10);
// 读取时:BCD转十进制
ucRtc[i] = temp/16*10 + temp%16;
4. 代码详解
4.1 底层函数:写一个字节
void Write_Ds1302(unsigned char temp)
{
unsigned char i;
for (i=0; i<8; i++) // 循环8次,每次发送1位
{
SCK = 0; // ① 先拉低时钟
SDA = temp & 0x01; // ② 取出最低位放到数据线
temp >>= 1; // ③ 右移,准备下一位
SCK = 1; // ④ 时钟上升沿,DS1302锁存数据
}
}
图解过程:
发送0x23 (00100011B)
循环1: temp=00100011, SDA=1 → SCK上升 → 锁存bit0=1
循环2: temp=00010001, SDA=1 → SCK上升 → 锁存bit1=1
循环3: temp=00001000, SDA=0 → SCK上升 → 锁存bit2=0
...依此类推
4.2 底层函数:读一个字节
unsigned char Read_Ds1302_Byte(unsigned char address)
{
unsigned char i, temp = 0x00;
RST = 0; _nop_(); // ① 确保CE是低电平
SCK = 0; _nop_(); // ② 确保时钟是低电平
RST = 1; _nop_(); // ③ CE拉高,开始通信
Write_Ds1302(address); // ④ 发送读命令(如0x81)
for (i=0; i<8; i++) // ⑤ 读取8位数据
{
SCK = 0; // ⑥ 时钟拉低
temp >>= 1; // ⑦ 先右移一位,准备接收
if (SDA) // ⑧ 读取数据线
temp |= 0x80; // ⑨ 如果是1,设置最高位
SCK = 1; // ⑩ 时钟拉高,准备下一位
}
RST = 0; _nop_(); // ⑪ CE拉低,结束通信
SCK = 0; _nop_();
SCK = 1; _nop_();
SDA = 0; _nop_();
SDA = 1; _nop_(); // 复位I/O引脚状态
return temp;
}
关键理解:
temp >>= 1先执行,所以第一个读到的bit 0会移到最高位- 循环8次后,bit 0在最低位,bit 7在最高位(正确顺序)
4.3 应用函数:写一个寄存器
void Write_Ds1302_Byte(unsigned char address, unsigned char dat)
{
RST = 0; _nop_(); // ① CE拉低
SCK = 0; _nop_(); // ② 时钟拉低
RST = 1; _nop_(); // ③ CE拉高,开始通信
Write_Ds1302(address); // ④ 发送命令字节(如0x80)
Write_Ds1302(dat); // ⑤ 发送数据字节(如0x23)
RST = 0; // ⑥ CE拉低,结束通信
}
4.4 高级函数:设置时间
// 输入:ucRtc[0]=11, ucRtc[1]=12, ucRtc[2]=13
// 目标:设置时间为 13:12:11
void Set_Rtc(unsigned char *ucRtc)
{
unsigned char i;
// ① 关闭写保护
Write_Ds1302_Byte(0x8e, 0x00);
// ② 停止时钟(写秒寄存器时bit7=1)
Write_Ds1302_Byte(0x80, 0x80);
// ③ 循环写入时、分、秒
for (i=0; i<3; i++)
{
// 地址:0x84(时) → 0x82(分) → 0x80(秒)
// 数据:十进制转BCD码
Write_Ds1302_Byte(0x84 - 2*i,
ucRtc[i]/10*16 + ucRtc[i]%10);
}
// ④ 开启写保护
Write_Ds1302_Byte(0x8e, 0x80);
}
地址计算逻辑:
i=0: 0x84 - 0 = 0x84 → 写时寄存器, ucRtc[0]=13 → 0x13
i=1: 0x84 - 2 = 0x82 → 写分寄存器, ucRtc[1]=12 → 0x12
i=2: 0x84 - 4 = 0x80 → 写秒寄存器, ucRtc[2]=11 → 0x11
重要:最后写秒寄存器时,0x11的bit7=0,自动启动时钟!
4.5 高级函数:读取时间
void Read_Rtc(unsigned char *ucRtc)
{
unsigned char i, temp;
EA = 0; // ① 关中断,防止读取过程被打断
for (i=0; i<3; i++)
{
// ② 读取寄存器(0x85时→0x83分→0x81秒)
temp = Read_Ds1302_Byte(0x85 - 2*i);
// ③ BCD转十进制
ucRtc[i] = temp/16*10 + temp%16;
}
EA = 1; // ④ 开中断
}
为什么要关中断?
防止读取过程中定时器中断打断,造成时间跳变(如12:59:59→13:00:00时正好被打断)。
5. 实战应用
5.1 你的main.c分析
// 全局变量
idata unsigned char ucRtc[3] = {11,12,13}; // 时分秒数组
void main()
{
System_Init(); // 系统初始化
Set_Rtc(ucRtc); // 设置初始时间为13:12:11
Timer1_Init(); // 启动定时器(1ms中断)
while(1)
{
Key_Proc(); // 按键处理
Led_Proc(); // LED处理
Seg_Proc(); // 数码管处理
Get_Time(); // 读取时间
Get_Temperature(); // 读取温度
}
}
5.2 定时读取时间
idata unsigned char Time_Slow_Down; // 慢速计数器
void Get_Time()
{
if (Time_Slow_Down < 100) return; // 未到100ms
Time_Slow_Down = 0; // 清零
Read_Rtc(ucRtc); // 读取时间到ucRtc数组
// 此时 ucRtc[0]=时, ucRtc[1]=分, ucRtc[2]=秒
}
// 定时器中断(每1ms执行一次)
void Timer1_Isr(void) interrupt 3
{
Time_Slow_Down++; // 累加计数器
// ...
}
逻辑:每100ms读一次DS1302,减少通信次数。
5.3 完整时钟示例
// 显示时间到数码管:HH-MM-SS
void Display_Time()
{
Seg_Buf[0] = ucRtc[0] / 10; // 时十位
Seg_Buf[1] = ucRtc[0] % 10; // 时个位
Seg_Buf[2] = 11; // '-' 符号
Seg_Buf[3] = ucRtc[1] / 10; // 分十位
Seg_Buf[4] = ucRtc[1] % 10; // 分个位
Seg_Buf[5] = 11; // '-' 符号
Seg_Buf[6] = ucRtc[2] / 10; // 秒十位
Seg_Buf[7] = ucRtc[2] % 10; // 秒个位
}
5.4 按键调时功能
unsigned char mode = 0; // 0=正常, 1=调时, 2=调分, 3=调秒
void Key_Proc()
{
if (Key_Down == 4) // S4切换模式
{
mode = (mode + 1) % 4;
}
if (Key_Down == 5) // S5加1
{
if (mode == 1) ucRtc[0] = (ucRtc[0] + 1) % 24; // 时+1
if (mode == 2) ucRtc[1] = (ucRtc[1] + 1) % 60; // 分+1
if (mode == 3) ucRtc[2] = (ucRtc[2] + 1) % 60; // 秒+1
Set_Rtc(ucRtc); // 写入DS1302
}
}
6. 常见问题
问题1:时间不走
现象:读取的时间一直是初始值
原因:
- CH位=1,时钟停止
- 晶振没焊接或损坏
- 写保护未关闭
解决:
// 确保启动时钟
Write_Ds1302_Byte(0x8e, 0x00); // 关闭写保护
Write_Ds1302_Byte(0x80, 0x00); // CH=0,启动时钟
问题2:时间乱码
现象:数码管显示25:78:99
原因:BCD转换错误
检查代码:
// ❌ 错误:直接写十进制
Write_Ds1302_Byte(0x84, 23); // 会写入0x17,显示为27!
// ✅ 正确:转BCD
Write_Ds1302_Byte(0x84, 0x23); // 或 23/10*16 + 23%10
问题3:通信失败
现象:读取全0或全FF
原因:
- 引脚定义错误
- 时序不对
- I/O引脚配置问题
检查:
// 确认引脚
sbit SCK = P1^7; // 对应硬件电路
sbit SDA = P2^3;
sbit RST = P1^3;
// I/O引脚要配置为准双向口或推挽输出
P2M1 &= ~0x08; P2M0 &= ~0x08; // P2.3配置为准双向
问题4:断电丢失时间
原因:没接备用电池
解决:VCC1引脚接CR2032纽扣电池(3V)
问题5:时间偶尔跳变
原因:读取时被中断打断
解决:读取时关中断
EA = 0; // 关总中断
Read_Rtc(ucRtc); // 读取
EA = 1; // 开总中断
7. 进阶练习
练习1:完整日期显示
任务:显示年-月-日
提示:
void Set_Date(unsigned char year, unsigned char month, unsigned char day)
{
Write_Ds1302_Byte(0x8e, 0x00);
Write_Ds1302_Byte(0x8c, year/10*16 + year%10); // 年
Write_Ds1302_Byte(0x88, month/10*16 + month%10); // 月
Write_Ds1302_Byte(0x86, day/10*16 + day%10); // 日
Write_Ds1302_Byte(0x8e, 0x80);
}
练习2:闹钟功能
任务:到设定时间蜂鸣器响
提示:
if (ucRtc[0] == alarm_hour && ucRtc[1] == alarm_min)
{
BEEP = !BEEP; // 蜂鸣器翻转
}
练习3:使用RAM存储数据
任务:掉电保存按键次数
提示:
// 写RAM(地址0xC0-0xFD)
Write_Ds1302_Byte(0xc0, count); // 写入
count = Read_Ds1302_Byte(0xc1); // 读取
练习4:12小时制显示
任务:显示AM/PM
提示:
// 读取时寄存器
unsigned char hour_reg = Read_Ds1302_Byte(0x85);
if (hour_reg & 0x80) // bit7=1表示12小时制
{
unsigned char hour = (hour_reg & 0x1F) / 16 * 10 + (hour_reg & 0x0F);
if (hour_reg & 0x20) // bit5=1表示PM
显示PM;
else
显示AM;
}
学习检查清单
完成以下任务,确保你真正掌握DS1302:
- 能画出DS1302的引脚连接图
- 理解3线串行通信的时序
- 掌握BCD码与十进制的转换
- 能解释命令字节的每一位含义
- 理解CH位和WP位的作用
- 能独立写出Write_Ds1302函数
- 能独立写出Read_Ds1302_Byte函数
- 能实现设置时间功能
- 能实现读取时间并显示
- 能添加按键调时功能
- 知道为什么读取时要关中断
- 能排查常见的时间不走、乱码问题
参考资料
- 数据手册:
DS1302.pdf(已在你的资料包中) - 你的驱动代码:
Driver/ds1302.c和ds1302.h - 你的主程序:
User/main.c - 蓝桥杯官方论坛:搜索DS1302历年真题
学习建议
第一阶段:理论理解(1天)
- 通读本文档,理解通信原理
- 对照数据手册,理解寄存器定义
- 手画时序图,加深理解
第二阶段:代码分析(1天)
- 逐行阅读
ds1302.c,添加注释 - 修改
main.c,显示时间到数码管 - 用逻辑分析仪观察波形(如果有)
第三阶段:功能扩展(2天)
- 实现按键调时
- 添加日期显示
- 实现闹钟功能
- 完成蓝桥杯历年真题中的DS1302题目
遇到问题怎么办?
- 先看本文档的"常见问题"章节
- 检查硬件连接(90%的问题出在这里)
- 用万用表测电压:VCC应为5V,晶振两脚应有微弱交流电压
- 对比工作代码:你的
ds1302.c是经过验证的 - 分步调试:先保证读写一个字节正确,再测试整体功能
祝你学习顺利!掌握DS1302后,你就可以做出各种时钟项目了! ![]()
文档版本:V1.0
最后更新:2026-02-08
适用于:蓝桥杯单片机竞赛 STC15F2K60S2