基于51单片机的可掉电走表万年历设计
前言
之前跟随郭天祥老师学习51单片机的时候就很想实现掉电走表这个功能,奈何那时候跟随视频学的还是非常浅显,到了这个这期刚好老师需要做一个万年历所以接触到了DS1302时钟模块以至于用了差不多10天时间每天捉摸一点点模块拼凑,终于完成这个功能,虽还有瑕疵,恳请各位指出我的不足和漏洞
下面的是效果演示,采用锂电池供电
使用到的模块
- LCD12864液晶
- 独立按钮
- DS18B20温度模块
- DS1302时钟模块
- LM7805降压模块和18650锂电池
本项目声明的变量
本文以模块化的方式阐述如何一步步丰富功能,担心下面有些模块里面的变量名读者看不懂现在先将其全部写出来,读者可以看了下面的再回来看上面的变量声明
#include <reg51.h>
#include <intrins.h>
#define uchar unsigned char
#define uint unsigned int
#define LCD_data P0
sbit LCD_RS = P3^5;
sbit LCD_RW = P3^6;
sbit LCD_EN = P3^4;
sbit LCD_PSB = P3^7;
sbit beep=P2^3;
sbit LED=P1^0;
sbit s1=P3^2;
sbit s2=P2^1;
sbit s3=P2^0;
sbit TSCLK = P1^1;
sbit TIO = P1^2;
sbit TRST = P1^3;
sbit ds=P2^2;
uint temp;
float f_temp;
void init_T1(void);
uchar code dis1[] = "20 -- -- 周 ";
uchar dis3[] = "室内温度:";
uchar code dis4[] = "开发作者:Ramsey ";
uchar code tab1[]={"时 分 秒"};
uchar num=0,i,h=23,m=00,s=00,hl,hr,ml,mr,sl,sr,s1num,n=1;
uint run,year=2021,month=11,day=13,y1,y2,y3,y4,mol,mor,dl,dr;
uchar testnum,Flag=0;
uchar week=0;
#define delayNOP(); {_nop_();_nop_();_nop_();_nop_();};
uchar IRDIS[2];
uchar IRCOM[4];
uchar count,s1num=0;
uchar testcount;
char miao,shi,fen;
void lcd_pos(uchar X,uchar Y);
void delay0(uchar x);
void beepon();
void display();
void dataconv();
void lcd_wcmd(uchar cmd);
void lcd_wdat(uchar dat);
void dsreset(void);
bit tmpreadbit(void);
bit tempreadbit(void);
uchar tempread(void);
void tempwritebyte(uchar dat);
void tempchange(void);
uint get_temp();
void tempdisplay();
程序用到的初始化函数
、
void init_T1(void)
{
TMOD=0x11;
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
TH1=(65536-50000)/256;
TL1=(65536-50000)%256;
EA=1;
ET0=1;
ET1=1;
TR0=1;
TR1=1;
EX0=1;
IT0=1;
}
LCD12864液晶代码
#define delayNOP(); {_nop_();_nop_();_nop_();_nop_();};
#define LCD_data P0
sbit LCD_RS = P3^5;
sbit LCD_RW = P3^6;
sbit LCD_EN = P3^4;
sbit LCD_PSB = P3^7;
uchar IRDIS[2];
uchar IRCOM[4];
uchar code dis1[] = "20 -- -- 周 ";
uchar dis3[] = "室内温度:";
uchar code dis4[] = "开发作者:Ramsey ";
uchar code tab1[]={"时 分 秒"};
bit lcd_busy()
{
bit result;
LCD_RS = 0;
LCD_RW = 1;
LCD_EN = 1;
delayNOP();
result = (bit)(P0&0x80);
LCD_EN = 0;
return(result);
}
void lcd_wcmd(uchar cmd)
{
while(lcd_busy());
LCD_RS = 0;
LCD_RW = 0;
LCD_EN = 0;
_nop_();
_nop_();
P0 = cmd;
delayNOP();
LCD_EN = 1;
delayNOP();
LCD_EN = 0;
}
void lcd_wdat(uchar dat)
{
while(lcd_busy());
LCD_RS = 1;
LCD_RW = 0;
LCD_EN = 0;
P0 = dat;
delayNOP();
LCD_EN = 1;
delayNOP();
LCD_EN = 0;
}
void lcd_init()
{
LCD_PSB = 1;
lcd_wcmd(0x34);
delay(5);
lcd_wcmd(0x30);
delay(5);
lcd_wcmd(0x0C);
delay(5);
lcd_wcmd(0x01);
delay(5);
lcd_wcmd(0x06);
delay(5);
}
void lcd_pos(uchar X,uchar Y)
{
uchar pos;
if (X==0)
{X=0x80;}
else if (X==1)
{X=0x90;}
else if (X==2)
{X=0x88;}
else if (X==3)
{X=0x98;}
pos = X+Y ;
lcd_wcmd(pos);
}
void display(){
uchar i;
delay(10);
lcd_pos(0,0);
i = 0;
while(dis1[i] != '\0')
{
lcd_wdat(dis1[i]);
i++;
}
lcd_wcmd(0x92);
for(i=0;i<10;i++)
{
lcd_wdat(tab1[i]);
delay(10);
}
lcd_pos(3,0);
i = 0;
while(dis4[i] != '\0')
{
lcd_wdat(dis4[i]);
i++;
}
}
上述是LCD12864的基础显示内容下面需要根据具体时间写数据到对应的位置,具体位置代码详情看LCD12864说明文档,下面不在赘述
void change()
{
hl=shi/10;
hr=shi%10;
ml=fen/10;
mr=fen%10;
sl=miao/10;
sr=miao%10;
y1=year/1000;
y2=year/100%10;
y3=year/10%10;
y4=year%10;
mol=month/10;
mor=month%10;
dl=day/10;
dr=day%10;
}
void display1()
{
change();
lcd_wcmd(0x80);
lcd_wdat(y1+48);
delay1(10);
lcd_wdat(y2+48);
delay1(10);
lcd_wdat(y3+48);
delay1(10);
lcd_wdat(y4+48);
lcd_wcmd(0x83);
lcd_wdat(mol+48);
delay1(10);
lcd_wdat(mor+48);
delay1(10);
lcd_wcmd(0x85);
lcd_wdat(dl+48);
delay1(10);
lcd_wdat(dr+48);
delay1(10);
delay1(10);
lcd_wcmd(0x91);
lcd_wdat(hl+48);
delay1(10);
lcd_wdat(hr+48);
delay1(10);
lcd_wcmd(0x93);
lcd_wdat(ml+48);
delay1(10);
lcd_wdat(mr+48);
delay1(10);
lcd_wcmd(0x95);
lcd_wdat(sl+48);
delay1(10);
lcd_wdat(sr+48);
delay1(10);
lcd_wcmd(0x97);
lcd_wdat(0x20);
lcd_wdat(0x20);
delay1(10);
}
上述代码就已经将年月日和时分秒的数据填入,现在需要根据年月日来计算今天是星期几 算法如下: 基姆拉尔森计算公式 W= (d+2m+3(m+1)/5+y+y/4-y/100+y/400) mod 7
在公式中d表示日期中的日数,m表示月份数,y表示年数。
注意:在公式中有个与其他公式不同的地方: 把一月和二月看成是上一年的十三月和十四月,例:如果是 2004-1-10则换算成:2003-13-10来代入公式计算。 以公元元年为参考,公元元年1月1日为星期一 基姆拉尔森计算公式引用地址
int CaculateWeek(int y, int m, int d) {
int tempweek;
if (m == 1 || m == 2) {
m += 12;
--y;
}
tempweek = (d + 2 * m + 3 * (m + 1) / 5 + y + (y >> 2) - y / 100 + y / 400) % 7 + 1;
return tempweek;
}
void displayweek()
{
week=CaculateWeek(year,month,day);
switch(week)
{
case 1:
lcd_wcmd(0x87);
lcd_wdat(0XD2);
lcd_wdat(0XBB);
delay1(10);
break;
case 2:
lcd_wcmd(0x87);
lcd_wdat(0XB6);
lcd_wdat(0XFE);
delay1(10);
break;
case 3:
lcd_wcmd(0x87);
lcd_wdat(0XC8);
lcd_wdat(0XFD);
delay1(10);
break;
case 4:
lcd_wcmd(0x87);
lcd_wdat(0XCB);
lcd_wdat(0XC4);
delay1(10);
break;
case 5:
lcd_wcmd(0x87);
lcd_wdat(0XCE);
lcd_wdat(0XE5);
delay1(10);
break;
case 6:
lcd_wcmd(0x87);
lcd_wdat(0XC1);
lcd_wdat(0XF9);
delay1(10);
break;
case 7:
lcd_wcmd(0x87);
lcd_wdat(0XC8);
lcd_wdat(0XD5);
delay1(10);
break;
}
}
独立按钮代码
独立按钮的需要实现的功能是对时间进行调整,我用了3个按钮分别是1个设置按钮和2个用来加减时间的按钮,设置按钮将其优先级设置最高,只有设置按钮按下的时候,加减按钮才会有效,因此作者将设置按钮的代码放在外部中断,具体请看下面的代码
sbit s1=P3^2;
sbit s2=P2^1;
sbit s3=P2^0;
void keyScan(){
LCD_RW=0;
if(testnum!=0)
{
if(s2==0)
{
delay1(5);
if(s2==0)
{
beepon();
while(!s2);
if(testnum==1)
{
miao++;
write_DS1302(0x8e,0);
write_DS1302(0x80,dat_to_BCD(miao));
write_DS1302(0x8e,0x80);
if(miao==60)
miao=0;
change();
delay1(10);
lcd_wcmd(0x95);
lcd_wdat(sl+48);
delay1(10);
lcd_wdat(sr+48);
lcd_wcmd(0x95);
}
if(testnum==2)
{
fen++;
write_DS1302(0x8e,0);
write_DS1302(0x82,dat_to_BCD(fen));
write_DS1302(0x8e,0x80);
delay1(100);
if(fen==60)
fen=0;
change();
delay1(10);
lcd_wcmd(0x93);
lcd_wdat(ml+48);
delay1(10);
lcd_wdat(mr+48);
lcd_wcmd(0x93);
}
if(testnum==3)
{
shi++;
write_DS1302(0x8e,0);
write_DS1302(0x84,dat_to_BCD(shi));
write_DS1302(0x8e,0x80);
if(shi==24)
shi=0;
change();
delay1(10);
lcd_wcmd(0x91);
lcd_wdat(hl+48);
delay1(10);
lcd_wdat(hr+48);
delay1(10);
lcd_wcmd(0x91);
}
if(testnum==4)
{
day++;
write_DS1302(0x8e,0);
write_DS1302(0x86,dat_to_BCD(day));
write_DS1302(0x8e,0x80);
if(day==31)
day=1;
change();
delay1(10);
lcd_wcmd(0x85);
lcd_wdat(dl+48);
delay1(10);
lcd_wdat(dr+48);
delay1(10);
lcd_wcmd(0x85);
displayweek();
lcd_wcmd(0x85);
}
if(testnum==5)
{
month++;
write_DS1302(0x8e,0);
write_DS1302(0x88,dat_to_BCD(month));
write_DS1302(0x8e,0x80);
if(month==13)
month=1;
change();
delay1(10);
lcd_wcmd(0x83);
lcd_wdat(mol+48);
delay1(10);
lcd_wdat(mor+48);
delay1(10);
lcd_wcmd(0x83);
displayweek();
lcd_wcmd(0x83);
}
if(testnum==6)
{
year++;
write_DS1302(0x8e,0);
write_DS1302(0x8c,dat_to_BCD(year%100));
write_DS1302(0x8e,0x80);
if(year==2040)
year=2021;
change();
delay1(10);
lcd_wcmd(0x81);
lcd_wdat(y3+48);
delay1(10);
lcd_wdat(y4+48);
lcd_wcmd(0x81);
displayweek();
lcd_wcmd(0x81);
}
}
}
if(s3==0)
{
delay1(5);
if(s3==0)
{
beepon();
while(!s3);
if(testnum==1)
{
miao--;
write_DS1302(0x8e,0);
write_DS1302(0x80,dat_to_BCD(miao));
write_DS1302(0x8e,0x80);
if(miao==-1)
miao=59;
change();
delay1(10);
lcd_wcmd(0x95);
lcd_wdat(sl+48);
delay1(10);
lcd_wdat(sr+48);
lcd_wcmd(0x95);
}
if(testnum==2)
{
fen--;
write_DS1302(0x8e,0);
write_DS1302(0x82,dat_to_BCD(fen));
write_DS1302(0x8e,0x80);
if(fen==-1)
fen=59;
change();
delay1(10);
lcd_wcmd(0x93);
lcd_wdat(ml+48);
delay1(10);
lcd_wdat(mr+48);
lcd_wcmd(0x93);
}
if(testnum==3)
{
shi--;
write_DS1302(0x8e,0);
write_DS1302(0x84,dat_to_BCD(shi));
write_DS1302(0x8e,0x80);
if(shi==-1)
shi=23;
change();
delay1(10);
lcd_wcmd(0x91);
lcd_wdat(hl+48);
delay1(10);
lcd_wdat(hr+48);
delay1(10);
lcd_wcmd(0x91);
}
if(testnum==4)
{
day--;
write_DS1302(0x8e,0);
write_DS1302(0x86,dat_to_BCD(day));
write_DS1302(0x8e,0x80);
if(day==-1)
day=30;
change();
delay1(10);
lcd_wcmd(0x85);
lcd_wdat(dl+48);
delay1(10);
lcd_wdat(dr+48);
delay1(10);
lcd_wcmd(0x85);
displayweek();
lcd_wcmd(0x85);
}
if(testnum==5)
{
month--;
write_DS1302(0x8e,0);
write_DS1302(0x88,dat_to_BCD(month));
write_DS1302(0x8e,0x80);
if(month==-1)
month=12;
change();
delay1(10);
lcd_wcmd(0x83);
lcd_wdat(mol+48);
delay1(10);
lcd_wdat(mor+48);
delay1(10);
lcd_wcmd(0x83);
displayweek();
lcd_wcmd(0x83);
}
if(testnum==6)
{
year--;
write_DS1302(0x8e,0);
write_DS1302(0x8c,dat_to_BCD(year%100));
write_DS1302(0x8e,0x80);
if(year==2009)
year=2040;
change();
delay1(10);
lcd_wcmd(0x81);
lcd_wdat(y3+48);
delay1(10);
lcd_wdat(y4+48);
lcd_wcmd(0x81);
displayweek();
lcd_wcmd(0x81);
}
}
}
}
}
void timer1() interrupt 0
{
LCD_RW=0;
if(s1==0)
{
delay1(5);
if(s1==0)
{
beepon();
testnum++;
while(!s1);
if(testnum==1)
{
Flag=1;
display1();
TR0=0;
TR1=0;
lcd_wcmd(0x95);
lcd_wcmd(0x0f);
}
}
if(testnum==2)
{
display1();
lcd_wcmd(0x93);
}
if(testnum==3)
{
display1();
lcd_wcmd(0x91);
}
if(testnum==4)
{
display1();
lcd_wcmd(0x85);
}
if(testnum==5)
{
display1();
lcd_wcmd(0x83);
}
if(testnum==6)
{
display1();
lcd_wcmd(0x81);
}
if(testnum==7)
{
Flag=0;
display1();
testnum=0;
lcd_wcmd(0x0c);
TR0=1;
TR1=1;
delay1(10);
}
}
DS18B20温度模块
DS18B20是常用的数字温度传感器,其输出的是数字信号,具有体积小,硬件开销低,抗干扰能力强,精度高的特点。 [1] DS18B20数字温度传感器接线方便,封装成后可应用于多种场合,如管道式,螺纹式,磁铁吸附式,不锈钢封装式,型号多种多样,有LTM8877,LTM8874等等。 DS18B20温度模块资料来源于百度百科 本次项目需求: 需要在一定的频率内将实时温度的数值呈现到显示屏上,所以我将温度的变化封装在第二个定时器 上具体代码如下
void dsreset(void)
{
uint i;
ds=0;
i=103;
while(i>0)i--;
ds=1;
i=4;
while(i>0)i--;
}
bit tempreadbit(void)
{
uint i;
bit dat;
ds=0;i++;
ds=1;i++;i++;
dat=ds;
i=8;while(i>0)i--;
return (dat);
}
uchar tempread(void)
{
uchar i,j,dat;
dat=0;
for(i=1;i<=8;i++)
{
j=tempreadbit();
dat=(j<<7)|(dat>>1);
}
return(dat);
}
void tempwritebyte(uchar dat)
{
uint i;
uchar j;
bit testb;
for(j=1;j<=8;j++)
{
testb=dat&0x01;
dat=dat>>1;
if(testb)
{
ds=0;
i++;i++;
ds=1;
i=8;
while(i>0)i--;
}
else
{
ds=0;
i=8;while(i>0)i--;
ds=1;
i++;i++;
}
}
}
void tempchange(void) {
dsreset();
delay(1);
tempwritebyte(0xcc);
tempwritebyte(0x44);
}
uint get_temp()
{
uchar a,b;
dsreset();
delay(1);
tempwritebyte(0xcc);
tempwritebyte(0xbe);
a=tempread();
b=tempread();
temp=b;
temp<<=8;
temp=temp|a;
f_temp=temp*0.0625;
temp=f_temp*10+0.5;
f_temp=f_temp+0.05;
return temp;
}
void tempdisplay()
{
uint mm;
tempchange();
mm=get_temp();
dis3[9]=mm%1000/100+'0';
dis3[10]=mm%100/10+'0';
dis3[11]='.';
dis3[12]=mm%10+'0';
lcd_wcmd(0x88);
for(i=0;i<14;i++)
{
lcd_wdat(dis3[i]);
delay(10);
}
}
DS1302时钟模块
DS1302时钟芯片是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能。DS1302芯片包含一个用于存储实时时钟/日历的 31 字节的静态 RAM,可通过简单的串行接口与微处理器通讯,将当前的是时钟存于RAM。DS1302芯片对于少于 31 天的月份月末会自动调整,并会自动对闰年进行校正。由于有一个 AM/PM 指示器,时钟可以工作在 12 小时制或者 24小时制。 ———————————————— 版权声明:本文为CSDN博主「_会飞_的鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文出处链接 DS1302时钟模块需求分析: 需要将模块内的时间显示在显示屏上,同时在用按钮调整时间的时候每调整一次加或减都要将数据写入到模块里面(上述按键检测有对应的代码),作者在设计的时候一开始犯得错误是调整完才进行写入导致不是想要的结果
void write_DS1302(uchar cmd,uchar dat)
{
uchar i ;
TRST = 0;
TSCLK = 0;
TRST = 1;
for(i = 0; i < 8; i ++)
{
TSCLK = 0;
TIO = cmd & 0x01;
TSCLK = 1;
cmd >>= 1;
}
for(i = 0; i < 8;i ++)
{
TSCLK = 0;
TIO = dat & 0x01;
TSCLK = 1;
dat >>= 1;
}
TRST = 0;
}
uchar read_DS1302(uchar cmd)
{
uchar i,dat;
TRST = 0;
TSCLK = 0;
TRST = 1;
for(i = 0; i < 8; i ++)
{
TSCLK = 0;
TIO = cmd & 0x01;
TSCLK = 1;
cmd >>= 1;
}
for(i = 0;i < 8; i ++)
{
TSCLK = 0;
dat >>= 1;
if(TIO)
{
dat |= 0x80;
}
TSCLK = 1;
}
TRST = 0;
return dat;
}
uchar dat_to_BCD(uchar dat)
{
uchar dat1,dat2;
dat1 = dat / 10;
dat2 = dat % 10;
dat2 = dat2 + dat1 * 16;
return dat2;
}
uchar BDD_to_dat(uchar dat)
{
uchar dat1,dat2;
dat1 = dat /16;
dat2 = dat % 16;
dat2 = dat2 + dat1 * 10;
return dat2;
}
void GetDate_ds1302()
{
shi = BDD_to_dat(read_DS1302(0x85));
fen = BDD_to_dat(read_DS1302(0x83));
miao = BDD_to_dat(read_DS1302(0x81));
day = BDD_to_dat(read_DS1302(0x87));
month = BDD_to_dat(read_DS1302(0x89));
year=BDD_to_dat(read_DS1302(0x8d))+2000;
write_DS1302(0x8e,0x80);
}
main函数(包括中断服务函数)
主函数只负责初始化在while大循环中只检测按键再根据按键调整时间 注: 上面在模块分析的时候已经给出对应的中断函数,此处给出只是为了实验的完整性和作者本人编程的风格(中断函数写在主函数的下面,功能函数放在主函数的上面)
main()
{
delay(10);
init_T1();
lcd_init();
display();
display1();
lcd_wcmd(0x8F);
lcd_wdat(0xA1);
lcd_wdat(0xE6);
displayweek();
delay1(10);
while(1){
keyScan();
delay(10);
GetDate_ds1302();
}
}
void timer0() interrupt 1
{
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
count++;
if(count==14)
{
display1();
LED=!LED;
count=0;
miao++;
if(miao==60)
{
miao=0;
fen++;
if(fen==60)
{
fen=0;
shi++;
if(shi==24)
{
shi=0;
}
}
}
}
}
void T1_ISR(void) interrupt 3
{
TH1=(65536-50000)/256;
TL1=(65536-50000)%256;
testcount++;
if(testcount==21)
{
testcount=0;
tempdisplay();
}
}
void timer1() interrupt 0
{
LCD_RW=0;
if(s1==0)
{
delay1(5);
if(s1==0)
{
beepon();
testnum++;
while(!s1);
if(testnum==1)
{
Flag=1;
display1();
TR0=0;
TR1=0;
lcd_wcmd(0x95);
lcd_wcmd(0x0f);
}
}
if(testnum==2)
{
display1();
lcd_wcmd(0x93);
}
if(testnum==3)
{
display1();
lcd_wcmd(0x91);
}
if(testnum==4)
{
display1();
lcd_wcmd(0x85);
}
if(testnum==5)
{
display1();
lcd_wcmd(0x83);
}
if(testnum==6)
{
display1();
lcd_wcmd(0x81);
}
if(testnum==7)
{
Flag=0;
display1();
testnum=0;
lcd_wcmd(0x0c);
TR0=1;
TR1=1;
delay1(10);
}
}
致谢
感谢CSDN的许多文章对我的帮助
参考文献
[1] 郭天祥.新概念51单片机C语言教程[M]. 北京:电子工业出版社, 2018. 120-140. [2] 魏二有.单片机应用系统设计与实现教程[M]. 北京:清华大学出版社, 2019. 100-120.
|