51单片机使用DS1302时钟芯片实现可调时钟

卢健
2023-12-01

1、实现方法

  • 通过对DS1302时钟芯片进行写入和读出数据,从而实现初始化和调整时间,再通过LCD1602进行显示,即实现可调时钟功能。

2、所需函数模块

  • DS1302.c:该函数主要实现把数据写入DS1302中从而实现时间初始化和时间写入,以及从时钟芯片中读取时间。
  • Timer0Init.c:实现定时器T0的初始化,此处定时器用于调整时间时,调整位闪烁。
  • Key.c:扫描按键,在主函数中体现在:当按键K1按下时,时钟暂停,此时接着按键K2按下进入时钟调节函数(同时LCD1602屏幕上“年”开始以1s为周期闪烁),调节完成后再按下按键K1,则时钟继续运行。当时钟运行时按下按键K1没有反应,想要调整时钟则必须先暂停。(此处时钟暂停并不是真正的暂停,只是显示在LCD1602显示屏上是静止状态,但其实只是没有读取DS1302芯片的寄存器而已)
  • Clock_Adjust.c:时钟调整函数,当进入时钟调整函数后再次按下按键K2,则LCD1602显示屏上从“年”闪烁变为“月”闪烁,再次按下按键K2,“月”闪烁变为“日”闪烁,以此类推,往复。在次函数中若按下按键K1,则break跳出此函数,时钟继续运行。在此函数中若按下按键K3,则所闪烁位数值加一,若按下按键K4,则所闪烁位数值减一。从而实现时钟调节功能。
  • LCD1602:显示函数,将从DS1302芯片中读取到的时间显示在屏幕上。
  • main:主函数,通过调用上面函数模块以及相应逻辑,从而实现时钟显示以及可调性。

3、时钟写入(读取)函数(DS1302.c

  • DS1302.h头文件中代码如下:
#ifndef __DS1302_H__
#define __DS1302_H__
	extern int DS1302_Time[];  //在DS1302.c中定义,此处声明外部可调用,用于主函数以及Clock_Adjust.c
	void DS1302_Init(void);
	void DS1302_WriteByte(unsigned char Command,Data);
	unsigned char DS1302_ReadByte(unsigned char Command);
	void DS1302_SetTime(void);
	void DS1302_ShowTime(void);
	//寄存器写入地址/指令定义
	#define SECOND 0x80
	#define MINUTE 0x82
	#define HOUR   0x84
	#define DATE   0x86
	#define MONTH  0x88
	#define DAY    0x8A
	#define YEAR   0x8C
	#define WP     0x8E
#endif
  • DS1302.c函数代码及注释如下:
#include <STC89C5xRC.H>
#include "DS1302.h"
//引脚定义
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;

//时间数组,索引0~6分别为年、月、日、时、分、秒、星期,设置为有符号的便于<0的判断
int DS1302_Time[]={22,5,2,23,59,55,4};

/**
  * @brief  DS1302初始化
  * @param  无
  * @retval 无
  */
void DS1302_Init(void)
{
	DS1302_SCLK=0;
	DS1302_CE=0;
}

/**
  * @brief  DS1302写一个字节
  * @param  Command 命令字/地址
  * @param  Data 要写入的数据
  * @retval 无
  */
void DS1302_WriteByte(unsigned char Command,Data)
{
	unsigned char i;
	DS1302_CE=1;
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		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;
}

/**
  * @brief  DS1302读一个字节
  * @param  Command 命令字/地址
  * @retval 读出的数据
  */
unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i,Data=0x00;
	Command=Command|0x01;        //将写指令转换为读指令
	DS1302_CE=1;
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=0;
		DS1302_SCLK=1;
	}
	for(i=0;i<8;i++)
	{
		DS1302_SCLK=1;
		DS1302_SCLK=0;
		if(DS1302_IO){Data=Data|(0x01<<i);}
	}
	DS1302_CE=0;
	DS1302_IO=0;         //读取后将IO设置为0,否则读出的数据会出错
	return Data;
}

/**
  * @brief  DS1302设置时间,调用之后,DS1302_Time数组的数字会被设置到DS1302中
  * @param  无
  * @retval 无
  */
void DS1302_SetTime(void)
{
	DS1302_WriteByte(WP,0x00);
	//十进制转BCD码后写入
	DS1302_WriteByte(YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);    
	DS1302_WriteByte(MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
	DS1302_WriteByte(DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
	DS1302_WriteByte(HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
	DS1302_WriteByte(MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
	DS1302_WriteByte(SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
	DS1302_WriteByte(DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
	//DS1302_WriteByte(WP,0x80);  //此处为写保护,这里关闭写保护是为了便于写入数据
}


/**
 * @brief  DS1302读取时间,调用之后,DS1302中的数据会被读取到DS1302_Time数组中
 * @param  无
 * @retval 无
  */
void DS1302_ShowTime(void)
{
	//BCD码转十进制后读取
	DS1302_Time[0]=DS1302_ReadByte(YEAR)/16*10+DS1302_ReadByte(YEAR)%16;
	DS1302_Time[1]=DS1302_ReadByte(MONTH)/16*10+DS1302_ReadByte(MONTH)%16;
	DS1302_Time[2]=DS1302_ReadByte(DATE)/16*10+DS1302_ReadByte(DATE)%16;
	DS1302_Time[3]=DS1302_ReadByte(HOUR)/16*10+DS1302_ReadByte(HOUR)%16;
	DS1302_Time[4]=DS1302_ReadByte(MINUTE)/16*10+DS1302_ReadByte(MINUTE)%16;
	DS1302_Time[5]=DS1302_ReadByte(SECOND)/16*10+DS1302_ReadByte(SECOND)%16;
	DS1302_Time[6]=DS1302_ReadByte(DAY);
}

4、定时器初始化函数(Timer0Init.c

  • 定时器T0初始化函数代码如下:
#include <STC89C5xRC.H>
void Timer0Init()
{
	TMOD=TMOD&0xf0;
	TMOD=TMOD|0x01;//配置定时器T0工作模式
	TF0=0;
	TR0=1;
	TH0=56320/256;
	TL0=56320%256;//计时10ms,11.0592MHz
	EA=1;
	ET0=1;
	IPH=IPH&0xFD;//中断优先级控制寄存器高,置零PT0H位
	PT0=0;       //中断优先级控制寄存器低,置零PT0位
}

当程序中只有一种中断时,中断优先级没有意义,IPHPT0也可以不配置。

5、按键扫描函数(Key.c

  • 用于检测按键按下的键码值,函数代码如下:
#include <STC89C5xRC.H>
#include "Delay.h"

/**
 * @brief  获取独立按键键码
 * @param  无
 * @retval 按下按键的键码,范围:0~4,无按键按下时返回值为0
  */
unsigned char Key()
{
	unsigned char KeyNumber=0;
	
	if(P31==0){Delay(20);while(P31==0);Delay(20);KeyNumber=1;}
	if(P30==0){Delay(20);while(P30==0);Delay(20);KeyNumber=2;}
	if(P32==0){Delay(20);while(P32==0);Delay(20);KeyNumber=3;}
	if(P33==0){Delay(20);while(P33==0);Delay(20);KeyNumber=4;}
	
	return KeyNumber;
}
  • 继承Delay函数代码如下:
#include <intrins.h>                //_nop_()函数头文件
void Delay(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms--)
	{
		_nop_();
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
}

6、时钟调整函数(Clock_Adjust.c

  • 定时器函数用于此函数中,其中断函数主要功能:每1s进入中断一次,每中断一次即改变一次LCD1602屏幕显示的内容,从而达到调节时钟时,其所调位的闪烁功能,中断函数代码如下:
void Timer0_Rountine(void) interrupt 1
{
	static unsigned int cont;
	cont++;
	TH0=56320/256;
	TL0=56320%256;    //重装载值,溢出率为10ms/次
	if(cont>100)      //每隔1s转换模式
	{
		cont=0;
		switch(MODE)
		{
			case 0:MODE=1;break;
			case 1:MODE=0;break;
		}
	}
}
  • 时钟调整函数代码如下:
#include <STC89C5xRC.H>
#include "Timer0Init.h"
#include "Key.h"
#include "DS1302.h"
#include "LCD1602.h"
unsigned char cont2,MODE;   //cont2为记录按键K2按下的次数,同时也是所调时钟的闪烁位置
extern unsigned char KeyNum;
void Clock_Adjust(void)
{
	Timer0Init();
	while(1)
	{
		if(MODE==0)
		{
			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,16,DS1302_Time[6],1);
		}
		if(MODE==1)
		{
			switch(cont2)
			{
				case 0:LCD_ShowString(1,1,"  "),LCD_ShowNum(2,16,DS1302_Time[6],1);break;
				case 1:LCD_ShowString(1,4,"  "),LCD_ShowNum(1,1,DS1302_Time[0],2);break;
				case 2:LCD_ShowString(1,7,"  "),LCD_ShowNum(1,4,DS1302_Time[1],2);break;
				case 3:LCD_ShowString(2,1,"  "),LCD_ShowNum(1,7,DS1302_Time[2],2);break;
				case 4:LCD_ShowString(2,4,"  "),LCD_ShowNum(2,1,DS1302_Time[3],2);break;
				case 5:LCD_ShowString(2,7,"  "),LCD_ShowNum(2,4,DS1302_Time[4],2);break;
				case 6:LCD_ShowString(2,16," "),LCD_ShowNum(2,7,DS1302_Time[5],2);break;
			}
		}
		KeyNum=Key();
		if(KeyNum==2)
		{
			cont2++;          //记录按键K2按下的次数,同时定位调节时钟所调整的位置
			if(cont2>6)
				cont2=0;
		}
		if(KeyNum==3)
		{
			switch(cont2)
			{
				case 0:   //调节年份++            
				{
					DS1302_Time[0]++;
					if(DS1302_Time[0]>99)
						DS1302_Time[0]=0;
					if(DS1302_Time[0]%4!=0&&DS1302_Time[1]==2&&DS1302_Time[2]>28)
						DS1302_Time[2]=1;
					break;
				}
				case 1:   //调节月份++
				{
					DS1302_Time[1]++;
					if(DS1302_Time[1]>12)
						DS1302_Time[1]=1;
					if((DS1302_Time[1]==4||DS1302_Time[1]==6||DS1302_Time[1]==9||DS1302_Time[1]==11)&&DS1302_Time[2]>30)
					{
						DS1302_Time[2]=1;
					}
					else if(DS1302_Time[0]%4==0&&DS1302_Time[1]==2&&DS1302_Time[2]>29)
					{
						DS1302_Time[2]=1;
					}
					else if(DS1302_Time[0]%4!=0&&DS1302_Time[1]==2&&DS1302_Time[2]>28)
					{
						DS1302_Time[2]=1;
					}
					break;
				}
				case 2:      //调节日++
				{
					DS1302_Time[2]++;
					if(DS1302_Time[1]==4||DS1302_Time[1]==6||DS1302_Time[1]==9||DS1302_Time[1]==11)
					{
						if(DS1302_Time[2]>30)
							DS1302_Time[2]=1;
					}
					else 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]>31)
							DS1302_Time[2]=1;
					}
					else if(DS1302_Time[1]==2)
					{
						if(DS1302_Time[0]%4==0&&DS1302_Time[2]>29)
						{
							DS1302_Time[2]=1;
						}
						else if(DS1302_Time[0]%4!=0&&DS1302_Time[2]>28)
						{
							DS1302_Time[2]=1;
						}
					}
					break;
				}
				case 3:        //调节时++
				{
					DS1302_Time[3]++;
					if(DS1302_Time[3]>23)
						DS1302_Time[3]=0;
					break;
				}
				case 4:        //调节分++
				{
					DS1302_Time[4]++;
					if(DS1302_Time[4]>59)
						DS1302_Time[4]=0;
					break;
				}
				case 5:        //调节秒++
				{
					DS1302_Time[5]++;
					if(DS1302_Time[5]>59)
						DS1302_Time[5]=0;
					break;
				}
				case 6:        //调节星期++
				{
					DS1302_Time[6]++;
					if(DS1302_Time[6]>7)
						DS1302_Time[6]=1;
					break;
				}
			}
		}
		if(KeyNum==4)
		{
			switch(cont2)      //调节年份--
			{
				case 0:
				{
					DS1302_Time[0]--;
					if(DS1302_Time[0]<0)
						DS1302_Time[0]=99;
					if(DS1302_Time[0]%4!=0&&DS1302_Time[1]==2&&DS1302_Time[2]>28)
						DS1302_Time[2]=1;
					break;
				}
				case 1:         //调节月份--
				{
					DS1302_Time[1]--;
					if(DS1302_Time[1]<1)
						DS1302_Time[1]=12;
					if((DS1302_Time[1]==4||DS1302_Time[1]==6||DS1302_Time[1]==9||DS1302_Time[1]==11)&&DS1302_Time[2]>30)
					{
						DS1302_Time[2]=1;
					}
					else if(DS1302_Time[0]%4==0&&DS1302_Time[1]==2&&DS1302_Time[2]>29)
					{
						DS1302_Time[2]=1;
					}
					else if(DS1302_Time[0]%4!=0&&DS1302_Time[1]==2&&DS1302_Time[2]>28)
					{
						DS1302_Time[2]=1;
					}
					break;
				}
				case 2:         //调节日--
				{
					DS1302_Time[2]--;
					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;
					}
					else 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]==2)
					{
						if(DS1302_Time[0]%4==0&&DS1302_Time[2]<1)
						{
							DS1302_Time[2]=29;
						}
						else if(DS1302_Time[0]%4!=0&&DS1302_Time[2]<1)
						{
							DS1302_Time[2]=28;
						}
					}
					break;
				}
				case 3:          //调节时--
				{
					DS1302_Time[3]--;
					if(DS1302_Time[3]<0)
						DS1302_Time[3]=23;
					break;
				}
				case 4:          //调节分--
				{
					DS1302_Time[4]--;
					if(DS1302_Time[4]<0)
						DS1302_Time[4]=59;
					break;
				}
				case 5:          //调节秒--
				{
					DS1302_Time[5]--;
					if(DS1302_Time[5]<0)
						DS1302_Time[5]=59;
					break;
				}
				case 6:          //调节星期--
				{
					DS1302_Time[6]--;
					if(DS1302_Time[6]<1)
						DS1302_Time[6]=7;
					break;
				}
			}
		}
		if(KeyNum==1)       //跳出闪烁时钟循环
		{
			cont2=0;
			break;                
		}
	}
}

7、显示函数(LCD1602.c

  • 显示函数代码如下:
#include <STC89C5xRC.H>
#include <intrins.h>//延时函数_nop_()头文件
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,11.0592MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;
	_nop_();
	i = 2;
	j = 199;
	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  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');
	}
}

8、主函数(main.c

  • 主函数实现的内容,以及调用函数逻辑:1.初始化LCD1602;2.初始化DS1302;3.设置初始时钟数值;4.进入死循环开始循环扫描DS1302时钟芯片中的时钟数值并读出,直到K1按键按下,停止读数据,此时若再次按下K1则重新开始继续读取数据,若按下按键K2,则进入调节时钟函数完成相应功能(详情见Clock_Adjust.c函数)

  • 主函数代码如下:

#include <STC89C5xRC.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Key.h"
#include "Clock_Adjust.h"
extern int DS1302_Time[];
unsigned char KeyNum;
void main()
{
	LCD_Init();
	DS1302_Init();
	LCD_ShowString(1,3,"-  -");
	LCD_ShowString(2,3,":  :");
	DS1302_SetTime();
	while(1)
	{
		DS1302_ShowTime();
		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,16,DS1302_Time[6],1);
		KeyNum=Key();
		if(KeyNum==1)        //满足条件时进入死循环
		{                    
			while(1)
			{
				KeyNum=Key();
				if(KeyNum==1)        //死循环中K1键再次按下时跳出死循环
					break;
				if(KeyNum==2)        //死循环中K2键按下时进入时钟调节函数
				{
					Clock_Adjust();  //进入用按键调节时钟函数
					break;           //跳出暂停时钟循环
				}
			}
			DS1302_SetTime();
		}
	}
}
 类似资料: