开始学单片机到定时器中断模块的时候就想着做出一个像手表一样的小玩意,但现在看来用定时器做时钟有点大材小用了,别人做好的集成电路(DS1302芯片)不香吗,而且用定时器不断进入中断会一直占用单片机的CPU,而且误差较大,DS1302还可以实现掉电走时的功能(这完美匹配了手表的应用)用计时器需要一直通电它才能进入中断走时,并且在DS1302中时间的逻辑配制已经在内部处理完成,例如大月小月平年闰年等
DS1302时钟芯片的介绍
DS1302的引脚配置图
这幅图其实很好理解,这里的VCC2是我们单片机接入的电源(USB)VCC1是我提到实现掉电走时的备用电源(这里要看你的单片机有没有配制),在断电时时钟芯片会自动接到备用电源上,并且还具备涓细电流充电的功能,x1x2接的是32.768KHz晶振,通过晶振发射的脉冲使得时钟芯片接收并开始走时,GND是电源地,CE是芯片使能,只有在芯片使能的状态下,数据的写入与读出才有效(即在使用时把CE置1,用完后置零),IO口是控制数据的串行输入,如同74HC595一样串行输入接收数据,SCLK是穿行始终,通过SCLK的变化才能使IO口读取到数据,在上升沿读取数据
实际的接入
内部的框架图
这里的RAM是用来存储年月日时分秒这些数据的寄存器,在读写数据是都是写入到这些寄存器里面
命令控制字节
寄存器的最后一位控制读写功能,把最后一位置1就是读取DS1302内部的数据,置0就是向DS1302的RAM写入数据,这里发送的字节表示的是表示对哪个地址的寄存器进行怎样的操作
寄存器的定义
这里给出了各个寄存器的位置以及操作它时要怎样做
比如先向IO口发送0x81的命令字节就表示了对秒寄存器进行读取数据
如果是发送0x80就是对秒寄存器进行写入数据
时序的定义
想要发送数据要按照这样的时序才能发送出去
在操作前的初始化
需要把芯片使能关闭(保证在使用前芯片IO口上的数据不被干扰),并且串行时钟置0(便于在操作时置1能产生上升沿且变化),DS1302会有写保护,在使用时需要把它关闭,命令位置在上图的0x8E,即WP表示(关闭写保护后才能对RAM写入数据)对0x8E发送0x00即可关闭
这里我来简单介绍一下这个时序原理图如何实现(写入数据)
首先在使用时我们要对芯片使能,这样才能保证数据的传输,CE=1;
我们要给定一个命令字节(Command)来表达我们要进行的操作是什么,随即把Command通过IO口输入进IO口中,这样我们的时钟芯片就会获得指令,在我们写入数据时还需要把数据按位再传到IO口上,最后IO口会改变你想要操作的寄存器中的数据,那么我们如何实现一位一位地串行输入到IO口上呢?这里我们通过SCLK的变化来实现数据的串行输入,首先我们把数据中的某一位赋给IO口,随后SCLK置高电平(1),数据位移动,随即SCLK置低电平(0),由于我们的51单片机工作速度慢,所以不用延时,这不影响我们的数据传输,置0过后IO口上的数据就会产生移位,,重复这样的操作即可把数据全部移动到IO口上
void DS1302_WriteByte(unsigned char Command,Data)
{
unsigned char i; //这里定义注意要放在函数开头,否则它会报一些奇怪的错误
DS1302_CE=1; //芯片使能开始操作
for(i=0;i<8;i++) //通过循环把命令字节写入到了IO口上
{
DS1302_IO=Command&(0x01<<i); //取出Command的第0位(控制读写功能的命令位)
DS1302_SCLK=1; //上升沿传输数据
DS1302_SCLK=0;
}
for(i=0;i<8;i++) //这里是把要写入的数据写到了IO口上,传到对应寄存器内写入
{
DS1302_IO=Data&(0x01<<i); //取出Command的第0位(控制读写功能的命令位)
DS1302_SCLK=1; //上升沿传输数据
DS1302_SCLK=0;
}
DS1302_IO=0; //这里把IO口重置一下
DS1302_CE=0; //写完数据把使能关闭才能表示写入完成
}
读取数据
读取数据命令字节部分的发送和写入字节相同,注意读取时命令字节最后一位要置1,发送完指令后就是读取RAM里面的数据这里读数据定义一个Data来存,在最后return回去,即把读到的数据传送了回去你想要的变量中,读取数据是由时钟芯片控制数据送到IO口中,这里我们只需要变化串行时钟即可把数据串行通过IO口传回来
unsigned char DS1302_ReadByte(unsigned char Command)
{
unsigned char i,Data=0x00; //初始化Data
DS1302_CE=1; //芯片使能
for(i=0;i<8;i++) //这里也是先发送命令位
{
DS1302_IO=Command&(0x01<<i); //取出Command的第0位(控制读写功能的命令位)
DS1302_SCLK=0;
DS1302_SCLK=1; //上升沿传输数据
}
//这里和74HC595不一样的是数据的传输从SER变成了IO口上(通过脉冲信号SCLK传进IO口)
for(i=0;i<8;i++) //读取RAM里面的数据
{
DS1302_SCLK=1; //给下降沿单片机控制权转移给芯片,随后芯片向单片机发送数据,即读数据
DS1302_SCLK=0;
//这里的判断是如果IO口上的元素是1,则改变Data对应的元素位置,
//是0就不改变,这样实现了数据的读取
if(DS1302_IO)
{
Data|=(0x01<<i);
}
}
DS1302_IO=0;
DS1302_CE=0; //关闭使能
return Data; //返回读取的值
}
需要注意的是在时钟芯片内部存储用4位二进制数来表示1位十进制数(BCD码)
它用0x85来表示十进制中的85
这里在读取的时候进行转换,在读出数据时需要按照16进制转换10进制数一样转换
这里给出转换代码
unsigned char BCD_to_ten(unsigned char Data) //在读出数据时转化成十进制显示出来
{
Data=Data/16*10+Data%16;
return Data;
}
想要实现走时的功能就把读取数据ReadByte放在while函数内,使其不断读取实时的时间,再配合上LCD1602的显示即可做出我们的小闹钟,下面给出LCD1602和DS1302的源码以及main函数里面的实现过程
main函数
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
void main()
{
DS1302_Init();
LCD_Init();
LCD_ShowString(1,1," - - ");
LCD_ShowString(2,1," : : ");
DS1302_SetTime(); //在函数中先设定时间的初值
while(1)
{
DS1302_ReadTime(); //在while里面不断读取并显示
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);
}
}
LCD1602实现代码
#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');
}
}
DS1302时钟显示代码
#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 char DS1302_Time[]={24,5,2,21,33,55,4};
void DS1302_WriteByte(unsigned char Command,Data)
{
unsigned char i;
DS1302_CE=1;
for(i=0;i<8;i++)
{
DS1302_IO=Command&(0x01<<i); //取出Command的第0位(控制读写功能的命令位)
DS1302_SCLK=1; //上升沿传输数据
DS1302_SCLK=0;
}
for(i=0;i<8;i++)
{
DS1302_IO=Data&(0x01<<i); //取出Command的第0位(控制读写功能的命令位)
DS1302_SCLK=1; //上升沿传输数据
DS1302_SCLK=0;
}
DS1302_IO=0;
DS1302_CE=0;
}
void DS1302_Init()
{
DS1302_CE=0;
DS1302_SCLK=0;
DS1302_WriteByte(0x8E,0x00);
}
unsigned char DS1302_ReadByte(unsigned char Command)
{
unsigned char i,Data=0x00;
Command|=0x01;//上面的宏定义是写的地址,在写的地址后面或上一个0x01就变成了读的地址(因为读的地址是写的加一)
DS1302_CE=1;
for(i=0;i<8;i++)
{
DS1302_IO=Command&(0x01<<i); //取出Command的第0位(控制读写功能的命令位)
DS1302_SCLK=0;
DS1302_SCLK=1; //上升沿传输数据
}
//这里和74HC595不一样的是数据的传输从SER变成了IO口上(通过脉冲信号SCLK传进IO口)
for(i=0;i<8;i++)
{
DS1302_SCLK=1; //给下降沿单片机控制权转移给芯片,随后芯片向单片机发送数据,即读数据
DS1302_SCLK=0;
if(DS1302_IO)
{
Data|=(0x01<<i);
}
}
DS1302_IO=0;
DS1302_CE=0;
return Data;
}
unsigned char Ten_to_BCD(unsigned char Data) //写入时间时把十进制数据转化成BCD码存储
{
Data=Data/10*16+Data%10;
return Data;
}
//设置时间
void DS1302_SetTime()
{
DS1302_WriteByte(DS1302_WP,0x00); //关闭写保护
DS1302_WriteByte(DS1302_Year,Ten_to_BCD(DS1302_Time[0]));
DS1302_WriteByte(DS1302_Month,Ten_to_BCD(DS1302_Time[1]));
DS1302_WriteByte(DS1302_Date,Ten_to_BCD(DS1302_Time[2]));
DS1302_WriteByte(DS1302_Hour,Ten_to_BCD(DS1302_Time[3]));
DS1302_WriteByte(DS1302_Minute,Ten_to_BCD(DS1302_Time[4]));
DS1302_WriteByte(DS1302_Second,Ten_to_BCD(DS1302_Time[5]));
DS1302_WriteByte(DS1302_Day,Ten_to_BCD(DS1302_Time[6]));
DS1302_WriteByte(DS1302_WP,0x80); //打开写保护
}
unsigned char BCD_to_ten(unsigned char Data) //在读出数据时转化成十进制显示出来
{
Data=Data/16*10+Data%16;
return Data;
}
void DS1302_ReadTime()
{
DS1302_Time[0]=BCD_to_ten(DS1302_ReadByte(DS1302_Year));
DS1302_Time[1]=BCD_to_ten(DS1302_ReadByte(DS1302_Month));
DS1302_Time[2]=BCD_to_ten(DS1302_ReadByte(DS1302_Date));
DS1302_Time[3]=BCD_to_ten(DS1302_ReadByte(DS1302_Hour));
DS1302_Time[4]=BCD_to_ten(DS1302_ReadByte(DS1302_Minute));
DS1302_Time[5]=BCD_to_ten(DS1302_ReadByte(DS1302_Second));
DS1302_Time[6]=BCD_to_ten(DS1302_ReadByte(DS1302_Day));
}