PROTEUS仿真——万年历
LCD1602显示年月日、星期、时间和温度。
原理图如下:
DS1302时钟芯片驱动代码
//DS1302写8位地址和数据
void RTC_wcom(uchar addr,uchar wdata)
{
uchar i,j;
RTC_RST=0;
RTC_CLK=1;
RTC_RST=1;
for(i=0;i<8;i++)
{
RTC_DAT=addr >> i & 0x01;
RTC_CLK=0;
RTC_CLK=1;
}
for(j=0;j<8;j++)
{
RTC_DAT=wdata >> j & 0x01;
RTC_CLK=0;
RTC_CLK=1;
}
RTC_CLK=1;
RTC_RST=0;
}
//DS1302读数据
uchar RTC_rcom(uchar addr)
{
uchar i,rdata=0;
RTC_RST=1;
for(i=0;i<8;i++)
{
RTC_CLK=0;
RTC_DAT=addr>>i & 0x01;
RTC_CLK=1;
}
for(i=0;i<8;i++)
{
RTC_CLK=1;
RTC_CLK=0;
if(RTC_DAT)
rdata |= (0x01<<i);
}
RTC_DAT=0;
RTC_CLK=1;
RTC_RST=0;
return rdata;
}
//DS1302初始化
void RTC_init(void)
{
RTC_wcom(0x8e,0x00); //00允许写,80写保护
RTC_wcom(0x8c,0x00); //年,00~99,初始00
RTC_wcom(0x88,0x01); //月,01~12,初始01
RTC_wcom(0x86,0x01); //日,01~31,初始01
RTC_wcom(0x8a,0x06); //星期,1~7,初始6
RTC_wcom(0x84,0x12); //时,00~23,初始12
RTC_wcom(0x82,0x00); //分,00~59,初始00
RTC_wcom(0x80,0x00); //秒,00~59,初始00
}
DS18B20驱动代码
uint tmp;
uchar tmp_sign;
//DS18B20对时序有一定要求,有问题的话查一下延迟时序是否符合规格书要求
//DS18B20初始化
void tmp_init(void)
{
TMP_DQ=0;
delay(80); //拉低480~960us
TMP_DQ=1;
delay(1);
while(TMP_DQ); //等待应答
while(!TMP_DQ); //应答结束后等待DS18B20释放总线
}
//DS18B20写命令
void tmp_write(uchar cmd)
{
uchar i;
for(i=0;i<8;i++)
{
TMP_DQ=0;
_nop_();
TMP_DQ=cmd & 0x01;
cmd >>= 1;
delay(5);
TMP_DQ=1;
_nop_();
}
}
//读取数据,先低后高
uchar tmp_read(void)
{
uchar i;
uchar dat=0;
for(i=0;i<8;i++)
{
TMP_DQ=0;
_nop_();
TMP_DQ=1;
_nop_();
dat >>= 1;
if(TMP_DQ==1)
dat |= 0x80;
delay(5);
TMP_DQ=1;
_nop_();
}
return dat;
}
void get_tmp(void)
{
uchar tmpL,tmpH;
tmp_init(); //初始化
tmp_write(0xcc); //跳过ROM
tmp_write(0x44); //温度转换
tmp_init(); //再初始化
tmp_write(0xcc); //跳过ROM
tmp_write(0xbe); //请求数据读取
tmpL=tmp_read(); //接收温度低位
tmpH=tmp_read(); //接收温度低位
tmp=tmpH; //高位和低位合并
tmp=(tmp<<8)|tmpL;
if(tmp<0x0fff) //判断是正温度
{
tmp_sign=10; //符号为空
tmp=tmp*5/8; //数值*0.0625为温度值,留一位小数,为了方便数据处理,乘以10倍,转为整型
} //这里用小数的话,代码长度刚好超2k了,一个方法是直接改成(*5/8),节省空间
else //另一个方法是设置里,把ROM由small改为large
{
tmp_sign=11; //负温度显示负号“-”
tmp=(~tmp+1)*5/8;
}
}
LCD1602驱动代码
uchar code num[] = "0123456789 -";
uchar code week[][3] = {"MON","TUE","WED","THU","FRI","SAT","SUN"};
uchar yyH,yyL,moH,moL,ddH,ddL,hhH,hhL,miH,miL,ssH,ssL;
uint wk;
//LCD1602写命令
void LCD_wr_cmd(uchar cmd)
{
busy_check();
LCD_RS=0;
LCD_RW=0;
LCD_CE=1;
LCD_DA=cmd;
delay(100);
LCD_CE=0;
}
//LCD1602写数据
void LCD_wr_dat(uchar dat)
{
busy_check();
LCD_RS=1;
LCD_RW=0;
LCD_CE=1;
LCD_DA=dat;
delay(100);
LCD_CE=0;
}
//LCD1602查忙,检查前面数据是否已处理完
void busy_check(void)
{
uchar busy_flag;
busy_flag = 0xff;
LCD_RS=0;
LCD_RW=1;
do
{
LCD_CE=1;
busy_flag=LCD_DA;
delay(100);
LCD_CE=0;
}while(busy_flag & 0x80);
}
//LCD初始化
void LCD_init(void)
{
LCD_wr_cmd(0x01); //清显示
LCD_wr_cmd(0x06); //光标右移,文字不动
LCD_wr_cmd(0x0c); //开显示,无光标,不闪烁
LCD_wr_cmd(0x38); //8位总线,双行显示,5X7的点阵字符
}
//获取年月日,星期,时间数据
void get_dat(void)
{
yyH=RTC_rcom(0x8d)/16;
yyL=RTC_rcom(0x8d)%16;
moH=RTC_rcom(0x89)/16;
moL=RTC_rcom(0x89)%16;
ddH=RTC_rcom(0x87)/16;
ddL=RTC_rcom(0x87)%16;
wk =RTC_rcom(0x8b);
hhH=RTC_rcom(0x85)/16;
hhL=RTC_rcom(0x85)%16;
miH=RTC_rcom(0x83)/16;
miL=RTC_rcom(0x83)%16;
ssH=RTC_rcom(0x81)/16;
ssL=RTC_rcom(0x81)%16;
}
按键设置
uchar key=0;
uchar code month_day[]={31,28,31,30,31,30,31,31,30,31,30,31};
//按键扫描
void key_scan(void)
{
if(!t_set)
{
delay10ms(1);
if(!t_set)
while(!t_set);
key=1;
}
if(!t_rst)
{
delay10ms(1);
if(!t_rst)
while(!t_rst);
key=2;
}
}
//按加减键设置时间
void manual_set(uchar min,uchar max,uchar LCD_cmd,uchar RTC_cmd)
{
uchar temp=min;
do
{
key_scan(); //按键扫描
if(!t_add) //加按键按下,按住就一直增加
{
delay10ms(10);
if(!t_add)
{
++temp;
if(temp==max+1)temp=min;
LCD_wr_cmd(LCD_cmd|0x80); //显示调整的值
LCD_wr_dat(num[temp/10]);
LCD_wr_dat(num[temp%10]);
delay10ms(20); //增加延时,调节增加的速度
}
}
if(!t_dec) //减按键按下,功能同上
{
delay10ms(10);
if(!t_dec)
{
if(temp==min)temp=max+1;
--temp;
LCD_wr_cmd(LCD_cmd|0x80);
LCD_wr_dat(num[temp/10]);
LCD_wr_dat(num[temp%10]);
delay10ms(20);
}
}
}while(key!=1); //确定设置键是否按下,按下就跳出循环
key=0; //按键值清零
RTC_wcom(RTC_cmd,temp/10*16+temp%10); //最终值写入DS1302
}
//星期的计算
void week_set(void)
{
uchar i;
yyH=RTC_rcom(0x8d)/16; //获取年月日数据
yyL=RTC_rcom(0x8d)%16;
moH=RTC_rcom(0x89)/16;
moL=RTC_rcom(0x89)%16;
ddH=RTC_rcom(0x87)/16;
ddL=RTC_rcom(0x87)%16;
wk=(yyH*10+yyL)*365+(yyH*10+yyL)/4; //先按年的天数计算,加上闰年天数
for(i=0;i<moH*10+moL-1;i++) //再加上月的天数
{
wk=wk+month_day[i];
}
wk=wk+ddH*10+ddL; //再加上天数
if((yyH*10+yyL)/4==0) //如果当年是闰年
{
if(moH*10+moL<3) //且当月不到3月份
wk=wk-1; //需减去1天
}
wk=(wk%7+5)%7+1; //总天数除以7取余,加上2000年1月1日补偿数5,可能超过7,再取余,避免出现0,再加1
RTC_wcom(0x8a,wk); //写入寄存器
}
//时间设置
void time_set(void)
{
key_scan();
if(key==1) //设置键按下
{
key=0;
LCD_wr_cmd(0x01); //清显示
LCD_wr_cmd(0x0f); //开显示,有光标,闪烁
LCD_wr_cmd(0x00|0x80);
LCD_wr_dat('2');
LCD_wr_dat('0');
LCD_wr_dat('0');
LCD_wr_dat('0');
RTC_wcom(0x8e,0x00); //00允许写
RTC_wcom(0x80,0x80); //停止计时
manual_set(0,99,0x02,0x8c); //设置年
LCD_wr_dat('-');
LCD_wr_dat('0');
LCD_wr_dat('1');
manual_set(1,12,0x05,0x88); //设置月
LCD_wr_dat('-');
LCD_wr_dat('0');
LCD_wr_dat('1');
manual_set(1,31,0x08,0x86); //设置日
LCD_wr_cmd(0x40|0x80);
LCD_wr_dat('0');
LCD_wr_dat('0');
manual_set(0,23,0x40,0x84); //设置小时
LCD_wr_dat(':');
LCD_wr_dat('0');
LCD_wr_dat('0');
manual_set(0,59,0x43,0x82); //设置分钟
week_set();
RTC_wcom(0x80,0x00); //开始计时
LCD_wr_cmd(0x0c); //开显示,无光标,不闪烁
}
if(key==2) //复位键按下
{
key=0;
RTC_init(); //DS1302恢复初始值
}
}
主函数及显示部分:
#include <reg51.h>
sbit LCD_RS=P2^0; //LCD相关端口
sbit LCD_RW=P2^1;
sbit LCD_CE=P2^2;
sbit t_set=P2^4; //按键
sbit t_add=P2^5;
sbit t_dec=P2^6;
sbit t_rst=P2^7;
sbit RTC_DAT=P1^0; //DS1302相关端口
sbit RTC_CLK=P1^1;
sbit RTC_RST=P1^2;
sbit TMP_DQ=P3^7;
#define LCD_DA P0 //LCD数据口
typedef unsigned char uchar;
typedef unsigned int uint;
void display(void)
{
uchar i;
uchar a,b,c;
get_dat();
//显示日期
LCD_wr_cmd(0x00|0x80); //从1行0列开始显示
LCD_wr_dat('2');
LCD_wr_dat('0');
LCD_wr_dat(num[yyH]);
LCD_wr_dat(num[yyL]);
LCD_wr_dat('-');
LCD_wr_dat(num[moH]);
LCD_wr_dat(num[moL]);
LCD_wr_dat('-');
LCD_wr_dat(num[ddH]);
LCD_wr_dat(num[ddL]);
//显示星期
LCD_wr_cmd(0x0c|0x80);
for(i=0;i<3;i++)
{
LCD_wr_dat(week[wk-1][i]);
}
//显示时间
LCD_wr_cmd(0x40|0x80); //从2行0列开始显示
LCD_wr_dat(num[hhH]);
LCD_wr_dat(num[hhL]);
LCD_wr_dat(':');
LCD_wr_dat(num[miH]);
LCD_wr_dat(num[miL]);
LCD_wr_dat(':');
LCD_wr_dat(num[ssH]);
LCD_wr_dat(num[ssL]);
//显示温度
get_tmp(); //获取已乘10倍的温度值
a=tmp/100; //取百位数作为十位数
b=tmp/10%10; //取十位数作为个位数
c=tmp%10; //取个位数作为小数点后一位数
LCD_wr_cmd(0x49|0x80); //从2行9列开始显示
LCD_wr_dat(num[tmp_sign]); //显示温度符号,正温度显示空格
if(a!=0) //如果十位不是0,就显示,否则跳过不显示
LCD_wr_dat(num[a]);
LCD_wr_dat(num[b]);
LCD_wr_dat('.');
LCD_wr_dat(num[c]);
LCD_wr_dat(0xdf); //角度符号,加上下面的大写“C”,作为温度符号
LCD_wr_dat('C');
LCD_wr_dat(num[10]); //末尾加一个空格,避免十位不显示时,这里有残留的“C”符号
}
void main(void)
{
RTC_init(); //DS1302初始化
LCD_init(); //LCD1602初始化
while (1)
{
display(); //显示
time_set(); //时间设置
}
}