传感器驱动系列之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. 初始化:

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

    • 发送或接收 8 位数据字节
    • 数据传输完成后,拉低 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值