基于C51单片机的万年历设计(LCD1602显示)

C51单片机万年历设计

注:该程序基于普中科技C51 V2.2开发板设计,库函数和硬件资料均来自普中科技,侵删。main.c大部分为原创,如有雷同,纯属巧合。

名称:C51万年历。

硬件:以C51芯片为核心,LCD1602作为显示,DS18B20获取温度信息,按键设置。

软件:可以显示年、月、日、周、时、分、秒以及当前温度。

指标
自动完成平年闰年判断(体现在2月份的天数变化);
其他不同月份对应的天数不同;
温度显示精度为0.01℃;
四个按键:K1进入设置模式,K2选择设置项,K3增加,K4减少。

硬件原理图:可以参考普中科技资料自行设计最小系统板。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
main.c代码

/*******************************************************************************
* 实验名称		 :万年历
* 实验效果       :LCD1602显示万年历,按K1进入时钟设置,按K2选择设置的年月日周时分秒,按K3选择设置+1,按K4选择设置-1
*******************************************************************************/
#include <reg51.h>
#include "lcd.h"
#include "ds1302.h"
#include "temp.h"

sbit K1 = P3 ^ 0; //设置/完成设置
sbit K2 = P3 ^ 1; //切换设置位
sbit K3 = P3 ^ 2; //+
sbit K4 = P3 ^ 3; //—

void EnterTimeSet();
void ExitTimeSet();
void IncSetTime();
void DecSetTime();
void RefreshSetShow();	
void RightShiftTimeSet();
unsigned char KeyScan(); //不支持连按
void KeyAction();

void Delay10ms(void); //误差 0us
void LcdDisplay();
void LcdDisplay_temp(int temp);

unsigned char flag, year;
unsigned char SetPlace = 9;
unsigned char keynum = 5;

/*******************************************************************************
* 函数名         : main
* 函数功能		   : 主函数
*******************************************************************************/
void main()
{
	LcdInit();
	Ds1302Init();
	LcdWriteCom(0x8D); //写地址 80表示初始地址
	LcdWriteData('A');
	LcdWriteData('u');
	LcdWriteData('t');
	while (1)
	{
		keynum = KeyScan(); //按键扫描
		
		//判断平年闰年
		if ((2000 + TIME[0]) % 400 == 0)
			year = 0;
		else if ((2000 + TIME[0]) % 4 == 0 && (2000 + TIME[0]) % 100 != 0)
			year = 0;
		else
			year = 1;
		
		KeyAction();
		
		if (SetPlace == 9)
		{ 
			Ds1302ReadTime();
			LcdDisplay();
			LcdWriteCom(0x40 + 0x8F); //写地址 80表示初始地址
			LcdWriteData('C');
			LcdDisplay_temp(Ds18b20ReadTemp());
		}
	}
}

/*******************************************************************************
* 函数名         : LcdDisplay_temp()
* 函数功能		   : 温度显示函数
*******************************************************************************/
void LcdDisplay_temp(int temp) //lcd显示
{

	unsigned char datas[] = {0, 0, 0, 0}; //定义数组
	float tp;
	if (temp < 0) //当温度值为负数
	{
		LcdWriteCom(0x40 + 0x89); //写地址 80表示初始地址
		LcdWriteData('-');		  //显示负
		//因为读取的温度是实际温度的补码,所以减1,再取反求出原码
		temp = temp - 1;
		temp = ~temp;
		tp = temp;
		temp = tp * 0.0625 * 100 + 0.5;
		//留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为整型的时候把小数点
		//后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
		//算由?.5,还是在小数点后面。
	}
	else
	{
		LcdWriteCom(0x40 + 0x89); //写地址 80表示初始地址
		LcdWriteData('+');		  //显示正
		tp = temp;				  //因为数据处理有小数点所以将温度赋给一个浮点型变量
		//如果温度是正的那么,那么正数的原码就是补码它本身
		temp = tp * 0.0625 * 100 + 0.5;
		//留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为整型的时候把小数点
		//后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
		//算加上0.5,还是在小数点后面。
	}
	datas[0] = temp % 10000 / 1000;
	datas[1] = temp % 1000 / 100;
	datas[2] = temp % 100 / 10;
	datas[3] = temp % 10;

	LcdWriteCom(0x40 + 0x8A);	  //写地址 80表示初始地址
	LcdWriteData('0' + datas[0]); //十位

	LcdWriteCom(0x40 + 0x8B);	  //写地址 80表示初始地址
	LcdWriteData('0' + datas[1]); //个位

	LcdWriteCom(0x40 + 0x8C); //写地址 80表示初始地址
	LcdWriteData('.');		  //显示 ‘.’

	LcdWriteCom(0x40 + 0x8D);	  //写地址 80表示初始地址
	LcdWriteData('0' + datas[2]); //显示小数点

	LcdWriteCom(0x40 + 0x8E);	  //写地址 80表示初始地址
	LcdWriteData('0' + datas[3]); //显示小数点
}

/*******************************************************************************
* 函数名         : LcdDisplay()
* 函数功能		   : 显示函数
*******************************************************************************/
void LcdDisplay()
{
	LcdWriteCom(0x80 + 0x40);
	LcdWriteData('0' + TIME[4] / 16); //时
	LcdWriteData('0' + (TIME[4] & 0x0f));
	LcdWriteData(':');
	LcdWriteData('0' + TIME[5] / 16); //分
	LcdWriteData('0' + (TIME[5] & 0x0f));
	LcdWriteData(':');
	LcdWriteData('0' + TIME[6] / 16); //秒
	LcdWriteData('0' + (TIME[6] & 0x0f));

	LcdWriteCom(0x80);
	LcdWriteData('2');
	LcdWriteData('0');
	LcdWriteData('0' + TIME[0] / 16); //年
	LcdWriteData('0' + (TIME[0] & 0x0f));
	LcdWriteData('-');
	LcdWriteData('0' + TIME[1] / 16); //月
	LcdWriteData('0' + (TIME[1] & 0x0f));
	LcdWriteData('-');
	LcdWriteData('0' + TIME[2] / 16); //日
	LcdWriteData('0' + (TIME[2] & 0x0f));
	LcdWriteCom(0x8B);
	LcdWriteData('0' + (TIME[3] & 0x07)); //星期
}

/*******************************************************************************
* 函数名         : Delay10ms
* 函数功能		   : 延时函数,延时10ms
* 输入           : 无
* 输出         	 : 无
*******************************************************************************/
void Delay10ms(void) //误差 0us
{
	unsigned char a, b, c;
	for (c = 1; c > 0; c--)
		for (b = 38; b > 0; b--)
			for (a = 130; a > 0; a--)
				;
}

/*******************************************************************************
* 
* 时间设置相关函数
* 
*******************************************************************************/
/* 进入时间设置状态 */
void EnterTimeSet()
{
	LcdWriteCom(0x8D); //写地址 80表示初始地址
	LcdWriteData('S');
	LcdWriteData('e');
	LcdWriteData('t');
    SetPlace = 0;	  //把设置索引设置为0,即可进入设置状态
	RefreshSetShow(); //刷新光标位置
	LcdOpenCursor();  //打开光标闪烁效果
}
/* 退出时间设置状态*/
void ExitTimeSet()
{
	LcdWriteCom(0x8D); //写地址 80表示初始地址
	LcdWriteData('A');
	LcdWriteData('u');
	LcdWriteData('t');
	SetPlace = 9;	  //把设置索引设置为9,即可退出设置状态
	LcdCloseCursor(); //关闭光标显示
	Ds1302Init();
	LcdWriteCom(0x40 + 0x8F); //写地址 80表示初始地址
			LcdWriteData('C');
			LcdDisplay_temp(Ds18b20ReadTemp());
}

/* 递增当前设置位的值 */
void IncSetTime()
{
	TIME[SetPlace]++;
	if ((TIME[SetPlace] & 0x0f) > 9) //换成BCD码
	{
		TIME[SetPlace] = TIME[SetPlace] + 6;
	}
	
	if ((TIME[SetPlace] >= 0x9A) && (SetPlace == 0)) //年只能到2099
	{
		TIME[SetPlace] = 0;
	}
	if ((TIME[SetPlace] >= 0x13) && (SetPlace == 1)) //月只能到12
	{
		TIME[SetPlace] = 1;
	}
	//不同月份的天数不同:
	if (TIME[1] == 1 || TIME[1] == 3 || TIME[1] == 5 || TIME[1] == 7 || TIME[1] == 8 || TIME[1] == 10 || TIME[1] == 12) //月份为大
	{
		if((TIME[SetPlace] >= 0x32) && (SetPlace == 2)) //日只能到31
		{
			TIME[SetPlace] = 1;
		}
	}
	if (TIME[1] == 4 || TIME[1] == 6 || TIME[1] == 9 || TIME[1] == 11) //月份为小
	{
		if ((TIME[SetPlace] >= 0x31) && (SetPlace == 2)) //日只能到30
		{
			TIME[SetPlace] = 1;
		}
	}
	if ((TIME[1] == 2) && (SetPlace == 2)) //月份为2月
	{
		if ((TIME[SetPlace] >= 0x30) && (year == 0)) //闰年日只能到29
		{
			TIME[SetPlace] = 1;
		}
		if ((TIME[SetPlace] >= 0x29) && (year == 1)) //平年日只能到28
		{
			TIME[SetPlace] = 1;
		}
	}

	if ((TIME[SetPlace] >= 0x08) && (SetPlace == 3)) //周只能到7
	{
		TIME[SetPlace] = 1;
	}
	if ((TIME[SetPlace] >= 0x24) && (SetPlace == 4)) //时只能到23
	{
		TIME[SetPlace] = 0;
	}
	if ((TIME[SetPlace] >= 0x60) && (SetPlace == 5)) //分只能到60
	{
		TIME[SetPlace] = 0;
	}
	if ((TIME[SetPlace] >= 0x60) && (SetPlace == 6)) //秒只能到60
	{
		TIME[SetPlace] = 0;
	}
	LcdDisplay();	  //刷新时间显示
	RefreshSetShow(); //刷新光标显示
}

/* 递减当前设置位的值 */
void DecSetTime()
{
	TIME[SetPlace]--;
	if ((TIME[SetPlace] & 0xf0) < 0x00) //换成BCD码
	{
		TIME[SetPlace] = TIME[SetPlace] - 0x10;
	}	
	if ((TIME[SetPlace] & 0x0f) > 9) //换成BCD码
	{
		TIME[SetPlace] = TIME[SetPlace] - 6;
	}		   
	
	if (('0' +(TIME[0]/16)=='?') && (SetPlace == 0)) //年只能到2099	
	{
		TIME[SetPlace] = 0x99;
	}
	if ((TIME[SetPlace] <= 0x00) && (SetPlace == 1)) //月只能到12
	{
		TIME[SetPlace] = 0x12;
	}
	//不同月份的天数不同:
	if (TIME[1] == 1 || TIME[1] == 3 || TIME[1] == 5 || TIME[1] == 7 || TIME[1] == 8 || TIME[1] == 10 || TIME[1] == 12) //月份为大
	{
		if((TIME[SetPlace] <= 0x00) && (SetPlace == 2)) //日只能到31
		{
			TIME[SetPlace] = 0x31;
		}
	}
	if (TIME[1] == 4 || TIME[1] == 6 || TIME[1] == 9 || TIME[1] == 11) //月份为小
	{
		if((TIME[SetPlace] <= 0x00) && (SetPlace == 2)) //日只能到31
		{
			TIME[SetPlace] = 0x30;
		}
	}
	if ((TIME[1] == 2) && (SetPlace == 2)) //月份为2月
	{
		if ((TIME[SetPlace] <= 0x00) && (year == 0)) //闰年日只能到29
		{
			TIME[SetPlace] = 0x29;
		}
		if ((TIME[SetPlace] <= 0x00) && (year == 1)) //平年日只能到28
		{
			TIME[SetPlace] = 0x28;
		}
	}
	
	if ((TIME[SetPlace] <= 0x00) && (SetPlace == 3)) //周只能到7
	{
		TIME[SetPlace] = 0x07;
	}
	if (('0' +(TIME[4] / 16)=='?') && (SetPlace == 4)) //时只能到23
	{
		TIME[SetPlace] = 0x23;
	}
	if (('0' +(TIME[5] / 16)=='?') && (SetPlace == 5)) //分只能到60
	{
		TIME[SetPlace] = 0x59;
	}
	if (('0' +(TIME[6] / 16)=='?') && (SetPlace == 6)) //秒只能到60
	{
		TIME[SetPlace] = 0x59;
	}
	LcdDisplay();	  //刷新时间显示
	RefreshSetShow(); //刷新光标显示
}

/* 刷新当前设置位的光标指示 */
void RefreshSetShow()
{
	switch (SetPlace)
	{
	case 0:
		LcdSetCursor(3, 0); //年
		break;
	case 1:
		LcdSetCursor(6, 0); //月
		break;
	case 2:
		LcdSetCursor(9, 0); //日
		break;
	case 3:
		LcdSetCursor(11, 0); //周
		break;
	case 4:
		LcdSetCursor(1, 1); //时
		break;
	case 5:
		LcdSetCursor(4, 1); //分
		break;
	case 6:
		LcdSetCursor(7, 1); //秒
		break;
	default:
		break;
	}
}

/* 右移时间设置位 */
void RightShiftTimeSet()
{
	if (SetPlace != 9)
	{
		if (SetPlace < 6)
			SetPlace++;
		else
			SetPlace = 0;
		RefreshSetShow();
	}
}

/*******************************************************************************
* 
* 按键相关函数
* 
*******************************************************************************/
unsigned char KeyScan() //不支持连按
{
	static unsigned char key_up = 1;
	if (key_up && (K1 == 0 || K2 == 0 || K3 == 0 || K4 == 0))
	{
		Delay10ms();
		key_up = 0;
		if (K1 == 0)
			return 1;
		else if (K2 == 0)
			return 2;
		else if (K3 == 0)
			return 3;
		else if (K4 == 0)
			return 4;
	}else if(K1==1&&K2==1&&K3==1&&K4==1)key_up=1; 
	return 0;
}

void KeyAction()
{
	switch (keynum)
	{
	case 1:
		if (SetPlace == 9) //不处于设置状态时,进入设置状态
		{
			EnterTimeSet();
		}
		else //已处于设置状态时,保存时间并退出设置状态
		{
			ExitTimeSet();
		}
		break;
	case 2:
		RightShiftTimeSet();
		break;
	case 3:
		IncSetTime();
		break;
	case 4:
		DecSetTime();
		break;
	default:
		break;
	}
}

实物图
在这里插入图片描述
补充:类似程序网上有很多例程,但是大多并不完善(不是没有平年闰年判断,就是设置项只能加不能减,要么就是设置项顺序紊乱),因而产生了自己做一个比较完善的万年历C51程序的想法。由于本人还是高校学生,水平有限,若有HXD有发现BUG或者有更优秀的程序设计,欢迎指正交流。

本文程序资源链接:https://download.csdn.net/download/weixin_43586860/12510787

  • 31
    点赞
  • 343
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
洛 阳 理 工 学 院 课 程 设 计 报 告 课程名称 单片机原理与应用   设计题目 基于STC89C51万年历设计与实现 专 业 物联网工程       班 级 学 号 姓 名 完成日期 大约在冬季 "课 程 设 计 任 务 书 " "设计题目: 基于STC89C51万年历设计与实现  " "设计内容与要求: " "设计内容 " "利用STC89C51单片机、专用时钟芯片DS1302、DS18B20数字温度采集器及1602 " "显示器件设计一个万年历,要求实现:(1)正确显示年月日,时分秒,星期 " "等信息;(2)显示环境温度;(3)具有闹钟功能,可以整点报时;(4)能 " "够通过按键调整时间和设置闹钟。 " "二、设计要求 " "1.分析系统功能,确定系统设计方案,掌握总体设计的方法与思路。 " "2.系统硬件设计,确定外设与单片机的硬件接口。掌握单片机系统外部接口的" "扩展设计方法。 " "3.系统软件设计,结合硬件设计,编写相应控制程序,并进行Protuse仿真执 " "行。 " "4.熟练掌握程序烧录及调试过程。 " "5.按照要求撰写课程设计论文。 " "指导教师: " "2019年 11 月 26 日 " "课 程 设 计 评 语 " " " " " " " "成绩: 指导教师:_______________ " "年 月 日 " 目录 摘 要 2 一、设计目标与内容 3 1.1设计目标 3 1.2 设计内容 3 1.3设计要求 3 1.4 本章小结 3 二、系统设计 3 2.1 电路设计框图 3 2.2 系统硬件概述 4 2.3 主要单元电路的设计 4 2.3.1 时钟电路模块的设计 4 2.3.2温度传感器电路设计 6 2.3.3显示模块的设计 8 2.4本章小结 8 三、系统的软件设计 9 3.1程序流程图 9 3.1.1 系统总流程图 9 3.1.2 温度程序流程图 9 3.1.3 DS1302时钟程序流程图 10 3.1.4 LCD显示程序流程图 11 3.2程序的设计 11 3.2.1 DS18B20测温程序 11 3.2.2 DS1302读写程序 13 3.2.3液晶显示程序 14 3.3本章小结 15 四、仿真与调试 15 4.1 Keil软件调试流程 15 4.2 Proteus软件运行流程 17 4.3本章小结 18 总结 18 基于STC89C51万年历设计与实现 摘 要 古人依靠日冕、漏刻记录时间,而随着科技的发展,电子万年历已经成为日渐流行的 日常计时工具。 本文研究的万年历系统拟用STC89C52单片机控制,以DS1302时钟芯片计时、DS18B20 采集温度、1602液晶屏显示。系统主要由温度传感器电路,单片机控制电路,显示电路 以及校正电路四个模块组成。本文阐述了系统的硬件工作原理,所应用的各个接口模块 的功能以及其工作过程,论证了设计方案理论的可行性。系统程序采用C语言编写,经Ke il软件进行调试后在Proteus软件中进行仿真,可以显示年、月、日、星期、时、分、秒 和温度并具有校准功能和与即时时间同步的功能。 实验结果明此万年历实现后具有读取方便、显示直观、功能多样、电路简洁等诸多 优点,符合电子仪器仪的发展趋势,具有广阔的市场前景。 关键词:万年历 单片机 仿真 一、设计目标与内容 1.1设计目标 制作出可以检测温度并报警的的电子万年历。 1.2 设计内容 用keilC51编译程序。 用proteus仿真电路图。 将电路图使用万用板或其他电路板焊接实物图。 1.3设计要求 具备在液晶上显示年、月、日、星期、时、分、秒的功能。 具备年、月、日、星期、时、分、秒校准功能。 具有与即时时间同步的功能。 1.4 本章小结 通过设计程序,制作仿真,焊接实物三个步骤,以小组合作的方式来设计满足设计要 求的万年历并简单描述可以实现的功能,制作结束后,进行课程设计答辩并编写一份课 程设计报告。 二、系统设计 1. 2. 2.1 电路设计框图 根据上章确定的方案给出了系统整体的设计框图: 图1.1系统结构框图 为使时钟走时与标准时间一致,校时电路是必不可少的,键盘模块用来校正液晶上显 示的时间;温度传感器则用来检测当前的环境温度;STC89C52单片机通过输出各种电脉 冲信号来驱动控制各部分正常工作;而系统的时间、温度等数据则最终通过液晶模块显 示出来。 2.2 系统硬件概述 本电路是以STC89C52单片机为控制核心,该芯片具有在线编程功能,功耗低,能在3 .3V的超低压下工作;时钟芯片采用DS1302,它是一款高性能、低功耗、自带RAM的实时 时钟芯片,具有使用寿命长,精度高和功耗低等特点,同时具有掉电自动保存功能,可以 对年、月、日、星期、时、分、秒进行计时,具有
很高兴为您解答问题。基于C51单片机实现在LCD 1602上显示时间,可以通过串口修改LCD1602显示的时间并且可以设置闹钟的代码如下: ```c #include <reg51.h> #include <stdio.h> #include <string.h> #define LCD_RS P1_0 #define LCD_RW P1_1 #define LCD_EN P1_2 #define LCD_DATA P2 void delay(unsigned int i) { while (i--); } void LCD_WriteCommand(unsigned char cmd) { LCD_RS = 0; LCD_RW = 0; LCD_DATA = cmd; LCD_EN = 1; delay(100); LCD_EN = 0; } void LCD_WriteData(unsigned char dat) { LCD_RS = 1; LCD_RW = 0; LCD_DATA = dat; LCD_EN = 1; delay(100); LCD_EN = 0; } void LCD_Clear() { LCD_WriteCommand(0x01); delay(500); } void LCD_Init() { LCD_WriteCommand(0x38); delay(500); LCD_WriteCommand(0x0C); delay(500); LCD_WriteCommand(0x06); delay(500); LCD_WriteCommand(0x01); delay(500); } void LCD_SetCursor(unsigned char x, unsigned char y) { unsigned char addr; if (y == 0) { addr = 0x80 + x; } else { addr = 0xC0 + x; } LCD_WriteCommand(addr); } void UART_Init() { TMOD = 0x20; SCON = 0x50; TH1 = 0xFD; TR1 = 1; } unsigned char UART_Receive() { while (!RI); RI = 0; return SBUF; } void UART_Send(unsigned char dat) { SBUF = dat; while (!TI); TI = 0; } void main() { unsigned char hour = 0, minute = 0, second = 0; unsigned char buf[16] = {0}; unsigned char ch; unsigned char alarm_hour = 0, alarm_minute = 0; LCD_Init(); UART_Init(); while (1) { LCD_SetCursor(0, 0); sprintf(buf, "%02d:%02d:%02d", hour, minute, second); LCD_WriteData(buf[0]); LCD_WriteData(buf[1]); LCD_WriteData(':'); LCD_WriteData(buf[3]); LCD_WriteData(buf[4]); LCD_WriteData(':'); LCD_WriteData(buf[6]); LCD_WriteData(buf[7]); ch = UART_Receive(); if (ch == 'h') { UART_Send(hour); } else if (ch == 'm') { UART_Send(minute); } else if (ch == 's') { UART_Send(second); } else if (ch == 'a') { UART_Send(alarm_hour); UART_Send(alarm_minute); } else if (ch == 'H') { hour = UART_Receive(); } else if (ch == 'M') { minute = UART_Receive(); } else if (ch == 'S') { second = UART_Receive(); } else if (ch == 'A') { alarm_hour = UART_Receive(); alarm_minute = UART_Receive(); } second++; if (second == 60) { second = 0; minute++; if (minute == 60) { minute = 0; hour++; if (hour == 24) { hour = 0; } } } if (hour == alarm_hour && minute == alarm_minute && second == 0) { LCD_SetCursor(0, 1); LCD_WriteData('A'); LCD_WriteData('L'); LCD_WriteData('A'); LCD_WriteData('R'); LCD_WriteData('M'); } delay(1000); } } ``` 这段代码实现了基于C51单片机控制LCD 1602显示时间,并且可以通过串口修改时间和设置闹钟。在代码中,使用了定时器和串口通信,定时器用于控制时间的显示和闹钟的触发,串口用于接收修改时间和设置闹钟的指令,并返回当前时间和闹钟设置的值。具体实现过程可以参考注释。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值