传感器驱动系列详解:DS1302实时时钟模块工作原理与操作指南
目录
一、DS1302实时时钟模块简介
1.1 主要特性
1.2 引脚说明
1.3 工作原理
1.4 基本通信时序
1.5 地址/命令字节
1.6 寄存器说明
二、STM32驱动源码
三、驱动示例
END
一、DS1302实时时钟模块简介
DS1302 是一款由达拉斯半导体(Dallas Semiconductor)公司生产的实时时钟(RTC)芯片,它能够为微控制器提供年、月、日、星期、时、分、秒的时间信息,并且可以通过简单的串行接口进行通信。DS1302 具有低功耗、备用电池接口和 RAM 存储等功能,广泛应用于需要时间记录的电子设备中。其实物图见下图1。

1.1 主要特性
提供秒、分、小时、日、月、星期和年信息
支持闰年计算
三线串行接口(I/O、SCLK、CE)
与微控制器的连接简单,通信速率可达2 Mbps
内部具有一个供备用电池连接的接口,以保证在主电源断开时仍能继续计时
备用电池电流低至1 µA
内部包含31字节的静态RAM,可用于存储临时数据
正常工作模式下电流为300 µA
备用模式下电流为1 µA
1.2 引脚说明
模块的芯片引脚封装示意图如下图2所示。

上述图中各个引脚的功能及其详细说明如下所示:
1.3 工作原理
DS1302 通过三线串行接口与微控制器进行通信。微控制器可以通过设置CE引脚为高电平来使能DS1302,并通过SCLK引脚提供串行时钟信号,然后通过I/O引脚进行数据的读写操作。DS1302 的时钟和日历数据采用二进制编码的十进制(BCD码,即字节的高四位是数据的十位,字节的低四位是数据的个位)格式存储,这样方便读取和显示。
1.4 基本通信时序
-
初始化:
- 使能 CE 引脚(高电平)
- 发送或接收 8 位命令字节
- 根据命令字节确定是读操作还是写操作
-
数据传输:
- 发送或接收 8 位数据字节
- 数据传输完成后,拉低 CE 引脚
1.5 地址/命令字节
DS1302的寄存器命令字节读取/写入的说明如下图3所示。

其中,最高位固定为1;第6位用于指定当前是操作RAM存储区还是时钟/日历数据,若为1表示指定操作RAM,为0表示操作时钟/日历数据;第1-5位表示指定要输入或输出的指定寄存器地址;第0位表示读还是写(置1表示读,置0表示写)。
1.6 寄存器说明
DS1302的相关寄存器定义如下图4所示。

其中:
寄存器80h的最高位(CH位)为时钟暂停标志位,当该位设置为逻辑1时,DS1302将被置为低功耗待机模式,该位写入0则时钟开始运行; 寄存器84h的最高位(12/24)用于指定显示时间为12小时制还是24小时制,为0表示24小时制,为1表示12小时制;当在12小时制下,第五位则用于指示AM(上午)/PM(下午),为0表示AM(上午),为1则表示PM(下午); 寄存器8Eh的最高位(WP位)为写保护控制位,该位写入1时,表示写入操作无效; 寄存器90h的最高位(TCS位)用于控制涓流充电,一般不进行设置。
此外,DS1302还提供31Bytes的字节写入,方便用户存储静态数据,写入寄存器地址说明见下图5。

最后提供DS1302的数据手册供读者参考,下载链接:DS1302.pdf – 蓝奏云。
二、STM32驱动源码
ds1302.c驱动实现如下:
#include "ds1302.h"
#include "stm32f10x.h"
#include "stdio.h"
#include "time.h"
#include "string.h"
/* DS1302 引脚定义 */
#define DS_CLK_GPIO_RCLK RCC_APB2Periph_GPIOA
#define DS_CLK_GPIO_PORT GPIOA
#define DS_CLK_GPIO_PIN GPIO_Pin_3
#define DS_DAT_GPIO_RCLK RCC_APB2Periph_GPIOA
#define DS_DAT_GPIO_PORT GPIOA
#define DS_DAT_GPIO_PIN GPIO_Pin_2
#define DS_RST_GPIO_RCLK RCC_APB2Periph_GPIOA
#define DS_RST_GPIO_PORT GPIOA
#define DS_RST_GPIO_PIN GPIO_Pin_0
#define DS_CLK(x) GPIO_WriteBit(DS_CLK_GPIO_PORT, DS_CLK_GPIO_PIN, (BitAction)x)
#define DS_DAT(x) GPIO_WriteBit(DS_DAT_GPIO_PORT, DS_DAT_GPIO_PIN, (BitAction)x)
#define DS_RST(x) GPIO_WriteBit(DS_RST_GPIO_PORT, DS_RST_GPIO_PIN, (BitAction)x)
#define DS_DAT_STATUS GPIO_ReadInputDataBit(DS_DAT_GPIO_PORT, DS_DAT_GPIO_PIN)
#define DEC2BCD(x) ((((x) / 10u) << 4u) + ((x) % 10u)) /* 十进制转BCD */
#define BCD2DEC(x) ((((x) >> 4u) * 10u) + ((x) & 0x0Fu)) /* BCD转十进制 */
/* ------------------ 静态函数声明区 ------------------ */
static void ds1302_soft_delay(void);
static void ds1302_writeByte(unsigned char data);
static void ds1302_writeReg(unsigned char reg, unsigned char data);
static unsigned char ds1302_readReg(unsigned char reg);
static void ds1302_dat_pin_inout(unsigned char inout);
static void ds1302_gpio_config(void);
static void ds1302_struct_config(void);
/* ------------------ 静态变量定义区 ------------------ */
static ds1302_typedef ds1302;
/**
* @brief 软件延时
* @param[in] 无
* @retval 无
*/
static void ds1302_soft_delay(void)
{
unsigned char i, j;
for(i=5; i>0; i--) {
for(j=2; j>0; j--);
}
}
/**
* @brief ds1302 写一个字节
* @param[in] data 待写入的字节数据
* @retval 无
*/
static void ds1302_writeByte(unsigned char data)
{
unsigned char i;
ds1302_dat_pin_inout(1);
DS_CLK(0);
for(i=0; i<8; i++) {
DS_CLK(0);
((data&0x01)==1) ? DS_DAT(1) : DS_DAT(0);
DS_CLK(1);
data >>= 1;
}
}
/**
* @brief ds1302 向寄存器写入数据
* @param[in] reg 寄存器地址
* @param[in] data 待写入的字节数据
* @retval 无
*/
static void ds1302_writeReg(unsigned char reg, unsigned char data)
{
DS_RST(0); ds1302_soft_delay();
DS_CLK(1); ds1302_soft_delay();
DS_RST(1); ds1302_soft_delay();
ds1302_writeByte(reg);
ds1302_writeByte(data);
DS_RST(0); ds1302_soft_delay();
DS_CLK(0); ds1302_soft_delay();
}
/**
* @brief ds1302 读取寄存器中的数据
* @param[in] reg 寄存器地址
* @retval 寄存器中的数据
*/
static unsigned char ds1302_readReg(unsigned char reg)
{
unsigned char i, data;
DS_RST(0); ds1302_soft_delay();
DS_CLK(0); ds1302_soft_delay();
DS_RST(1); ds1302_soft_delay();
ds1302_writeByte(reg);
ds1302_dat_pin_inout(0); /* 数据引脚转为输入模式读取数据 */
ds1302_soft_delay();
for(i=0; i<8; i++) {
DS_CLK(0);
data >>= 1;
if(DS_DAT_STATUS) {
data |= 0x80;
}
DS_CLK(1);
}
DS_RST(0); ds1302_soft_delay();
DS_DAT(0); ds1302_soft_delay();
return (data);
}
/**
* @brief ds1302 数据脚 输入/输出模式配置
* @param[in] inout 0-输入 1-输出
* @retval 无
*/
static void ds1302_dat_pin_inout(unsigned char inout)
{
RCC_APB2PeriphClockCmd(DS_DAT_GPIO_RCLK, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure; /* 定义结构体变量 */
if(inout == 1) {
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; /* 设置推挽输出模式 */
} else {
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; /* 设置浮空输入模式 */
}
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; /* 设置传输速率 */
GPIO_InitStructure.GPIO_Pin = DS_DAT_GPIO_PIN; /* 配置IO口 */
GPIO_Init(DS_DAT_GPIO_PORT, &GPIO_InitStructure); /* 初始化GPIO */
}
/**
* @brief ds1302 引脚配置
* @param[in] 无
* @retval 无
*/
static void ds1302_gpio_config(void)
{
RCC_APB2PeriphClockCmd(DS_CLK_GPIO_RCLK | DS_DAT_GPIO_RCLK | DS_RST_GPIO_RCLK, ENABLE); /* 打开端口时钟 */
GPIO_InitTypeDef GPIO_InitStructure; /* 定义结构体变量 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; /* 设置推挽输出模式 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; /* 设置传输速率 */
GPIO_InitStructure.GPIO_Pin = DS_CLK_GPIO_PIN; /* 配置IO口 */
GPIO_Init(DS_CLK_GPIO_PORT, &GPIO_InitStructure); /* 初始化GPIO */
GPIO_InitStructure.GPIO_Pin = DS_DAT_GPIO_PIN; /* 配置IO口 */
GPIO_Init(DS_DAT_GPIO_PORT, &GPIO_InitStructure); /* 初始化GPIO */
GPIO_InitStructure.GPIO_Pin = DS_RST_GPIO_PIN; /* 配置IO口 */
GPIO_Init(DS_RST_GPIO_PORT, &GPIO_InitStructure); /* 初始化GPIO */
}
/**
* @brief ds1302 结构体配置
* @param[in] 无
* @retval 无
*/
static void ds1302_struct_config(void)
{
ds1302.year = 2024;
ds1302.month = 7;
ds1302.date = 30;
ds1302.week = 2;
ds1302.hour = 22;
ds1302.minute = 06;
ds1302.second = 00;
ds1302_set_time(ds1302);
}
/**
* @brief ds1302 初始化
* @param[in] 无
* @retval 无
*/
void ds1302_init(void)
{
ds1302_gpio_config();
ds1302_struct_config();
}
/**
* @brief ds1302 处理函数 用于定时获取一次寄存器数据
* @param[in] 无
* @retval 无
* @note 建议1s调度一次该函数
*/
void ds1302_handle(void *priv)
{
/* 将读取到的BCD码转换成十进制 */
ds1302.year = BCD2DEC(ds1302_readReg(WR_REG_YEAR | 0x01));
ds1302.month = BCD2DEC(ds1302_readReg(WR_REG_MONTH | 0x01));
ds1302.date = BCD2DEC(ds1302_readReg(WR_REG_DATE | 0x01));
ds1302.week = BCD2DEC(ds1302_readReg(WR_REG_WEEK | 0x01));
ds1302.hour = BCD2DEC(ds1302_readReg(WR_REG_HOUR | 0x01));
ds1302.minute = BCD2DEC(ds1302_readReg(WR_REG_MINUTE | 0x01));
ds1302.second = BCD2DEC(ds1302_readReg(WR_REG_SECOND | 0x01)) & 0x7F;
/* 设置小时制和走时控制 */
}
/**
* @brief ds1302 获取时间
* @param[in] format - 指定时间格式
* @retval 输出的指定时间格式数据
*/
char* ds1302_get_time(char *format)
{
static char buff[30];
struct tm timeinfo; // 填充 tm 结构体
timeinfo.tm_year = ds1302.year - 1900 + 2000; // -1900转换为公元纪年 +2000转换为21世纪
timeinfo.tm_mon = ds1302.month - 1;
timeinfo.tm_mday = ds1302.date;
timeinfo.tm_hour = ds1302.hour;
timeinfo.tm_min = ds1302.minute;
timeinfo.tm_sec = ds1302.second;
timeinfo.tm_isdst = -1; // 不使用夏令时
// 检查是否需要12小时制
if (strstr(format, "%p") || strstr(format, "%I") || strstr(format, "%r")) {
// 确保小时数为12小时制
if (timeinfo.tm_hour >= 12) {
timeinfo.tm_hour -= 12;
}
if (timeinfo.tm_hour == 0) {
timeinfo.tm_hour = 12;
}
}
// 使用 strftime 格式化时间
strftime(buff, sizeof(buff), format, &timeinfo);
return buff;
}
/**
* @brief ds1302 设置时间
* @param[in] ds_time - 时间结构体
* @retval 无
*/
void ds1302_set_time(ds1302_typedef ds_time)
{
ds1302_writeReg(WR_REG_WP, 0x00); /* 写保护失能 */
ds1302_writeReg(WR_REG_SECOND, DEC2BCD(ds_time.second));
ds1302_writeReg(WR_REG_MINUTE, DEC2BCD(ds_time.minute));
ds1302_writeReg(WR_REG_HOUR, DEC2BCD(ds_time.hour));
ds1302_writeReg(WR_REG_WEEK, DEC2BCD(ds_time.week));
ds1302_writeReg(WR_REG_DATE, DEC2BCD(ds_time.date));
ds1302_writeReg(WR_REG_MONTH, DEC2BCD(ds_time.month));
ds1302_writeReg(WR_REG_YEAR, DEC2BCD(ds_time.year - 2000));
ds1302_writeReg(WR_REG_WP, 0x80); /* 写保护使能 */
}
/**
* @brief ds1302 设置时间
* @param[in] year - 年
* @param[in] month - 月
* @param[in] date - 日
* @param[in] week - 周几
* @param[in] hour - 时
* @param[in] minute - 分
* @param[in] second - 秒
* @retval 无
*/
void ds1302_set_datetime(unsigned short year, unsigned char month, unsigned char date, unsigned char week,
unsigned char hour, unsigned char minute, unsigned char second)
{
ds1302_writeReg(WR_REG_WP, 0x00); /* 写保护失能 */
ds1302_writeReg(WR_REG_SECOND, DEC2BCD(second));
ds1302_writeReg(WR_REG_MINUTE, DEC2BCD(minute));
ds1302_writeReg(WR_REG_HOUR, DEC2BCD(hour));
ds1302_writeReg(WR_REG_WEEK, DEC2BCD(week));
ds1302_writeReg(WR_REG_DATE, DEC2BCD(date));
ds1302_writeReg(WR_REG_MONTH, DEC2BCD(month));
ds1302_writeReg(WR_REG_YEAR, DEC2BCD(year - 2000));
ds1302_writeReg(WR_REG_WP, 0x80); /* 写保护使能 */
}
/**
* @brief ds1302 设置是否继续工作
* @param[in] cmd - 使能命令 0-继续走时 1-暂停走时
* @retval 无
*/
void ds1302_stop_work(unsigned char cmd)
{
unsigned char seconds = BCD2DEC(ds1302_readReg(WR_REG_SECOND | 0x01));
if (cmd == 0) { // 设置CH位为1以暂停走时
seconds |= 0x80;
} else { // 清除CH位以继续走时
seconds &= 0x7F;
}
ds1302_writeReg(WR_REG_WP, 0x00); /* 写保护失能 */
ds1302_writeReg(WR_REG_SECOND, DEC2BCD(seconds));
ds1302_writeReg(WR_REG_WP, 0x80); /* 写保护使能 */
}
/**
* @brief ds1302 设置小时制
* @param[in] mode - 小时制 0-24小时制 1-12小时制
* @retval 无
*/
void ds1302_set_hour_system(unsigned char hour_system)
{
unsigned char hour = ds1302_readReg(WR_REG_HOUR | 0x01);
if (hour_system == 0) { // 24小时工作制 清除bit 7
hour &= 0x7F;
} else { // 12小时工作制 设置bit 7
hour |= 0x80;
}
ds1302_writeReg(WR_REG_WP, 0x00); /* 写保护失能 */
ds1302_writeReg(WR_REG_HOUR, DEC2BCD(hour));
ds1302_writeReg(WR_REG_WP, 0x80); /* 写保护使能 */
}
/**
* @brief ds1302 写RAM
* @param[in] address - RAM地址 (0-30)
* @param[in] data - 要写入的数据
* @retval 无
*/
void ds1302_write_ram(unsigned char address, unsigned char data)
{
unsigned char ram_reg;
if (address > 30) {
return; // RAM地址超出范围
}
ram_reg = RAM_START | (address << 1); // 构建RAM地址
ds1302_writeReg(WR_REG_WP, 0x00); // 写保护失能
ds1302_writeReg(ram_reg, DEC2BCD(data));
ds1302_writeReg(WR_REG_WP, 0x80); // 写保护使能
}
/**
* @brief ds1302 读RAM
* @param[in] address - RAM地址 (0-30)
* @retval 读取到的数据
*/
unsigned char ds1302_read_ram(unsigned char address)
{
unsigned char data, ram_reg;
if (address > 30) {
return 0; // RAM地址超出范围
}
ram_reg = (RAM_START | 0x01) | (address << 1); // 构建RAM地址
data = BCD2DEC(ds1302_readReg(ram_reg));
return data;
}
ds1302.h文件源码如下:
#ifndef __DS1302_H
#define __DS1302_H
/* DS1302 写寄存器地址 */
typedef enum {
WR_REG_SECOND = 0x80, /* 秒 */
WR_REG_MINUTE = 0x82, /* 分 */
WR_REG_HOUR = 0x84, /* 时 */
WR_REG_DATE = 0x86, /* 日 */
WR_REG_MONTH = 0x88, /* 月 */
WR_REG_WEEK = 0x8A, /* 周 */
WR_REG_YEAR = 0x8C, /* 年 */
WR_REG_WP = 0x8E, /* 写保护位 最高位:0-失能 1-使能 */
WR_REG_TCS = 0x90, /* 涓流充电 1010-使能 其他-失能 */
RAM_START = 0xC0, /* RAM数据存储区起始地址 */
} DS1302_REG_E;
typedef struct {
unsigned char second;
unsigned char minute;
unsigned char hour;
unsigned char week;
unsigned char date;
unsigned char month;
unsigned short year;
} ds1302_typedef;
/* ---------------------- 函数清单 --------------------- */
/* DS1302初始化 */
void ds1302_init(void);
/* 定时处理函数 获取日历/时间 */
void ds1302_handle(void *priv);
/* 设置走时的开始时间 结构体方式 */
void ds1302_set_time(ds1302_typedef ds_time);
/* 设置走时的开始时间 直接数值方式 */
void ds1302_set_datetime(unsigned short year, unsigned char month, unsigned char date,
unsigned char week, unsigned char hour, unsigned char minute, unsigned char second);
/* 获取当前走时时间 可指定输出格式 */
char* ds1302_get_time(char *format);
/* 设置是否停止走时 0-继续走时 1-暂停走时 */
void ds1302_stop_work(unsigned char cmd);
/* 设置小时制 0-24小时制 1-12小时制 */
void ds1302_set_hour_system(unsigned char hour_system);
/* 写RAM */
void ds1302_write_ram(unsigned char address, unsigned char data);
/* 读RAM */
unsigned char ds1302_read_ram(unsigned char address);
#endif
需注意上述驱动中的ds1302_stop_work()函数和ds1302_set_hour_system()函数需要在while中循环设置才行,测试只在初始化设置一次时并不会一直生效。
三、驱动示例
代码一般驱动和使用流程如下:
具体调用代码示例如下:
/* 硬件层 */
#include "delay.h"
#include "usart.h"
#include "timer.h"
#include "ds1302.h"
/* ---------------- 变量定义区 ---------------- */
static unsigned char ds1302_timer_id = 0;
char *time_str;
int main(void)
{
delay_init();
usart1_init(115200);
basic_tim_init();
/* 外设初始化 */
ds1302_init();
/* 定时任务创建 */
if(ds1302_timer_id == 0) {
ds1302_timer_id = timer_add_task(ds1302_handle, NULL, 1000);
}
while (1) {
time_str = ds1302_get_time("%Y-%m-%d %H:%M:%S");
printf("当前时间: %s\n", time_str);
delay_ms(500);
}
}
END
作者:JJ_KING&