单片机内置RTC或专用RTC芯片
简介
DS1302是美国DALLAS公司推出的一种高性能、低功耗的实时时钟芯片,附加31字节静态RAM,采用SPI三线接口与CPU进行同步通信,并可采用突发方式一次传送多个字节的时钟信号和RAM数据。实时时钟可提供秒、分、时、日、星期、月和年,一个月小与31天时可以自动调整,且具有闰年补偿功能。工作电压宽达2.5~5.5V。采用双电源供电(主电源和备用电源),可设置备用电源充电方式,提供了对后背电源进行涓细电流充电的能力。DS1302的外部引脚分配如图1所示及内部结构如图2所示。DS1302用于数据记录,特别是对某些具有特殊意义的数据点的记录上,能实现数据与出现该数据的时间同时记录,因此广泛应用于测量系统中。 图1 DS1302的外部引脚分配 图2 DS1302的内部结构 各引脚的功能为: Vcc1:主电源;Vcc2:备份电源。当Vcc2>Vcc1+0.2V时,由Vcc2向DS1302供电,当Vcc2< Vcc1时,由Vcc1向DS1302供电。 SCLK:串行时钟,输入; I/O:三线接口时的双向数据线; CE:输入信号,在读、写数据期间,必须为高。该引脚有两个功能:第一,CE开始控制字访问移位寄存器的控制逻辑;其次,CE提供结束单字节或多字节数据传输的方法。 DS1302有下列几组寄存器: ① DS1302有关日历、时间的寄存器共有12个,其中有7个寄存器(读时81h~8Dh,写时80h~8Ch),存放的数据格式为BCD码形式,如图3所示。 图 3 DS1302有关日历、时间的寄存器 小时寄存器(85h、84h)的位7用于定义DS1302是运行于12小时模式还是24小时模式。当为高时,选择12小时模式。在12小时模式时,位5是 ,当为1时,表示PM。在24小时模式时,位5是第二个10小时位。 秒寄存器(81h、80h)的位7定义为时钟暂停标志(CH)。当该位置为1时,时钟振荡器停止,DS1302处于低功耗状态;当该位置为0时,时钟开始运行。 控制寄存器(8Fh、8Eh)的位7是写保护位(WP),其它7位均置为0。在任何的对时钟和RAM的写操作之前,WP位必须为0。当WP位为1时,写保护位防止对任一寄存器的写操作。 ② DS1302有关RAM的地址 DS1302中附加31字节静态RAM的地址如图4所示。 图4 ③ DS1302的工作模式寄存器 所谓突发模式是指一次传送多个字节的时钟信号和RAM数据。突发模式寄存器如图5所示。 图5 ④此外,DS1302还有充电寄存器等。 2 读写时序说明 DS1302是SPI总线驱动方式。它不仅要向寄存器写入控制字,还需要读取相应寄存器的数据。 要想与DS1302通信,首先要先了解DS1302的控制字。DS1302的控制字如图6。 图6 控制字(即地址及命令字节) 控制字的最高有效位(位7)必须是逻辑1,如果它为0,则不能把数据写入到DS1302中。 位6:如果为0,则表示存取日历时钟数据,为1表示存取RAM数据; 位5至位1(A4~A0):指示操作单元的地址; 位0(最低有效位):如为0,表示要进行写操作,为1表示进行读操作。 控制字总是从最低位开始输出。在控制字指令输入后的下一个SCLK时钟的上升沿时,数据被写入DS1302,数据输入从最低位(0位)开始。同样,在紧跟8位的控制字指令后的下一个SCLK脉冲的下降沿,读出DS1302的数据,读出的数据也是从最低位到最高位。数据读写时序如图7 。 图7 数据读写时序 具体操作见驱动程序。
电路
电路原理图如图8,DS1302与单片机的连接也仅需要3条线:CE引脚、SCLK串行时钟引脚、I/O串行数据引脚,Vcc2为备用电源,外接32.768kHz晶振,为芯片提供计时脉冲。 在这里我要特别说明的是DS1302的备用电源的选择,当然可以用电池,我以前的板子上就是用了一颗纽扣电池CR2032。但是,如果断电时间较短(几小时或几天)时,就可以用漏电较小的普通电解电容器代替。一般地,100 μF就可以保证1小时的正常走时。我现在是用了电脑主板的电解电容,是3000uF的。强大至极,一个星期都能保持住时间。 备用电源B1,可以用电池或者超级电容(10万uF以上)。虽然DS1302在主电源掉电后,耗电很小,但如果要长时间保证时钟正常,最好选用小型充电电池。可以用老式电脑主板上的那种3.6V充电电池。如果断电时间较短(几小时或几天)时,就可以用漏电较小的普通电解电容代替。100uF就可以保证1小时的正常走时。DS1302在第一次加电后,须进行初始化操作。初始化后就可以按正常方法调整时间及闹铃 用电容的话换掉电池即可 32.768K晶振很多没见加电容,加上22p的电容会影响精度?
驱动程序
//寄存器宏定义 #define WRITE_SECOND 0x80 #define WRITE_MINUTE 0x82 #define WRITE_HOUR 0x84 #define READ_SECOND 0x81 #define READ_MINUTE 0x83 #define READ_HOUR 0x85 #define WRITE_PROTECT 0x8E //位寻址寄存器定义 sbit ACC_7 = ACC^7; //管脚定义 sbit SCLK = P3^5; // DS1302时钟信号 7脚 sbit DIO= P3^6; // DS1302数据信号 6脚 sbit CE = P3^7; // DS1302片选 5脚 //地址、数据发送子程序 void Write1302 ( unsigned char addr,dat ) { unsigned char i,temp; CE=0; //CE引脚为低,数据传送中止 SCLK=0; //清零时钟总线 CE = 1; //CE引脚为高,逻辑控制有效 //发送地址 for ( i=8; i>0; i– ) //循环8次移位 { SCLK = 0; temp = addr; DIO = (bit)(temp&0x01); //每次传输低字节 addr >>= 1; //右移一位 SCLK = 1; } //发送数据 for ( i=8; i>0; i– ) { SCLK = 0; temp = dat; DIO = (bit)(temp&0x01); dat >>= 1; SCLK = 1; } CE = 0; } //数据读取子程序 unsigned char Read1302 ( unsigned char addr ) { unsigned char i,temp,dat1,dat2; CE=0; SCLK=0; CE = 1; //发送地址 for ( i=8; i>0; i– ) //循环8次移位 { SCLK = 0; temp = addr; DIO = (bit)(temp&0x01); //每次传输低字节 addr >>= 1; //右移一位 SCLK = 1; } //读取数据 for ( i=8; i>0; i– ) { ACC_7=DIO; SCLK = 0; ACC>>=1; SCLK = 1; } CE=0; dat1=ACC; dat2=dat1/16; //数据进制转换 dat1=dat1%16; //十六进制转十进制 dat1=dat1+dat2*10; return (dat1); } //初始化DS1302 void Initial(void) { Write1302 (WRITE_PROTECT,0X00); //禁止写保护 Write1302 (WRITE_SECOND,0x56); //秒位初始化 Write1302 (WRITE_MINUTE,0x34); //分钟初始化 Write1302 (WRITE_HOUR,0x12); //小时初始化 Write1302 (WRITE_PROTECT,0x80); //允许写保护 } ==== 对于熟悉了集成开发环境后,下面我们来做我们的第一个电子作品——电子时钟。用一片pic16f877a作处理器,时钟芯片选用ds1302。用数码管显示时间。六位数码管显示时间和日期,默认显示的是时间,通过小数点区分时与分、分与秒。通过一个按键接到RB1口,按下按键显示年月日。具体程序如下: //实验目的:熟悉时钟芯片DS1302的使用 //先设置时间和日期为:秒(08),分(58),时(05),日(13),月(01),年(55) //六位数码观显示时间和日期,默认显示的是时间,通过小数点区分时与分、分与秒 //按下RB1键不放,显示切换到日期显示。 //硬件要求:拨码开关S9全部置ON // 拨码开关S5、S6全部置ON // 拨码开关S1第7位置ON,其他位置OFF // 其他拨码开关置OFF #i nclude<pic.h> //包含单片机内部资源预定义 //__CONFIG(0x1832); //芯片配置字,看门狗关,上电延时开,掉电检测关,低压编程关,加密,4M晶体HS振荡 #define i_o RB4 //定义DS1302的数据口 #define sclk RB0 //定义DS1302的时钟口 #define rst RB5 //定义DS1302的复位口 // unsigned char time_rx; unsigned char time_rx @ 0x30; //定义接收寄存器 static volatile bit time_rx7 @ (unsigned)&time_rx*8+7; //接收寄存器的最高位 //static volatile bit temp0 @ (unsigned)&temp*8+0; void port_init(); //申明引脚初始化函数 void ds1302_init(); //申明DS1302初始化函数 void set_time(); //申明设置时间函数 void get_time(); //申明读取时间函数 void display(); //申明显示函数 void time_write_1(unsigned char time_tx); //申明写一个字节函数 unsigned char time_read_1(); //申明读一个字节函数 void delay(); //申明延时函数 //定义待设置的时间:秒、分、时、日、月、星期、年、控制字 const char table[]={0x00,0x42,0x13,0x11,0x5,0x05,0x07,0x00}; //定义读取时间和日期存放表格 char table1[7]; //定义0-9的显示代码 const char table2[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; //———————————————- //主函数 void main() { port_init(); //调用引脚初始化函数 ds1302_init(); //调用DS1302初始化函数 set_time(); //调用设置时间函数 while(1) { get_time(); //调用取时间函数 display(); //调用显示函数 } } //——————————————— //DS1302初始化函数 void ds1302_init() { sclk=0; //拉低时钟信号 rst =0; //复位DS1302 rst=1; //使能DS1302 time_write_1(0x8e); //发控制命令 time_write_1(0); //允许写DS1302 rst=0; //复位 } //——————————————— //设置时间函数 void set_time() { int i; //定义循环变量 rst=1; //使能DS1302 time_write_1(0xbe); //时钟多字节写命令 从控制字地址? for(i=0;i<8;i++) //连续写8个字节数据 { time_write_1(table[i]); //调用写一个字节函数 } rst=0; //复位 } //——————————————— //读取时间函数 void get_time() { int i; //设置循环变量 rst=1; //使能DS1302 time_write_1(0xbf); //发送多字节读取命令 for(i=0;i<7;i++) //连续读取7个字节数据 { table1[i]=time_read_1(); //调用读取1个字节数据的函数 } rst=0; //复位DS1302 } //——————————————– //写一个字节数据函数 void time_write_1(unsigned char time_tx) { int j; //设置循环变量 for(j=0;j<8;j++) //连续写8bit { i_o=0; //先设置数据为0 sclk=0; //时钟信号拉低 if(time_tx&0x01) //判断待发送的数据位是0或1 { i_o=1; //待发送数据位是1 } time_tx=time_tx>>1; //待发送的数据右移1位 sclk=1; //拉高时钟信号 } sclk=0; //写完一个字节,拉低时钟信号 } //——————————————— //读一个字节函数 unsigned char time_read_1() { int j; //设置循环变量 TRISB4=1; //设置数据口方向为输入 for(j=0;j<8;j++) //连续读取8bit { sclk=0; //拉低时钟信号 time_rx=time_rx>>1; //接收寄存器右移1位 time_rx7=i_o; //把接收到的数据放到接收寄存器的最高位 sclk=1; //拉高时钟信号 } TRISB4=0; //恢复数据口方向为输出 sclk=0; //拉低时钟信号 return(time_rx); //返回读取到的数据 } //——————————————– //引脚定义函数 void port_init() { TRISA=0x00; //设置A口全输出 TRISD=0X00; //设置D口全输出 ADCON1=0X06; //设置A口为普通I/O口 TRISB=0X02; //设置RB1为输入,其他为输出 OPTION=0X00; //开启B口弱上拉 PORTA=0XFF; PORTD=0XFF; //先熄灭所有显示 } //——————————————- //显示函数 void display() { int i; //定义查表变量 if(RB1==0) //判断RB1是否按下,如果按下的话显示日期,不按下显示时间 { table1[0]=table1[3]; //日送table1[0] table1[1]=table1[4]; //月送table1[1] table1[2]=table1[6]; //年送table1[2] } i=table1[0]&0x0f; //求秒的个位 PORTD=table2[i]; //送D口显示 PORTA=0x1f; //点亮秒的个位 delay(); //延长一段时间,保证亮度 i=table1[0]&0xf0; //求秒的十位 i=i>>4; //右移4位 PORTD=table2[i]; //送D口显示 PORTA=0x2f; //点亮秒的十位 delay(); //延长一段时间,保证亮度 i=table1[1]&0x0f; //求分的个位 PORTD=table2[i]&0x7f; //送D口显示,并显示小数点 PORTA=0x37; //点亮分的个位 delay(); //延时一定时间,保证亮度 i=table1[1]&0xf0; //求分的十位 i=i>>4; PORTD=table2[i]; //送D口显示 PORTA=0x3b; //点亮分的十位 delay(); //延长一段时间,保证亮度 i=table1[2]&0x0f; //求时的个位 PORTD=table2[i]&0x7f; //送D口显示,并加上小数点 PORTA=0x3d; //点亮时的个位 delay(); //延时一定时间,保证亮度 i=table1[2]&0xf0; //求时的十位 i=i>>4; PORTD=table2[i]; //送D口显示 PORTA=0x3e; //点亮时的十位 delay(); //延长一段时间,保证亮度 } //—————————————————————— //延时程序 void delay() //延时程序 { int i; //定义整形变量 for(i=0x64;i–;); //延时 } === 实时时钟电路的原理及应用 1 引言 现在流行的串行时钟电路很多,如DS1302、DS1307、PCF8485等。这些电路的接口简单、价格低廉、使用方便,被广泛地采用。本文介绍的实时时钟电路DS1302是DALLAS公司的一种具有涓细电流充电能力的电路,主要特点是采用串行数据传输,可为掉电保护电源提供可编程的充电功能,并且可以关闭充电功能。采用普通32.768kHz晶振。 2 DS1302的结构及工作原理 DS1302是美国DALLAS公司推出的一种高性能、低功耗、带RAM的实时时钟电路,它可以对年、月、日、周日、时、分、秒进行计时,具有闰年补偿功能,工作电压为2.5V~5.5V。采用三线接口与CPU进行同步通信,并可采用突发方式一次传送多个字节的时钟信号或RAM数据。DS1302内部有一个31×8的用于临时性存放数据的RAM寄存器。DS1302是DS1202的升级产品,与DS1202兼容,但增加了主电源/后背电源双电源引脚,同时提供了对后背电源进行涓细电流充电的能力。 2.1 引脚功能及结构 图1示出DS1302的引脚排列,其中Vcc1为后备电源,VCC2为主电源。在主电源关闭的情况下,也能保持时钟的连续运行。DS1302由Vcc1或Vcc2两者中的较大者供电。当Vcc2大于Vcc1+0.2V时,Vcc2给DS1302供电。当Vcc2小于Vcc1时,DS1302由Vcc1供电。X1和X2是振荡源,外接32.768kHz晶振。RST是复位/片选线,通过把RST输入驱动置高电平来启动所有的数据传送。RST输入有两种功能:首先,RST接通控制逻辑,允许地址/命令序列送入移位寄存器;其次,RST提供终止单字节或多字节数据的传送手段。当RST为高电平时,所有的数据传送被初始化,允许对DS1302进行操作。如果在传送过程中RST置为低电平,则会终止此次数据传送,I/O引脚变为高阻态。上电运行时,在Vcc≥2.5V之前,RST必须保持低电平。只有在SCLK为低电平时,才能将RST置为高电平。I/O为串行数据输入输出端(双向),后面有详细说明。SCLK始终是输入端。 .2 DS1302的控制字节 DS1302的控制字如图2所示。控制字节的最高有效位(位7)必须是逻辑1,如果它为0,则不能把数据写入DS1302中,位6如果为0,则表示存取日历时钟数据,为1表示存取RAM数据;位5至位1指示操作单元的地址;最低有效位(位0)如为0表示要进行写操作,为1表示进行读操作,控制字节总是从最低位开始输出。 2.3 数据输入输出(I/O) 在控制指令字输入后的下一个SCLK时钟的上升沿时,数据被写入DS1302,数据输入从低位即位0开始。同样,在紧跟8位的控制指令字后的下一个SCLK脉冲的下降沿读出DS1302的数据,读出数据时从低位0位到高位7。 2.4 DS1302的寄存器 DS1302有12个寄存器,其中有7个寄存器与日历、时钟相关,存放的数据位为BCD码形式,其日历、时间寄存器及其控制字见表1。 此外,DS1302还有年份寄存器、控制寄存器、充电寄存器、时钟突发寄存器及与RAM相关的寄存器等。时钟突发寄存器可一次性顺序读写除充电寄存器外的所有寄存器内容。DS1302与RAM相关的寄存器分为两类:一类是单个RAM单元,共31个,每个单元组态为一个8位的字节,其命令控制字为C0H~FDH,其中奇数为读操作,偶数为写操作;另一类为突发方式下的RAM寄存器,此方式下可一次性读写所有的RAM的31个字节,命令控制字为FEH(写)、FFH(读)。 3 DS1302实时显示时间的软硬件 DS1302与CPU的连接需要三条线,即SCLK(7)、I/O(6)、RST(5)。图3示出DS1302与89C2051的连接图,其中,时钟的显示用LCD。 3.1 DS1302与CPU的连接 实际上,在调试程序时可以不加电容器,只加一个32.768kHz的晶振即可。只是选择晶振时,不同的晶振,误差也较大。另外,还可以在上面的电路中加入DS18B20,同时显示实时温度。只要占用CPU一个口线即可。LCD还可以换成LED,还可以使用北京卫信杰科技发展有限公司生产的10位多功能8段液晶显示模块LCM101,内含看门狗(WDT)/时钟发生器及两种频率的蜂鸣器驱动电路,并有内置显示RAM,可显示任意字段笔划,具有3-4线串行接口,可与任何单片机、IC接口。功耗低,显示状态时电流为2μA(典型值),省电模式时小于1μA,工作电压为2.4V~3.3V,显示清晰。 .2 DS1302实时时间流程 图4示出DS1302的实时时间流程。根据此流程框图,不难采集实时时间。下面结合流程图对DS1302的基本操作进行编程: 根据本人在调试中遇到的问题,特作如下说明: DS1302与微处理器进行数据交换时,首先由微处理器向电路发送命令字节,命令字节最高位MSB(D7)必须为逻辑1,如果D7=0,则禁止写DS1302,即写保护;D6=0,指定时钟数据,D6=1,指定RAM数据;D5~D1指定输入或输出的特定寄存器;最低位LSB(D0)为逻辑0,指定写操作(输入),D0=1,指定读操作(输出)。 在DS1302的时钟日历或RAM进行数据传送时,DS1302必须首先发送命令字节。若进行单字节传送,8位命令字节传送结束之后,在下2个SCLK周期的上升沿输入数据字节,或在下8个SCLK周期的下降沿输出数据字节。 DS1302与RAM相关的寄存器分为两类:一类是单个RAM单元,共31个,每个单元组态为一个8位的字节,其命令控制字为C0H~FDH,其中奇数为读操作,偶数为写操作;再一类为突发方式下的RAM寄存器,在此方式下可一次性读、写所有的RAM的31个字节。 要特别说明的是备用电源B1,可以用电池或者超级电容器(0.1F以上)。虽然DS1302在主电源掉电后的耗电很小,但是,如果要长时间保证时钟正常,最好选用小型充电电池。可以用老式电脑主板上的3.6V充电电池。如果断电时间较短(几小时或几天)时,就可以用漏电较小的普通电解电容器代替。100 μF就可以保证1小时的正常走时。DS1302在第一次加电后,必须进行初始化操作。初始化后就可以按正常方法调整时间。 4 结论 DS1302存在时钟精度不高,易受环境影响,出现时钟混乱等缺点。DS1302可以用于数据记录,特别是对某些具有特殊意义的数据点的记录,能实现数据与出现该数据的时间同时记录。这种记录对长时间的连续测控系统结果的分析及对异常数据出现的原因的查找具有重要意义。传统的数据记录方式是隔时采样或定时采样,没有具体的时间记录,因此,只能记录数据而无法准确记录其出现的时间;若采用单片机计时,一方面需要采用计数器,占用硬件资源,另一方面需要设置中断、查询等,同样耗费单片机的资源,而且,某些测控系统可能不允许。但是,如果在系统中采用时钟芯片DS1302,则能很好地解决这个问题。 === 引自: http://www.72hour.cn/article.asp?id=138 /******************* DS1302.H ********************/ sbit T_CLK=P1^1; sbit T_IO =P1^2; sbit T_RST=P1^3; sbit ACC0=ACC^0; sbit ACC7=ACC^7; //********DS1302读写程序*************** /******************************************************************** 函 数 名:RTInputByte() 功 能:实时时钟写入一字节 说 明:往DS1302写入1Byte数据 (内部函数) 入口参数:d 写入的数据 返 回 值:无 ***********************************************************************/ void RTInputByte(unsigned char d) { unsigned char i; ACC = d; for(i=8; i>0; i–) { T_IO = ACC0; //相当于汇编中的 RRC T_CLK = 1; T_CLK = 0; ACC = ACC >> 1; } } /******************************************************************** 函 数 名:RTOutputByte() 功 能:实时时钟读取一字节 说 明:从DS1302读取1Byte数据 (内部函数) 入口参数:无 返 回 值:ACC 设 计:zhaojunjie 日 期:2002-03-19 修 改: 日 期: ***********************************************************************/ unsigned char RTOutputByte(void) { unsigned char i; for(i=8; i>0; i–) { ACC = ACC >>1; //相当于汇编中的 RRC ACC7 = T_IO; T_CLK = 1; T_CLK = 0; } return(ACC); } /******************************************************************** 函 数 名:Write1302() 功 能:往DS1302写入数据 说 明:先写地址,后写命令/数据 (内部函数) 调 用:RTInputByte() , RTOutputByte() 入口参数:ucAddr: DS1302地址, ucData: 要写的数据 返 回 值:无 ***********************************************************************/ void Write1302(unsigned char ucAddr, unsigned char ucDa) { T_RST = 0; T_CLK = 0; T_RST = 1; RTInputByte(ucAddr); // 地址,命令 RTInputByte(ucDa); // 写1Byte数据 T_CLK = 1; T_RST = 0; } /******************************************************************** 函 数 名:Read1302() 功 能:读取DS1302某地址的数据 说 明:先写地址,后读命令/数据 (内部函数) 调 用:RTInputByte() , RTOutputByte() 入口参数:ucAddr: DS1302地址 返 回 值:ucData :读取的数据 ***********************************************************************/ unsigned char Read1302(unsigned char ucAddr) { unsigned char ucData; T_RST = 0; T_CLK = 0; T_RST = 1; RTInputByte(ucAddr); // 地址,命令 ucData = RTOutputByte(); // 读1Byte数据 T_CLK = 1; T_RST = 0; return(ucData); } /******************************************************************** 函 数 名:Set1302() 功 能:设置初始时间 说 明:先写地址,后读命令/数据(寄存器多字节方式) 调 用:Write1302() 入口参数:pClock: 设置时钟数据地址 格式为: 秒 分 时 日 月 星期年 7Byte (BCD码)1B 1B 1B 1B 1B 1B 1B 返 回 值:无 ***********************************************************************/ void Set1302(unsigned char *pClock) { unsigned char i; unsigned char ucAddr = 0x80; Write1302(0x8e,0x00); // 控制命令,WP=0,写操作 for(i =7; i>0; i–) { Write1302(ucAddr,*pClock); // 秒 分 时 日 月 星期 年 pClock++; ucAddr +=2; } Write1302(0x8e,0x80); // 控制命令,WP=1,写保护 } /********************************************** LED时钟.c **********************************************/ #include <REGX51.H> #include “ds1302.h” //包含DS1302头文件 #define leddata P0 //定义LED数据口 //#define sec 0x80 //1302秒寄存器地址 #define min 0x82 //1302分寄存器地址 #define hou 0x84 //1302时寄存器地址 #define read 0x01 //读操作,因为读的时候地址要加1,使最低位为1 sbit MODE=P3^4; //按键定义,下同 sbit SET=P3^5; sbit UP=P3^6; sbit DOWN=P3^7; sbit led0=P2^0; //LED位选,因为布线不是按顺序布的,程序定义一下就可以了,下同 sbit led1=P2^1; sbit led2=P2^4; sbit led3=P2^6; //**************函数声明***************** void delays(unsigned char); void display(void); void Scan_Key(void); void id_case1_key(); void Set_id(unsigned char ,unsigned char); //*************变量定义****************** unsigned char id=0,timecount,re_disp=0; //定义用到的变量,id为调整模式用,不为0时表示调整模式,调整哪个量由id值确定 //timecount用于500ms定时记数,时间到取反flag标志位,re_disp记数200次共10s,调整状态下按键无操作10s自动返回正常显示状态 bit hour,minute,second,flag; //定义位变量,hour,minute,second分别为调整时闪烁标志位,flag 500ms取反一次,调整位闪烁及冒号闪烁用 unsigned char code tab[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90}; //LED码表,根据硬件修改 unsigned char inittime[7]={0x00,0x00,0x12,0x16,0x11,0x06,0x04}; //初始化1302时用到的初始化数据 // 秒 分钟小时 日 月 年 星期 void t0(void) interrupt 1 using 0 //中断处理程序,主要用于取反标志位,返回正常显示状态 { TH0=(65535-50000)/256; //50ms定时 TL0=(65535-50000)%256; timecount++;re_disp++; if(timecount>9) { timecount=0; flag=flag; } if(re_disp>200){re_disp=0;if(id)id=0;} } void delays(unsigned char k) //延时函数 { unsigned char i,j; for(i=0;i<k;i++) for(j=0;j<50;j++); } void display(void) //显示函数 { if(flag&hour) //如hour为1表示调整时,flag为1时不显示 { led0=0;leddata=0xff;delays(10);led0=1; led1=0;leddata=0xff&((unsigned char)flag<<7);delays(10);led1=1; //&((unsigned char)flag<<6)该句根据flag的值决定来显示小数点,为1时显示,4个小数点组成两对冒号,下同 } else //flag为0时显示,产生闪烁效果,下同 { led3=1;leddata=tab[Read1302(houread)/16];delays(10);led3=0;leddata=0xff; led2=1;leddata=tab[Read1302(houread)%16]&((unsigned char)flag<<7);delays(10);led2=0;leddata=0xff; } if(flag&minute) { led2=0;leddata=0xff&((unsigned char)flag<<7);delays(10);led2=1; led3=0;leddata=0xff&((unsigned char)flag<<7);delays(10);led3=1; } else { led1=1;leddata=tab[Read1302(minread)/16]&((unsigned char)flag<<7);delays(10);led1=0;leddata=0xff; led0=1;leddata=tab[Read1302(minread)%16]&((unsigned char)~flag<<7);delays(10);led0=0;leddata=0xff; } } void Scan_Key(void) //键盘检测函数 { display(); //程序开头调用显示函数 if(!SET) { while(!SET)display(); //等待按键释放,如一直按下一直调用显示函数,防止显示中断 re_disp=0; //清除记数,重新开始10s定时 id++;if(id>2)id=0; //id加1,后面根据id值对应调整项目 } if(id==0){hour=0;minute=0;} //根据id值跳到相应处理函数 if(id==1){hour=1;id_case1_key();} //id为1,选择调整小时位,闪烁标志位置1,然后跳到键盘处理函数,下同 if(id==2){hour=0;minute=1;id_case1_key();} } void id_case1_key(void) //键盘处理函数,只有按下set键时才会进入, { display(); if (!DOWN) //减少 { while(!DOWN)display(); //等待按键释放,如一直按下一直调用显示函数,防止显示中断 re_disp=0; //清除记数,重新开始10s定时 Set_id(id,0); //跳到加减判断函数,下同, } if (!UP) //增加 { while(!UP)display(); re_disp=0; Set_id(id,1); } } //根据选择调整相应项目并写入DS1302 void Set_id(unsigned char sel,unsigned char sel_1) //执行调整项目的函数 { signed char max,mini,address,item; if(sel==1) {address=hou; max=23;mini=0;} //小时 //根据id值确定要调整的项,并确定调整上下限,下同 if(sel==2) {address=min; max=59;mini=0;} //分钟 // if(sel==3) {address=sec; max=0;mini=0;} //秒 item=Read1302(addressread)/16*10+Read1302(addressread)%16; //从相应的地址读取当前数据并转换为十进制 if (sel_1==0) item–; else item++; //确定是对项目加还是减,并对越限处理 if(item>max) item=mini; if(item<mini) item=max; Write1302(0x8e,0x00);//允许写操作 Write1302(address,item/10*16+item%10); //将调整结果转换成压缩BCD码重新写入1302 Write1302(0x8e,0x80);//写保护,禁止写操作 } void main() //主函数 { TMOD=0x01; //初始化定时器 TH0=(65535-50000)/256; TL0=(65535-50000)%256; EA=1; ET0=1; TR0=1; Write1302(0x90,0xa0);//关闭充电二级管,不能对后备电池进行充电,防止发胀,原来的程序是打开的请关闭 Write1302(0x8e,0x80);//写保护,禁止写操作 if(!UP&!DOWN)Set1302(inittime); //如果同时按下UP和DOWN键则初始化1302,该语句在while(1)前,只执行一次,需要复位,防止误操作 while(1) { Scan_Key(); //主程序一直调用键盘检测函数即可 } ==== DS1302实时时钟程序[C语言]2007-10-12 13:49//实验目的:熟悉时钟芯片DS1302的使用 //先设置时间和日期为:秒(08),分(58),时(05),日(13),月(01),年(55) //六位数码观显示时间和日期,默认显示的是时间,通过小数点区分时与分、分与秒 //按下RB1键不放,显示切换到日期显示。 //硬件要求:拨码开关S9全部置ON // 拨码开关S5、S6全部置ON // 拨码开关S1第7位置ON,其他位置OFF // 其他拨码开关置OFF #include<pic.h> //包含单片机内部资源预定义 __CONFIG(0x1832); //芯片配置字,看门狗关,上电延时开,掉电检测关,低压编程关,加密,4M晶体HS振荡 #define i_o RB4 //定义DS1302的数据口 #define sclk RB0 //定义DS1302的时钟口 #define rst RB5 //定义DS1302的复位口 // unsigned char time_rx; unsigned char time_rx @ 0x30; //定义接收寄存器 static volatile bit time_rx7 @ (unsigned)&time_rx*8+7; //接收寄存器的最高位 //static volatile bit temp0 @ (unsigned)&temp*8+0; void port_init(); //申明引脚初始化函数 void ds1302_init(); //申明DS1302初始化函数 void set_time(); //申明设置时间函数 void get_time(); //申明读取时间函数 void display(); //申明显示函数 void time_write_1(unsigned char time_tx); //申明写一个字节函数 unsigned char time_read_1(); //申明读一个字节函数 void delay(); //申明延时函数 //定义待设置的时间:秒、分、时、日、月、星期、年、控制字 const char table[]={0x00,0x58,0x12,0x8,0x3,0x06,0x06,0x00}; //定义读取时间和日期存放表格 char table1[7]; //定义0-9的显示代码 const char table2[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; //———————————————- //主函数 void main() { port_init(); //调用引脚初始化函数 ds1302_init(); //调用DS1302初始化函数 set_time(); //调用设置时间函数 while(1) { get_time(); //调用取时间函数 display(); //调用显示函数 } } //——————————————— //DS1302初始化函数 void ds1302_init() { sclk=0; //拉低时钟信号 rst =0; //复位DS1302 rst=1; //使能DS1302 time_write_1(0x8e); //发控制命令 time_write_1(0); //允许写DS1302 rst=0; //复位 } //——————————————— //设置时间函数 void set_time() { int i; //定义循环变量 rst=1; //使能DS1302 time_write_1(0xbe); //时钟多字节写命令 for(i=0;i<8;i++) //连续写8个字节数据 { time_write_1(table[i]); //调用写一个字节函数 } rst=0; //复位 } //——————————————— //读取时间函数 void get_time() { int i; //设置循环变量 rst=1; //使能DS1302 time_write_1(0xbf); //发送多字节读取命令 for(i=0;i<7;i++) //连续读取7个字节数据 { table1[i]=time_read_1(); //调用读取1个字节数据的函数 } rst=0; //复位DS1302 } //——————————————– //写一个字节数据函数 void time_write_1(unsigned char time_tx) { int j; //设置循环变量 for(j=0;j<8;j++) //连续写8bit { i_o=0; //先设置数据为0 sclk=0; //时钟信号拉低 if(time_tx&0x01) //判断待发送的数据位是0或1 { i_o=1; //待发送数据位是1 } time_tx=time_tx>>1; //待发送的数据右移1位 sclk=1; //拉高时钟信号 } sclk=0; //写完一个字节,拉低时钟信号 } //——————————————— //读一个字节函数 unsigned char time_read_1() { int j; //设置循环变量 TRISB4=1; //设置数据口方向为输入 for(j=0;j<8;j++) //连续读取8bit { sclk=0; //拉低时钟信号 time_rx=time_rx>>1; //接收寄存器右移1位 time_rx7=i_o; //把接收到的数据放到接收寄存器的最高位 sclk=1; //拉高时钟信号 } TRISB4=0; //恢复数据口方向为输出 sclk=0; //拉低时钟信号 return(time_rx); //返回读取到的数据 } //——————————————– //引脚定义函数 void port_init() { TRISA=0x00; //设置A口全输出 TRISD=0X00; //设置D口全输出 ADCON1=0X06; //设置A口为普通I/O口 TRISB=0X02; //设置RB1为输入,其他为输出 OPTION=0X00; //开启B口弱上拉 PORTA=0XFF; PORTD=0XFF; //先熄灭所有显示 } //——————————————- //显示函数 void display() { int i; //定义查表变量 if(RB1==0) //判断RB1是否按下,如果按下的话显示日期,不按下显示时间 { table1[0]=table1[3]; table1[1]=table1[4]; table1[2]=table1[6]; } i=table1[0]&0x0f; //求秒的个位 PORTD=table2[i]; //送D口显示 PORTA=0x1f; //点亮秒的个位 delay(); //延长一段时间,保证亮度 i=table1[0]&0xf0; //求秒的十位 i=i>>4; //右移4位 PORTD=table2[i]; //送D口显示 PORTA=0x2f; //点亮秒的十位 delay(); //延长一段时间,保证亮度 i=table1[1]&0x0f; //求分的个位 PORTD=table2[i]&0x7f; //送D口显示,并显示小数点 PORTA=0x37; //点亮分的个位 delay(); //延时一定时间,保证亮度 i=table1[1]&0xf0; //求分的十位 i=i>>4; PORTD=table2[i]; //送D口显示 PORTA=0x3b; //点亮分的十位 delay(); //延长一段时间,保证亮度 i=table1[2]&0x0f; //求时的个位 PORTD=table2[i]&0x7f; //送D口显示,并加上小数点 PORTA=0x3d; //点亮时的个位 delay(); //延时一定时间,保证亮度 i=table1[2]&0xf0; //求时的十位 i=i>>4; PORTD=table2[i]; //送D口显示 PORTA=0x3e; //点亮时的十位 delay(); //延长一段时间,保证亮度 } //—————————————————————— //延时程序 void delay() //延时程序 { int i; //定义整形变量 for(i=0x64;i–;); //延时 } ==== 简介 在DS1302的实际使用中,采用辅助电容法,可以解决DS1302在应用中由于晶振的负载电容不匹配而引起的停振问题。 关键词 时钟 负载电容 匹配 概述 DS1302是Dallas公司生产的一种实时时钟芯片。它通过串行方式与单片机进行数据传送,能够向单片机提供包括秒、分、时、日、月、年等在内的实时时间信息,并可对月末日期、闰年天数自动进行调整;它还拥有用于主电源和备份电源的双电源引脚,在主电源关闭的情况下,也能保持时钟的连续运行。另外,它还能提供31字节的用于高速数据暂存的RAM。鉴于上述特点,DS1302已在许多单片机系统中得到应用,为系统提供所需的实时时钟信息。 一、 DS1302的主要特性 1. 引脚排列 图1 DS1302引脚排列图 DS1302的引脚排列如图1所示,各引脚的功能如下: X1,X2——32768Hz晶振引脚端; RST——复位端; I/O——数据输入/输出端; SCLK——串行时钟端; GND——地; VCC2,VCC1——主电源与后备电源引脚端。 2. 主要功能 DS1302时钟芯片内主要包括移位寄存器、控制逻辑电路、振荡器、实时时钟电路以及用于高速暂存的31字节RAM。DS1302与单片机系统的数据传送依靠RST,I/O,SCLK三根端线即可完成。其工作过程可概括为:首先系统RST引脚驱动至高电平,然后在作用于SCLK时钟脉冲的作用下,通过I/O引脚向DS1302输入地址/命令字节,随后再在SCLK时钟脉冲的配合下,从I/O引脚写入或读出相应的数据字节。因此,其与单片机之间的数据传送是十分容易实现的。 二、 时钟的产生及存在的问题 (1) 在实际使用中,我们发现DS1302的工作情况不够稳定,主要表现在实时时间的传送有时会出现误差,有时甚至整个芯片停止工作。我们对DS1302的工作电路进行了分析,其与单片机系统的连接如图2所示。从图中可以看出,DS1302的外部电路十分简单,惟一外接的元件是32768Hz的晶振。通过实验我们发现:当外接晶振电路振荡时,DS1302计时正确;当外接晶振电路停振时,DS1302计时停止。因此,我们认为32768Hz晶振是造成DS1302工作不稳定的主要原因。 图2 DS1302与单片机系统的连接图 (2) DS1302时钟的产生基于外接的晶体振荡器,振荡器的频率为32768Hz。该晶振通过引脚X1、X2直接连接至DS1302,即DS1302是依靠外部晶振与其内部的电容配合来产生时钟脉冲的。由于DS1302在芯片本身已经集成了6pF的电容,所以,为了获得稳定可靠的时钟,必须选用具有6pF负载电容的晶振。 然而,许多人在选用晶振时仅仅注意了晶振的额定频率值,而忽视了晶振的负载电容大小,甚至连许多经销商也不能提供所售晶振的负载电容。所以即使在使用中选用了符合32768Hz的晶振,但如果该晶振的负载电容与DS1302提供的6pF不一致时,就会影响晶振的起振或导致振荡频率的偏移,出现上述在应用中的问题。 三、 利用辅助电容实现负载匹配 (1) 当所选的晶振负载电容不是6pF时,可以采用增加辅助电容的方法提高或降低DS1302振荡器的电容性负载,使之与晶体所需的电容值匹配。如果已知晶体的负载电容为CI,若CI<6pF,则可以增加一个并联电容CS以产生所需的总负载电容CI,即CI=6pF+CS;若CI>6pF,则可以在晶体的一端增加一个串联电容CS,以产生所需的负载电容CI,即1/CI=1/6pF+1/CS,通过计算即可得出应增加的辅助电容大小。辅助电容的接法如图3所示。 图3 CS连接电路图 (2) 在使用前对晶体的负载电容并不知道的情况下,通过测定晶体振荡频率的方法可以确定该晶体的负载电容。 对于晶体振荡器来说,其振荡频率与负载电容之间的关系是确定的。以本文讨论的DS1302使用的32768Hz晶振为例:当它工作于所要求的负载电容时,能较准确地产生 32768Hz的频率;当它的负载电容小于6pF时,其振荡频率会正向偏移;当它的负载电容大于6pF时,其振荡频率就会负向偏移。因此,对于未知负载电容的晶体应首先采用实验的方法,在其两端加入辅助电容使晶体起振,然后用频率计测出振荡频率。若测得频率大于32768Hz,说明负载电容偏小;若测得频率小于32768Hz,说明负载电容偏大。对辅助电容逐步调整,最终使振荡频率尽可能接近32768Hz,则此时晶体端所接负载电容的总和就是适合该晶体的负载电容。 结论 以上方法经我们在实际工作中多次使用,证明确实有效。它放宽了DS1302在使用中对晶振的条件要求,增强了DS1302在工作中的稳定性,对DS1302更广泛地应用具有积极的意义。