DS1302时钟
1.1 简介
DS1302是一个低功耗、实时时钟(RTC)芯片,如下图所示:
1.2 引脚功能
32.768kHz分频15次即频率为1hz,一秒一次
引脚号 | 名称 | 方向 | 功能说明 |
---|---|---|---|
1 | VCC2 | 电源输入 | 主电源(+5V 或 +3.3V),通常是系统电源供给 |
2 | X1 | 输入 | 32.768kHz 晶振的第一端(接晶振) |
3 | X2 | 输出 | 32.768kHz 晶振的第二端 |
4 | GND | 电源 | 地线(0V) |
5 | CE | 输入 | 芯片使能信号(Chip Enable,高电平有效),通信时需先拉高 |
6 | I/O | 双向 | 数据线(串行输入/输出),命令、数据都通过它传输 |
7 | SCLK | 输入 | 串行时钟信号(上升沿或下降沿采样,DS1302在上升沿接收数据) |
8 | VCC1 | 电源输入 | 备用电源(接纽扣电池,掉电时维持时钟运行) |
1.3 基本工作原理
基本组成结构
工作流程
1.4 通信协议
根据上述的工作流程可知,使用DS1302时,核心操作就是读写寄存器。
1.4.1 写操作
以下是向寄存器写入单个字节的时序图。
- 发送数据前,需要将CE拉高,使能通信。需要注意的是,拉高CE时,需确保SCLK处于低电平
- 需要先发送命令字节,再发送数据字节,发送时都是低位优先
- 命令字节用于指定寄存器地址以及声明读/写操作,各位的含义如下
- 数据字节为发送的具体数据,每位数据都需要在一个时钟周期上升沿发送,意味着在上升沿之前要准备数据
1.4.2 读操作
以下是从寄存器读取单个字节的时序图:
- 接收数据前,需要将CE拉高,使能通信。需要注意的是,拉高CE时,需确保SCLK处于低电平
- 需要先发送命令字节,再接受数据字节,发送和接收时都是低位优先
- 发送每位数据要在时钟信号的上升沿,读取每位数据要在时钟信号的下降沿
注意:第8个时钟周期,需要完成最后1位数据的发送和第1位数据的接收
1.4.3 常用寄存器
以下是各时间寄存器的地址和每位的定义:
1)中文版
2)英文版
# | 寄存器名称 | 读地址 | 写地址 | 位分布说明 | 范围或含义 |
---|---|---|---|---|---|
1 | 秒寄存器 | 81h | 80h | BIT7: CH(1=停止,0=运行)BIT6~4: 十秒位(0–5)BIT3~0: 个位秒(0–9) | 00–59 秒 |
2 | 分钟寄存器 | 83h | 82h | BIT6~4: 十分钟位(0–5)BIT3~0: 个位分钟(0–9) | 00–59 分钟 |
3 | 小时寄存器 | 85h | 84h | BIT7: 12/24 小时制(1=12hr,0=24hr);BIT6: 保留,固定0; BIT5: AM/PM(12hr制下,1=PM,0=AM);BIT4~0: 小时数值 | 1–12 / 0–23 小时 |
4 | 日期(日)寄存器 | 87h | 86h | BIT5~0: 日期 | 1–31 |
5 | 月份寄存器 | 89h | 88h | BIT4~0: 月份 | 1–12 |
6 | 星期寄存器 | 8Bh | 8Ah | BIT2~0: 星期 | 1–7 |
7 | 年寄存器 | 8Dh | 8Ch | BIT7~0: 年份(低两位) | 00–99 |
8 | 写保护寄存器(WP) | 8Fh | 8Eh | BIT7: WP(1=禁止写操作,0=允许写) | — |
注意:在向DS1302写入数据前需要先关闭写保护,写完再开启写保护。
1.5 相关资料
(1)购买链接:https://item.szlcsc.com/15455.html
(2)实物图、原理图、封装
如何设置和显示时间信息?
1.6 硬件原理图
1.7 软件设计
1.7.1 初始化
初始化 DS1302 RTC 芯片 ,上电时先给它一个已知的稳定状态
void Int_DS1302_Init(void)
{
SCLK = 0; // 时钟信号
IO = 0; // 数据
CE = 0; // 使能
}
1.7.2 写数据
向 DS1302 写 1 个字节数据
static void Int_DS1302_WriteByte(u8 byte)
{
u8 i;
for (i = 0; i < 8; i++) { // 执行 8 次,依次把这个字节的 最低位(LSB)到最高位(MSB) 依序送给 DS1302
IO = byte & 0x01; // 准备数据,取当前字节的最低位
SCLK = 1;
SCLK = 0; // 先将 SCLK 时钟线拉高再拉低,产生一个上升沿/下降沿组合
byte >>= 1; // 把 byte 右移一位,这样下一次循环时最低位就是原来的第二位
}
}
1.7.3 读数据
从 DS1302 读取一个字节数据
static u8 Int_DS1302_ReadByte()
{
u8 i, byte = 0;
for (i = 0; i < 8; i++) { // 循环 8 次,每次从 DS1302 读 1 位, 读到的第一个位应该放在最低位
byte >>= 1; // 右移一位,为即将读入的新位腾出最高位,第一次无所谓,反正都是0
if (IO == 1) { // 如果是高电平(1),就把当前字节的 最高位 置 1
byte = byte | 0x80;
}
SCLK = 1; // 产生一个时钟脉冲,通知 DS1302 送出下一位数据
SCLK = 0;
}
return byte;
}
1.7.4 按寄存器地址操作
定义写寄存器地址,读寄存器地址在写寄存器地址上加1即可
#define SECOND 0x80
#define MINUTE 0x82
#define HOUR 0x84
#define DAY 0x86
#define MONTH 0x88
#define DAY_OF_WEEK 0x8A
#define YEAR 0x8C
#define WP 0x8E
写寄存器函数
static void Int_DS1302_SetRegister(u8 addr, u8 byte)
{
CE = 1;
Int_DS1302_WriteByte(addr); // 先发寄存器地址
Int_DS1302_WriteByte(byte); // 再发要写的数据
CE = 0;
}
读寄存器函数
static u8 Int_DS1302_GetRegister(u8 addr)
{
u8 byte;
CE = 1;
Int_DS1302_WriteByte(addr | 1); /// addr | 1 → 把寄存器地址最低位(BIT0)设置为 1,变成 读地址。
byte = Int_DS1302_ReadByte(); // 调用 Int_DS1302_ReadByte() 读取 1 个字节
CE = 0;
return byte;
}
1.7.5 设置 DS1302 日期时间
结构体定义, 用于存放时间日期的普通十进制值
typedef struct
{
u8 second; // 0–59
u8 minute; // 0–59
u8 hour; // 0–23
u8 day; // 1–31
u8 month; // 1–12
u8 year; // 0–99
u8 day_of_week; // 1–7
} Struct_Date;
设置函数
void Int_DS1302_SetDate(Struct_Date *p_st_date)
{
u8 temp;
// 关闭写保护
Int_DS1302_SetRegister(WP, 0x00);
// 设置秒
temp = (p_st_date->second % 10) | ((p_st_date->second / 10) << 4); // % 10 取个位,/ 10 取十位,<< 4 把十位移到 BCD 的高 4 位
Int_DS1302_SetRegister(SECOND, temp);
// 设置分钟
temp = (p_st_date->minute % 10) | ((p_st_date->minute / 10) << 4);
Int_DS1302_SetRegister(MINUTE, temp);
// 设置小时
temp = (p_st_date->hour % 10) | ((p_st_date->hour / 10) << 4);
Int_DS1302_SetRegister(HOUR, temp);
// 设置日
temp = (p_st_date->day % 10) | ((p_st_date->day / 10) << 4);
Int_DS1302_SetRegister(DAY, temp);
// 设置月
temp = (p_st_date->month % 10) | ((p_st_date->month / 10) << 4);
Int_DS1302_SetRegister(MONTH, temp);
// 设置年
temp = (p_st_date->year % 10) | ((p_st_date->year / 10) << 4);
Int_DS1302_SetRegister(YEAR, temp);
// 设置星期
Int_DS1302_SetRegister(DAY_OF_WEEK, p_st_date->day_of_week % 10);
// 开启写保护
Int_DS1302_SetRegister(WP, 0x80);
}
1.7.6 读取当前时间/日期
void Int_DS1302_GetDate(Struct_Date *p_st_date)
{
u8 temp;
// 获取秒
temp = Int_DS1302_GetRegister(SECOND);
p_st_date->second = (temp >> 4) * 10 + (temp & 0x0F); // (temp >> 4) → 取高 4 位(十位数)(temp & 0x0F) → 取低 4 位(个位数)最终得到十进制的实际数值。
temp = Int_DS1302_GetRegister(MINUTE);
p_st_date->minute = (temp >> 4) * 10 + (temp & 0x0F);
temp = Int_DS1302_GetRegister(HOUR);
p_st_date->hour = (temp >> 4) * 10 + (temp & 0x0F);
temp = Int_DS1302_GetRegister(DAY);
p_st_date->day = (temp >> 4) * 10 + (temp & 0x0F);
temp = Int_DS1302_GetRegister(MONTH);
p_st_date->month = (temp >> 4) * 10 + (temp & 0x0F);
temp = Int_DS1302_GetRegister(YEAR);
p_st_date->year = (temp >> 4) * 10 + (temp & 0x0F);
temp = Int_DS1302_GetRegister(DAY_OF_WEEK);
p_st_date->day_of_week = (temp >> 4) * 10 + (temp & 0x0F);
}
1.7.7 写入读取测试
定义星期格式化数组:
// WEEK_NAME[0] → "Mon",WEEK_NAME[6] → "Sun"
code char *WEEK_NAME[] = {
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"};
u8 xdata str[17]; // 时间字符串缓存
Struct_Date st_date;
// st_date 结构体为一个固定的时间(2025 年 8 月 7 日 17:28:30,星期四
st_date.year = 25;
st_date.month = 8;
st_date.day = 7;
st_date.day_of_week = 4;
st_date.hour = 17;
st_date.minute = 28;
st_date.second = 30;
// 初始化时钟并设置时间
Int_DS1302_Init();
Int_DS1302_SetDate(&st_date);
while (1) {
Int_DS1302_GetDate(&st_date); // // 从 DS1302 读取当前时间
// 组合成 "2025/08/07 Thu" 这样的字符串。可以将str显示在oled屏幕上
sprintf(str, "20%02d/%02d/%02d %s",
(int)st_date.year,
(int)st_date.month,
(int)st_date.day,
WEEK_NAME[st_date.day_of_week - 1]);
Delay_1MS(1000);
}
1 游客 2025-09-01 11:27 回复
1
1 游客 2025-09-01 12:14 回复
1
@@XJimK 游客 2025-09-01 12:15 回复
1
1 游客 2025-09-01 12:18 回复
1
1 游客 2025-09-01 12:19 回复
1