DS1302 时钟模块

STM32F103C8T6 · HAL库开发 · 3线串行通信

工作电压
2.0V ~ 5.5V
通信方式
3线串行
数据格式
BCD码

1. 传感器原理与功能

1.1 工作原理

DS1302 是 DALLAS 公司推出的低功耗实时时钟/日历芯片,内置一个实时时钟/日历和 31 字节静态 RAM。它通过一个简单的 3 线串行接口与微控制器通信:

DS1302 内部时钟/日历寄存器使用 BCD(Binary Coded Decimal) 格式存储数据。每个时间单位(秒、分、时、日、月、周、年)占用一个寄存器,高低 4 位分别存储十位和个位。例如 0x26 表示 26(年 2026),0x11 表示 11(时 11点)。

芯片内置涓流充电器,可外接备份电池(如 CR2032),在主电源断电时自动切换到备份电源,保证时钟持续运行。晶振使用 32.768kHz 标准时钟晶振。

1.2 核心功能

🕑

实时计时

提供秒、分、时、日、月、周、年完整时间信息,自动处理闰年、大小月等日历逻辑,无需MCU干预。

🔋

掉电保持

内置电源检测电路,主电源断电时自动切换至备份电池,确保时钟持续运行,数据不丢失。

📥

31字节RAM

内置31字节静态RAM,可用于存储用户数据,通过相同的3线串行接口读写,断电后由备份电池保持。

⚡

涓流充电

内置可编程涓流充电器,支持对备份电池进行充电管理,可选择二极管和电阻配置充电电流。

1.3 引脚定义

引脚号 名称 功能 STM32连接
1VCC主电源正极 (2.0V~5.5V)5V
2X132.768kHz 晶振引脚1外部晶振
3X232.768kHz 晶振引脚2外部晶振
4GND电源地GND
5RST/CE复位/使能信号PB5
6DSIO双向数据 I/OPB4
7SCLK串行时钟PB3
8BAT备份电池正极CR2032 3V

2. 模块外观展示

DS1302模块外观
DS1302 时钟模块正面外观
DS1302模块整体
DS1302 时钟模块整体视图

3. 原理图与PCB封装

DS1302原理图
DS1302 PCB封装

引脚对应关系: DS1302 模块的 SCLK → STM32 PB3,DSIO → STM32 PB4,RST/CE → STM32 PB5。 OLED 显示屏通过软件 I2C 驱动,SCL → PB15,SDA → PB14。

4. 程序分析

4.1 DS1302 引脚定义与初始化

DS1302 使用 3 个 GPIO 引脚进行通信,均配置为推挽输出模式(DSIO 在读取时动态切换为输入模式)。引脚通过宏定义映射,便于移植。

ds1302.h — 引脚定义
//---定义DS1302使用的IO口---//
#define DSIO_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOB_CLK_ENABLE()
#define RST_GPIO_CLK_ENABLE()    __HAL_RCC_GPIOB_CLK_ENABLE()
#define SCLK_GPIO_CLK_ENABLE()   __HAL_RCC_GPIOB_CLK_ENABLE()

#define DSIO_Pin   GPIO_PIN_4
#define DSIO_GPIO_Port  GPIOB
#define RST_Pin    GPIO_PIN_5
#define RST_GPIO_Port   GPIOB
#define SCLK_Pin   GPIO_PIN_3
#define SCLK_GPIO_Port  GPIOB

//---GPIO操作宏---//
#define DSIO(a) if(a){HAL_GPIO_WritePin(DSIO_GPIO_Port,DSIO_Pin,GPIO_PIN_SET);}\
        else{HAL_GPIO_WritePin(DSIO_GPIO_Port,DSIO_Pin,GPIO_PIN_RESET);}
#define RST(a)  if(a){HAL_GPIO_WritePin(RST_GPIO_Port,RST_Pin,GPIO_PIN_SET);}\
        else{HAL_GPIO_WritePin(RST_GPIO_Port,RST_Pin,GPIO_PIN_RESET);}
#define SCLK(a) if(a){HAL_GPIO_WritePin(SCLK_GPIO_Port,SCLK_Pin,GPIO_PIN_SET);}\
        else{HAL_GPIO_WritePin(SCLK_GPIO_Port,SCLK_Pin,GPIO_PIN_RESET);}
#define DSIO_IN HAL_GPIO_ReadPin(DSIO_GPIO_Port,DSIO_Pin)
ds1302.c — GPIO初始化
void DS1302_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    SCLK_GPIO_CLK_ENABLE();
    RST_GPIO_CLK_ENABLE();
    DSIO_GPIO_CLK_ENABLE();

    // RST (PB5) - 推挽输出
    GPIO_InitStruct.Pin   = RST_Pin;
    GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(RST_GPIO_Port, &GPIO_InitStruct);

    // SCLK (PB3) - 推挽输出
    GPIO_InitStruct.Pin   = SCLK_Pin;
    GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(SCLK_GPIO_Port, &GPIO_InitStruct);
}
ds1302.c — DSIO方向切换
// 设置DSIO为输入模式(读取数据时使用)
void IO_INPUT(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin  = DSIO_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(DSIO_GPIO_Port, &GPIO_InitStruct);
}

// 设置DSIO为输出模式(写入数据时使用)
void IO_OUTPUT(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    HAL_GPIO_WritePin(DSIO_GPIO_Port, DSIO_Pin, GPIO_PIN_SET);
    GPIO_InitStruct.Pin   = DSIO_Pin;
    GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(DSIO_GPIO_Port, &GPIO_InitStruct);
}

4.2 DS1302 读写时序

DS1302 的通信时序:首先拉低 SCLK,拉高 RST 开始通信;然后发送 8 位地址命令(LSB优先),数据在 SCLK 上升沿被 DS1302 读取;接着写入或读取 8 位数据。通信结束后拉低 RST。

ds1302.c — 写入数据
void Ds1302Write(uchar addr, uchar dat)
{
    uchar n;
    RST(0); delay_us(1);
    SCLK(0); delay_us(1);  // SCLK先置低
    RST(1); delay_us(1);  // RST拉高,开始通信
    IO_OUTPUT();

    for (n=0; n<8; n++) {    // 发送8位地址命令 (LSB优先)
        if(addr & 0x01){DSIO(1);}
        else{DSIO(0);}
        addr >>= 1;
        SCLK(1); delay_us(1);  // 上升沿,DS1302读取数据
        SCLK(0); delay_us(1);
    }
    for (n=0; n<8; n++) {    // 写入8位数据
        if(dat & 0x01){DSIO(1);}
        else{DSIO(0);}
        dat >>= 1;
        SCLK(1); delay_us(1);
        SCLK(0); delay_us(1);
    }
    RST(0); delay_us(1);  // 通信结束
}
ds1302.c — 读取数据
uchar Ds1302Read(uchar addr)
{
    uchar n, dat, dat1;
    RST(0); delay_us(1);
    SCLK(0); delay_us(1);
    RST(1); delay_us(1);
    IO_OUTPUT();

    for(n=0; n<8; n++) {    // 发送8位地址命令
        if(addr & 0x01){DSIO(1);}
        else{DSIO(0);}
        addr >>= 1;
        SCLK(1); delay_us(1);
        SCLK(0); delay_us(1);  // 下降沿,DS1302准备数据
    }
    delay_us(1);
    IO_INPUT();             // 切换为输入模式
    for(n=0; n<8; n++) {    // 读取8位数据 (LSB优先)
        if(DSIO_IN == 1){dat1=1;}
        else{dat1=0;}
        dat = (dat>>1) | (dat1<<7);  // 组装数据
        SCLK(1); delay_us(1);
        SCLK(0); delay_us(1);
    }
    IO_OUTPUT();
    RST(0);
    return dat;
}

4.3 寄存器地址与初始时间

DS1302 的时钟寄存器共 7 个字节,分别对应秒、分、时、日、月、周、年。读地址最低位为 1,写地址最低位为 0。数据以 BCD 码格式存储。

ds1302.c — 寄存器地址定义
//---读取地址命令: 秒 分 时 日 月 周 年---//
uchar READ_RTC_ADDR[7]  = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
//---写入地址命令: 秒 分 时 日 月 周 年---//
uchar WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};

//---初始时间: 2026年08月20日 星期四 11:00:00 (BCD码)---//
//  存储顺序:                秒  分  时  日  月  周  年
uchar TIME[7] = {0, 0, 0x11, 0x20, 0x08, 0x04, 0x26};

BCD码与十进制对照表

时间单位BCD码计算公式十进制值
秒 (TIME[0])0x000x10*10 + 0x00 = 000
分 (TIME[1])0x000x10*10 + 0x00 = 000
时 (TIME[2])0x110x01*10 + 0x01 = 1111
日 (TIME[3])0x200x02*10 + 0x00 = 2020
月 (TIME[4])0x080x00*10 + 0x08 = 088
周 (TIME[5])0x040x00*10 + 0x04 = 044 (周四)
年 (TIME[6])0x260x02*10 + 0x06 = 262026

4.4 主程序逻辑

主循环每 500ms 读取一次 DS1302 时间数据,将 BCD 码转换为十进制,然后在 OLED 上显示日期、时间和星期。系统时钟配置为 72MHz(HSE 8MHz × PLL9)。

main.c — 主循环
while (1)
{
    if(HAL_GetTick() - Time_Now >= 500)  // 500ms更新一次
    {
        Ds1302ReadTime();  // 读取DS1302时间数据

        // BCD码转十进制
        Year = 2000 + TIME[6]/0x10*10 + TIME[6]%0x10;
        Week = TIME[5]/0x10*10 + TIME[5]%0x10;
        Mon  = TIME[4]/0x10*10 + TIME[4]%0x10;
        Day  = TIME[3]/0x10*10 + TIME[3]%0x10;
        Hour = TIME[2]/0x10*10 + TIME[2]%0x10;
        Min  = TIME[1]/0x10*10 + TIME[1]%0x10;
        Sec  = TIME[0]/0x10*10 + TIME[0]%0x10;

        OLED_Clear();  // 清屏

        // 显示日期和时间
        OLED_Printf(0, 0, OLED_8X16, "%04d.%02d.%02d", Year, Mon, Day);
        OLED_Printf(0, 16, OLED_8X16, "%02d:%02d:%02d", Hour, Min, Sec);

        // 显示星期
        switch(Week) {
            case 1: OLED_Printf(104, 0, OLED_8X16, "Mon"); break;
            case 2: OLED_Printf(104, 0, OLED_8X16, "Tue"); break;
            case 3: OLED_Printf(104, 0, OLED_8X16, "Wed"); break;
            case 4: OLED_Printf(104, 0, OLED_8X16, "Thu"); break;
            case 5: OLED_Printf(104, 0, OLED_8X16, "Fri"); break;
            case 6: OLED_Printf(104, 0, OLED_8X16, "Sat"); break;
            case 7: OLED_Printf(104, 0, OLED_8X16, "Sun"); break;
        }

        OLED_Update();  // 更新显示
        Time_Now = HAL_GetTick();  // 更新时间戳
    }
}
main.c — 系统时钟配置 (72MHz)
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

    // HSE 8MHz 外部晶振
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;  // 8MHz × 9 = 72MHz

    HAL_RCC_OscConfig(&RCC_OscInitStruct);

    // SYSCLK = PLLCLK = 72MHz, APB1 = 36MHz, APB2 = 72MHz
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}

4.5 微秒级延时

DS1302 通信需要微秒级延时,利用 SysTick 计数器实现精确延时。SYSCLK 宏定义为 64(对应 72MHz/1000 的近似值),通过读取 SysTick->VAL 计数差实现延时。

delay.c — 微秒延时
#define SYSCLK 64  // 系统时钟参数

void delay_us(uint16_t udelay)
{
    uint32_t startval, tickn, delays, wait;

    startval = SysTick->VAL;
    tickn = HAL_GetTick();
    delays = udelay * SYSCLK;

    if(delays > startval) {
        while(HAL_GetTick() == tickn) {}
        wait = SYSCLK*1000 + startval - delays;
        while(wait < SysTick->VAL) {}
    } else {
        wait = startval - delays;
        while(wait < SysTick->VAL && HAL_GetTick() == tickn) {}
    }
}

5. 程序流程图

开始 HAL_Init() 初始化HAL库 SystemClock_Config() 72MHz MX_GPIO_Init() GPIO初始化 OLED_Init() OLED初始化 DS1302_GPIO_Init() DS1302初始化 while(1) 主循环 HAL_GetTick() - Time_Now ≥ 500? 否 是 Ds1302ReadTime() 读取时间 BCD码 → 十进制转换 OLED显示日期/时间/星期 Time_Now = HAL_GetTick()

6. 交互式动画演示

DS1302 芯片通信模拟

DS1302
SCLK (PB3)
DSIO (PB4)
RST/CE (PB5)
BCD转换示例:
0x00 → 00 秒
0x00 → 00 分
0x11 → 11 时
通信状态待机
SCLK 脉冲数0
读取次数0

OLED 显示输出

2026.08.20
11:00:00
Thu
年份 (Year)2026
月份 (Month)08
日期 (Day)20
星期 (Week)4 (Thu)
时 (Hour)11
分 (Min)00
秒 (Sec)00
更新间隔500ms

动画说明