基于51单片机的电子万年历的设计-源代码

电子万年历的设计目录

电子万年历的设计

电子万年历的系统整体设计框图

所需要的元件

DS18B20模块

DS18B20的特点

DS18B20内部结构

DS18B20管脚排列

 DS1302模块

SPI时序的初步认识

SPI的四种模式

实时时钟芯片DS1302

5DS1302寄存器介绍

液晶显示模块LCD1602

学校的开发板就有时钟芯片和温度检测模块

具体的电路图 (黄圈的就是)

万年历其实很简单

系统总流程图

温度模块程序流程图

DS1302时钟程序流程图

LCD显示程序流程图

源代码

源代码文件

源代码仿真资源


电子万年历的设计

设计要求:

利用51单片机、专用时钟芯片DS1302、DS18B20数字温度采集器及1602液晶显示器件设计一个万年历

  1. 正确显示年月日,时分秒,星期等信息
  2. 显示环境温度
  3. 能够通过按键调整时间和设置闹钟

电子万年历的系统整体设计框图

为使时钟走时与标准时间一致,校时电路是必不可少的,键盘模块用来校正液晶上显示的时间;温度传感器则用来检测当前的环境温度;STC89C52单片机通过输出各种电脉冲信号来驱动控制各部分正常工作;而系统的时间、温度等数据则最终通过液晶模块显示出来。

本电路是以STC89C52单片机为控制核心,该芯片具有在线编程功能,功耗低,能在3.3V的超低压下工作;时钟芯片采用DS1302,它是一款高性能、低功耗、自带RAM的实时时钟芯片,具有使用寿命长,精度高和功耗低等特点,同时具有掉电自动保存功能,可以对年、月、日、星期、时、分、秒进行计时,具有闰年补偿功能,其工作电压为2.5V~5.5V;温度检测模块由DS18B20构成,它采用独特的单线接口仅需一个端口引脚进行通讯, 具有测量精度高、测量范围广等优点,其测温范围在-55~+125℃,工作电压为3v~5.5v;显示部份使用1602液晶显示屏来实现,该显示屏具有低功耗、寿命长、可靠性高的特点,其工作电压为5v。

所需要的元件

STC89C52单片机

时钟芯片DS1302

温度检测模块DS18B20

LCD 1602

我所采用的51单片机是学校的开发板

DS18B20模块

DS18B20是常用的数字温度传感器,其输出的是数字信号,具有体积小,硬件开销低,抗干扰能力强,精度高的特点。  DS18B20数字温度传感器接线方便,封装成后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式,型号多种多样,有LTM8877,LTM8874等等。

        采用单总线的接口方式。与微处理器连接时仅需要一条口线即可实现微处理器与 DS18B20 的双向通讯。单总线具有经济性好,抗干扰能力强,适合于恶劣环境的现场温度测量。

        测量温度范围宽,测量精度高 DS18B20 的测量范围为 -55 ℃ ~+ 125 ℃ ; 在 -10~+ 85°C范围内,精度为 ± 0.5°C 。

DS18B20的特点

DS18B20 单线数字温度传感器,即“一线器件”,其具有独特的优点:

( 1 )采用单总线的接口方式 与微处理器连接时 仅需要一条口线即可实现微处理器与 DS18B20 的双向通讯。 单总线具有经济性好,抗干扰能力强,适合于恶劣环境的现场温度测量,使用方便等优点,使用户可轻松地组建传感器网络,为测量系统的构建引入全新概念。

( 2 )测量温度范围宽,测量精度高 DS18B20 的测量范围为 -55 ℃ ~+ 125 ℃ ; 在 -10~+ 85°C 范围内,精度为 ± 0.0625°C 。

( 3 )在使用中不需要任何外围元件。

( 4 )持多点组网功能 多个 DS18B20 可以并联在惟一的单线上,实现多点测温。

( 5 )供电方式灵活 DS18B20 可以通过内部寄生电路从数据线上获取电源。因此,当数据线上的时序满足一定的要求时,可以不接外部电源,从而 使系统结构更趋简单,可靠性更高。

( 6 )测量参数可配置 DS18B20 的测量分辨率可通过程序设定 9~12 位。

( 7 ) 负压特性 电源极性接反时,温度计不会因发热而烧毁,但不能正常工作。

( 8 )掉电保护功能 DS18B20 内部含有 EEPROM ,在系统掉电以后,它仍可保存分辨率及报警温度的设定值。

DS18B20 具有体积更小、适用电压更宽、更经济、可选更小的封装方式,更宽的电压适用范围,适合于构建自己的经济的测温系统,因此也就被设计者们所青睐。

DS18B20内部结构

      主要由4部分组成:64 位ROM、温度传感器、非挥发的温度报警触发器TH和TL、配置寄存器。ROM中的64位序列号是出厂前被光刻好的,它可以看作 是该DS18B20的地址序列码,每个DS18B20的64位序列号均不相同。64位ROM的排的循环冗余校验码(CRC=X^8+X^5+X^4+1)。 ROM的作用是使每一个DS18B20都各不相同,这样就可以实现一根总线上挂接多个DS18B20的目的。

DS18B20管脚排列

DS18B20的管脚排列

1.  GND为电源 地;

2.  DQ为数字信号输入/输出端;

3.  VDD为外接供电电源输入端(在寄生电源接线方式时接地)

 DS1302模块

SPI时序的初步认识

  • SPI :Serial Peripheral Interface 是一种高速的、全双工、同步通信总线。
  • 应用:单片机和EEPROM、实时时钟、数字信号处理器
  • SSEL:片选,也写做SCS,从设备片选使能信号。
  • SCLK:时钟,也写作SCK,由主机产生,和SCL类似
  • MOSI:主机输出从机输入,Master Output/Slave Input,主机给从机发送指令或者数据的通道。
  • MISO:主机输入从机输出,Master Input/Slave Output,主机读取从机的状态或者数据的通道

SPI的四种模式

  • CPOL:Clock Polarity,即时钟极性。
    • 空闲状态为高电平,则CPOL=1,空闲状态为低电平,则CPOL=0。
  • CPHA:Clock Phase,即时钟相位。
    • CPHA=1表示数据的输出是在一个时钟周期的第一个沿。CPHA=0表示数据的采样是在一个时钟周期的第一个沿。

实时时钟芯片DS1302

  • 1、DS1302是一个实时时钟芯片,可以提供秒、分、小时、日期、月、年等信息,并且还有软件自动调整的能力,可以通过配置AM/PM来决定采用24小时格式还是12小时格式。
  • 2、拥有31字节数据存储RAM。
  • 3、串行I/O通信方式,相对并行来说比较节省IO口的使用。
  • 4、DS1302的工作电压比较宽,在2.0~5.5V的范围内都可以正常工作。
  • 5、DS1302这种时钟芯片功耗一般都很低,它在工作电压2.0V的时候,工作电流小于300nA。
  • 6、DS1302共有8个引脚,有两种封装形式,一种是DIP-8封装,芯片宽度(不含引脚)是300mil,一种是SOP-8封装,有两种宽度,一种是150mil,一种是208mil。
  • 7、当供电电压是5V的时候,兼容标准的TTL电平标准,这里的意思是,可以完美的和单片机进行通信。
  • 8、由于DS1302是DS1202的升级版本,所以所有的功能都兼容DS1202。此外DS1302有两个电源输入,一个是主电源,另外一个是备用电源,比如可以用电池或者大电容,这样做是为了在系统掉电的情况下,我们的时钟还会继续走。

引脚编号

引脚名称

引脚功能

1

Vcc2

主电源引脚,当Vcc2比Vcc1高0.2V以上时,DS1302由Vcc2供电,当Vcc2低于Vcc1时,由Vcc1供电。

2

X1

这两个引脚需要接一个32.768K的晶振,给DS1302提供一个基准。特别注意,要求这个晶振的引脚负载电容必须是6pF,而不是要加6pF的电容。如果使用有源晶振的话,接到X1上即可,X2悬空。

3

X2

4

GND

接地。

5

CE

DS1302的使能输入引脚。当读写DS1302的时候,这个引脚必须是高电平,DS1302这个引脚内部有一个40k的下拉电阻。

6

I/O

这个引脚是一个双向通信引脚,读写数据都是通过这个引脚完成。DS1302这个引脚的内部含有一个40k的下拉电阻。

7

SCLK

输入引脚。SCLK是用来作为通信的时钟信号。DS1302这个引脚的内部含有一个40k的下拉电阻。

8

Vcc1

备用电源引脚。

5DS1302寄存器介绍

液晶显示模块LCD1602

本设计中由于要对时间、温度进行显示,所以选择液晶显示屏1602模块作为输出。1602字符型LCD通常有14条引脚线或16条引脚线的LCD,多出来的2条线是背光电源线。它可以显示两行,每行16个字符,采用单+5V电源供电,外围电路配置简单,价格便宜,具有很高的性价比。1602液晶模块内部的字符发生存储器(CGROM)已经存储了160个不同的点阵字符图形,这些字符有:阿拉伯数字、英文字母的大小写、常用的符号、和日文假名等,每一个字符都有一个固定的代码,比如大写的英文字母“A”的代码是01000001B(41H),显示时模块把地址41H中的点阵字符图形显示出来,我们就能看到字母“A”。将L1602的RS端和P2.0,R/W端和P2.1, E 端和P2.2相连,当RS=0时,对LCD1602写入指令;当RS=1时,对LCD1602写入数据。当R/W端接高电平时芯片处于读数据状态,反之处于写数据状态,E端为使能信号端。当R/W为高电平,E端也为高电平,RS为低电平时,液晶显示屏显示需要显示的示数。

学校的开发板就有时钟芯片和温度检测模块

具体的电路图 (黄圈的就是)

万年历其实很简单

系统总流程图

电子万年历设计题目的系统总流程图如图8.1所示。流程图分析:首先系统初始化,系统开始运行,当有设置键按下时进入修改时间模式,无按键按下时读取时间、温度等数据送入液晶屏显示;在修改时间模式下设置时间完成后再送数据到液晶屏显示。

温度模块程序流程图

温度读取流程图如图8.2所示。流程图分析:开始进入初始化DS18B20,就是通过主机拉低单线产生复位脉冲然后释放该线,如果有应答脉冲,即发起ROM命令当成功的执行操作命令后,就使用Convert T命令即开始温度转换,当转换完后,又初始化DS18B20是否有应答脉冲,若有,就发起Read Scratchpad(读取暂存器和CRC字节)命令,既同时读出第1,2个字节,即为温度的数据。

DS1302时钟程序流程图

时钟流程图如图8.3所示。流程图分析:DS1302开始计时时,首先进行初始化,当有中断信号时,读取时钟芯片的数据送入液晶屏显示。这时若有设置键按下时,进行时间修改,完成后将数据送入时钟芯片;若没有按键按下,则直接存入EPROM,送入液晶屏显示。

LCD显示程序流程图

显示程序流程图如图8.4。流程图分析:首先对1602显示屏进行初始化(初始化大约持续10ms左右),然后检查忙信号,若BF=0,则获得显示RAM的地址,写入相应的数据显示;若BF=1,则代表模块正在进行内部操作,不接受任何外部指令和数据,直到BF=0为止。

源代码

/*
对了,有问题要自己学会找问题,别什么都来问我,我不是你们爹
2021.6.18
端口自己改哈
*/
#include <reg52.h>
#define uchar unsigned char
#define uint  unsigned int
#include "eeprom52.h"
#include "intrins.h"
bit flag_200ms ;
bit flag_100ms ;
sbit beep = P2^3;	  //蜂鸣器定义
bit flag_beep_en;
uint clock_value;     //用作闹钟用的
sbit dq = P2^2;	  //18b20 IO口
uint temperature ;    //温度
uchar menu_1,menu_2;
uchar key_time,flag_value;      //连加中间变量
bit key_500ms  ;
uchar n_nian,n_yue,n_ri,RP;		//显示的函数
#include "ds1302.h"
#include "lcd1602.h"
/******************把数据保存到单片机内部eeprom中******************/
void write_eeprom()
{
	SectorErase(0x2000);
	byte_write(0x2000, fen1);
	byte_write(0x2001, shi1);
	byte_write(0x2002, open1);
	byte_write(0x2058, a_a);	
}

/******************把数据从单片机内部eeprom中读出来*****************/
void read_eeprom()
{
	fen1  = byte_read(0x2000);
	shi1  = byte_read(0x2001);
	open1  = byte_read(0x2002);
	a_a      = byte_read(0x2058);
}

/**************开机自检eeprom初始化*****************/
void init_eeprom()
{
	read_eeprom();		//先读
	if(a_a != 1)		//新的单片机初始单片机内问eeprom
	{
		fen1  = 3;
		shi1  = 8;
		open1  = 1;
		a_a = 1;
		write_eeprom();	   //保存数据
	}	
}

/***********************18b20初始化函数*****************************/
void init_18b20()
{
	bit q;
	dq = 1;				//把总线拿高
	delay_uint(1);	    //15us
	dq = 0;				//给复位脉冲
	delay_uint(80);		//750us
	dq = 1;				//把总线拿高 等待
	delay_uint(10);		//110us
	q = dq;				//读取18b20初始化信号
	delay_uint(20);		//200us
	dq = 1;				//把总线拿高 释放总线
}

/*************写18b20内的数据***************/
void write_18b20(uchar dat)
{
	uchar i;
	for(i=0;i<8;i++)
	{					 //写数据是低位开始
		dq = 0;			 //把总线拿低写时间隙开始 
		dq = dat & 0x01; //向18b20总线写数据了
		delay_uint(5);	 // 60us
		dq = 1;			 //释放总线
		dat >>= 1;
	}	
}

/*************读取18b20内的数据***************/
uchar read_18b20()
{
	uchar i,value;
	for(i=0;i<8;i++)
	{
		dq = 0;			 //把总线拿低读时间隙开始 
		value >>= 1;	 //读数据是低位开始
		dq = 1;			 //释放总线
		if(dq == 1)		 //开始读写数据 
			value |= 0x80;
		delay_uint(5);	 //60us	读一个时间隙最少要保持60us的时间
	}
	return value;		 //返回数据
}

/*************读取温度的值 读出来的是小数***************/
uint read_temp()
{
	uint value;
	uchar low;			   //在读取温度的时候如果中断的太频繁了,就应该把中断给关了,否则会影响到18b20的时序
	init_18b20();		   //初始化18b20
	write_18b20(0xcc);	   //跳过64位ROM
	write_18b20(0x44);	   //启动一次温度转换命令
	delay_uint(50);		   //500us

	init_18b20();		   //初始化18b20
	
	write_18b20(0xcc);	   //跳过64位ROM
	write_18b20(0xbe);	   //发出读取暂存器命令
	
	EA = 0;
	low = read_18b20();	   //读温度低字节
	value = read_18b20();  //读温度高字节
	EA = 1;
	value <<= 8;		   //把温度的高位左移8位
	value |= low;		   //把读出的温度低位放到value的低八位中
	value *= 0.625;	       //转换到温度值 小数
	return value;		   //返回读出的温度 带小数
}




void delay_1ms(uint q)
{
	uint i,j;
	for(i=0;i<q;i++)
		for(j=0;j<120;j++);
}

void write_week(uchar hang,uchar add,uchar week)//写星期函数
{
	if(hang==1)   
		write_com(0x80+add);
	else
		write_com(0x80+0x40+add);	  
	switch(week)
	{
		case 1:write_data('M');//星期数为1时,显示
			   write_data('O');
			   write_data('N');
			   break;
	   
		case 2:write_data('T');//星期数据为2时显示
			   write_data('U');
			   write_data('E');
			   break;
		
		case 3:write_data('W');//星期数据为3时显示
			   write_data('E');
			   write_data('D');
			   break;
		
		case 4:write_data('T');//星期数据为4是显示
			   write_data('H');
			   write_data('U');
			   break;
		
		case 5:write_data('F');//星期数据为5时显示
			   write_data('R');
			   write_data('I');
			   break;
		
		case 6:write_data('S');//星期数据为6时显示
			   write_data('T');
			   write_data('A');
			   break;
		
		case 7:write_data('S');//星期数据为7时显示
			   write_data('U');
			   write_data('N');
			   break;
	}
}

void init_1602_ds1302()
{
		write_sfm2_ds1302(1,1,shi);		   //显示时
		write_sfm2_ds1302(1,4,fen);		   //显示分
		write_sfm2_ds1302(1,7,miao);	   //显示秒
		write_week(2,12,week);//显示星期
		write_sfm3_18B20(1,12,temperature);	   //显示温度
		write_sfm2_ds1302(2,2,nian);   //显示年
		write_sfm2_ds1302(2,5,yue);	   //显示月	
		write_sfm2_ds1302(2,8,ri);	   //显示日 	
		write_sfm2_ds1302(2,8,ri);	   //显示日 		
		write_sfm2_ds1302(1,10,RP);//显示闰年平年
}									   


void init_time0()	  //定时器0初始化程序
{
	EA   = 1;	 	  //开总中断
	TMOD = 0X01;	  //定时器0、工作方式1
	ET0  = 1;		  //开定时器0中断 
	TR0  = 1;		  //允许定时器0定时
}


void menu_dis()//闹钟报警
{
	static uchar mm,value;
	if(flag_100ms == 1)		  //100ms执行一次
	{
		flag_100ms = 0;
		if(open1 == 1)	//如果闹钟打开
		{
			if((miao == 0) && (fen == fen1) && (shi == shi1)) 
			{		
				flag_beep_en = 1;	//有报警蜂鸣器响标志位				
			}		
			if(flag_beep_en == 1)	//闹钟以被打开
			{
				clock_value++;
				if(clock_value <= 30)	 
					beep = ~beep;	   //蜂鸣器叫3秒
				else if(clock_value > 30)
				{
					beep = 1;		    //蜂鸣器停1秒
					if(clock_value > 40)
						clock_value = 0;

					}
				}
				//  1 分钟后关闭闹钟
				value ++;
				if(value >= 10)
				{
					value = 0;
					mm++;
					if(mm >= 60)
					{
						mm = 0;
						flag_beep_en = 0;
						beep = 1;	
					}
				}					
			}
		}	
	}


uchar key_can;

void key()	 //独立按键程序
{
	static uchar key_new;
	key_can = 20;
	P3 |= 0xff;
	if((P3 & 0xff) != 0xff)		//按键按下
	{
		delay_1ms(1);	     	//按键消抖动
		if(((P3 & 0xff) != 0xff) && (key_new == 1))
		{						//确认是按键按下
			key_new = 0;
			switch(P3 & 0xff)
			{
				case 0xef:  key_can = 4;  break;	 //得到按键值
				case 0xdf:  key_can = 3;  break;	 //得到按键值
				case 0xbf:  key_can = 2;  break;	 //得到按键值
				case 0x7f:  key_can = 1;  break;	 //得到按键值
			}
		}			
	}
	else 
		key_new = 1;	
}




void key_with()//设置函数
{
	if(key_can == 1)	//设置键
	{
		menu_1++;
		if(menu_1 == 1)	  //设置时间
		{
			menu_2 = 1;
			write_string(1,0,"    :  :    W:  ");			
			write_string(2,0," 20  -  -       ");	
		}
		if(menu_1 == 2)	  //设置闹钟
		{
			menu_2 = 1;
			write_string(1,0,"   set clock    ");			
			write_string(2,0,"    Y  00:00      ");	
		}
		if(menu_1 > 2)    //回到正常显示
		{
			menu_1 = 0;
			write_guanbiao(1,2,0);	 //关闭光标
			init_1602_dis_csf();      //初始化液晶显示		
		}
	}
	if(key_can == 2)	//选择键
	{
		flag_200ms = 1;
		if(menu_1 == 1)		  //设置时间
		{
			menu_2 ++;
			if(menu_2 > 7)
				menu_2 = 1;
		}
		if(menu_1 == 2)		 //设置闹钟
		{
			menu_2 ++;
			if(menu_2 > 3)
				menu_2 = 1;				
		}
	}
	if(menu_1 == 1)
	{
		if(menu_2 == 1)		  //设置时
		{
			if(key_can == 3)	//加
			{
				shi+=0x01;
				if((shi & 0x0f) >= 0x0a)
					shi = (shi & 0xf0) + 0x10;	   ///***shi & 0xf0低四位清零,(shi & 0xf0) + 0x10向高位进1(高四位加1)***///				if(shi >= 0x24)
					shi = 0;
			}		
			if(key_can == 4)	//减
			{
				if(shi == 0x00)	  
					shi = 0x24;
				if((shi & 0x0f) == 0x00)  
					shi = (shi | 0x0a) - 0x10;	 ///***如果个位为0,高四位减1***///
				shi -- ; 
			}	  				
		}
		if(menu_2 == 2)		  //设置分
		{
			if(key_can == 3)	//加
			{
				fen+=0x01;
				if((fen & 0x0f) >= 0x0a)
					fen = (fen & 0xf0) + 0x10;
				if(fen >= 0x60)
					fen = 0;
			}		
			if(key_can == 4)	//减	  
			{
				if(fen == 0x00)
					fen = 0x5a;
				if((fen & 0x0f) == 0x00)
					fen = (fen | 0x0a) - 0x10;
				fen -- ;
			}	
		}
		if(menu_2 == 3)		  //设置秒
		{
			if(key_can == 3)	//加
			{
				miao+=0x01;
				if((miao & 0x0f) >= 0x0a)
					miao = (miao & 0xf0) + 0x10;
				if(miao >= 0x60)
					miao = 0;
			}	
			if(key_can == 4)	//减	  
			{
				if(miao == 0x00)
					miao = 0x5a;
				if((miao & 0x0f) == 0x00)
					miao = (miao | 0x0a) - 0x10;
				miao -- ;			
			}
		}
		if(menu_2 == 4)		  //设置星期
		{
			if(key_can == 3)	//加
			{
	    		week+=0x01;
				if((week & 0x0f) >= 0x0a)
					week = (week & 0xf0) + 0x10;
				if(week >= 0x08)
					week = 1;
			}		
			if(key_can == 4)	//减	  
			{
				if(week == 0x01)
					week = 0x08;
				if((week & 0x0f) == 0x00)
					week = (week | 0x0a) - 0x10;
				week -- ;
			}	
		}
		if(menu_2 == 5)		  //设置年
		{
			if(key_can == 3)	//加
			{
		    	nian+=0x01;
				if((nian & 0x0f) >= 0x0a)
					nian = (nian & 0xf0) + 0x10;
				if(nian >= 0x9a)
					nian = 1;
			}		
			if(key_can == 4)	//减	  
			{
				if(nian == 0x01)
					nian = 0x9a;
				if((nian & 0x0f) == 0x00)
					nian = (nian | 0x0a) - 0x10;
				nian -- ;		
			}	
		}
		if(menu_2 == 6)		  //设置月
		{
			if(key_can == 3)	//加
			{
		    	yue+=0x01;
				if((yue & 0x0f) >= 0x0a)
					yue = (yue & 0xf0) + 0x10;
				if(yue >= 0x13)
					yue = 1;
			}		
			if(key_can == 4)	//减	  
			{
				if(yue == 0x01)
					yue = 0x13;
				if((yue & 0x0f) == 0x00)
					yue = (yue | 0x0a) - 0x10;
				yue -- ;					
			}	
		}
		if(menu_2 == 7)		  //设置日
		{
			if(key_can == 3)	//加
			{
	    	ri+=0x01;
			if((ri & 0x0f) >= 0x0a)
				ri = (ri & 0xf0) + 0x10;
			if(ri >= 0x32)
				ri = 0;			
			}		
			if(key_can == 4)	//减	  
			{
				if(ri == 0x01)
					ri = 0x32;
				if((ri & 0x0f) == 0x00)
					ri = (ri | 0x0a) - 0x10;
				ri -- ;			
			}	
		}
		write_sfm2_ds1302(1,2,shi);	   ///第一行第二字节开始显示时///
		write_sfm2_ds1302(1,5,fen);	   //显示分
		write_sfm2_ds1302(1,8,miao);	   //显示秒
		write_sfm1(1,14,week);	   //显示星期					
		write_sfm2_ds1302(2,3,nian);	   //显示年
		write_sfm2_ds1302(2,6,yue);	   //显示月
		write_sfm2_ds1302(2,9,ri);	   //显示日

		switch(menu_2)	   // 光标显示
		{
			case 1:  write_guanbiao(1,2,1);  break;
			case 2:  write_guanbiao(1,5,1);  break;
			case 3:  write_guanbiao(1,8,1);  break;
			case 4:  write_guanbiao(1,14,1);  break;
			case 5:  write_guanbiao(2,3,1);  break;
			case 6:  write_guanbiao(2,6,1);  break;
			case 7:  write_guanbiao(2,9,1);  break;
		}
		write_time();	   //把时间写进去
	}	
/***************设置闹钟*********************/
	if(menu_1 == 2)
	{
		if(menu_2 == 1)		  //设置闹钟开关
		{
			if(key_can == 3)	
			{
				open1 = 1;	  //闹钟开
			}		
			if(key_can == 4)	
			{
				open1 = 0;	  //闹钟关
			}	  				
		}
		if(menu_2 == 2)		  //设置闹钟时
		{
			if(key_can == 3)	//加
			{
		    	shi1+=0x01;
				if((shi1 & 0x0f) >= 0x0a)
					shi1 = (shi1 & 0xf0) + 0x10;
				if(shi1 >= 0x24)
					shi1 = 0;
			}		
			if(key_can == 4)	//减	  
			{
				if(shi1 == 0x00)
					shi1 = 0x5a;
				if((shi1 & 0x0f) == 0x00)
					shi1 = (shi1 | 0x0a) - 0x10;
				shi1 -- ;
			}	
		}
		if(menu_2 == 3)		  //设置秒
		{
			if(key_can == 3)	//加
			{
	    		fen1+=0x01;
				if((fen1 & 0x0f) >= 0x0a)
					fen1 = (fen1 & 0xf0) + 0x10;
				if(fen1 >= 0x60)
					fen1 = 0;
			}	
			if(key_can == 4)	//减	  
			{
				if(fen1 == 0x00)
					fen1 = 0x5a;
				if((fen1 & 0x0f) == 0x00)
					fen1 = (fen1 | 0x0a) - 0x10;
				fen1 -- ;			
			}
		}
		if(open1 == 1)
			write_string(2,4,"Y");	
		else 
			write_string(2,4,"N");	
		write_sfm2_ds1302(2,7,shi1);	   //显示闹钟时
		write_sfm2_ds1302(2,10,fen1);	   //显示闹钟分
		switch(menu_2)	   // 光标显示
		{
			case 1:  write_guanbiao(2,4,1);  break;
			case 2:  write_guanbiao(2,7,1);  break;
			case 3:  write_guanbiao(2,10,1);  break;
		}	
		write_eeprom();     //保存闹钟时间
	}			
}

/*****************主函数********************/
void main()
{	
	P0 = P1 = P2 = P3 = 0xff;
	init_time0();
	init_1602();		 //lcd1602初始化
	init_1602_dis_csf(); //lcd1602初始化显示
	init_eeprom();       //开始初始化保存的数据
	temperature = read_temp();	//先读出温度的值
	delay_1ms(650);
	temperature = read_temp();	//先读出温度的值
	
	while(1)
	{//判断闰年。平年 RP标志位
		if(nian%400==0)
        RP=0;
  else
   {
        if(nian%4==0&&nian%100!=0)
           RP=0;
        else
            RP=1;
    }
		key();			 //按键程序
		if(key_can < 10)
		{
			if(flag_beep_en == 0)  //只有闹钟关了的时候才能进入设置
				key_with();	
			else 
			{
				flag_beep_en = 0;	 //按下任意键关闭闹钟
				beep = 1;
				clock_value = 0;
				read_time();
			}
		}
		if(flag_200ms == 1)
		{	
			flag_200ms = 0;
			if(menu_1 == 0)
			{
				read_time();		  //读时间
				temperature = read_temp();	//先读出温度的值
				init_1602_ds1302();   //显示时钟
			}
		}
		menu_dis();	   //闹钟报警函数
		delay_1ms(1);
	}
}

/**************定时器0中断程序*****************/
void time0() interrupt 1
{			 
	static uchar value;
	TH0 = 0X3C;
	TL0 = 0XB0;	   //50ms
	value ++;	   
	if((value % 2) == 0)	 //100ms  
		flag_100ms = 1;	 
	if(value >= 6)			 //200ms
	{
		value = 0;
		flag_200ms = 1;
	}
}

源代码文件

链接: 点我(百度云)

提取码: tshw

源代码仿真资源

基于51单片机的电子万年历的设计-源代码-电路仿真图

版权声明:本文为CSDN博主「浅羽唐唐」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Delisa/article/details/118068622

电子万年历的设计目录

电子万年历的设计

电子万年历的系统整体设计框图

所需要的元件

DS18B20模块

DS18B20的特点

DS18B20内部结构

DS18B20管脚排列

 DS1302模块

SPI时序的初步认识

SPI的四种模式

实时时钟芯片DS1302

5DS1302寄存器介绍

液晶显示模块LCD1602

学校的开发板就有时钟芯片和温度检测模块

具体的电路图 (黄圈的就是)

万年历其实很简单

系统总流程图

温度模块程序流程图

DS1302时钟程序流程图

LCD显示程序流程图

源代码

源代码文件

源代码仿真资源


电子万年历的设计

设计要求:

利用51单片机、专用时钟芯片DS1302、DS18B20数字温度采集器及1602液晶显示器件设计一个万年历

  1. 正确显示年月日,时分秒,星期等信息
  2. 显示环境温度
  3. 能够通过按键调整时间和设置闹钟

电子万年历的系统整体设计框图

为使时钟走时与标准时间一致,校时电路是必不可少的,键盘模块用来校正液晶上显示的时间;温度传感器则用来检测当前的环境温度;STC89C52单片机通过输出各种电脉冲信号来驱动控制各部分正常工作;而系统的时间、温度等数据则最终通过液晶模块显示出来。

本电路是以STC89C52单片机为控制核心,该芯片具有在线编程功能,功耗低,能在3.3V的超低压下工作;时钟芯片采用DS1302,它是一款高性能、低功耗、自带RAM的实时时钟芯片,具有使用寿命长,精度高和功耗低等特点,同时具有掉电自动保存功能,可以对年、月、日、星期、时、分、秒进行计时,具有闰年补偿功能,其工作电压为2.5V~5.5V;温度检测模块由DS18B20构成,它采用独特的单线接口仅需一个端口引脚进行通讯, 具有测量精度高、测量范围广等优点,其测温范围在-55~+125℃,工作电压为3v~5.5v;显示部份使用1602液晶显示屏来实现,该显示屏具有低功耗、寿命长、可靠性高的特点,其工作电压为5v。

所需要的元件

STC89C52单片机

时钟芯片DS1302

温度检测模块DS18B20

LCD 1602

我所采用的51单片机是学校的开发板

DS18B20模块

DS18B20是常用的数字温度传感器,其输出的是数字信号,具有体积小,硬件开销低,抗干扰能力强,精度高的特点。  DS18B20数字温度传感器接线方便,封装成后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式,型号多种多样,有LTM8877,LTM8874等等。

        采用单总线的接口方式。与微处理器连接时仅需要一条口线即可实现微处理器与 DS18B20 的双向通讯。单总线具有经济性好,抗干扰能力强,适合于恶劣环境的现场温度测量。

        测量温度范围宽,测量精度高 DS18B20 的测量范围为 -55 ℃ ~+ 125 ℃ ; 在 -10~+ 85°C范围内,精度为 ± 0.5°C 。

DS18B20的特点

DS18B20 单线数字温度传感器,即“一线器件”,其具有独特的优点:

( 1 )采用单总线的接口方式 与微处理器连接时 仅需要一条口线即可实现微处理器与 DS18B20 的双向通讯。 单总线具有经济性好,抗干扰能力强,适合于恶劣环境的现场温度测量,使用方便等优点,使用户可轻松地组建传感器网络,为测量系统的构建引入全新概念。

( 2 )测量温度范围宽,测量精度高 DS18B20 的测量范围为 -55 ℃ ~+ 125 ℃ ; 在 -10~+ 85°C 范围内,精度为 ± 0.0625°C 。

( 3 )在使用中不需要任何外围元件。

( 4 )持多点组网功能 多个 DS18B20 可以并联在惟一的单线上,实现多点测温。

( 5 )供电方式灵活 DS18B20 可以通过内部寄生电路从数据线上获取电源。因此,当数据线上的时序满足一定的要求时,可以不接外部电源,从而 使系统结构更趋简单,可靠性更高。

( 6 )测量参数可配置 DS18B20 的测量分辨率可通过程序设定 9~12 位。

( 7 ) 负压特性 电源极性接反时,温度计不会因发热而烧毁,但不能正常工作。

( 8 )掉电保护功能 DS18B20 内部含有 EEPROM ,在系统掉电以后,它仍可保存分辨率及报警温度的设定值。

DS18B20 具有体积更小、适用电压更宽、更经济、可选更小的封装方式,更宽的电压适用范围,适合于构建自己的经济的测温系统,因此也就被设计者们所青睐。

DS18B20内部结构

      主要由4部分组成:64 位ROM、温度传感器、非挥发的温度报警触发器TH和TL、配置寄存器。ROM中的64位序列号是出厂前被光刻好的,它可以看作 是该DS18B20的地址序列码,每个DS18B20的64位序列号均不相同。64位ROM的排的循环冗余校验码(CRC=X^8+X^5+X^4+1)。 ROM的作用是使每一个DS18B20都各不相同,这样就可以实现一根总线上挂接多个DS18B20的目的。

DS18B20管脚排列

DS18B20的管脚排列

1.  GND为电源 地;

2.  DQ为数字信号输入/输出端;

3.  VDD为外接供电电源输入端(在寄生电源接线方式时接地)

 DS1302模块

SPI时序的初步认识

  • SPI :Serial Peripheral Interface 是一种高速的、全双工、同步通信总线。
  • 应用:单片机和EEPROM、实时时钟、数字信号处理器
  • SSEL:片选,也写做SCS,从设备片选使能信号。
  • SCLK:时钟,也写作SCK,由主机产生,和SCL类似
  • MOSI:主机输出从机输入,Master Output/Slave Input,主机给从机发送指令或者数据的通道。
  • MISO:主机输入从机输出,Master Input/Slave Output,主机读取从机的状态或者数据的通道

SPI的四种模式

  • CPOL:Clock Polarity,即时钟极性。
    • 空闲状态为高电平,则CPOL=1,空闲状态为低电平,则CPOL=0。
  • CPHA:Clock Phase,即时钟相位。
    • CPHA=1表示数据的输出是在一个时钟周期的第一个沿。CPHA=0表示数据的采样是在一个时钟周期的第一个沿。

实时时钟芯片DS1302

  • 1、DS1302是一个实时时钟芯片,可以提供秒、分、小时、日期、月、年等信息,并且还有软件自动调整的能力,可以通过配置AM/PM来决定采用24小时格式还是12小时格式。
  • 2、拥有31字节数据存储RAM。
  • 3、串行I/O通信方式,相对并行来说比较节省IO口的使用。
  • 4、DS1302的工作电压比较宽,在2.0~5.5V的范围内都可以正常工作。
  • 5、DS1302这种时钟芯片功耗一般都很低,它在工作电压2.0V的时候,工作电流小于300nA。
  • 6、DS1302共有8个引脚,有两种封装形式,一种是DIP-8封装,芯片宽度(不含引脚)是300mil,一种是SOP-8封装,有两种宽度,一种是150mil,一种是208mil。
  • 7、当供电电压是5V的时候,兼容标准的TTL电平标准,这里的意思是,可以完美的和单片机进行通信。
  • 8、由于DS1302是DS1202的升级版本,所以所有的功能都兼容DS1202。此外DS1302有两个电源输入,一个是主电源,另外一个是备用电源,比如可以用电池或者大电容,这样做是为了在系统掉电的情况下,我们的时钟还会继续走。

引脚编号

引脚名称

引脚功能

1

Vcc2

主电源引脚,当Vcc2比Vcc1高0.2V以上时,DS1302由Vcc2供电,当Vcc2低于Vcc1时,由Vcc1供电。

2

X1

这两个引脚需要接一个32.768K的晶振,给DS1302提供一个基准。特别注意,要求这个晶振的引脚负载电容必须是6pF,而不是要加6pF的电容。如果使用有源晶振的话,接到X1上即可,X2悬空。

3

X2

4

GND

接地。

5

CE

DS1302的使能输入引脚。当读写DS1302的时候,这个引脚必须是高电平,DS1302这个引脚内部有一个40k的下拉电阻。

6

I/O

这个引脚是一个双向通信引脚,读写数据都是通过这个引脚完成。DS1302这个引脚的内部含有一个40k的下拉电阻。

7

SCLK

输入引脚。SCLK是用来作为通信的时钟信号。DS1302这个引脚的内部含有一个40k的下拉电阻。

8

Vcc1

备用电源引脚。

5DS1302寄存器介绍

液晶显示模块LCD1602

本设计中由于要对时间、温度进行显示,所以选择液晶显示屏1602模块作为输出。1602字符型LCD通常有14条引脚线或16条引脚线的LCD,多出来的2条线是背光电源线。它可以显示两行,每行16个字符,采用单+5V电源供电,外围电路配置简单,价格便宜,具有很高的性价比。1602液晶模块内部的字符发生存储器(CGROM)已经存储了160个不同的点阵字符图形,这些字符有:阿拉伯数字、英文字母的大小写、常用的符号、和日文假名等,每一个字符都有一个固定的代码,比如大写的英文字母“A”的代码是01000001B(41H),显示时模块把地址41H中的点阵字符图形显示出来,我们就能看到字母“A”。将L1602的RS端和P2.0,R/W端和P2.1, E 端和P2.2相连,当RS=0时,对LCD1602写入指令;当RS=1时,对LCD1602写入数据。当R/W端接高电平时芯片处于读数据状态,反之处于写数据状态,E端为使能信号端。当R/W为高电平,E端也为高电平,RS为低电平时,液晶显示屏显示需要显示的示数。

学校的开发板就有时钟芯片和温度检测模块

具体的电路图 (黄圈的就是)

万年历其实很简单

系统总流程图

电子万年历设计题目的系统总流程图如图8.1所示。流程图分析:首先系统初始化,系统开始运行,当有设置键按下时进入修改时间模式,无按键按下时读取时间、温度等数据送入液晶屏显示;在修改时间模式下设置时间完成后再送数据到液晶屏显示。

温度模块程序流程图

温度读取流程图如图8.2所示。流程图分析:开始进入初始化DS18B20,就是通过主机拉低单线产生复位脉冲然后释放该线,如果有应答脉冲,即发起ROM命令当成功的执行操作命令后,就使用Convert T命令即开始温度转换,当转换完后,又初始化DS18B20是否有应答脉冲,若有,就发起Read Scratchpad(读取暂存器和CRC字节)命令,既同时读出第1,2个字节,即为温度的数据。

DS1302时钟程序流程图

时钟流程图如图8.3所示。流程图分析:DS1302开始计时时,首先进行初始化,当有中断信号时,读取时钟芯片的数据送入液晶屏显示。这时若有设置键按下时,进行时间修改,完成后将数据送入时钟芯片;若没有按键按下,则直接存入EPROM,送入液晶屏显示。

LCD显示程序流程图

显示程序流程图如图8.4。流程图分析:首先对1602显示屏进行初始化(初始化大约持续10ms左右),然后检查忙信号,若BF=0,则获得显示RAM的地址,写入相应的数据显示;若BF=1,则代表模块正在进行内部操作,不接受任何外部指令和数据,直到BF=0为止。

源代码

/*
对了,有问题要自己学会找问题,别什么都来问我,我不是你们爹
2021.6.18
端口自己改哈
*/
#include <reg52.h>
#define uchar unsigned char
#define uint  unsigned int
#include "eeprom52.h"
#include "intrins.h"
bit flag_200ms ;
bit flag_100ms ;
sbit beep = P2^3;	  //蜂鸣器定义
bit flag_beep_en;
uint clock_value;     //用作闹钟用的
sbit dq = P2^2;	  //18b20 IO口
uint temperature ;    //温度
uchar menu_1,menu_2;
uchar key_time,flag_value;      //连加中间变量
bit key_500ms  ;
uchar n_nian,n_yue,n_ri,RP;		//显示的函数
#include "ds1302.h"
#include "lcd1602.h"
/******************把数据保存到单片机内部eeprom中******************/
void write_eeprom()
{
	SectorErase(0x2000);
	byte_write(0x2000, fen1);
	byte_write(0x2001, shi1);
	byte_write(0x2002, open1);
	byte_write(0x2058, a_a);	
}

/******************把数据从单片机内部eeprom中读出来*****************/
void read_eeprom()
{
	fen1  = byte_read(0x2000);
	shi1  = byte_read(0x2001);
	open1  = byte_read(0x2002);
	a_a      = byte_read(0x2058);
}

/**************开机自检eeprom初始化*****************/
void init_eeprom()
{
	read_eeprom();		//先读
	if(a_a != 1)		//新的单片机初始单片机内问eeprom
	{
		fen1  = 3;
		shi1  = 8;
		open1  = 1;
		a_a = 1;
		write_eeprom();	   //保存数据
	}	
}

/***********************18b20初始化函数*****************************/
void init_18b20()
{
	bit q;
	dq = 1;				//把总线拿高
	delay_uint(1);	    //15us
	dq = 0;				//给复位脉冲
	delay_uint(80);		//750us
	dq = 1;				//把总线拿高 等待
	delay_uint(10);		//110us
	q = dq;				//读取18b20初始化信号
	delay_uint(20);		//200us
	dq = 1;				//把总线拿高 释放总线
}

/*************写18b20内的数据***************/
void write_18b20(uchar dat)
{
	uchar i;
	for(i=0;i<8;i++)
	{					 //写数据是低位开始
		dq = 0;			 //把总线拿低写时间隙开始 
		dq = dat & 0x01; //向18b20总线写数据了
		delay_uint(5);	 // 60us
		dq = 1;			 //释放总线
		dat >>= 1;
	}	
}

/*************读取18b20内的数据***************/
uchar read_18b20()
{
	uchar i,value;
	for(i=0;i<8;i++)
	{
		dq = 0;			 //把总线拿低读时间隙开始 
		value >>= 1;	 //读数据是低位开始
		dq = 1;			 //释放总线
		if(dq == 1)		 //开始读写数据 
			value |= 0x80;
		delay_uint(5);	 //60us	读一个时间隙最少要保持60us的时间
	}
	return value;		 //返回数据
}

/*************读取温度的值 读出来的是小数***************/
uint read_temp()
{
	uint value;
	uchar low;			   //在读取温度的时候如果中断的太频繁了,就应该把中断给关了,否则会影响到18b20的时序
	init_18b20();		   //初始化18b20
	write_18b20(0xcc);	   //跳过64位ROM
	write_18b20(0x44);	   //启动一次温度转换命令
	delay_uint(50);		   //500us

	init_18b20();		   //初始化18b20
	
	write_18b20(0xcc);	   //跳过64位ROM
	write_18b20(0xbe);	   //发出读取暂存器命令
	
	EA = 0;
	low = read_18b20();	   //读温度低字节
	value = read_18b20();  //读温度高字节
	EA = 1;
	value <<= 8;		   //把温度的高位左移8位
	value |= low;		   //把读出的温度低位放到value的低八位中
	value *= 0.625;	       //转换到温度值 小数
	return value;		   //返回读出的温度 带小数
}




void delay_1ms(uint q)
{
	uint i,j;
	for(i=0;i<q;i++)
		for(j=0;j<120;j++);
}

void write_week(uchar hang,uchar add,uchar week)//写星期函数
{
	if(hang==1)   
		write_com(0x80+add);
	else
		write_com(0x80+0x40+add);	  
	switch(week)
	{
		case 1:write_data('M');//星期数为1时,显示
			   write_data('O');
			   write_data('N');
			   break;
	   
		case 2:write_data('T');//星期数据为2时显示
			   write_data('U');
			   write_data('E');
			   break;
		
		case 3:write_data('W');//星期数据为3时显示
			   write_data('E');
			   write_data('D');
			   break;
		
		case 4:write_data('T');//星期数据为4是显示
			   write_data('H');
			   write_data('U');
			   break;
		
		case 5:write_data('F');//星期数据为5时显示
			   write_data('R');
			   write_data('I');
			   break;
		
		case 6:write_data('S');//星期数据为6时显示
			   write_data('T');
			   write_data('A');
			   break;
		
		case 7:write_data('S');//星期数据为7时显示
			   write_data('U');
			   write_data('N');
			   break;
	}
}

void init_1602_ds1302()
{
		write_sfm2_ds1302(1,1,shi);		   //显示时
		write_sfm2_ds1302(1,4,fen);		   //显示分
		write_sfm2_ds1302(1,7,miao);	   //显示秒
		write_week(2,12,week);//显示星期
		write_sfm3_18B20(1,12,temperature);	   //显示温度
		write_sfm2_ds1302(2,2,nian);   //显示年
		write_sfm2_ds1302(2,5,yue);	   //显示月	
		write_sfm2_ds1302(2,8,ri);	   //显示日 	
		write_sfm2_ds1302(2,8,ri);	   //显示日 		
		write_sfm2_ds1302(1,10,RP);//显示闰年平年
}									   


void init_time0()	  //定时器0初始化程序
{
	EA   = 1;	 	  //开总中断
	TMOD = 0X01;	  //定时器0、工作方式1
	ET0  = 1;		  //开定时器0中断 
	TR0  = 1;		  //允许定时器0定时
}


void menu_dis()//闹钟报警
{
	static uchar mm,value;
	if(flag_100ms == 1)		  //100ms执行一次
	{
		flag_100ms = 0;
		if(open1 == 1)	//如果闹钟打开
		{
			if((miao == 0) && (fen == fen1) && (shi == shi1)) 
			{		
				flag_beep_en = 1;	//有报警蜂鸣器响标志位				
			}		
			if(flag_beep_en == 1)	//闹钟以被打开
			{
				clock_value++;
				if(clock_value <= 30)	 
					beep = ~beep;	   //蜂鸣器叫3秒
				else if(clock_value > 30)
				{
					beep = 1;		    //蜂鸣器停1秒
					if(clock_value > 40)
						clock_value = 0;

					}
				}
				//  1 分钟后关闭闹钟
				value ++;
				if(value >= 10)
				{
					value = 0;
					mm++;
					if(mm >= 60)
					{
						mm = 0;
						flag_beep_en = 0;
						beep = 1;	
					}
				}					
			}
		}	
	}


uchar key_can;

void key()	 //独立按键程序
{
	static uchar key_new;
	key_can = 20;
	P3 |= 0xff;
	if((P3 & 0xff) != 0xff)		//按键按下
	{
		delay_1ms(1);	     	//按键消抖动
		if(((P3 & 0xff) != 0xff) && (key_new == 1))
		{						//确认是按键按下
			key_new = 0;
			switch(P3 & 0xff)
			{
				case 0xef:  key_can = 4;  break;	 //得到按键值
				case 0xdf:  key_can = 3;  break;	 //得到按键值
				case 0xbf:  key_can = 2;  break;	 //得到按键值
				case 0x7f:  key_can = 1;  break;	 //得到按键值
			}
		}			
	}
	else 
		key_new = 1;	
}




void key_with()//设置函数
{
	if(key_can == 1)	//设置键
	{
		menu_1++;
		if(menu_1 == 1)	  //设置时间
		{
			menu_2 = 1;
			write_string(1,0,"    :  :    W:  ");			
			write_string(2,0," 20  -  -       ");	
		}
		if(menu_1 == 2)	  //设置闹钟
		{
			menu_2 = 1;
			write_string(1,0,"   set clock    ");			
			write_string(2,0,"    Y  00:00      ");	
		}
		if(menu_1 > 2)    //回到正常显示
		{
			menu_1 = 0;
			write_guanbiao(1,2,0);	 //关闭光标
			init_1602_dis_csf();      //初始化液晶显示		
		}
	}
	if(key_can == 2)	//选择键
	{
		flag_200ms = 1;
		if(menu_1 == 1)		  //设置时间
		{
			menu_2 ++;
			if(menu_2 > 7)
				menu_2 = 1;
		}
		if(menu_1 == 2)		 //设置闹钟
		{
			menu_2 ++;
			if(menu_2 > 3)
				menu_2 = 1;				
		}
	}
	if(menu_1 == 1)
	{
		if(menu_2 == 1)		  //设置时
		{
			if(key_can == 3)	//加
			{
				shi+=0x01;
				if((shi & 0x0f) >= 0x0a)
					shi = (shi & 0xf0) + 0x10;	   ///***shi & 0xf0低四位清零,(shi & 0xf0) + 0x10向高位进1(高四位加1)***///				if(shi >= 0x24)
					shi = 0;
			}		
			if(key_can == 4)	//减
			{
				if(shi == 0x00)	  
					shi = 0x24;
				if((shi & 0x0f) == 0x00)  
					shi = (shi | 0x0a) - 0x10;	 ///***如果个位为0,高四位减1***///
				shi -- ; 
			}	  				
		}
		if(menu_2 == 2)		  //设置分
		{
			if(key_can == 3)	//加
			{
				fen+=0x01;
				if((fen & 0x0f) >= 0x0a)
					fen = (fen & 0xf0) + 0x10;
				if(fen >= 0x60)
					fen = 0;
			}		
			if(key_can == 4)	//减	  
			{
				if(fen == 0x00)
					fen = 0x5a;
				if((fen & 0x0f) == 0x00)
					fen = (fen | 0x0a) - 0x10;
				fen -- ;
			}	
		}
		if(menu_2 == 3)		  //设置秒
		{
			if(key_can == 3)	//加
			{
				miao+=0x01;
				if((miao & 0x0f) >= 0x0a)
					miao = (miao & 0xf0) + 0x10;
				if(miao >= 0x60)
					miao = 0;
			}	
			if(key_can == 4)	//减	  
			{
				if(miao == 0x00)
					miao = 0x5a;
				if((miao & 0x0f) == 0x00)
					miao = (miao | 0x0a) - 0x10;
				miao -- ;			
			}
		}
		if(menu_2 == 4)		  //设置星期
		{
			if(key_can == 3)	//加
			{
	    		week+=0x01;
				if((week & 0x0f) >= 0x0a)
					week = (week & 0xf0) + 0x10;
				if(week >= 0x08)
					week = 1;
			}		
			if(key_can == 4)	//减	  
			{
				if(week == 0x01)
					week = 0x08;
				if((week & 0x0f) == 0x00)
					week = (week | 0x0a) - 0x10;
				week -- ;
			}	
		}
		if(menu_2 == 5)		  //设置年
		{
			if(key_can == 3)	//加
			{
		    	nian+=0x01;
				if((nian & 0x0f) >= 0x0a)
					nian = (nian & 0xf0) + 0x10;
				if(nian >= 0x9a)
					nian = 1;
			}		
			if(key_can == 4)	//减	  
			{
				if(nian == 0x01)
					nian = 0x9a;
				if((nian & 0x0f) == 0x00)
					nian = (nian | 0x0a) - 0x10;
				nian -- ;		
			}	
		}
		if(menu_2 == 6)		  //设置月
		{
			if(key_can == 3)	//加
			{
		    	yue+=0x01;
				if((yue & 0x0f) >= 0x0a)
					yue = (yue & 0xf0) + 0x10;
				if(yue >= 0x13)
					yue = 1;
			}		
			if(key_can == 4)	//减	  
			{
				if(yue == 0x01)
					yue = 0x13;
				if((yue & 0x0f) == 0x00)
					yue = (yue | 0x0a) - 0x10;
				yue -- ;					
			}	
		}
		if(menu_2 == 7)		  //设置日
		{
			if(key_can == 3)	//加
			{
	    	ri+=0x01;
			if((ri & 0x0f) >= 0x0a)
				ri = (ri & 0xf0) + 0x10;
			if(ri >= 0x32)
				ri = 0;			
			}		
			if(key_can == 4)	//减	  
			{
				if(ri == 0x01)
					ri = 0x32;
				if((ri & 0x0f) == 0x00)
					ri = (ri | 0x0a) - 0x10;
				ri -- ;			
			}	
		}
		write_sfm2_ds1302(1,2,shi);	   ///第一行第二字节开始显示时///
		write_sfm2_ds1302(1,5,fen);	   //显示分
		write_sfm2_ds1302(1,8,miao);	   //显示秒
		write_sfm1(1,14,week);	   //显示星期					
		write_sfm2_ds1302(2,3,nian);	   //显示年
		write_sfm2_ds1302(2,6,yue);	   //显示月
		write_sfm2_ds1302(2,9,ri);	   //显示日

		switch(menu_2)	   // 光标显示
		{
			case 1:  write_guanbiao(1,2,1);  break;
			case 2:  write_guanbiao(1,5,1);  break;
			case 3:  write_guanbiao(1,8,1);  break;
			case 4:  write_guanbiao(1,14,1);  break;
			case 5:  write_guanbiao(2,3,1);  break;
			case 6:  write_guanbiao(2,6,1);  break;
			case 7:  write_guanbiao(2,9,1);  break;
		}
		write_time();	   //把时间写进去
	}	
/***************设置闹钟*********************/
	if(menu_1 == 2)
	{
		if(menu_2 == 1)		  //设置闹钟开关
		{
			if(key_can == 3)	
			{
				open1 = 1;	  //闹钟开
			}		
			if(key_can == 4)	
			{
				open1 = 0;	  //闹钟关
			}	  				
		}
		if(menu_2 == 2)		  //设置闹钟时
		{
			if(key_can == 3)	//加
			{
		    	shi1+=0x01;
				if((shi1 & 0x0f) >= 0x0a)
					shi1 = (shi1 & 0xf0) + 0x10;
				if(shi1 >= 0x24)
					shi1 = 0;
			}		
			if(key_can == 4)	//减	  
			{
				if(shi1 == 0x00)
					shi1 = 0x5a;
				if((shi1 & 0x0f) == 0x00)
					shi1 = (shi1 | 0x0a) - 0x10;
				shi1 -- ;
			}	
		}
		if(menu_2 == 3)		  //设置秒
		{
			if(key_can == 3)	//加
			{
	    		fen1+=0x01;
				if((fen1 & 0x0f) >= 0x0a)
					fen1 = (fen1 & 0xf0) + 0x10;
				if(fen1 >= 0x60)
					fen1 = 0;
			}	
			if(key_can == 4)	//减	  
			{
				if(fen1 == 0x00)
					fen1 = 0x5a;
				if((fen1 & 0x0f) == 0x00)
					fen1 = (fen1 | 0x0a) - 0x10;
				fen1 -- ;			
			}
		}
		if(open1 == 1)
			write_string(2,4,"Y");	
		else 
			write_string(2,4,"N");	
		write_sfm2_ds1302(2,7,shi1);	   //显示闹钟时
		write_sfm2_ds1302(2,10,fen1);	   //显示闹钟分
		switch(menu_2)	   // 光标显示
		{
			case 1:  write_guanbiao(2,4,1);  break;
			case 2:  write_guanbiao(2,7,1);  break;
			case 3:  write_guanbiao(2,10,1);  break;
		}	
		write_eeprom();     //保存闹钟时间
	}			
}

/*****************主函数********************/
void main()
{	
	P0 = P1 = P2 = P3 = 0xff;
	init_time0();
	init_1602();		 //lcd1602初始化
	init_1602_dis_csf(); //lcd1602初始化显示
	init_eeprom();       //开始初始化保存的数据
	temperature = read_temp();	//先读出温度的值
	delay_1ms(650);
	temperature = read_temp();	//先读出温度的值
	
	while(1)
	{//判断闰年。平年 RP标志位
		if(nian%400==0)
        RP=0;
  else
   {
        if(nian%4==0&&nian%100!=0)
           RP=0;
        else
            RP=1;
    }
		key();			 //按键程序
		if(key_can < 10)
		{
			if(flag_beep_en == 0)  //只有闹钟关了的时候才能进入设置
				key_with();	
			else 
			{
				flag_beep_en = 0;	 //按下任意键关闭闹钟
				beep = 1;
				clock_value = 0;
				read_time();
			}
		}
		if(flag_200ms == 1)
		{	
			flag_200ms = 0;
			if(menu_1 == 0)
			{
				read_time();		  //读时间
				temperature = read_temp();	//先读出温度的值
				init_1602_ds1302();   //显示时钟
			}
		}
		menu_dis();	   //闹钟报警函数
		delay_1ms(1);
	}
}

/**************定时器0中断程序*****************/
void time0() interrupt 1
{			 
	static uchar value;
	TH0 = 0X3C;
	TL0 = 0XB0;	   //50ms
	value ++;	   
	if((value % 2) == 0)	 //100ms  
		flag_100ms = 1;	 
	if(value >= 6)			 //200ms
	{
		value = 0;
		flag_200ms = 1;
	}
}

源代码文件

链接: 点我(百度云)

提取码: tshw

源代码仿真资源

基于51单片机的电子万年历的设计-源代码-电路仿真图

版权声明:本文为CSDN博主「浅羽唐唐」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Delisa/article/details/118068622

生成海报
点赞 0

浅羽唐唐

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

基于8051单片机实现电子时钟+数字秒表设计

概述 电子时钟是一种利用数字电路来显示秒、分、时的计时装置,与传统的机械钟相比,它具有走时准确、显 示直观、无机械传动装置等优点,因而得到广泛应用。随着人们生活环境的不断改善和美化,在许