能力有限,只看效果,归纳总结可以使用的代码,视频仅作为理解。

视频学习链接:https://www.bilibili.com/video/BV1Zv411C758/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=b91967c499b23106586d7aa35af46413

芯片数据手册介绍链接:
中文:https://item.szlcsc.com/379831.html
英文:https://item.szlcsc.com/15455.html

说明:DS1302模块初始上电时或初始写入(设置)寄存器值时,时钟可能不会稳定(走的特别快)。此时,用手触碰下DS11302模块上的晶振,可能会走时正常,但是,重新再启动或者再设置后,还是走的特别快,此时,不要慌,让系统多运行一段时间,可能就变的正常了。

一、如何操作一个芯片

在这里插入图片描述

二、DS1302

2.1 DS1302的芯片介绍

在这里插入图片描述

在工程应用中采用多字节数据传送方式。

在这里插入图片描述

涓流充电只适用于可充电的电池,而这里多用纽扣电池。

2.2 DS1302的引脚定义

在这里插入图片描述

注:Vcc2为主电源。

RST 为复位,也称为片选引脚。

2.3 DS1302的外围电路

下图在英文手册中,
在这里插入图片描述

当 Vcc2(主电源) 大于 Vcc1 (备用电源)时(一般超过 0.2 V 就行),此时使用 Vcc2, 否则使用 Vcc1。

2.4 DS1302 的时序命令字节

注:以英文手册为主。

命令字节图 3 显示了命令字节。一个命令字节启动每次数据传输。MSB(位7) 必须是一个逻辑 1. 如果为 0,将禁用对 DS1302 的写入。如果位 6 为逻辑 0,则指定时钟/日历数据;如果位 6 为逻辑 1,则指定RAM数据。第 1 位到第 5 位指定输入或输出寄存器,LSB(0位)指定逻辑 0 时的写操作(输入)或逻辑 1 时的读操作(输出)。命令字节总是从LSB(位0)开始输入。

写/读操作是对 DS1302 而言的。

在这里插入图片描述

一般来说,一次指令操作是按照两个字节(一个字节是命令字节,一个是数据字节)来进行的,如下图所示,I/O引脚的时序就表明是两个字节。

注:下图为单字节模式下的操作图(上图中注:在突发模式下,CE 保持高位,并发送额外的 SCLK 周期,直到突发结束。),
在这里插入图片描述

此外,还有突发(BURST)模式,在突发模式下,I/O引脚处的时序中的命令字节就变为:
在这里插入图片描述
此时,可进行多个字节的读写操作。

下图为寄存器地址的定义,
在这里插入图片描述
解释下,上表中的 READ 和 WRITE 就对应 I/O 时序中的命令字节,当进行 RTC 进行写操作时,

此时 R/ W 位为 0,A0-A4为 0, R/ C 位为 0,位7为1,也就是 0x80。

当进行 RTC 进行读操作时,也就是 0x81。
在这里插入图片描述

表格后面的 BIT 7 - BIT 0 就是数据字节,其中,表中的 Day 指的是星期几。WP是写保护位。

最后的一行中的 91h…TCS…DS…RS 用于控制涓流充电(数据手册中有详述)。

此外,在突发模式下,可以将 Seconds … Year 这七个字节给读取出来或写入进去。

2.5 DS1302 时序代码的编写

1、DS1302初始化的函数

void ds1302_init(void) 

2、向DS1302写入一字节数据的函数

void ds1302_write_byte(uchar addr, uchar d) 

3、从DS1302读出一字节数据的函数

uchar ds1302_read_byte(uchar addr)

4、向DS302写入时钟数据的函数

void ds1302_write_time(void) 

5、从DS302读出时钟数据的函数

void ds1302_read_time(void)  

视频中介绍了 Burst 模式的读写,这里并没有使用到。因为如果要使用 Burst 模式的话,就需要按照视频介绍中的那样去定义读写 DS1302 的驱动方式,也就是要

#include "ds1302_spi.h"

void DS1302WriteByte(unsigned char dat) 
{
		unsigned char i;
		
		for(i = 0; i<8; i++) // 低位在前,高位在后
		{
			DS1302_IO = dat & 0x01;
			dat >>= 1;	
			DS1302_SCLK = 1;	// 上升沿 DS1302来采样(读) 单片机把数据放到总线上
			DS1302_SCLK = 0;
		}
}

unsigned char DS1302ReadByte()
{
		unsigned char temp;
		unsigned char dat = 0;
		
		for(temp = 0x01; temp != 0; temp <<= 1) // 低位在前,高位在后
		{
			if(DS1302_IO != 0)
				dat|= temp;
			DS1302_SCLK = 1; // 上升沿 单片机来采样(读) 数据是DS1302输出到总线上
			DS1302_SCLK = 0;
		}
		return dat;
}

但是,上述代码在仿真上验证的并不好,也不确定能不能在实物上跑的起来。所以后续程序中,并没有采用 Burst 模式。

2.6 心路总结与归纳

完全按照视频中的代码敲下来,在仿真中,会发现:秒数计数不正常(差不多三秒才进位一次),显示星期几也有问题(后面会莫名其妙的多出个“冒号:”)。在实际电路中并没有实验。

因为存在上述问题,所以我寻找该模块的示例代码,分别是通过串口打印和LCD1602显示的程序,均验证通过。

示例代码下载地址:

1、淘宝(https://pan.baidu.com/s/1dIvsVFP1Z8jioSn9W0ev0Q 提取码: 6666 )

2、已上传CSDN

效果如下:

LCD1602显示:该代码使用了定时器2,使用方法也进行了总结https://blog.csdn.net/xuechanba/article/details/131492867
在这里插入图片描述
串口打印:12MHz,2400 bps

在这里插入图片描述

之后,我尝试将示例代码中的读一个字节的函数和写一个字节的函数替换视频中的相应部分,但是并没有得到解决。

在这里插入图片描述
此外,视频中的

// 初始化时间为2023年5月8日星期一 8点00分00秒 存储顺序是秒、分、时、日、月、星期、年 
// 存储格式为BCD码,即用4位二进制数来表示一位十进制数
uchar time[]  ={0x00,0x00,0x08,0x08,0x05,0x02,0x23}; 

我也觉得不是很直观,所以进行了修改。

// 初始化时间为2023年5月8日 8点00分00秒 星期一
// 存储格式为BCD码,即用4位二进制数来表示一位十进制数
uchar time[]  ={0x20,0x23,0x05,0x08,0x08,0x00,0x00,0x02};   

总结:为了避免麻烦,直接采用示例代码的基础上进行代码的编写,不再管视频中的代码,仅仅作为理解使用。

(怀疑:视频中的这个代码存在问题的原因,可能不光是ds1302的驱动有问题,还有可能是LCD1602的驱动问题或其他方面的问题(程序架构不合理,因为51的性能确实比较有限,按后面的采用定时器刷新的方式(代码三)或许可行)

争议的地方:

关于视频中所说的星期(视频中说1对应星期天,2对应星期1,…,7对应星期6),我认为是有问题的,查看数据手册:

在这里插入图片描述

说的是低三位表示星期,

示例代码中是这样处理的:

dis_time_buf[14]=(time_buf[7]&0x07); //星期

也就是(1对应星期1,2对应星期2,…,7对应星期7)。

后续显示中文,

void DisplayWeek(uchar day)
{
	switch(day)
	{
		case 1: display_graphic_16x16(6,(1+16+8+8+8+2+16+16+2),yi1); break;
		case 2: display_graphic_16x16(6,(1+16+8+8+8+2+16+16+2),er1); break;
		case 3: display_graphic_16x16(6,(1+16+8+8+8+2+16+16+2),san1);break;
		case 4: display_graphic_16x16(6,(1+16+8+8+8+2+16+16+2),si1); break;
		case 5: display_graphic_16x16(6,(1+16+8+8+8+2+16+16+2),wu1); break;
		case 6: display_graphic_16x16(6,(1+16+8+8+8+2+16+16+2),liu1);break;
		case 7: display_graphic_16x16(6,(1+16+8+8+8+2+16+16+2),tian1);break;
		default: break;
	}	
}

显示也没问题。

还有就是:

寄存器6:高四位表示年的十位,低四位表示年的个位。00 ~ 99指的是2000年~2099年。

在示例程序中:

虽然初始时间被定义为:

uchar time_buf[8] = {0x20,0x23,0x07,0x02,0x23,0x59,0x50,0x07};//初始时间2023年7月2号23点59分50秒 星期日

但是,写入时

ds1302_write_byte(ds1302_year_add,time_buf[1]);		//年 

也就是说 time_buf[0],其实不用定义,默认年份就是 20xx 。

2.5 关于 Proteus 对 DS1302 时钟模块的操作细节

1、显示电脑的系统时间

在程序初始化时,如果没有向 DS1302 写入初始时间的话,将显示电脑系统当前的时间。

在这里插入图片描述
在这里插入图片描述
否则,读取的就是初始写入的时间。

为什么会这样呢?

双击 DS1302 模块后,

在这里插入图片描述

2、显示时间的小窗口

在这里插入图片描述

3、显示DS1302的 RAM 值

首先,将仿真暂停

在这里插入图片描述

其次,将下图中的 RAM 勾上
在这里插入图片描述
最后,可以查看写入的 RAM 值。
在这里插入图片描述

如果找不到,可以参考下面的方式。

在这里插入图片描述

要按暂停键(否则会一闪而过),然后在菜单\debug\DS1302(一般在最下一条)。

三、实验展示

3.1 代码一

功能:实现时钟在JLX12864液晶屏的显示

效果如下:

在这里插入图片描述
代码已上传至资料。

3.2 代码二

功能:上述代码存在一个问题,就是当单片机掉电后,重新上电后,仍然会读取初始设置的写入时钟,但是实际上, DS1302 并没有掉电,所以应该读取的是实际上DS1302 的时钟。

改进:

在寄存器中的秒寄存器的最高位即 BIT 7(CH)就是用来指示时钟是否是暂停和启动的,

在这里插入图片描述
在这里插入图片描述

当该位为 1 时,就表示时钟暂停(DS1302 的备用电源也掉电了),当该位为 0 时,就表示时钟启动。因此,我们就可以通过单片机初始上电对该位的读取,进而判断是否重新写入预设时间。

//向DS1302写入时钟数据
void ds1302_write_time(void) 
{
	unsigned char state;
	state = ds1302_read_byte(0x81); 
	if((state & 0x80)!= 0) // BIT 7 不为 0,说明备用电源掉电(时钟停止),此时重新写入预设时间
	{
		ds1302_write_byte(ds1302_control_add,0x00);				//关闭写保护 
		ds1302_write_byte(ds1302_sec_add,0x80);						//暂停时钟 
		//ds1302_write_byte(ds1302_charger_add,0xa9);	    //涓流充电 
		ds1302_write_byte(ds1302_year_add,time_buf[1]);		//年 
		ds1302_write_byte(ds1302_month_add,time_buf[2]);	//月 
		ds1302_write_byte(ds1302_date_add,time_buf[3]);		//日 
		ds1302_write_byte(ds1302_hr_add,time_buf[4]);			//时 
		ds1302_write_byte(ds1302_min_add,time_buf[5]);		//分
		ds1302_write_byte(ds1302_sec_add,time_buf[6]);		//秒
		ds1302_write_byte(ds1302_day_add,time_buf[7]);		//周 
		ds1302_write_byte(ds1302_control_add,0x80);				//打开写保护   
	}		
}

由此引申,可以加入功能,按键1按下暂停时钟,按键2按下启动时钟。

//DS1302时钟停止
void ds1302_stop_time(void) 
{
	uchar temp;
	
	temp=ds1302_read_byte(0x81);
	ds1302_write_byte(ds1302_control_add,0x00);				//关闭写保护
	ds1302_write_byte(ds1302_sec_add,temp|0x80);			//暂停时钟 
	ds1302_write_byte(ds1302_control_add,0x80);				//打开写保护 
}

//DS1302时钟启动
void ds1302_start_time(void) 
{
	uchar temp;
	
	temp=ds1302_read_byte(0x81);
	ds1302_write_byte(ds1302_control_add,0x00);				//关闭写保护 
	ds1302_write_byte(ds1302_sec_add,temp&0x7f);
	ds1302_write_byte(ds1302_control_add,0x80);				//打开写保护 
}

此外,与之相关的一点还有在从 DS1302 中读出时钟数据的秒数据时,通常会 &0x7f,这就是避免第7位(有可能会读出为0x80,超过 59)。

time_buf[6]=(ds1302_read_byte(ds1302_sec_add))&0x7f;	//秒,屏蔽秒的第7位,避免超出59

3.3 代码三

效果三_理论值消抖按键切换小灯及控制暂停启动时钟_JLX12864定时器200ms刷新显示实时时钟,已上传至CSDN。

3.4 代码四

按键分配:预设四个按键分别为A、B、C、D,按键A短按下,显示实时时钟界面;按键B短按下,进入设置时钟界面,首先调整年份,再次按下,调整月份,再次按下,调整日期,再次按下,调整时,再次按下,调整分,再次按下,调整秒,再次按下,调整星期,再次按下,(回去)调整年份,按键C短按下,为加,长按为连加,按键D按下,为减,长按为连减。按键B长按下,保存并写入设置的时钟并显示实时时钟界面。

参考上传到资料库中的代码《DS1302调整年月日思路有了没完全写完_用到再学》,没完全写完。

3.5 代码五

前面的代码是通过秒寄存器的最高位即 BIT 7 来判断 DS1302 是否掉电了,这里将通过 DS1302 的内部 RAM 来实现检测 DS1302 是否掉电,从而实现是否需要在初始化的时候,来进行初始时间的设置。

DS1302的内部 RAM 是一个掉电可丢失的单元,如果主电源和备用电源都掉电的话,RAM 内部的数据将丢失。因此,可以提前在 RAM 中写入一个数据,然后在下次开机时,首先来判断该数据是否丢失(是我们写入的那个数据),如果丢失的话,就说明主电源和备用电源都掉电了。

暂时够用了,不进行下去了,以后再学习下。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐