注:该程序基于普中科技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
#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