DS1302
DS1302是美国DALLAS公司推出的一款实时时钟电路芯片。它具有高性能和低功耗的特点,可以通过SPI三线接口与CPU进行同步通信。DS1302能够提供秒、分、时、日、星期、月和年等时间信息,并且可以自动调整日期,在闰年时也具备补偿功能。它的工作电压范围广泛,可以在2.5V至5.5V之间使用。
DS1302可以用于制作电子时钟等应用,它内置了一个实时时钟/日历和31字节的静态RAM。通过简单的串行接口与单片机或其他控制器连接,可以读取和设置时间数据,并且可以存储一些额外的信息。
DS1302相比于定时器的计时,它属于硬件上的计时时钟,而计时器来说,还需要通过软件程序来加工后才可以计时时钟,所以,DS1302只要通过简单的程序设计,就能实现时钟功能;
引脚定义和应用电路
这是51单片机上的DS1302芯片;
左上图是两种不同的封装工艺方式,DIP封装也叫双列直插式封装技术,是一种最简单的封装方式;SOP封装是一种元件封装形式,一般封装材料较多样:塑料、陶瓷、玻璃、金属等;现在普遍使用塑料封装;主要应用于各种集成电路中。
在我们的STC89C52单片机上就是用SOP的封装方式;
它在单片机上有8个引脚VCC为电源,其中VCC1为备用电池,也就是主电源断电时,由备用电池供电,但需要注意的是,在该款单片机上并没有备用电池;且VCC2才是主电源,不能于备用电池弄反了;GND接地;X1、X2接上32.768kHz的晶振、与定时器相同的原理,都是通过晶振震动频率来计时;下图的圆柱体就是单片机上的晶振元件;接着是CE(core enable)芯片使能,芯片通过该路线得以控制该元件;IO是数据的输入输出口,用来数据传输;SCLK为串行时钟,通常用于同步数据传输的时钟信号,控制数据的传输速率和时序。
接下来看右边的图片;CPU通过CE、I\O、SCLK三根引脚控制DS1302时钟;DS1302接上电源和电池、然后接地;再接上晶振接口。
这是51单片机上的原理图。
内部结构框图
对于内部来说,除了电源控制,一般可以简化为4个模块:
- 时钟模块:主要负责提供精确的时钟信号。它由一个32.768kHz的晶振驱动,产生系统时钟信号,以保持时间计数的准确性。
- RAM模块:DS1302内部集成了31个字节的RAM,用于存储时间和日期信息、控制寄存器以及用户自定义数据等。
- 控制逻辑模块:该模块接收来自主机的指令,并根据指令对时钟和RAM进行读写操作。它还包括时钟使能控制、写保护控制、定时器控制等功能。
- 通信接口:DS1302提供了串行接口与主机进行通信,一般为3线式接口(I\O、SCLK、CE),通过这些接口与主机进行数据传输和控制。
寄存器的定义
这是控制时钟最主要的寄存器;根据上面框图,对于DS1302来说,是需要用过读和写的操作,分为两部分来完成的;简单的来说,**我们通过程序对DS1302时钟写入信息,然后信息后存储在RAM中,然后要读取时再从RAM中取出;**所以我们会看到寄存器中有READ和WRITE两个位;
第一行就是控制秒钟,位7的CH是时钟暂停标志,为1时,时钟振荡器停止,时钟停止运行,0时,时钟开始运行;
第二行控制的是分钟;
第三行控制的是小时,可以是12小时制和24小时制,高电平为12小时模式,且位5为1时,表示PM;低电平为24小时;
依次下去控制的依次是日、月、星期、年;
最后第二行为写入保护(WP:write protect),将对应的读写地址置0x00时就打开了写入保护;
最后一行是与备用电池相关的,由于单片机上没有,就不做具体介绍;
底下的图是地址或命令字节:
通过相对应的地址,找到对应的年月日,时分秒;
当低位处于0时为写入,1时为读出;
时序定义
这是CPU与DS1302时钟连接的三条引脚接口,对于我们来说,程序就是通过这三条接口来实现与DS1302时钟的链接;在这里,寄存器均为不可位寻址;
先看写入的部分:
I\O口为数据传输,这里有两个字节,第一个为地址的字节,第二个是数据对应的字节;当我们要数据传输时,就得把CE置1,为高点平;我们传输时,要考虑到SCLK串行时钟,意味着我们得一位一位的进行传输,又考虑到是不可位寻址,所以我们利用按位与和二进位左移右移的方式进行数据传输,传输1位时,SCLK先置于低电平,然后再高电平,利用上升沿的方式,将对应的位进行存储;如此循环8次就完成对地址的传输;然后再重复8次对数据的传输,就完成写入的操作;最后再把CE置0;
再看写入部分:
如果我们细数SCLK对应的凹凸位,会发现只有15个,所以在这里,我们要对一个凹凸位进行重复利用1次,就可以解决;其他操作与写入无异;
BCD码
BCD码(Binary Coded Decimal),用4位二进制数来表示1位十进制数
举个例子,13,它是这个表示的:0001 0011;
就是利用4个bit位来表示对应的十进制的数字,但这种表示方法在显示屏上也会有问题,如:0001 1010用16进制表示的话就是0x1A,而十进制就不合法了;
所以我们要通过计算,我们输入数据存储时,要将10进制的数字转换为BCD码进行存储,要进行读取时,要将BCD码转换为10进制数;
BCD码转十进制:DEC=BCD/1610+BCD%16; (2位BCD)
十进制转BCD码:BCD=DEC/1016+DEC%10; (2位BCD)
我们也可以观察到在寄存器里面就是利用对应的寄存器中的位数来存储表示的;并且4位最后就进10位,这就是BCD码。
DS1302时钟代码
LED1602.c
#include <REGX52.H> //引脚配置: sbit LCD_RS=P2^6; sbit LCD_RW=P2^5; sbit LCD_EN=P2^7; #define LCD_DataPort P0 //函数定义: /** * @brief LCD1602延时函数,12MHz调用可延时1ms * @param 无 * @retval 无 */ void LCD_Delay() { unsigned char i, j; i = 2; j = 239; do { while (--j); } while (--i); } /** * @brief LCD1602写命令 * @param Command 要写入的命令 * @retval 无 */ void LCD_WriteCommand(unsigned char Command) { LCD_RS=0; LCD_RW=0; LCD_DataPort=Command; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602写数据 * @param Data 要写入的数据 * @retval 无 */ void LCD_WriteData(unsigned char Data) { LCD_RS=1; LCD_RW=0; LCD_DataPort=Data; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602设置光标位置 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @retval 无 */ void LCD_SetCursor(unsigned char Line,unsigned char Column) { if(Line==1) { LCD_WriteCommand(0x80|(Column-1)); } else if(Line==2) { LCD_WriteCommand(0x80|(Column-1+0x40)); } } /** * @brief LCD1602初始化函数 * @param 无 * @retval 无 */ void LCD_Init() { LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵 LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关 LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动 LCD_WriteCommand(0x01);//光标复位,清屏 } /** * @brief 在LCD1602指定位置上显示一个字符 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @param Char 要显示的字符 * @retval 无 */ void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char) { LCD_SetCursor(Line,Column); LCD_WriteData(Char); } /** * @brief 在LCD1602指定位置开始显示所给字符串 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串 * @retval 无 */ void LCD_ShowString(unsigned char Line,unsigned char Column,char *String) { unsigned char i; LCD_SetCursor(Line,Column); for(i=0;String[i]!='\0';i++) { LCD_WriteData(String[i]); } } /** * @brief 返回值=X的Y次方 */ int LCD_Pow(int X,int Y) { unsigned char i; int Result=1; for(i=0;i<Y;i++) { Result*=X; } return Result; } /** * @brief 在LCD1602指定位置开始显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~65535 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以有符号十进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:-32768~32767 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length) { unsigned char i; unsigned int Number1; LCD_SetCursor(Line,Column); if(Number>=0) { LCD_WriteData('+'); Number1=Number; } else { LCD_WriteData('-'); Number1=-Number; } for(i=Length;i>0;i--) { LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以十六进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~0xFFFF * @param Length 要显示数字的长度,范围:1~4 * @retval 无 */ void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i,SingleNumber; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { SingleNumber=Number/LCD_Pow(16,i-1)%16; if(SingleNumber<10) { LCD_WriteData(SingleNumber+'0'); } else { LCD_WriteData(SingleNumber-10+'A'); } } } /** * @brief 在LCD1602指定位置开始以二进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~1111 1111 1111 1111 * @param Length 要显示数字的长度,范围:1~16 * @retval 无 */ void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0'); } }
LCD1602.h
#ifndef __LCD1602_H__ #define __LCD1602_H__ //用户调用函数: void LCD_Init(); void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char); void LCD_ShowString(unsigned char Line,unsigned char Column,char *String); void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length); void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); #endif
DS1302.c
```c #include <REGX52.H> //通过名称的转换,易于引用 //进行引脚定义 sbit DS1302_SCLK=P3^6; sbit DS1302_IO=P3^4; sbit DS1302_CE=P3^5; //寄存器写入地址 #define DS1302_SECOND 0x80 #define DS1302_MINUTE 0x82 #define DS1302_HOUR 0x84 #define DS1302_DATE 0x86 #define DS1302_MONTH 0x88 #define DS1302_DAY 0x8A #define DS1302_YEAR 0x8C #define DS1302_WP 0x8E //写入保护 //时间数组,分别表示年月日,时分秒,星期 unsigned int DS1302_Time[]={23,7,15,17,47,20,6}; //对DS1302进行初始化 //由于DS1302接电时,会将CE和SCLK置于高电平;¯ void DS1302_Init() { DS1302_CE=0; DS1302_SCLK=0; } //DS1302写入一个字节 void DS1302_WriteByte(unsigned char Command,Data) { unsigned int i; //将CE置1 DS1302_CE=1; //先写入地址 for(i=0;i<8;i++) { DS1302_IO=Command&(0x01<<i); //将地址一位一位与上0x01通过循环左移1, DS1302_SCLK=1;//上升沿开; DS1302_SCLK=0; //在每一次循环中,上升沿会将对应的位进行上升,使之不会影响到下一次按位与; } //再写入数据 for(i=0;i<8;i++) { DS1302_IO=Data&(0x01<<i); DS1302_SCLK=1; DS1302_SCLK=0; } DS1302_CE=0; } unsigned char DS1302_ReadByte(unsigned char Command) { unsigned char i,Data=0x00; Command|=0x01; //将指令或上0x01将最低位置1,转换为读指令; DS1302_CE=1; for(i=0;i<8;i++) { DS1302_IO=Command&(0x01<<i); DS1302_SCLK=0; DS1302_SCLK=1; //在这里,先将SCLK先置0,后置1,便于对SCLK一个凹凸位多次利用 } for(i=0;i<8;i++) { //利用与上一个循环SCLK最后为1,这次循环开始也将SCLK置1,就能达到重复利用一个凹凸位; DS1302_SCLK=1; DS1302_SCLK=0; //判断数据是否为空 if(DS1302_IO) { Data|=(0x01<<i); } } DS1302_CE=0; DS1302_IO=0; //读取后需要将IO置为0,否则将会出现错误; return Data; } //DS1302设置时间,十进制转换为BCD码 void DS1302_SetTime() { DS1302_WriteByte(DS1302_WP,0x00); DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10); DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10); DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10); DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10); DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10); DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10); DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10); DS1302_WriteByte(DS1302_WP,0x80); } //DS1302读取数据,将BCD码转为十进制 void DS1302_ReadTime() { unsigned int Temp; Temp=DS1302_ReadByte(DS1302_YEAR); DS1302_Time[0]=Temp/16*10+Temp%16; Temp=DS1302_ReadByte(DS1302_MONTH); DS1302_Time[1]=Temp/16*10+Temp%16; Temp=DS1302_ReadByte(DS1302_DATE); DS1302_Time[2]=Temp/16*10+Temp%16; Temp=DS1302_ReadByte(DS1302_HOUR); DS1302_Time[3]=Temp/16*10+Temp%16; Temp=DS1302_ReadByte(DS1302_MINUTE); DS1302_Time[4]=Temp/16*10+Temp%16; Temp=DS1302_ReadByte(DS1302_SECOND); DS1302_Time[5]=Temp/16*10+Temp%16; Temp=DS1302_ReadByte(DS1302_DAY); DS1302_Time[6]=Temp/16*10+Temp%16; }
DS1302.h
```c #ifndef __DS1302_H__ #define __DS1302_H__ extern unsigned int DS1302_Time[]; void DS1302_Init();¯ void DS1302_WriteByte(unsigned char Command,Data); unsigned char DS1302_ReadByte(unsigned char Command); void DS1302_SetTime(); void DS1302_ReadTime(); #endif
main.c
#include <REGX52.H> #include"Ds1302.h" #include"LCD1602.h" void main() { LCD_Init(); DS1302_Init(); LCD_ShowString(1,1," _ _ "); LCD_ShowString(2,1," : : "); DS1302_SetTime(); while(1) { DS1302_ReadTime(); LCD_ShowNum(1,1,DS1302_Time[0],2); LCD_ShowNum(1,4,DS1302_Time[1],2); LCD_ShowNum(1,7,DS1302_Time[2],2); LCD_ShowNum(2,1,DS1302_Time[3],2); LCD_ShowNum(2,4,DS1302_Time[4],2); LCD_ShowNum(2,7,DS1302_Time[5],2); } }