DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等功能
RTC(Real Time Clock):实时时钟,是一种集成电路,通常称为时钟芯片(如:DS1302、DS3231(精度很高,自带晶振)、DS12C887(自带电池))
单片机定时器的缺点:
精度低,占用单片机的CPU时间、掉电不能继续运行
DS1302可以在单片机不工作时使用电池供电,在单片机断电的情况下持续计时
DS1302的引脚定义(图源:DS1302中文手册)
引脚名 | 用途 | 引脚名 | 用途 |
---|---|---|---|
VCC2 | 主电源 | CE | 芯片使能 |
VCC1 | 备用电源 | IO | 数据输入/输出 |
GND | 接地 | SCLK | 串行时钟 |
X1、X2 | 于频率为36.768KHz的晶振相连 |
DS1302典型工作电路(图源:DS1302中文手册)
主电源接单片机的VCC,备用电源接电池VCC
在主电源有电时,可对电池(可充电)充电(即涓细电流充电能力)
X1、X2接晶振(实时时钟的晶振一般都是32.768KHz,该频率适合时钟工作),提供稳定脉冲
利用CE、I/O、SCLK对寄存器进行读写(通信模式和74HC595类似, 专用协议)
开发板上的DS1302(图源:普中科技开发手册)
开发板上的DS1302是没有电池的,断电之后停止工作
DS1302的内部结构(图源:DS1302中文手册)
晶振的脉冲在经过处理后,生成1Hz的标准平频率,内部的时间储存在寄存器RAM中(31 * 8 )
移位寄存器用于串行数据交互
CE在不使能时,芯片依旧工作,CE相当于一个出入输出的开关(高电平为接通)
DS1302的内部寄存器定义(图源:DS1302中文手册)
RCT表示时钟相关寄存器,内部还有其它寄存器(如通用RAM、RAM脉冲串等)
Date为日期,Day为星期
Year的0~99说明能工作在2000~2099
WP为写保护,置1时写入无效,但可以读取
最后一行为涓流充电相关寄存器
CH置1,时钟停止,CH置0,时钟计数
RCT操作相关命令字(图源:DS1302中文手册)
地址命令字用于控制在哪写入什么,在哪读出什么
7:最高位固定为1,如果是0则禁止写入
6:置1操作RAM,置0操作时钟
5~1:地址
0:置1为读取,置0写入
具体操作可见RTC表中“READ”和“WRITE”
DS1302在收发信息时的时序定义(图源:DS1302中文手册)
在写入过程,CE全程为高电平
SCLK提供时钟
IO提供数据
在时钟的上升沿,IO的数据将被写入;在时钟的下降沿,DS1302会将数据输出
IO可输入也可输出,在写入时,单片机发送两个字节
读写工作流程:CE置1->IO发送最低位,决定是读还是写->时钟提供上升沿,第一位写入芯片->时钟提供下降沿->IO发送第二位数据->时钟提供上升沿->第二位数据写入芯片->…->最高位写入,完成命令字的写入->如果是写入,那么接着发送第二个字节;如果是读,在发送完第一个字节后,每一次下降沿,读取一个数据到IO->时钟置0,CE置0
在使用LCD1602时要将OE接VCC
BCD码(Binary Coded Decimal),用4位二进制数来表示1位十进制数,分别表示十位和个位(方便数码管译码)
DS1302用于计时的即为BCD码
在BCD码中,一个字节的前四位表示十位,后四个字节表示各位
(eg:0x55用BCD表示即为十进制的55,因此a~f在BCD中是非法的)
BCD码转十进制:DEC=BCD/16*10+BCD%16; (2位BCD)
十进制转BCD码:BCD=DEC/10*16+DEC%10; (2位BCD)
//main.c
#include
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
#include "Uart.h"
unsigned char Second = 0;
unsigned char Minute = 0;
void main()
{
LCD_Init();
LCD_ShowString(1, 1, " - - ");
LCD_ShowString(2, 1, " : : ");
DS1302_Init();
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);
//LCD_ShowNum(2, 10, DS1302_Time[6], 2);
Delay(50);
}
}
//DS1302.c
#include
#include "LCD1602.h"
#include "Uart.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 char DS1302_Time[] = {23, 12, 14, 20, 36, 4};
//年,月,日,时,分,秒,星期
void DS1302_Init()
//初始化
{
DS1302_CE = 0;
DS1302_SCLK = 0;
}
void DS1302_WriteByte(unsigned char Command, unsigned char Data)
//单字节写入
{
unsigned char i = 0;
DS1302_CE = 1;
for(i = 0; i < 8; i++)
{
DS1302_IO = Command & (0x01 << i);
DS1302_SCLK = 1;
//如果马上置0,时钟有可能反应不过来
DS1302_SCLK = 0;
//对运行速度快的单片机,可能需要加延时,这里不用
}
for(i = 0; i < 8; i++)
{
DS1302_CE = 1;
DS1302_IO = Data & (0x01 << i);
DS1302_SCLK = 1;
DS1302_SCLK = 0;
}
DS1302_CE = 0;
DS1302_SCLK = 0;
}
unsigned char DS1302_ReadByte(unsigned char Command)
//单字节读取
{
unsigned char Data = 0x00;
unsigned char i = 0;
Command |= 0x01;//第一位置1
DS1302_CE = 1;
for(i = 0; i < 8; i++)
{
DS1302_CE = 1;
DS1302_IO = Command & (0x01 << i);
DS1302_SCLK = 0;
DS1302_SCLK = 1;
//在最后一个循环时为上升沿
}
for(i = 0; i < 8; i++)
{
DS1302_SCLK = 1;
//重复置1,减去一个脉冲周期
DS1302_SCLK = 0;
//读取一位,同时使执行结束时为0
if(DS1302_IO)
{
Data |= (0x01 << i);
//读取的第一位为最低位
}
}
DS1302_CE = 0;
DS1302_SCLK = 0;
DS1302_IO = 0;//这里需要将IO重置,否则读取的数据会在输入数据和ff之间横跳
return Data;
}
void DS1302_SetTime()
//将初始时间写入DS1302
{
DS1302_WriteByte(DS1302_WP, 0x00);//关闭写保护
DS1302_WriteByte(DS1302_YEAR, DS1302_Time[0] / 10 * 16 + DS1302_Time[0] % 10);//10转16进制
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, 0x81);//打开写保护
}
void DS1302_ReadTime()
{
unsigned char Temp = 0;
//用于暂存接收到的时间数据
Temp = DS1302_ReadByte(DS1302_YEAR);
DS1302_Time[0] = Temp / 16 * 10 + Temp % 16;
//将BDC转为十进制
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;
}
#include
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
#include "Timer0.h"
#include "Key.h"
unsigned char Second = 0;
unsigned char Minute = 0;
unsigned char KeyNum = 0;
unsigned char Mode = 0;
unsigned char TimeSet_Select = 0;
unsigned char TimeSet_Flag = 0;
void TimeShow()
{
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);
Delay(50);
}
unsigned char LongMonth[] = {1, 3, 5, 7, 8, 10 ,12};
unsigned char ShortMonth[] = {4, 6, 9, 11};
void TimeSet()
{
unsigned char i = 0;
if(KeyNum == 2)
{
TimeSet_Select++;
TimeSet_Select %= 6;
}
if(KeyNum == 3)//加
{
DS1302_Time[TimeSet_Select]++;
//合法性检查
if(DS1302_Time[0] > 99) DS1302_Time[0] = 0;
if(DS1302_Time[1] > 12) DS1302_Time[1] = 1;
for(i = 0; i < 7; i++)
{
if(DS1302_Time[1] == LongMonth[i])//判断是否为大月
{
if(DS1302_Time[2] > 31) DS1302_Time[2] = 1;
}
}
for(i = 0;i < 4;i++)
{
if(DS1302_Time[1] == ShortMonth[i])//判断是否为小月
{
if(DS1302_Time[2] > 30) DS1302_Time[2] = 1;
}
}
if(DS1302_Time[1] == 2)
{
if(DS1302_Time[0] % 4 == 0)
//判断闰年,DS1302只有99年的计时,不用考虑是否逢百
{
if(DS1302_Time[2] > 29) DS1302_Time[2] = 1;
}
else
{
if(DS1302_Time[2] > 28) DS1302_Time[2] = 1;
}
}
if(DS1302_Time[3] > 23) DS1302_Time[3] = 0;
if(DS1302_Time[4] > 59) DS1302_Time[4] = 0;
if(DS1302_Time[5] > 59) DS1302_Time[5] = 0;
}
if(KeyNum == 4)//减
{
DS1302_Time[TimeSet_Select]--;
//这里将数据改为有符号类型,方便判断(注意头文件中的unsigned也要去掉)
if(DS1302_Time[0] < 0) DS1302_Time[0] = 99;
if(DS1302_Time[1] < 1) DS1302_Time[1] = 12;
if(DS1302_Time[1] == 1 || DS1302_Time[1] == 3 ||DS1302_Time[1] == 5 ||DS1302_Time[1] == 7 ||
DS1302_Time[1] == 8 || DS1302_Time[1] == 10 || DS1302_Time[1] == 12)//判断是否为大月
{
if(DS1302_Time[2] < 1) DS1302_Time[2] = 31;
}
else if(DS1302_Time[1] == 4 ||DS1302_Time[1] == 6 ||DS1302_Time[1] == 9 ||
DS1302_Time[1] == 11)//判断是否为小月
{
if(DS1302_Time[2] < 1) DS1302_Time[2] = 30;
if(DS1302_Time[2] > 30) DS1302_Time[2] = 1;
}
else if(DS1302_Time[1] == 2)
{
if(DS1302_Time[0] % 4 == 0)
//判断闰年,DS1302只有99年的计时,不用考虑是否逢百
{
if(DS1302_Time[2] < 1) DS1302_Time[2] = 29;
if(DS1302_Time[2] > 29) DS1302_Time[2] = 1;
}
else
{
if(DS1302_Time[2] < 1) DS1302_Time[2] = 28;
if(DS1302_Time[2] > 28) DS1302_Time[2] = 1;
}
}
if(DS1302_Time[3] < 0) DS1302_Time[3] = 23;
if(DS1302_Time[4] < 0) DS1302_Time[4] = 59;
if(DS1302_Time[5] < 0) DS1302_Time[5] = 59;
}
//LCD_ShowNum(2, 10, TimeSet_Select, 2);
//更新显示,同时实现闪烁
if(TimeSet_Select == 0 && TimeSet_Flag == 1)
{
LCD_ShowString(1, 1, " ");
}
else
{
LCD_ShowNum(1, 1, DS1302_Time[0], 2);
}
if(TimeSet_Select == 1 && TimeSet_Flag == 1)
{
LCD_ShowString(1, 4, " ");
}
else
{
LCD_ShowNum(1, 4, DS1302_Time[1], 2);
}
if(TimeSet_Select == 2 && TimeSet_Flag == 1)
{
LCD_ShowString(1, 7, " ");
}
else
{
LCD_ShowNum(1, 7, DS1302_Time[2], 2);
}
if(TimeSet_Select == 3 && TimeSet_Flag == 1)
{
LCD_ShowString(2, 1, " ");
}
else
{
LCD_ShowNum(2, 1, DS1302_Time[3], 2);
}
if(TimeSet_Select == 4 && TimeSet_Flag == 1)
{
LCD_ShowString(2, 4, " ");
}
else
{
LCD_ShowNum(2, 4, DS1302_Time[4], 2);
}
if(TimeSet_Select == 5 && TimeSet_Flag == 1)
{
LCD_ShowString(2, 7, " ");
}
else
{
LCD_ShowNum(2, 7, DS1302_Time[5], 2);
}
}
void main()
{
LCD_Init();
LCD_ShowString(1, 1, " - - ");
LCD_ShowString(2, 1, " : : ");
DS1302_Init();
DS1302_SetTime();
Timer0_Init();
while(1)
{
KeyNum = Key();
if(KeyNum == 1)
{
if(Mode == 1)
{
Mode = 0;
DS1302_SetTime();
}
else Mode = 1;
}
switch(Mode)
{
case 0: TimeShow(); break;
case 1: TimeSet(); break;
}
}
}
void Timer0_Isr(void) interrupt 1
{
static unsigned int T0_count = 0;
TL0 = 0x66;
TH0 = 0xFC;
T0_count++;
if(T0_count >= 500)
//实现1s为周期的闪烁
{
T0_count = 0;
TimeSet_Flag = !TimeSet_Flag;
//!是逻辑取反,~为按位取反,这里只需要逻辑取反
}
}
//DS1302.h
#ifndef __DS1302_H__
#define __DS1302_H__
char DS1302_Time[];
//数组和函数可以不加extern
void DS1302_Init();
void DS1302_WriteByte(unsigned char Command, unsigned char Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_ReadTime();
void DS1302_SetTime();
#endif