🎀 文章作者:二土电子
🌸 关注公众号获取完整程序工程!
🐸 期待大家一起学习交流!
文章目录
一、DS1302简介
我们首先来简单介绍一下DS1302,首先DS1302具有实时时钟的功能,DS1302能够精确记录当前的时间,包括秒、分钟、小时、日期、月份以及年份,并支持闰年的自动调整。
除了实时时钟功能外,DS1302还具备数据保存功能,DS1302内置有31字节静态RAM用于数据备份,即使外部供电中断也能依靠后备电池保持数据不丢失。
DS1302采用简单的三线SPI兼容串行接口进行通讯,仅需三条信号线即可完成全部操作命令传输及与MCU的数据交互。DS1302使用一跟数据线实现双向数据传输,类似于我们之前介绍过的DS18B20和DHT11。
这里插一小段,在使用DS1302之前不知道大家有没有使用过STM32的RTC功能,对于像一些STM32F103ZET6这种核心板上有这个3V电池的板子来说,用起来还是比较友好的,但是像我们平时用的STM32F103C8T6这种比较小巧的核心板来讲,没有这个3V电池,如果使用到RTC功能,想要做到断电能够继续计时的话需要给单片机的VBAT引脚接上3V电池供电。
二、引脚介绍
DS1302总共有五个引脚
引脚 | 功能 |
---|---|
VCC | 电源引脚,接3.3V/5V |
CLK | 时钟引脚 |
DAT | 数据线,与MCU实现双向数据交互 |
RST | 复位引脚,可以理解成类似片选 |
三、程序设计
3.1 DAT引脚处理
DAT引脚由于需要完成MCU和DS1302的双向数据传输,所以在不同情况下需要配置成不同的状态,我们这里封装一个函数来方便处理,当然也可以使用宏定义函数来实现
/*
*==============================================================================
*函数名称:Drv_Ds1302_DatIoCtrl
*函数功能:DS1302 DAT引脚控制
*输入参数:state:状态(0输入/1输出)
*返回值:无
*备 注:无
*==============================================================================
*/
void Drv_Ds1302_DatIoCtrl (u8 state)
{
// 结构体定义
GPIO_InitTypeDef GPIO_InitStructure;
// 使能时钟
RCC_APB2PeriphClockCmd (DS1302_DAT_CLK, ENABLE);
if (state)
{
// 配置结构体
GPIO_InitStructure.GPIO_Pin = DS1302_DAT_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(DS1302_DAT_PORT, &GPIO_InitStructure);
}
else
{
// 配置结构体
GPIO_InitStructure.GPIO_Pin = DS1302_DAT_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DS1302_DAT_PORT, &GPIO_InitStructure);
}
}
3.2 DS1302读写寄存器
/*
*==============================================================================
*函数名称:Med_Ds1302_WriteByte
*函数功能:DS1302写入寄存器
*输入参数:
*返回值:无
*备 注:无
*==============================================================================
*/
void Med_Ds1302_WriteByte (u8 addr,u8 data)
{
u8 i;
RST = 0; // 关闭数据传输
CLK = 0; // 拉低时钟
RST = 1; // 启动数据传输
Drv_Ds1302_DatIoCtrl(1); // DAT引脚设置成输出模式
addr = addr & 0xFE; // 最低位置零,寄存器0位为0时写,为1时读
// 写入目标地址
for(i = 0;i < 8;i ++)
{
if (addr & 0x01)
DAT = 1;
else
DAT = 0;
CLK = 1; // 时钟上升沿写入数据
CLK = 0;
addr = addr >> 1;
}
// 写入数据
for (i = 0;i < 8;i ++)
{
if(data & 0x01)
DAT = 1;
else
DAT = 0;
CLK = 1; // 时钟上升沿写入数据
CLK = 0;
data = data >> 1;
}
CLK = 1; // 将时钟电平置于高电平状态
RST = 0; // 关闭数据传输
}
/*
*==============================================================================
*函数名称:Med_Ds1302_ReadByte
*函数功能:DS1302读取寄存器
*输入参数:
*返回值:无
*备 注:无
*==============================================================================
*/
u8 Med_Ds1302_ReadByte (u8 addr)
{
u8 i,data;
RST = 0; // 关闭数据传输
CLK = 0; // 拉低时钟
RST = 1; // 启动数据传输
Drv_Ds1302_DatIoCtrl(1); // DAT引脚设置成输出模式
addr = addr | 0x01; // 最低位置高,寄存器0位为0时写,为1时读
// 写入目标地址
for(i = 0;i < 8;i ++)
{
if (addr & 0x01)
DAT = 1;
else
DAT = 0;
CLK = 1; // 时钟上升沿写入数据
CLK = 0;
addr = addr >> 1;
}
// 从DS1302读出数据
Drv_Ds1302_DatIoCtrl(0); // DAT引脚设置为输入模式
for(i = 0;i < 8;i ++)
{
data = data >> 1;
if (DS1302_DAT_IN())
data |= 0x80;
else
data &= 0x7F;
CLK = 1; // 时钟上升沿写入数据
CLK = 0;
}
CLK = 1; // 将时钟电平置于高电平状态
RST = 0; // 关闭数据传输
return data;
}
3.3 DS1302设置时间
/*
*==============================================================================
*函数名称:Med_Ds1302_SetTime
*函数功能:DS1302设置时间
*输入参数:writeTime:要设置的时间
*返回值:无
*备 注:无
*==============================================================================
*/
void Med_Ds1302_SetTime (uint8_t *targetTime)
{
Med_Ds1302_WriteByte(DS1302_CONTROL_ADDR,0x00); // 关闭写入
Med_Ds1302_WriteByte(DS1302_SEC_ADDR,0x80); // 暂停时钟
// Med_Ds1302_WriteByte(DS1302_CHARGER_ADDR,0xa9); // 涓流充电
Med_Ds1302_WriteByte(DS1302_YEAR_ADDR,targetTime[0]); // 年
Med_Ds1302_WriteByte(DS1302_MONTH_ADDR,targetTime[1]); // 月
Med_Ds1302_WriteByte(DS1302_DAY_ADDR,targetTime[2]); // 日
Med_Ds1302_WriteByte(DS1302_HOUR_ADDR,targetTime[3]); // 时
Med_Ds1302_WriteByte(DS1302_MIN_ADDR,targetTime[4]); // 分
Med_Ds1302_WriteByte(DS1302_SEC_ADDR,targetTime[5]); // 秒
Med_Ds1302_WriteByte(DS1302_WEEK_ADDR,targetTime[6]); // 周
Med_Ds1302_WriteByte(DS1302_CHARGER_ADDR,0xA5); // 打开充电功能 选择2K电阻充电方式
Med_Ds1302_WriteByte(DS1302_CONTROL_ADDR,0x80); // 打开写入
}
3.4 DS1302获取当前时间
/*
*==============================================================================
*函数名称:Med_Ds1302_GetTime
*函数功能:DS1302获取时间
*输入参数:writeTime:要设置的时间
*返回值:无
*备 注:无
*==============================================================================
*/
void Med_Ds1302_GetTime (DS1302TimeStruct *timeData)
{
u8 readtime[8]; // 当前时间
readtime[0] = Med_Ds1302_ReadByte(DS1302_YEAR_ADDR); // 年
readtime[1] = Med_Ds1302_ReadByte(DS1302_MONTH_ADDR); // 月
readtime[2] = Med_Ds1302_ReadByte(DS1302_DAY_ADDR); // 日
readtime[3] = Med_Ds1302_ReadByte(DS1302_HOUR_ADDR); // 时
readtime[4] = Med_Ds1302_ReadByte(DS1302_MIN_ADDR); // 分
readtime[5] = (Med_Ds1302_ReadByte(DS1302_SEC_ADDR)) & 0x7f; // 秒,屏蔽秒的第7位,避免超出59
readtime[6] = Med_Ds1302_ReadByte(DS1302_WEEK_ADDR); // 周
timeData -> year = (readtime[0] >> 4) * 10 + (readtime[0] & 0x0F); // 计算年份
timeData -> month = (readtime[1] >> 4) * 10 + (readtime[1] & 0x0F); // 计算月份
timeData -> day = (readtime[2] >> 4) * 10 + (readtime[2] & 0x0F); // 计算日期
timeData -> hour = (readtime[3] >> 4) * 10 + (readtime[3] & 0x0F); // 计算小时
timeData -> minute = (readtime[4] >> 4) * 10 + (readtime[4] & 0x0F); // 计算分钟
timeData -> second = (readtime[5] >> 4) * 10 + (readtime[5] & 0x0F); // 计算秒钟
timeData -> week = (readtime[6] & 0x0F); // 计算星期
}
四、使用示例
设置初始日期时间为2025年3月10日20点52分00秒周一,之后通过串口每1s打印一次当前时间
#include "med_mcu.h"
#include "med_ds1302.h"
#include "delay.h"
#include "usart.h"
int main(void)
{
// 初始时间
u8 initTime[8] = {0x25, 0x03, 0x10, 0x20, 0x52, 0x00, 0x01};
// 存储读取到的时间
DS1302TimeStruct getTime;
Med_Mcu_Iint(); // 系统初始化
// 设置初始时间
Med_Ds1302_SetTime(initTime);
while(1)
{
Med_Ds1302_GetTime(&getTime); // 获取时间
printf("20%d-%d-%d %d:%d:%d\r\n", getTime.year, getTime.month, getTime.day, getTime.hour, getTime.minute, getTime.second);
delay_ms(1000);
}
}
五、注意事项
4.1 设置时间
为什么我按键设置完时间之后显示的不是我设置的时间?
我们可以注意一下示例程序中设置的初始时间的写法,并不是直接写十进制的25,而是0x25
,转换成十进制并不是25年,我们用这个参数写入寄存器之后读出来为什么就是25年呢,因为在获取当前时间函数中我们对从寄存器读出的数据进行了处理
timeData -> year = (readtime[0] >> 4) * 10 + (readtime[0] & 0x0F); // 计算年份
比如我们要通过设置一下当前时间,在进入设置页面之前我们先获取一下当前时间作为设置的初始值,在此基础上进行加减,我们可以想一下,现在我们的初始值是不是25而不是0x25,当我们对其进行加减,变成其他数比如28,这时再调用写入时间函数,写入的是28,而不是0x28,所以再使用获取时间获取时年份应该是22年,并不是28年,这就是为什么我们设置完之后显示的不是我们设置的时间,因此在设置之前需要对时间进行反变换之后再使用设置时间的函数写入,反变换示例
setTime[0] = ((setTime[0] / 10) << 4) | (setTime[0] % 10);
这样写入之后再获取出来就是我们设置的28年。