传感器驱动系列详解: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   DS1302实时时钟模块

1.1 主要特性

  • 提供秒、分、小时、日、月、星期和年信息

  • 支持闰年计算

  • 三线串行接口(I/O、SCLK、CE)

  • 与微控制器的连接简单,通信速率可达2 Mbps

  • 内部具有一个供备用电池连接的接口,以保证在主电源断开时仍能继续计时

  • 备用电池电流低至1 µA

  • 内部包含31字节的静态RAM,可用于存储临时数据

  • 正常工作模式下电流为300 µA

  • 备用模式下电流为1 µA

  • 1.2 引脚说明

            模块的芯片引脚封装示意图如下图2所示。

    图2   引脚封装示意图

            上述图中各个引脚的功能及其详细说明如下所示:

  • 主电源(VCC1)和备用电池电源(VCC2)
  • 地(GND)
  • 片选信号(CE),高电平使能
  • 双向数据线(I/O),用于传输数据
  • 串行时钟输入(SCLK),用于提供数据传输所需的时钟
  • 1.3 工作原理

            DS1302 通过三线串行接口与微控制器进行通信。微控制器可以通过设置CE引脚为高电平来使能DS1302,并通过SCLK引脚提供串行时钟信号,然后通过I/O引脚进行数据的读写操作。DS1302 的时钟和日历数据采用二进制编码的十进制(BCD码,即字节的高四位是数据的十位,字节的低四位是数据的个位)格式存储,这样方便读取和显示。

    1.4 基本通信时序

    1. 初始化:

    2. 使能 CE 引脚(高电平)
    3. 发送或接收 8 位命令字节
    4. 根据命令字节确定是读操作还是写操作
    5. 数据传输:

    6. 发送或接收 8 位数据字节
    7. 数据传输完成后,拉低 CE 引脚

    1.5 地址/命令字节

            DS1302的寄存器命令字节读取/写入的说明如下图3所示。

    图3   DS1302寄存器命令字节读写说明

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

    1.6 寄存器说明

            DS1302的相关寄存器定义如下图4所示。

    图4   DS1302寄存器说明

            其中:

  • 寄存器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。

    图5  DS1302 RAM寄存器地址

            最后提供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中循环设置才行,测试只在初始化设置一次时并不会一直生效。

    三、驱动示例

            代码一般驱动和使用流程如下:

  • 1. 在ds1302.c文件中修改传感器的接口引脚宏定义,使之适配自己的端口引脚;
  • 2. 调用ds1302_init()函数用于初始化传感器引脚;
  • 3. 周期性的调用ds1302_handle()函数用于获取模块的计时时间;
  • 4. 在需要获取时间的地方调用ds1302_get_time()函数获取格式化之后的时间数据。
  •         具体调用代码示例如下:

    /* 硬件层 */
    #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&

    物联沃分享整理
    物联沃-IOTWORD物联网 » 传感器驱动系列详解:DS1302实时时钟模块工作原理与操作指南

    发表回复