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

C51单片机万年历设计

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

名称:C51万年历。

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

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

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

硬件原理图:可以参考普中科技资料自行设计最小系统板。
基于C51单片机的万年历设计(LCD1602显示)_第1张图片
基于C51单片机的万年历设计(LCD1602显示)_第2张图片
基于C51单片机的万年历设计(LCD1602显示)_第3张图片
基于C51单片机的万年历设计(LCD1602显示)_第4张图片
main.c代码

/*******************************************************************************
* 实验名称		 :万年历
* 实验效果       :LCD1602显示万年历,按K1进入时钟设置,按K2选择设置的年月日周时分秒,按K3选择设置+1,按K4选择设置-1
*******************************************************************************/
#include 
#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单片机的万年历设计(LCD1602显示)_第5张图片
补充:类似程序网上有很多例程,但是大多并不完善(不是没有平年闰年判断,就是设置项只能加不能减,要么就是设置项顺序紊乱),因而产生了自己做一个比较完善的万年历C51程序的想法。由于本人还是高校学生,水平有限,若有HXD有发现BUG或者有更优秀的程序设计,欢迎指正交流。

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

你可能感兴趣的:(C51,单片机)