蓝桥杯电子类单片机学习四:DS1302实时时钟教程

目录

前言

单片机资源数据包_2023(点击下载)

一、DS1302的驱动与功能

1.DS1302的驱动

2.DS1302的功能

3.DS1302的电路连接

二、关于DS1302

1.DS1302的寄存器

2.补充:BCD码

3.代码实现

三、测试代码



前言

本系列文章意在帮助各位正在准备蓝桥杯单片机组的同学,提供一个参考与指南,但是所有指南的前提是,默认你已经有单片机基础,本系列文章会提供本人对蓝桥杯单片机组编程方面的一些源码实现。当然,或许与你写代码的style完全不想同,那咱们也可以彼此相互交流各自的优缺点,或许你可能只是一个小白,就是想来抄一些可供借鉴的代码,这些都不重要,重要的是能给正在准备单片机组的同学提供到一些帮助。

此外代码可能有写的不完善的地方,但是每一个代码都是经过测试可行之后才发出来的,不敢保证十全十美,但是跑起来应该没问题。

此外,比赛时会提供一个单片机资源数据包,里面的内容比较多,这里只放一个下载链接,是2023年省赛是提供的单片机资源数据包(今年才2024,已经是最新的资源数据包了),正文中会直接使用资源数据包内的资料:

单片机资源数据包_2023(点击下载)

关于此资源数据包还得补充两句,他的3-底层驱动代码参考与往些年不同(但其实差异也很小,这里不再赘述),而且还是快比赛时才公布的这个资源数据包。也就是说,在写底层驱动代码时,我都是以2023年的资源数据包为基础写的,如果你使用的不是2023年的资源数据包的话,可能会跟我使用的底层驱动不完全相同,不过影响也不大。

下图为单片机资源数据包_2023内的所有资源,其中最关键的就是3-底层代码驱动以及SCH_硬件原理图V30了,当然共阳数码管段码表也十分常用,不过一般也就新写一个项目时,会把里面的东西CV到main函数里,然后这个文件就用不着了。

​​

一、DS1302的驱动与功能

1.DS1302的驱动

单片机资源数据包_2023中给出来了ds1302驱动的底层代码:

/*	# 	DS1302代码片段说明
	1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
	2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
		中对单片机时钟频率的要求,进行代码调试和修改。
*/								

//
void Write_Ds1302(unsigned  char temp) 
{
	unsigned char i;
	for (i=0;i<8;i++)     	
	{ 
		SCK = 0;
		SDA = temp&0x01;
		temp>>=1; 
		SCK=1;
	}
}   

//
void Write_Ds1302_Byte( unsigned char address,unsigned char dat )     
{
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
 	RST=1; 	_nop_();  
 	Write_Ds1302(address);	
 	Write_Ds1302(dat);		
 	RST=0; 
}

//
unsigned char Read_Ds1302_Byte ( unsigned char address )
{
 	unsigned char i,temp=0x00;
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
 	RST=1;	_nop_();
 	Write_Ds1302(address);
 	for (i=0;i<8;i++) 	
 	{		
		SCK=0;
		temp>>=1;	
 		if(SDA)
 		temp|=0x80;	
 		SCK=1;
	} 
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
	SCK=1;	_nop_();
	SDA=0;	_nop_();
	SDA=1;	_nop_();
	return (temp);			
}

与其他驱动代码相同,我们直接把这个文件加入到项目中并点击编译,编译器会报错,为了使编译器不报错,我们需要先对驱动代码进行完善:添加相应的头文件以及引脚定义,新建并添加ds1302.h的头文件,方便我们引用。

DS1302的原理图如下:

这里需要简单提一点,我们在ds1302.c文件中定义三个引脚,分别是:SCK,SDA和RST,但是从DS1302的连接图上只有SCL,I/O和RST,不难理解,其实I/O引脚就是我们要找的SDA,所以我们需要在ds1302.c文件前添加如下代码:

#include <stc15.h>
#include <intrins.h>
#include "ds1302.h"

sbit SDA=P2^3;
sbit RST=P1^3;
sbit SCK=P1^7;

 并且新建ds1302.h文件,ds1302.h的内容为空,代码如下:

#ifndef _DS1302_H_
#define _DS1302_H_

#endif

 当然,做到这里我们只完成了第一步:点击编译时让编译器不报错,但是此时还是会有一些警告,这些警告是说已经定义的函数未被调用,暂时可以不用去管,因为我们还没写完呢。

2.DS1302的功能

DS1302是一款实时时钟(RTC)芯片,由美国达拉斯半导体(Dallas Semiconductor)公司生产。它被广泛应用于各种电子设备中,如数字时钟、计时器、温度计等。

DS1302采用串行接口与控制器通信,可以通过三线串行总线(Data、Clock、RST)与微控制器相连。它内部包括了一个时钟电路、一个时钟频率发生器、一个时钟计数器、静态RAM存储器和控制逻辑电路。

DS1302的时钟电路可以提供给外部系统一个稳定的时钟信号,其精度高,误差非常小。时钟频率发生器可以产生不同的时钟频率,以适应不同的应用需求。时钟计数器可以记录当前的时间和日期,包括年、月、日、时、分、秒等信息。

DS1302还具有一个静态RAM存储器,用于存储温度补偿系数、控制寄存器、时钟计数器的时间和日期等数据。通过串行通信接口,可以读写这些数据。

DS1302具有低功耗特性,工作电流低,非常适合用于电池供电的系统。此外,它还具有防止数据丢失的特性,可以在断电情况下保持时间和日期的准确性。

总之,DS1302是一款功能强大、稳定可靠的实时时钟芯片,广泛应用于各种电子设备中,提供准确的时间和日期信息。

3.DS1302的电路连接

这一部分内容蓝桥杯考试不会考,但是这里还是简单介绍一下:

DS1302是由三根线与单片机连接并传输数据的,虽然和SPI总线类似,也是由三根线传输数据的,但是DS1302的三根线并不是我们所说的SPI总线传输,它只能叫做三总线传输。

DS1302的电路连接示意图:

很明显的可以看到DS1302有两个电源正极,其中一个电源正极连接Vcc,另一个则是连接了一个电池。

其实,正常的DS1302在低功耗模式完全可以由纽扣电池来供电,我们的手表等也往往是采用纽扣电池来供电的。一般学习用的板子上的DS1302为了方便处理,会提供外部供电(由板子上的电路供电)以及一个电子,使得我们再给板子上电之后(不管操不操作DS1302),DS1302会使用外部电源供电,在给板子断电之后,DS1302又会使用纽扣电池供电,从而形成类似于“掉电不丢失”的现象,当然,其实在这个过程中DS1302并没有断电。

从蓝桥杯的原理图可以看出,DS1302右上角的VCC1连接的是N BATTERY网络(字面意思就是电池供电的网络),但是实际上并没有这个网路,也就是说DS1302并没有纽扣电池给它供电,这点从板子上也能看出来,根本没有装纽扣电池,所以只要板子一断电,DS1302的数据就停了(相当于一个钟表不转了,停在了某一个时刻,但是数据不会消失)。

从刚才的原理图中我们还可以看到,DS1302连接了一个32.768的晶振,在板子上大概是一个细圆柱形,银白色的东西。

DS1302使用32.768KHz的晶振是因为这个频率是非常稳定的,并且具有重要的特性。

首先,32.768KHz频率是实时时钟(RTC)系统中常用的标准频率。它是2的15次方,可以方便地与RTC芯片的计数器进行配合。通过精确的频率划分,能够实现秒、分、时、日、月和年的计数,从而精确地获取时间和日期信息。

二、关于DS1302

1.DS1302的寄存器

DS1302的时间数据都存储在寄存器当中,我们在更新时间数据或者从DS1302中读取时间时,其实都是在对寄存器操作,写入或者读取寄存器内的信息。对 DS1302的操作就是对其内部寄存器的操作, DS1302内部共有12个寄存器,其中有:7 个寄存器与日历、时钟相关,存放的数据位为 BCD 码形式。此外, DS1302 还有年份寄存器、控制寄存器、充电寄存器、时钟突发寄存器及与 RAM 相关的寄存器等。时钟突发寄存器可一次性顺序读写除充电寄存器以外的寄存器。

下面列举几个常用寄存器及其地址:

时钟日历包含在 7 个读/写寄存器内,读/写寄存器中的数据是二——十进制的 BCD 码。

这个表格的第一列写的是读取某一行的寄存器时,需要发送的地址的值。

第二列写的是写入某一行的寄存器时,需要发送的地址的值。

往某个地址写入值的过程就是在控制寄存器的过程,这个“往某个地址写入值”与EEPROM存数据时类似,后续会有代码演示,这是不在赘述。

与之前讲的类似,每个寄存器的八位地址中,最后以为表示读/写为,为1时表示读操作。为0时表示写操作,比如读取秒寄存器发送的是0x81,也就是1000 0001,而写时则发生0x80,也就是1000 0000.

对于秒寄存器(第一行),BIT7位表示暂停,BIT7为1时时钟暂停,进入低功耗模式,为0时正常工作。BIT6到BIT4为秒的10位数,BIT3到BIT0表示秒的个位数字。比如我们想给秒存入59,也就是当前时间是59秒,则BIT6到BIT4为5,转化为二进制就是0101,BIT3到BIT0的值为9,转化为二进制就是1001,则数据位就是0101 1001.

第二到七行分别表示分钟,小时,日,月,星期,年。表示方法与秒类似。

关于第三行小时,常用的计时方法有24小时计时还有12小时计时。当BT7为1时表示12小时计时,为0时表示24小时计时。相应的,如果是12小时计时的话,小时的十位只有0或者1,需要一位二进制数就可以表示,如果24小时计时的话十位数是0 1 或者2,需要两位二进制来表示。如果是12小时计时的话,BIT6表示小时的十位,BIT5就表示AM/PM,BIT5=0:AM,BIT5=1PM;如是24小时计时的话,BIT6和BIT5共同表示小时的十位。

第八行是写保护寄存器,BIT也就是WP为0是写保护位,对时钟/日历寄存器或 RAM 进行写操作之前, WP 必须为 0, 当 WP 为高电平的时候,不能对任何时钟/日历寄存器或 RAM 进行写操作。
 

除此之外,我们还发现,这些寄存器上下两个之间刚好相差2,这也方便了我们操作。

2.补充:BCD码

之前提到,存入DS1302的数据是以BCD码的方式存储的,这里介绍一下BCD码与十进制的转化:

1. 十进制转BCD:将十进制数的每一位数值转换为对应的BCD码。

例如,将十进制数567转换为BCD码:
– 个位数7转换为BCD码0111
– 十位数6转换为BCD码0110
– 百位数5转换为BCD码0101

则567的BCD码为0101 0110 0111。

2. BCD转十进制:将BCD码的每一位数值组合成对应的十进制数值。

例如,将BCD码0101 0110 0111转换为十进制数:
– 个位数的BCD码为0111,数值为7
– 十位数的BCD码为0110,数值为6
– 百位数的BCD码为0101,数值为5

则BCD码0101 0110 0111对应的十进制数为567。

需要注意的是,BCD码是以4位二进制数表示一个十进制数位,因此在转换过程中需要保证每一位的BCD码不超过4位。如果在转换过程中出现超过4位的BCD码,需要进行修正。

3.代码实现

我们在使用DS1302时只需要进行两个操作,一个是写入,一个是读取,而写入时间的操作我们往往只需要上电进行一次,所以我们也可以把写入操作称之为对DS1302的初始化。

我们把时间数据存到一个数组里,方便我们读取和写入。

具体代码如下:

unsigned char Time[6]={55,59,23,1,1,1};//定义一个数组,储存秒 分 时 日 月 星期数据

//初始化DS1302,也就是写入DS1302
void ds_init(void)
{
    unsigned char add;//定义一个变量用于暂时存储地址
    unsigned char i=0;
    add=0x80;//0x80是秒的地址,下边分 时等的地址以此加2即可。
    Write_Ds1302_Byte(0x8E,0x00);//关闭写保护,使得后续可以正常写入数据
    for(i=0;i<6;i++)//依次写入秒分时日月星期,如果需要写年的话,可以把Time数组加长,并把这里的6改为7
    {
        Write_Ds1302_Byte(add,(Time[i]/10<<4)|(Time[i]%10));//写入数据,写之前需要将Time转化为BCD码
        add+=2;//地址加2,对下一个寄存器进行操作
    }
    Write_Ds1302_Byte(0x8E,0x80);//开写保护
}
//读DS1302,读取到的数据直接存到Time数组中
void read_ds1302(void)
{
    unsigned char add;//定义一个变量用于暂时存储地址
    unsigned char i=0;
    unsigned char dat=0;
    add=0x81;//0x81是秒的地址,下边分 时等的地址以此加2即可。
    for(i=0;i<6;i++)//依次读取秒分时日月星期
    {
        dat=Read_Ds1302_Byte(add);//读取数据到dat内
        Time[i]=dat/16*10+dat%16;//将dat转化为十进制存入Time数组
        add+=2;//地址加2,对下一个寄存器进行操作
    }
}

三、测试代码

这里写一个小代码,对刚才的功能进行测试与总结,主要完成以下功能:

1.上电关闭所以LED灯

2.初始化DS1302的数据,为23时,59分55秒。

3.读取DS1302的数据,并将其显示到数码管上,显示格式如下:

数码管第一二位显示小时

数码管第三位显示“-”

数码管第四五位显示分钟

数码管第六位显示“-”

数码管第七八位显示秒

main.c

#include <stc15.h>
#include <intrins.h>
#include "ds1302.h"
code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0xBF
};//共阳极断码表,不用记,考试时会提供,直接CV过来即可
 
unsigned char LED_Num=0xFF;//用来记录当前LED的状态
unsigned char ULN=0x00;//用来记录当前ULN的值,ULN主要控制蜂鸣器和继电器,也会用来控制电机
 
//LED有关的三个宏函数的思路都是,先更新LED_Num的值,然后将P0=LED_Num,再然后开关一次LED灯的锁
//存器,以更新数据
//点亮第x的LED灯,将LED_Num中第x位置为0(因为是共阳极LED灯)关闭同理,需将第x位置为1
#define LED_ON(x)		LED_Num&=~(0x01<<x);	P0=LED_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
#define LED_OFF(x)	    LED_Num|=0x01<<x;		P0=LED_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
#define LED_OFF_ALL()	LED_Num=0xFF;			P0=0xFF;P2|=0x80;P2&=0x9F;P2&=0x1F;
 
//关于数码管的两个函数,因为需要从数组内读取待显示的数据,还要进行位选,
//所以这两个函数只起到开关锁存器的功能,没有更新数据(会在其他地方实现数据更新和获取)
#define NIXIE_CHECK()	P2|=0xC0;P2&=0xDF;P2&=0x1F;
#define NIXIE_ON()		P2|=0xE0;P2&=0xFF;P2&=0x1F;
 
void Timer0_Init(void);		//1毫秒@11.0592MHz
void Delay100ms(void);	//@11.0592MHz
unsigned char location=0;//用于记录当前到哪一位数码管要显示数据了。是一个中间变量
unsigned char Nixie_num[]={1,2,3,4,5,6,7,8};//用来存储数码管待显示的数据
extern unsigned char Time[6];//定义在ds1302.c
void main()
{
	LED_OFF_ALL();//熄灭所以LED灯
	ds_init();//初始化DS1302
	Timer0_Init();//定时器初始化
	EA=1;//开总中断
	Delay100ms();
	while(1)
	{
		read_ds1302();//读DS1302
		Nixie_num[0]=Time[2]/10%10;//显示小时的十位
		Nixie_num[1]=Time[2]/1%10;//显示小时的个位
		Nixie_num[2]=10;
		Nixie_num[3]=Time[1]/10%10;//显示分钟的十位
		Nixie_num[4]=Time[1]/1%10;//显示分钟的个位
		Nixie_num[5]=10;
		Nixie_num[6]=Time[0]/10%10;//显示秒的十位
		Nixie_num[7]=Time[0]/1%10;//显示秒的个位
		Delay100ms();
	}
	
}
void Timer0_Isr(void) interrupt 1
{
	P0=0x01<<location;NIXIE_CHECK();//选择要显示的某一位
	P0=Seg_Table[Nixie_num[location]];NIXIE_ON();//从数组中读取数据并显示
	
	if(++location==8)//位数+1,从而实现扫描
		location=0;//位数=8,重新清零(location取值是0到7八个数)
}
 
void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	AUXR |= 0x80;			//定时器时钟1T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0xCD;				//设置定时初始值
	TH0 = 0xD4;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	ET0 = 1;				//使能定时器0中断
}
void Delay100ms(void)	//@11.0592MHz
{
	unsigned char data i, j, k;
 
	_nop_();
	_nop_();
	i = 5;
	j = 52;
	k = 195;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

ds1302.c

/*	# 	DS1302代码片段说明
	1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
	2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
		中对单片机时钟频率的要求,进行代码调试和修改。
*/								
#include <stc15.h>
#include <intrins.h>
#include "ds1302.h"

sbit SDA=P2^3;
sbit RST=P1^3;
sbit SCK=P1^7;

unsigned char Time[6]={55,59,23,1,1,1};//定义一个数组,储存秒 分 时 日 月 星期数据
//
void Write_Ds1302(unsigned  char temp) 
{
	unsigned char i;
	for (i=0;i<8;i++)     	
	{ 
		SCK = 0;
		SDA = temp&0x01;
		temp>>=1; 
		SCK=1;
	}
}   

//
void Write_Ds1302_Byte( unsigned char address,unsigned char dat )     
{
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
 	RST=1; 	_nop_();  
 	Write_Ds1302(address);	
 	Write_Ds1302(dat);		
 	RST=0; 
}

//
unsigned char Read_Ds1302_Byte ( unsigned char address )
{
 	unsigned char i,temp=0x00;
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
 	RST=1;	_nop_();
 	Write_Ds1302(address);
 	for (i=0;i<8;i++) 	
 	{		
		SCK=0;
		temp>>=1;	
 		if(SDA)
 		temp|=0x80;	
 		SCK=1;
	} 
 	RST=0;	_nop_();
 	SCK=0;	_nop_();
	SCK=1;	_nop_();
	SDA=0;	_nop_();
	SDA=1;	_nop_();
	return (temp);			
}

//初始化DS1302,也就是写入DS1302
void ds_init(void)
{
	unsigned char add;//定义一个变量用于暂时存储地址
	unsigned char i=0;
	add=0x80;//0x80是秒的地址,下边分 时等的地址以此加2即可。
	Write_Ds1302_Byte(0x8E,0x00);//关闭写保护,使得后续可以正常写入数据
	for(i=0;i<6;i++)//依次写入秒分时日月星期,如果需要写年的话,可以把Time数组加长,并把这里的6改为7
	{
		Write_Ds1302_Byte(add,(Time[i]/10<<4)|(Time[i]%10));//写入数据,写之前需要将Time转化为BCD码
		add+=2;//地址加2,对下一个寄存器进行操作
	}
	Write_Ds1302_Byte(0x8E,0x80);//开写保护
}
//读DS1302,读取到的数据直接存到Time数组中
void read_ds1302(void)
{
	unsigned char add;//定义一个变量用于暂时存储地址
	unsigned char i=0;
	unsigned char dat=0;
	add=0x81;//0x81是秒的地址,下边分 时等的地址以此加2即可。
	for(i=0;i<6;i++)//依次读取秒分时日月星期
	{
		dat=Read_Ds1302_Byte(add);//读取数据到dat内
		Time[i]=dat/16*10+dat%16;//将dat转化为十进制存入Time数组
		add+=2;//地址加2,对下一个寄存器进行操作
	}
}

ds1302.h

#ifndef _DS1302_H_
#define _DS1302_H_

void ds_init(void);
void read_ds1302(void);

#endif

其实,学到这里几乎已经可以开始上手省赛题目了,剩下的NE555,超声波和串口,省赛考的比较少,但是最近几年省赛题目逐渐变难,这些模块也开始出现在省赛题目了。

后续会带大家逐步深入的分享蓝桥杯比赛的代码

物联沃分享整理
物联沃-IOTWORD物联网 » 蓝桥杯电子类单片机学习四:DS1302实时时钟教程

发表评论