实时时钟芯片DS1302单片机C语言驱动程序

发布时间:2024年01月14日

实时时钟RTC相关索引

1.单片机RTC及时钟芯片的时间到底从哪一年起始?
2.STM32F103单片机内部RTC实时时钟驱动程序
3.实时时钟芯片DS1302单片机C语言驱动程序

一、DS1302简介

DS1302 是 DALLAS(达拉斯)公司推出的一款涓流充电时钟芯片。
主要特点:
1.可计算秒、分、时、日、日期、星期、月、年的能力,并有闰年补偿功能;
2.内部含有31个字节静态RAM;
3.采用串行数据传送方式,3线接口;
4.与TTL兼容,Vcc=5V;
5.时钟或RAM数据的读/写有两种传送方式:单字节传送和多字节传送方式;
6.工作电压范围宽:2.0~5.5V;
7.功耗低,2.0V时工作电流小于300nA。
8.采用8脚DIP封装或SOIC封装。
9.可选工业级温度范围:-40℃~+85℃;

DS1302的引脚如下图示意,其中X1/X2接32.768KHz的晶振,SCLK为数据时钟引脚,I/O为数据收发引脚,CE为芯片的使能引脚,Vcc1为后备电池供电引脚,Vcc2为主电源供电引脚。
在这里插入图片描述

二、驱动程序

1.读写时序

在这里插入图片描述先看上图,无论是“读单字节”时序,还是“写单字节”时序,CE需要保持高电平,且在读写数据D0~D7之前,都要先写入一个字节的“命令字节”,即Command Byte(下面会讲到),SCLK上升沿的时候,写入数据,SCLK下降沿的时候读出数据,传输方式是LSB,低位在前,因此我们可以写出DS1302读或写单个字节的程序:

/*******************************************************************************
  * 函数名:DS1302_WriteByte
  * 功  能:写1个字节
  * 参  数:data:写入的数据
  * 返回值:无
  * 说  明:SCLK上升沿时,写入数据,LSB
*******************************************************************************/
void DS1302_WriteByte(uint8_t data)
{
	uint8_t i;
	DS1302_IOModeOut();//IO为输出模式
	for (i = 0; i < 8; i++)
	{		
		DS1302_SCLK_L();
		(data & 0x01) ? (DS1302_IOOutput_H()) : (DS1302_IOOutput_L());
		data >>= 1;//右移1位,准备发送下一位
		DS1302_SCLK_H();//上升沿发出
	}
}
/*******************************************************************************
  * 函数名:DS1302_ReadByte
  * 功  能:读1个字节
  * 参  数:无
  * 返回值:data:写入的数据
  * 说  明:SCLK下降沿时,读取数据,LSB
*******************************************************************************/
uint8_t DS1302_ReadByte(void)
{
	uint8_t i;
	uint8_t data = 0;
	for (i = 0; i < 8; i++)
	{
		DS1302_SCLK_H();
		DS1302_IOModeIn();//IO为输入模式
		data >>= 1;
		DS1302_SCLK_L();//下降沿
		data |= (DS1302_IORead() ? 0x80 : 0x00);
	}
	return data;
}

其中,引脚定义如下,可根据实际情况,改成自己使用的单片机引脚,笔者使用的是SM32F103C8T6:

#define DS1302_CE_H()					HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_SET)
#define DS1302_CE_L()					HAL_GPIO_WritePin(DS1302_CE_GPIO_Port, DS1302_CE_Pin, GPIO_PIN_RESET)

#define DS1302_SCLK_H()					HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_SET)
#define DS1302_SCLK_L()					HAL_GPIO_WritePin(DS1302_SCLK_GPIO_Port, DS1302_SCLK_Pin, GPIO_PIN_RESET)

#define DS1302_IOModeOut()				Pin_SetOutputMode(DS1302_IO_GPIO_Port, DS1302_IO_Pin, GPIO_MODE_OUTPUT_PP, GPIO_NOPULL, GPIO_SPEED_FREQ_MEDIUM)
#define DS1302_IOModeIn()				Pin_SetInputMode(DS1302_IO_GPIO_Port, DS1302_IO_Pin, GPIO_MODE_INPUT, GPIO_NOPULL)
#define DS1302_IOOutput_H()				HAL_GPIO_WritePin(DS1302_IO_GPIO_Port, DS1302_IO_Pin, GPIO_PIN_SET)
#define DS1302_IOOutput_L()				HAL_GPIO_WritePin(DS1302_IO_GPIO_Port, DS1302_IO_Pin, GPIO_PIN_RESET)
#define DS1302_IORead()					HAL_GPIO_ReadPin(DS1302_IO_GPIO_Port, DS1302_IO_Pin)

2.命令字节

上面说到无论读还是写,都要先写入命令字节,接下来看看命令字节是什么:
在这里插入图片描述根据上图所示的描述,每次数据传输前都由命令字节开始,最高位bit7必须是1,否则DS1302会被禁用,停止工作;bit6是0的话,表示操作的是时钟/日历数据,是1的话表示操作的是RAM;bit1~bit5表示输入或输出的寄存器地址,bit0为0是写,为1是读操作,这些类似于24Cxx系列的EEPROM。传输的方式是LSB。

3.寄存器读写

3.1 基本寄存器读写

从下图寄存器地址定义表格中可看出,每个寄存器看上去有两个地址,读和写的地址不同,但实际上,结合上一小节“命令字节”每一位的描述,我们知道,命令字节的bit0位是读或写标志位,所以其实RTC的每个寄存器的地址是一样的,只是在读的时候,命令字节的bit0为1,写为0;而bit7必须为1,因此下图中RTC寄存器地址都是8或9开头的,0x81~0x8F、0x91的最高位是1,同理,CLOCK BURST、RAM及RAM BURST地址的最高位都是1;RTC和CLOCK BURST的地址,bit6都是0,表示时钟/日历的地址,而RAM、RAM BURST的地址,bit6都是1,表示RAM的地址;寄存器的真实地址,只有中间的A4/A3/A2/A1/A0位,例如,分钟寄存器的读地址,0x82=1000 0010b,对应A4/A3/A2/A1/A0为00001,因此各寄存器地址的宏定义如下:

#define WRITE					0x00//写命令
#define READ					0x01//读命令

#define REG_SEC					0x80//0x80 & (0x00 << 1),秒
#define REG_MIN					0x82//0x80 & (0x01 << 1),分
#define REG_HOUR				0x84//0x80 & (0x02 << 1),时
#define REG_DATE				0x86//0x80 & (0x03 << 1),日
#define REG_MONTH				0x88//0x80 & (0x04 << 1),月
#define REG_DAY					0x8A//0x80 & (0x05 << 1),周几
#define REG_YEAR				0x8C//0x80 & (0x06 << 1),年
#define REG_WRITE_PROTECT		0x8E//0x80 & (0x07 << 1),写保护
#define REG_RAM_1               0xC0//0xC0 & (0x00 << 1),RAM的第一个字节

在这里插入图片描述另外,需要注意的是,RTC寄存器中,数据是8421格式的BCD码存储的,这种编码方式在RTC功能中很常用。RTC各寄存器种关于时间的读写驱动如下:

/*******************************************************************************
  * 函数名:DS1302_WriteReg
  * 功  能:写寄存器
  * 参  数:reg:需要写入的寄存器地址;
			data:需要写入的数据
  * 返回值:无
  * 说  明:无
*******************************************************************************/
void DS1302_WriteReg(uint8_t reg, uint8_t data)
{
	DS1302_CE_L();
	DS1302_SCLK_L();
	DS1302_CE_H();//使能
	DS1302_WriteByte(reg | WRITE);
	DS1302_WriteByte(data);
	DS1302_CE_L();
}
/*******************************************************************************
  * 函数名:DS1302_ReadReg
  * 功  能:读寄存器
  * 参  数:reg:需要写入的寄存器地址;			
  * 返回值:data:读取的数据
  * 说  明:无
*******************************************************************************/
uint8_t DS1302_ReadReg(uint8_t reg)
{
	uint8_t data = 0;
	DS1302_CE_L();
	DS1302_SCLK_L();
	DS1302_CE_H();//使能
	DS1302_WriteByte(reg | READ);	
	data = DS1302_ReadByte();
	DS1302_CE_L();
	DS1302_IOModeOut();
	DS1302_IOOutput_L();
	return data;
}
/*******************************************************************************
  * 函数名:DS1302_GetYear
  * 功  能:获取年份
  * 参  数:无			
  * 返回值:年份,十进制
  * 说  明:将BCD码转为十进制
*******************************************************************************/
uint16_t DS1302_GetYear(void)
{
	uint16_t temp = 0;
	temp = DS1302_ReadReg(REG_YEAR);
	return BCDToDec(temp);
}
/*******************************************************************************
  * 函数名:DS1302_GetWeekDay
  * 功  能:获取星期几
  * 参  数:无			
  * 返回值:星期几,十进制,1~7
  * 说  明:无
*******************************************************************************/
uint8_t DS1302_GetDay(void)
{
	uint8_t temp = 0;
	temp = DS1302_ReadReg(REG_DAY);
	return BCDToDec(temp & 0x07);
}
/*******************************************************************************
  * 函数名:DS1302_GetMonth
  * 功  能:获取月份
  * 参  数:无			
  * 返回值:月份,十进制,1~12
  * 说  明:无
*******************************************************************************/
uint8_t DS1302_GetMonth(void)
{
	uint8_t temp = 0;
	temp = DS1302_ReadReg(REG_MONTH);
	return BCDToDec(temp & 0x1F);
}
/*******************************************************************************
  * 函数名:DS1302_GetDate
  * 功  能:获取日期
  * 参  数:无			
  * 返回值:日,十进制,1~31
  * 说  明:无
*******************************************************************************/
uint8_t DS1302_GetDate(void)
{
	uint8_t temp = 0;
	temp = DS1302_ReadReg(REG_DATE);
	return BCDToDec(temp & 0x3F);
}
/*******************************************************************************
  * 函数名:DS1302_GetHour
  * 功  能:获取小时
  * 参  数:无			
  * 返回值:小时,十进制,1~12或0~23
  * 说  明:返回的值需要结合小时模式及AM/PM位区分具体时间
*******************************************************************************/
uint8_t DS1302_GetHour(void)
{
	uint8_t temp = 0;
	temp = DS1302_ReadReg(REG_HOUR);
    if ((temp & 0x80) == 0x80)//12小时模式
    {
        return BCDToDec(temp & 0x1F);
    }else//24小时模式
    {
        return BCDToDec(temp & 0x3F);
    }
}
/*******************************************************************************
  * 函数名:DS1302_GetMinute
  * 功  能:获取分钟
  * 参  数:无			
  * 返回值:分钟,十进制,0~59
  * 说  明:无
*******************************************************************************/
uint8_t DS1302_GetMinute(void)
{
	uint8_t temp = 0;
	temp = DS1302_ReadReg(REG_MIN);
	return BCDToDec(temp & 0x7F);
}
/*******************************************************************************
  * 函数名:DS1302_GetSecond
  * 功  能:获取秒
  * 参  数:无			
  * 返回值:秒,十进制,0~59
  * 说  明:无
*******************************************************************************/
uint8_t DS1302_GetSecond(void)
{
	uint8_t temp = 0;
	temp = DS1302_ReadReg(REG_SEC);
	return BCDToDec(temp & 0x7F);
}
/*******************************************************************************
  * 函数名:DS1302_SetYear
  * 功  能:写入年份
  * 参  数:year:年份,十进制,0~99			
  * 返回值:无
  * 说  明:将十进制转为BCD码
*******************************************************************************/
void DS1302_SetYear(uint16_t year)
{
	uint8_t temp = DecToBCD(year);
	DS1302_WriteReg(REG_YEAR, temp);	
}
/*******************************************************************************
  * 函数名:DS1302_SetDay
  * 功  能:写入星期几
  * 参  数:day:星期几,1~7			
  * 返回值:无
  * 说  明:无
*******************************************************************************/
void DS1302_SetDay(uint8_t day)
{
	DS1302_WriteReg(REG_DAY, day & 0x07);	
}
/*******************************************************************************
  * 函数名:DS1302_SetMonth
  * 功  能:写入月份
  * 参  数:month:月份,1~12			
  * 返回值:无
  * 说  明:转为BCD码
*******************************************************************************/
void DS1302_SetMonth(uint8_t month)
{
    uint8_t temp = DecToBCD(month);
	DS1302_WriteReg(REG_MONTH, temp & 0x1F);
}
/*******************************************************************************
  * 函数名:DS1302_SetDate
  * 功  能:写入日
  * 参  数:date:日,1~31			
  * 返回值:无
  * 说  明:无
*******************************************************************************/
void DS1302_SetDate(uint8_t date)
{
    uint8_t temp = DecToBCD(date);
	DS1302_WriteReg(REG_DATE, temp & 0x3F);
}
/*******************************************************************************
  * 函数名:DS1302_SetHour
  * 功  能:写入小时
  * 参  数:hour:小时,1~12或0~23		
  * 返回值:无
  * 说  明:无
*******************************************************************************/
void DS1302_SetHour(uint8_t hour)
{
    uint8_t temp = DecToBCD(hour);
	DS1302_WriteReg(REG_HOUR, temp & 0x3F);
}
/*******************************************************************************
  * 函数名:DS1302_SetMinute
  * 功  能:写入分钟
  * 参  数:min:分钟,0~59		
  * 返回值:无
  * 说  明:无
*******************************************************************************/
void DS1302_SetMinute(uint8_t min)
{
    uint8_t temp = DecToBCD(min);
	DS1302_WriteReg(REG_MIN, temp & 0x7F);
}
/*******************************************************************************
  * 函数名:DS1302_SetSecond
  * 功  能:写入秒
  * 参  数:sec:秒,0~59		
  * 返回值:无
  * 说  明:无
*******************************************************************************/
void DS1302_SetSecond(uint8_t sec)
{
    uint8_t temp = DecToBCD(sec);
	DS1302_WriteReg(REG_SEC, temp & 0x7F);
}

其中BCD码与十进制数的转换,可参考BCD码与十进制数转换C语言程序

3.2 特殊功能读写

除了以上时钟、日历相关的寄存器外,还有一些特殊的功能,比如12/24小时模式,写保护功能等,接下来看这些功能的读写。

3.2.1 时钟停止标志位

先看80H的bit7位,这是秒寄存器的最高位,手册中的描述如下图:在这里插入图片描述当该位为1时,时钟晶振停止,为0时,时钟开始工作,而上电后该位的状态是未定义的,因此上电后需将该位清零,时钟才开始工作;如果使用DS1302,发现读取的时间一直是初始化的时间而不变,那么有可能是这一位没有清零导致的;

/*******************************************************************************
  * 函数名:DS1302_ClearHaltFlag
  * 功  能:Halt标志置0
  * 参  数:无			
  * 返回值:无
  * 说  明:先读时间秒,再将CH位与秒组合为1字节,该位为0,时钟才开始工作
*******************************************************************************/
void DS1302_ClearHaltFlag(void)
{
	uint8_t temp = 0;
	temp = DS1302_GetSecond();
	temp &= 0x7F;
	DS1302_WriteReg(REG_SEC, temp);
}
3.2.2 12/24小时模式

小时寄存器84H的最高位是12/24小时模式选择位,功能描述如下:
在这里插入图片描述DS1302可运行于12或24小时模式下,小时寄存器的bit7位是12/24小时模式选择位,1为12小时模式,0为24小时模式;在12小时模式下,bit5表示AM/PM(上午/下午),1为下午;24小时模式下,bit5是20~23小时的第2个“10小时”;当12/24小时模式改变了,小时这个数据也必须重新初始化。
上面说,24小时模式下,bit5是20~23小时的第2个“10小时”,这一点需要好好理解一下,意思是,24小时模式下,由于数据是BCD码格式的,当小时是0-19时,这个时候的bit5是0,例如19时,bit5-bit0表示为01 1001,即8421码的19,这个时候用不到bit5;当小时大于19,为20-23时,该位就是1了,比如22,bit5-bit0表示为10 0010;该功能的读写程序如下:

/*******************************************************************************
  * 函数名:DS1302_GetHourMode
  * 功  能:获取小时模式
  * 参  数:无			
  * 返回值:0,24小时模式,1,12小时模式
  * 说  明:bit7
*******************************************************************************/
uint8_t DS1302_GetHourMode(void)
{
    uint8_t temp = 0;
    temp = DS1302_ReadReg(REG_HOUR);   
    temp &= 0x80;
    return (temp >> 7);
}
/*******************************************************************************
  * 函数名:DS1302_SetHourMode
  * 功  能:设置小时模式
  * 参  数:mode,0,24小时模式,1,12小时模式			
  * 返回值:无
  * 说  明:bit7,更改模式时,小时必须重新初始化,
            因此先读取原小时时间,再设置模式,再将小时写入寄存器
*******************************************************************************/
void DS1302_SetHourMode(uint8_t mode)
{
    uint8_t hour = 0;//原时间
    uint8_t temp = 0;//原模式
    uint8_t reg = 0;//写入寄存器的值
    temp = DS1302_GetHourMode();
    if (mode != temp)//修改模式
    {
        reg |= ((mode & 0x01) << 7);//模式在最高位
        hour = DS1302_ReadReg(REG_HOUR);//读取原寄存器的值,保存原时间        
        if (mode == 0)//改为24小时模式,之前是12小时模式,先判断AM/PM
        {
            if ((hour & 0x20) == 0x20)//bit5,1=PM
            {
                hour &= 0x1F;//提取0~4位的时间
                hour = BCDToDec(hour);//BCD转为十进制
                hour += 12;
                reg |= DecToBCD(hour);//BCD
            }else//AM
            {
                hour &= 0x1F;//提取0~4位的时间
                reg |= hour;
            }
        }else//改为12小时模式,之前是24小时模式
        {
            hour &= 0x3F;
            hour = BCDToDec(hour);//BCD转为十进制
            if (hour > 12)//PM
            {
                hour -= 12;
                reg |= DecToBCD(hour);//BCD
                reg |= 0x20;//bit5置1
            }else//AM
            {
                reg |= DecToBCD(hour);//BCD
                reg &= 0xDF;//bit5置0
            }
        }
        DS1302_WriteReg(REG_HOUR, reg);	
    }    
}
/*******************************************************************************
  * 函数名:DS1302_SetAMPM
  * 功  能:设置AM/PM模式
  * 参  数:data,0为AM上午,1为PM下午		
  * 返回值:无
  * 说  明:小时寄存器bit5,须在12小时模式下使用
*******************************************************************************/
void DS1302_SetAMPM(uint8_t data)
{
    uint8_t temp = 0;
    temp = DS1302_ReadReg(REG_HOUR);//先把原数据读出
    (data == 0) ? (temp &= 0xDF) : (temp |= 0x20);
    DS1302_WriteReg(REG_HOUR, temp);
}
/*******************************************************************************
  * 函数名:DS1302_GetAMPM
  * 功  能:读取AM/PM模式
  * 参  数:无		
  * 返回值:0为AM上午,1为PM下午
  * 说  明:小时寄存器bit5,须在12小时模式下使用
*******************************************************************************/
uint8_t DS1302_GetAMPM(void)
{
    uint8_t temp = 0;
    temp = DS1302_ReadReg(REG_HOUR);
    return ((temp & 0x20) >> 5);
}
3.3.3 写保护功能

写保护位的描述见下图:
在这里插入图片描述控制寄存器的bit7位是写保护位,bit0-bit6位强制为0,读出也是0;在对时钟或RAM寄存器进行任何写入操作前,bit7位必须置位0,如果该位为1,将无法对任何寄存器进行写入操作;该位上电初始化的状态未定义,因此在试图对器件进行写入操作前需将写保护位清零。该功能相关程序如下:

/*******************************************************************************
  * 函数名:DS1302_WriteProtectEnable
  * 功  能:使能写保护
  * 参  数:无		
  * 返回值:无
  * 说  明:无
*******************************************************************************/
void DS1302_WriteProtectEnable(void)
{
	DS1302_WriteReg(REG_WRITE_PROTECT, 0x80);
}
/*******************************************************************************
  * 函数名:DS1302_WriteProtectDisable
  * 功  能:禁止写保护
  * 参  数:无		
  * 返回值:无
  * 说  明:取消写保护功能
*******************************************************************************/
void DS1302_WriteProtectDisable(void)
{
	DS1302_WriteReg(REG_WRITE_PROTECT, 0x00);
}

Burst模式相关的寄存器,以及涓流充电寄存器在此不做讨论。

4.时钟初始化

首次上电时,单片机开始工作,读取RAM寄存器的值,与预设的值比较,如果值不正确,则是首次配置,初始化时间,并在RAM中写入预设的值;如果值正确,则直接读取时间寄存器的值,将时间输出即可;如果有电池供电,在主电源断开时,会继续计时,RAM寄存器中的值也会保存下来;

void DS1302_Init(void)
{
    uint8_t temp = 0;
    temp = DS1302_ReadReg(0xC0);//读RAM第一字节
    if (temp != 0x5A)//不正确,重新初始化
    {
        DS1302_WriteProtectDisable();//关闭写保护
        DS1302_ClearHaltFlag();//清除Halt标志
        DS1302_SetHourMode(0);//24小时模式
        DS1302_SetYear(24);//初始化时间24-01-11 22:30:00,Thursday,根据实际需要修改
        DS1302_SetDay(4);
        DS1302_SetMonth(1);
        DS1302_SetDate(11);
        DS1302_SetHour(22);
        DS1302_SetMinute(30);
        DS1302_SetSecond(0);
        DS1302_WriteReg(0xC0, 0x5A);//写入0x5A
        DS1302_WriteProtectEnable();//打开写保护   
    }
}

三、实验验证

在笔者的另一篇文章《单片机RTC及时钟芯片的时间到底从哪一年起始?》中,我们知道了DS1302复位时的初始时间是00年,一般我们在使用时,可以加上2000按照2000年处理,比如2024年,可以将年份设置为24,读出时加上2000变为2024年即可,这样可以使用到2099年;下面是上面DS1302_Init()函数初始化后,读取时间并通过串口打印输出的效果:
在这里插入图片描述
接下来测试12小时模式,设置时间时,通过DS1302_SetAMPM函数设置是上午还是下午,在输出的时间后面,通过函数DS1302_GetAMPM读取AM/PM标志,然后输出,看时间在AM/PM需要切换时是否正常:
在这里插入图片描述
还有需要验证是否有效的函数,就是12/24小时切换功能DS1302_SetHourMode函数,程序设置每隔3秒切换小时模式,24小时模式正常输出时间,12小时模式在后面输出AM/PM,输出的效果:
在这里插入图片描述
模式切换自如,可知各函数的功能正常。

文章来源:https://blog.csdn.net/wanglong3713/article/details/121327006
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。