51 DS1302

51 DS1302

DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等功能

RTC(Real Time Clock):实时时钟,是一种集成电路,通常称为时钟芯片(如:DS1302、DS3231(精度很高,自带晶振)、DS12C887(自带电池))

单片机定时器的缺点:

精度低,占用单片机的CPU时间、掉电不能继续运行

DS1302可以在单片机不工作时使用电池供电,在单片机断电的情况下持续计时

引脚定义

DS1302的引脚定义(图源:DS1302中文手册)

51 DS1302_第1张图片

引脚名 用途 引脚名 用途
VCC2 主电源 CE 芯片使能
VCC1 备用电源 IO 数据输入/输出
GND 接地 SCLK 串行时钟
X1、X2 于频率为36.768KHz的晶振相连

DS1302典型工作电路(图源:DS1302中文手册)

51 DS1302_第2张图片

主电源接单片机的VCC,备用电源接电池VCC

在主电源有电时,可对电池(可充电)充电(即涓细电流充电能力)

X1、X2接晶振(实时时钟的晶振一般都是32.768KHz,该频率适合时钟工作),提供稳定脉冲

利用CE、I/O、SCLK对寄存器进行读写(通信模式和74HC595类似, 专用协议)

开发板上的DS1302(图源:普中科技开发手册)

51 DS1302_第3张图片

开发板上的DS1302是没有电池的,断电之后停止工作

工作原理

DS1302的内部结构(图源:DS1302中文手册)

51 DS1302_第4张图片

晶振的脉冲在经过处理后,生成1Hz的标准平频率,内部的时间储存在寄存器RAM中(31 * 8 )

移位寄存器用于串行数据交互

CE在不使能时,芯片依旧工作,CE相当于一个出入输出的开关(高电平为接通)

内部寄存器定义

DS1302的内部寄存器定义(图源:DS1302中文手册)

51 DS1302_第5张图片

RCT表示时钟相关寄存器,内部还有其它寄存器(如通用RAM、RAM脉冲串等)

Date为日期,Day为星期

Year的0~99说明能工作在2000~2099

WP为写保护,置1时写入无效,但可以读取

最后一行为涓流充电相关寄存器

CH置1,时钟停止,CH置0,时钟计数

命令字

RCT操作相关命令字(图源:DS1302中文手册)

51 DS1302_第6张图片

地址命令字用于控制在哪写入什么,在哪读出什么

7:最高位固定为1,如果是0则禁止写入

6:置1操作RAM,置0操作时钟

5~1:地址

0:置1为读取,置0写入

具体操作可见RTC表中“READ”和“WRITE”

时序定义

DS1302在收发信息时的时序定义(图源:DS1302中文手册)

51 DS1302_第7张图片

在写入过程,CE全程为高电平

SCLK提供时钟

IO提供数据

在时钟的上升沿,IO的数据将被写入;在时钟的下降沿,DS1302会将数据输出

IO可输入也可输出,在写入时,单片机发送两个字节

读写工作流程:CE置1->IO发送最低位,决定是读还是写->时钟提供上升沿,第一位写入芯片->时钟提供下降沿->IO发送第二位数据->时钟提供上升沿->第二位数据写入芯片->…->最高位写入,完成命令字的写入->如果是写入,那么接着发送第二个字节;如果是读,在发送完第一个字节后,每一次下降沿,读取一个数据到IO->时钟置0,CE置0

在使用LCD1602时要将OE接VCC

BCD码

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)

DS1302的运用

时钟

//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

你可能感兴趣的:(51单片机学习,单片机,嵌入式硬件,51单片机)