51单片机之RTC电子钟
1.项目背景
RTC,即实时时钟(Real-Time Clock),是一种能够在设备中持续运行并准确记录时间的电子组件。
RTC的工作原理通常基于一个高精度的振荡器(如晶体振荡器),该振荡器产生稳定的脉冲信号。RTC内部的计数器对这些脉冲信号进行计数,从而计算出当前的时间。同时,RTC还包含有用于存储时间信息的寄存器,以便在需要时读取或修改时间信息。
RTC能够持续记录当前的时间,包括年、月、日、时、分、秒等,甚至可能包括星期几和时区信息(尽管时区信息通常需要用户手动设置)。通常配备电池供电系统,以提供电源备份功能。这样,即使主电源关闭,RTC也能继续运行并维护时间信息。
由于RTC通常与电池供电相关联,它被设计为具有低功耗特性,以延长电池寿命。通常提供与主机设备通信的接口,如I2C或SPI。这些接口允许主机设备读取和设置RTC的时间信息,以及执行其他相关操作。通常具有抗干扰和稳定性能,以确保其在各种环境条件下都能正常运行,并保持时间的准确性。
2.应用场景
RTC广泛应用于计算机、手机、嵌入式系统、智能家居设备等多种场合,为这些设备提供精确的时间基准。具体应用场景包括:
系统启动时钟同步:在计算机启动时,RTC可以自动设置日期和时间,确保系统的时钟在启动后与实际时间保持同步。
定时和计时功能:RTC可以提供定时和计时功能,使计算机和其他设备可以执行预定的操作。例如,设置闹钟以便在特定时间触发警报或执行特定任务。
日志记录和时间戳:在数据记录和日志记录应用中,RTC可以提供精确的时间戳,帮助用户或系统了解数据或事件发生的具体时间。
数据备份和持久存储:RTC的电源备份功能使得即使在断电或关机时也能保持时间的准确性,这对于需要记录特定时间戳或时间相关数据的应用非常重要。
3.系统结构与硬件组成
4.硬件设计
4.1 DS1302实时时钟
DS1302 可慢速充电实时时钟芯片包含实时时钟/日历和 31 字节的非易失性静态 RAM。它经过一个简单的串行接口与微处理器通信。实时时钟/日历可对秒,分,时,日,周,月,和年进行计数,对于小于 31 天的月,月末的日期自动进行调整,还具有闰年校正的功能。时钟可以采用 24小时格式或带 AM(上午)/PM(下午)的 12 小时格式。31 字节的 RAM 可以用来临时保存一些重要数据。使用同步串行通信,简化了 DS1302 与微处理器的通信。与时钟/RAM 通信仅需 3 根线:RST(复位),I/O(数据线)和 SCLK(串行时钟)。数据可以以每次一个字节的单字节形式或多达31 字节的多字节形式传输。DS1302 能在非常低的功耗下工作,消耗小于 1W 的功率便能保存数据和时钟信息。
[ ] DS1302 特点
-
可对秒,分,时,日,周,月以及带闰年补偿的年进行计数;
-
用于高速数据暂存的 31 字节非易性静态 RAM 宽工作电源电压范围:2.0V~5.5V;
-
2.5V 时耗电小于 300nA;
-
用于时钟或 RAM 数据读/写的单字节或多字节数据传送方式;
-
简单的 3 线接口与单片机通信 TTL 兼容(Vcc=5V);
-
可选的工业温度范围-40℃至+85℃封装形式:DIP8 和 SOP8;
-
DS1302 封装图
-
DS1302 内部框图
4.1.1 DS1302读写时序
- 读时序
- 写时序
工作原理:如图所示,RST 信号有效后,移位寄存器单元会在 SCLK 同步脉冲信号的控制下从 I/O 上串行接收 8 位指令字节,然后将 8 位指令字节进行串并转换并送至 ROM 指令译码单元。由 ROM 指令译码单元对 8 位指令字节进行译码,以决定内部寄存器的地址以及读写状态。然后在接下来的 SCLK 同步脉冲信号的控制下将 8 位数据写进或者读出相应的寄存器。数据传送也可以采用多字节方式,先将 8 位相应的指令字节写入,然后在连续的 SCLK 的脉冲信号同步下,将数据字节连续写入或读出日历/时钟寄存器(或者 RAM 单元)。SCLK 脉冲的个数在单字节方式下为 8 加 8,在多字节方式下为 8 加最大可达到 248 的数。
4.1.2 DS1302命令字节
命令字节如下图所示,每一数据传送由命令字节初始化,最高有效位 MSB(位 7)必须为逻辑 1。如果它是零,禁止写 DS1302。位 6 为逻辑 0 指定时钟/日历数据。逻辑 1 指定 RAM 数据。位 1 至 5 指定进行输入或输出的特定寄存器。最低有效位 LSB(位 0)为逻辑 0 指定进行写操作(输入);逻辑 1 指定进行读操作(输出)。命令字节总是从最低有效 LSB 位 0 开始输入。
4.1.3 DS1302单字节读写时序
- 单字节读
先发送读取的位置地址,再返回读取的数据内容。注意读之前还是要先对寄存器写命令,从最低位开始写;可以看到,写数据是在 SCLK 的上升沿实现,而读数据在 SCLK 的下降沿实现。所以,在单字节读时序中,写命令的第八个上升沿结束后紧接着的第八个下降沿就将要读寄存器的第一位数据读到数据线上了!这个就是 DS1302操作中最特别的地方。当然读出来的数据也是最低位开始。在控制指令字输入后的下一个 SCLK 时钟的上升沿时,数据被写入 DS1302,数据输入从低位(位 0)开始。同样,在紧跟 8 位的控制指令字后的下一个 SCLK 脉冲的下降沿读出 DS1302、的数据,读出数据时从低位 0 位到高位 7。
/*
DS1302读数据
形参:addr --要读取的地址
返回值:返回读取的数据
*/
u8 DS1302_ReadByte(u8 addr)
{
u8 dat=0;
u8 i=0;
DS1302_RST=0;
DS1302_SCLK=0;
DS1302_RST=1;//使能
//写入要读取的地址
for(i=0;i<8;i++)
{
DS1302_DAT=(addr&0x01);//先写低位
addr>>=1;//继续写下一位
DS1302_SCLK=1;
DS1302_SCLK=0;
}
//从写入的地址中读取内容
for(i=0;i<8;i++)
{
dat>>=1;
if(DS1302_DAT)dat|=0x80;//读到数据1
DS1302_SCLK=1;
DS1302_SCLK=0;
}
//DS1302_RST=0;
_nop_();
DS1302_SCLK=1;
_nop_();
DS1302_DAT = 0;
_nop_();
DS1302_DAT = 1;
_nop_();
return dat;
}
4.1.4 单字节写时序
先发送读取的位置地址,再写入数据。
/*
DS1302写入数据
形参:addr --写入的寄存器地址
dat --要写入的数据
*/
void DS1302_WriteByte(u8 addr,u8 dat)
{
u8 i=0;
DS1302_RST=0;
DS1302_SCLK=0;
delay_us(1);
DS1302_RST=1;//使能
//写入命令
for(i=0;i<8;i++)
{
DS1302_DAT=(addr&0x01);//先写低位
DS1302_SCLK=1;//写完成
addr>>=1;//继续写下一位数据
DS1302_SCLK=0;
}
//写入数据
for(i=0;i<8;i++)
{
DS1302_DAT=(dat&0x01);
DS1302_SCLK=1;//写完成
dat>>=1;//继续写下一位数据
DS1302_SCLK=0;
}
DS1302_RST=1;//写完成
DS1302_SCLK=0;
}
4.1.5 相关寄存器
秒寄存器:低四位为秒的个位,高的次三位为秒的十位。最高位 CH 为 DS1302 的运行标志,
当 CH=0 时,DS1302 内部时钟运行,反之 CH=1 时停止;
小时寄存器:最高位为 12/24 小时的格式选择位,该位为 1 时表示 12 小时格式。当设置为 12 小时显示格式时,第 5 位的高电平表示下午(PM);而当设置为 24 小时格式时,第 5 位位具体的时间数据。
写保护寄存器:当该寄存器最高位 WP 为 1 时,DS1302 只读不写,所以要在往 DS1302 写数据之前确保 WP 为 0。
4.1.6 数据格式
DS1302 在日历/时钟寄存器中都是以 BCD 码存放数据,BCD 码是通过 4 位二进制码来表示 1 位十进制中的 0~10 这 10 个数码。如下所示:
4.2 数码管
数码管是一种半导体发光器件,其基本单元是发光二极管。 数码管也称 LED 数码管,不同行业人士对数码管的称呼不一样,其实都是同样的产品。数码管按段数可分为七段数码管和八段数码管,八段数码管比七段数码管多一个发光二极管单元,也就是多一个小数点(DP),这个小数点可以更精确的表示数码管想要显示的内容;按能显示多少个(8)可分为 1 位、2 位、3 位、4 位、5 位、6 位、7 位等数码管。按发光二极管单元连接方式可分为共阳极数码管和共阴极数码管。
共阳数码管是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳数码管在应用时应将公共极 COM 接到+5V,当某一字段发光二极管的阴极为低电平时,相应字段点亮,当某一字段的阴极为高电平时,相应字段就不亮。
共阴数码管是指将所有发光二极管的阴极接到一起形成公共阴极(COM)的数 码管,共阴数码管在应用时应将公共极 COM 接到地线 GND 上,当某一字段发光二极管的阳极为高电平时,相应字段就点亮,当某一字段的阳极为低电平时,相应字段就不亮。
不同位数的数码管实物图如下所示:
4.2.1 数码管工作原理
不管将几位数码管连在一起,数码管的显示原理都是一样的,都是靠点亮内部的发光二极管来发光,下面我们就来讲解一个数码管是如何亮起来的。数码管内部电路如下图所示:
从上图可看出,一位数码管的引脚是 10 个,显示一个 8 字需要 7 个小段,另外还有一个小数点,所以其内部一共有 8 个小的发光二极管,最后还有一个公共端,多数生产商为了封装统一,单位数码管都封装 10 个引脚,其中第 3 和第 8 引脚是连接在一起的。而它们的公共端又可分为共阳极和共阴极,图左一为共阴极内部原理图,中间图为共阳极内部原理图。
对共阴极数码来说,其 8 个发光二极管的阴极在数码管内部全部连接在一起, 所以称“共阴”,而它们的阳极是独立的,通常在设计电路时一般把阴极接地。当我们给数码管的任意一个阳极加一个高电平时,对应的这个发光二极管就点亮了。如果想要显示出一个 8 字,并且把右下角的小数点也点亮的话,可以给 8 个阳极全部送高电平,如果想让它显示出一个 0 字,那么我们可以除了给第“g, dp”这两位送低电平外,其余引脚全部都送高电平,这样它就显示出 0 字了。
4.3 硬件电路
从上图中可知,DS1302 芯片的控制管脚接至单片机 P3.4~P3.6 上,在芯片的 X1、X2 管脚处外接了一个 32.768KHZ 晶振,为时钟运行提供一个稳定的时钟频率,C2 和 C3 为旁路电容,目的是消除晶振起振时产生的电感干扰。对于本开发板无外接备用电池,如果需要可自行将外部备用电源接入第 8 脚 VCC1。
数码管 a~g、dp 段通过 74HC245 连接,使用 P0 端口驱动。数码管选择使用 74HC138 实现。
4.4 软件设计
4.4.1 DS302软件设计
按照秒分时日月周年顺序,读写寄存器地址如下:
//---DS1302 写入和读取时分秒的地址命令---//
//---秒分时日月周年 最低位读写位;-------//
u8 DS1302_ReadAddr[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d};
u8 DS1302_WriteAddr[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
按照寄存器地址顺序,设置时间数组如下:
//---DS1302 时钟初始化 24/12/17 9:37:45 周二---//
//---存储顺序是秒分时日月周年,存储格式是用 BCD 码---//
u8 DS1302_Time[7]={0x50,0x13,0x10,0x17,0x12,0x02,0x24};//设置基准时间24/12/17 9:37:45 周二
设置时间和读取时间函数实现如下:
/*设置时间*/
void DS1302_SetTime(void)
{
u8 i=0;
//取消写保护
DS1302_WriteByte(0x8E,0x0);
for(i=0;i<7;i++)
{
DS1302_WriteByte(DS1302_WriteAddr[i],DS1302_Time[i]);//设置时间
}
//开启写保护
DS1302_WriteByte(0x8E,0x80);
}
//读取时间
void DS1302_GetTime(void)
{
u8 i=0;
for(i=0;i<7;i++)
{
DS1302_Time[i]=DS1302_ReadByte(DS1302_ReadAddr[i]);//读取时间
}
}
4.4.2 数码管驱动
本函数实现数码管数据显示,通过填入要写入的字符串,以及要显示的位数,即可实现数据显示。
/*
数码管硬件接口:a~g、dp ---对应端口P0
数码管选择位:由74HC138进行控制
A --P2^2
B --P2^3
C --P2^4
*/
#define SMG_PORT P0 //数码管位段端口
sbit SMG_A=P2^2;
sbit SMG_B=P2^3;
sbit SMG_C=P2^4;
//数码管 0~9 . -
const u8 code led_smg[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x80,0x40};
/*
数码管字符串显示
形参:str --要显示的内容
len --要显示的长度
pos --开始显示的位置
调用方法:SMG_ShowStr(2,"1234",4);
*/
void SMG_ShowStr(u8 pos,u8 *str,u8 len)
{
u8 i=0;
if(len>8)len=8;
//pos=2
for(i=0;i<len;i++)
{
//选择要显示的位置
SMG_A=(7-pos-i)&0x1;
SMG_B=((7-pos-i)>>1)&0x01;
SMG_C=((7-pos-i)>>2)&0x01;
//显示内容
if(*str>='0' && *str<='9')
{
SMG_PORT=led_smg[*str-'0'];//要显示的内容 -->0~9
}
else if(*str=='.')
{
SMG_PORT=led_smg[10];//显示'.'
}
else if(*str=='-')
{
SMG_PORT=led_smg[11];//显示'-'
}
delay_us(75);//显示时间
SMG_PORT=0;//消隐
str++;
}
}
4.4 RTC电子钟示例
int main()
{
u8 i=0;
u8 temp[20]={0};
u8 key=0;
USART_Init();//串口初始化
USART_Sendstr("串口初始化完成\r\n");
DS1302_SetTime();//设置时间
while(1)
{
key=Key_GetValue();
if(key)
{
BEEP_Ctl(50);
}
if(usart_flag==1)
{
USART_Sendstr(usart_buf);
//解析时间
if(usart_cnt==16 && usart_buf[0]=='*')//串口校时
{
BEEP_Ctl(50);
//*20241211204745
temp[0]=(usart_buf[3]-'0')*16+(usart_buf[4]-'0');//年
gDS1302_TIME[6]=temp[0];
temp[0]=(usart_buf[5]-'0')*16+(usart_buf[6]-'0');//月
gDS1302_TIME[4]=temp[0];
temp[0]=(usart_buf[7]-'0')*16+(usart_buf[8]-'0');//日
gDS1302_TIME[3]=temp[0];
temp[0]=(usart_buf[9]-'0')*16+(usart_buf[10]-'0');//时
gDS1302_TIME[2]=temp[0];
temp[0]=(usart_buf[11]-'0')*16+(usart_buf[12]-'0');//分
gDS1302_TIME[1]=temp[0];
temp[0]=(usart_buf[13]-'0')*16+(usart_buf[14]-'0');//秒
gDS1302_TIME[0]=temp[0];
DS1302_SetTime();
}
usart_cnt=0;
usart_flag=0;
}
DS1302_ReadTime();//读取时间
for(i=0;i<3;i++)
{
temp[3*i]=gDS1302_TIME[2-i]/16+'0';
temp[3*i+1]=(gDS1302_TIME[2-i]&0xf)+'0';
if(i<2)temp[3*i+2]='-';
}
temp[3*i-1]='\0';
Digital_TubeShow(temp,8);
}
}
数码管时间显示:
串口时间校准: