实验名称 院(系)别 实验人姓名 以DS1302实时时钟芯片和液晶显示屏CD1602为基础设计的电子钟 班号 学号 实验日期 2009-6-25到28 实验目的:
1、能够以DS1302实时时钟芯片和液晶显示屏CD1602为基础设计一款电子钟 2、熟悉DS1302芯片的工作过程 3、熟悉CD1602芯片的工作过程
4、可以进行必要的扩展,如用第三DS18B20新品进行温度采集和显示 5、熟悉掌握51的c程序的编写
6、掌握用Proteus进行系统设计仿真验证
实验仪器、仪表目录
1、DS1302实时时钟芯片1片 2、LCD1602液晶显示屏1个, 3、80C52芯片1片 5、DS18B20芯片一片
6、晶振、电容、电阻、开关各若干等 7、proteus仿真软件 8、Keil C51、PC机
实验设计任务
以DS1302实时时钟芯片和液晶显示屏LCD1602为基础设计一个电子钟,要求:时间和日期可调整,按键采用3个按键;至少在Proteus上调试通过。扩展:闹钟和重要日期提醒功能(增加蜂鸣器), 闹铃音乐功能
实验步骤
1、打开Keil软件,新建一个工程文件,选择好芯片,并记得在 “Options for Target 1”的Output选项中,将Create HEX Fil选项勾起来。 2、将编写的程序保存成“.C”的形式
3、编译保存好的C文件,并根据提示修改程序中的错误,直到编译成功为止 4、打开proteus软件,画出实验电路图
5、在89C52中,载入原来已生成的HEX文档
1
6、按下运行键,对Proteus进行软件仿真,观察运行结果
原理、结果及分析
一、设计方案原理与设计特点分析
电子钟总的设计模块:
温度采集模块
DS1302时钟
采集模块
各个模块电路原理分析:
89C52CPU 控制模块 按键处理模块 LCD显示模块 1、DS1302时钟采集模块:
1.1电路原理图:
1.2 DS1302分析:
首先DS1302是DALLAS公司推出的涓流充电时钟芯片。内含有一个实时时钟/日历和31字节静态RAM通过简单的串行接口与单片机进行通信实时时钟/日历电路提供秒分时日日期月年的信息每月的天数和闰年的天数可自动调整时钟操作。
DS1302芯片广脚介绍:X1、X2为32.768KHz晶振管脚。GND 为地。RST复位脚。I/O数据输入/输出引脚。SCLK串行时钟。Vcc1,Vcc2电源供电管脚。与单片机连接的信号线为: DS1302_SCLK 接P1^6; 实时时钟时钟线引脚 DS1302_IO 接P1^7; 实时时钟数据线引脚 DS1302_RST 接P1^5; 实时时钟复位线引脚
特别注意DS1302芯片在读取或写入数据时,都是一位一位传送的,并且每传送一位,SCLK信号线要有一个负跳变。即单片机对SCLK咬先送高电平,再送低电平。数据时通过IO进行传送的。
1.3数据处理子程序流程图
2
是否调用时间获取子 否 程序DS1302
_GetTime
是
调用Read1302函数,读取各个时间参
数,并存放于ReadValue变量中
对ReadValue数据进行转换,转换成
十进制数,并赋给Time指针中。
调用年份转换成可供lcd显示的字
段子程序DateToStr
时间转换成可供lcd显示的字段子
程序TimeToStr
结束 因为DS1302芯片在读取或写入数据时,都是一位一位传送的,并且每传送一位,SCLK信号线要有一个负跳变。所以在对DS1302具体某地址进行一字节数据的写入或读取时,都要调用实时时钟写入一字节(内部函数) DS1302InputByte和实时时钟读取一字节(内部函数) DS1302OutputByte两个函数。
2、按键处理模块
2.1按键连线图
其中按键1为模式键,按键2为加1键,按键3为减1键。与单片机连线如下: mode连接P3^0; //设定修改位数
3
plus连接P3^3; //加键 dec连接P3^6; //减键 2.2按键扫描子程序流程图: Mode键是否按下 将mode按键次数存放于变 量mode_num中 否 mode_n 否 mode_nu 否 mode_nu 否 mode_n mode_num=1 ? um=2? m=3? m=4? um=5? 是 是 是 是 是 否 mode_n 否 mode_nu m=6? um=7? 移动光标,并返回 是 是
2.3加减键处理子程序流程图 判断是否为修改 否 模式 是 判断是否为加一 否 转减一键程序 键 是 判断判断num=3 判断 num=1 ? 否 num=2 ? 否 是 是 是 4 修改year值,并修改mouth值,修改day值, 返回 并返回 并返回 否 否 判断num=6? 判断num=5? 判断num=4? 是 是 是
修改秒值,并返修改分值,并返修改小时值,并
回 回 返回
减1子程序与加1子程序区别只在于修改数值处理不一样,其他都一样。
3、LCD显示模块
3.1 LCD显示模块电路原理图
5
3.2 LCD1602芯片以及连线分析
液晶显示器是一种功耗极低的被动式显示器件,1602广脚介绍:D0—D7数据传送引脚,VSS为接地线,VDD为电源线,VEE为 LCD驱动电压调节,由此可以调节显示亮度。RS为寄存器选择信号,高电平选择数据寄存器,低电平选择指令寄存器。RW为读写控制信号,高电平读,低电平写。EN使能信号,读状态下高电平有效,写状态下下降沿有效。 RS连接P2^0; 寄存器选择信号
RW连接P2^1; 读写控制信号线 EN连接P2^2; 使能信号线 3.3LCD初始化程序流程图:
写指令0x38,显示
模式设置
写指令,显示光标
写指令,光标移动
清屏
在初始化过程中,要反复调用到write_com函数,此函数实现向lcd写入命令的功能。要特别注意写命令和写数据的RS、RW、EN时序问题
4、温度采集显示模块
4.1温度采集显示模块的原理图:
4.2DS18B20芯片以及连线分析
DSl820数字温度计提供 9 位(二进制)温度读数,指示器件的温度、信息经过单线接口送入DSl820 或从DSl820送出。因此从主机CPU到DSl820仅需一条线(和地线) ,DSl820的电源可以由数据线本身提供而不需要外部电源。
DS18B20广脚说明:VCC为电源线,DQ为数据线,GND为地线。数据线DQ与单片机P1.0相连接。
6
4.3温度采集显示模块子程序流程图:
直接向18b20发送温
度变换命令
读取温度寄存器的温
度值 读低八位
读高八位
进行读取数据处理,
得出温度存放于变量
temp中
在此程序中,要特别注意初始化,写和读取数据时的时序处理。首先,初始化中,主机总线先发送一复位脉冲(最短为 480us 的低电平信号),接着刻释放总线并进入接收状态。 DSl8b20在检测到总线的上升沿之后,等待 15-60us,接着DS18b20发出存在脉冲(低电平 持续 60-240 us)。写数据时序:当主机总线先从高拉至低电平时,就产生写时间隙。读书序: 主机总线先从高拉至低电平时,总线只须保持低电平l7ts之后,再将总线拉高,产生读时间隙。
5、总原理图以及主程序流程图:
5.1总的原理图:
7
5.2主程序流程图:
初始化lcd
初始化内部定时器
初始化DS1302
从DS1302读取日期
和时间 移动光标并显示日期 移动光标并显示时间 移动光标并显示温度
重复进行按键扫描
二、Proteus仿真结果:
8
三、实验数据计算处理,性能分析
1、数据计算处理:
1.1计算星期数的算法
days=(today.day+1+2*today.month+3*(today.month+1)/5+today.year+today.year/4-today.year/100+today.year/400)%7,由年月日计算星期 ,用以显示星期数 1.2加减键对时间日期改变处理算法
对于年月日,时分秒来说,每个变量的最大值和最小值都不一样,所以当年月份时分秒改变时,进行处理的算法也不一样,年没有最大值,所以不用采取“封顶”措施,每次年加一处理时,直接自加,而对于月份来说,月份是不能超过13的,所以当月份自加到13时要重新置1,具体处理见程序,在每个语句后,我都有分析注释出来。 1.3时分秒进位算法
当秒,分达到60时,向分进位,且秒数置零,从新开始计数。当时达到24时,也一样处理。
1.4年月日进位算法
因为每个月份的天数都不同,所以事先先设定一数组:
dayofmonth[]={31,28,31,30,31,30,31,31,30,31,30,31}用以判定12个月的最大值,接着判断天数,当超过本月最大天数时,月份加一,且天数要置1。月份判断比较简单,只要超过12,年份就加1,月份置1。
2、性能分析:
首先,lcd能够正确的显示1302芯片上面的时间和日期。
其次,可以通过三个按键: K1, K2和K3键对电子钟进行时间和日期的调整。按K1键进行校时,可以分别对时及分进行单独校时,使其校正到标准时间,校时时需要校正哪一位哪一位就闪烁。按K2键是对闪烁位进行加一的操作。按K3键是对闪烁位进行减一的操作。多次按K1键,当全部闪烁位全闪烁完毕时,就可以退出调整模式。
但是,时间和日期显示正确,但温度显示错误。温度不能正常显示的主要原因是DS18b20的数据传输不正确,不过到现在为止,还没改正过来。
四、实验过程中故障分析与故障排除的描述
故障一:因为自己的知识有限,所以坦诚的说,源代码都是从网上载的,但是我载了2个程序,一个是运用1602以及18b20和按键处理显示时间以及温度,另一个程序是单运用ds1302和1602显示时间而没有按键处理,所以我做的工作就是这么把这两个程序结合起来,实现显示和调整时间的功能。
这里的故障排除过程:目的是在有按键调整时间的程序中,要添加DS1302功能。首先,要在主程序的c文件中,添加#include \"DS1302.h\"语句,并在main主函数中,加入 Initial_DS1302();语句,进行DS1302的初始化,并且添加以下语句:
Initial_DS1302(); //初始化DS1302 DateToStr(&CurrentTime); //从DS1302读取日期 TimeToStr(&CurrentTime); //从DS1302读取时间
显示的程序也要有所改变:display_string(CurrentTime.DateString); //显示日期
此时参数传递过程是先将DS1302内部的时间和日期通过DS1302_GetTime函数读取出来存放到Time中,日期通过DateToStr函数将Time中的数据传到DateString数组中,而时间通过imeToStr函数将Ti me中的数据传到TimeString数组中,最后通过display_string函数显示出来。
9
故障二:通过上面两个函数的合并后,发现编译一直出错,如下图所示:
后来发现,我只是在前面加入头文件\"DS1302.h\",但是忘记把这个文件放在工程目录下,所以该工程一直找不到,结果一直出错。
故障三:当修改完程序后,按下按键时,发现什么反应都没有,后来又在排查了程序,感觉程序都没错,但是就是没反应。偶然之间,发现要长按按键,就可以实现按键调整时间的功能。因为在按键扫描程序中,设置了软件防抖动功能,所以要长按按键,才能调整时间。
故障四:温度显示错误。因为时间有限,前面整合两个程序就花了太多的时间,来不及再调整温度显示模块,自己推测,应该是中间函数参数传递出现错误,在主程序的main函数中,显示温度是display_string(\"00.0C\")语句,所以温度一直显示00.0C,事后有时间会再进行参数调整。
故障五:在实验开始时,感觉不懂如何运用proteus,就连对C52进行程序烧写时,都不知道如何才能生成Hex文档,万事开头难,只能踏踏实实一步一步学习,后来请教同学才知道在“Options for Target 1”的Output选项中,要将Create HEX Fil选项勾起来才可以。
五、实验结论以及体会
实验结论:
1、在该电子钟的设计中修改定时或调整时间时采用了闪烁,在编程上,首先进行了初始化定义了程序的入口地址以及中断的入口地址,在主程序的开始定义了一组固定单元用来存储计数的秒,分,时以及定时时间的序号等。其次,时,分,秒显示用了软件译码(查表)的方式,再用了一段固定的程序段进行进制转化。最后,用查询方式对按键进行判断,若有键按下,则进行软件延时消抖,避免了抖动引起的干扰,执行相应的定时,选时或调时程序段。对当前时间或定时时间修改后又返回到最初的显示程序段,如此循环下去。
2、在硬件上,选用DS1302,LCD1602相结合,首先DS1302内含有一个实时时钟/日历和31字节静态RAM通过简单的串行接口与单片机进行通信实时时钟/日历电路提供秒分时日日期月年的信息每月的天数和闰年的天数可自动调整时钟操作,这样读取数据简单。其次,选用LCD1602进行显示时,数据位串行输入,接口连线少,低功耗,显示清晰。并且本实验的电子钟即要实现时间的现实,还要实现日期的现实,所以若是运用数码管进行显示的话,就算运用动态显示,所占用的IO口多,并且所需的数码管个数多,硬件复杂。
3、proteus是一个非常好用的仿真软件,其具有强大的电路原理图绘制功能,且可以实现模拟电路仿真、数字电路仿真、单片机及其外围电路组成的系统仿真、键盘、LCD系统仿真等多种功能;和keil联合使用时可以检测所编写的程序的正确与否。将keil和proteus联合起来使用是实现电子设计制作的初步阶段,可避免在实际的硬件操作中因为电路原理图或向单片机烧录的程序有误而造成的难以修改的为题。
实验心得:
1、通过本次实验,因为之前接触到的电子系统设计不多,所以一开始,感觉难以入手,就算上网载了很多程序,也看不懂。后来请求同学的帮助,了解了要对各种芯片编写程序时首先应找到该芯片的数据手册,根据数据手册上的说明、时序要求及流程图编写对应程序。
2、其次,再次巩固了Keil C51工程文件的建立,程序编写以及编译的掌握程度。最重要的是,因为只是水平有限,要自己编写C程序很难,但在此实验中,最大的收获莫过于看懂别人的程序,分析之后,自己拼凑编写以实现不同的功能。并且掌握了52C程序的编写过程。
3、掌握了Proteus的使用方法,从实际操作中认识到Proteus在仿真方面的优越性,激
10
发了自己学习Proteus的兴趣;
4、因为自己要修改程序,所以单单花费在程序分析的时间就很多,为了更好的理解程序,我把每句主要程序的后面都注释了该语句的意思,详情可以见程序清单,发现注释语义的工作量也是非常大的。写实验报告时,每个模块的流程图都是自己画的,用WORD文档画图真的很麻烦,而且不是很美观。因为时间比较仓促,流程图写的条理性不够,不过相信以后多多练习,就可以做得更好。
5、在这次实验中我遇到了很多故障,不过通过各种渠道(比如网络,请教同学,老师等等)解决了一些故障,虽然没有全部解决,但能在短短一周内通过此次作业,实现电子钟的功能,还是有点成就感的。在解决这些问题的过程中发现网络确实是一个很好的学习平台,利用前人的经验可以提高自己的解决实际问题的能力。通过这一个多礼拜的学习实践,使我对所学的知识进行了系统的复习和巩固,在以前学习中不够清晰的概念得到了更好的理解。相信通过不断的学习,能使自己扬长补短。
六、程序清单
1、主程序
#include #include\"ds18b20.h\" #include \"DS1302.h\" #define uchar unsigned char #define uint unsigned int #define TIMER0_COUNT 0xEE11 sbit mode=P3^0; //设定修改位数 sbit plus=P3^3; //加键 sbit dec=P3^6; //减键 uchar count,s1num,timer0_tick,count=0; typedef struct{ char hour; char minute; char second; }time; typedef struct{ uint year; uchar month; char day; }date; time now={10,20,0}; //显示时间初始值 date today={2009,1,8}; char code dayofmonth[]={31,28,31,30,31,30,31,31,30,31,30,31}; //设定月份数组,用以判定12个月的最大值 11 char code weekday[7][4]={\"Week1\\"Week4\ //设定行星期显示数 uchar monthday(uchar year,uchar month) { if(month==2 && year%4==0) //用以判定是否为润年,其2月有29天 return(29); else return(dayofmonth[month-1]); //非闰年时的该月份天数 } void display_week() //由年月日计算星期 ,用以显示星期数 { char days; days=(today.day+1+2*today.month+3*(today.month+1)/5+today.year+today.year/4-today.year/100+today.year/400)%7; display_string(&weekday[days][0]); } static void timer0_initialize(void) //timer0 initialize { EA=0; //设置不接受所有中断 timer0_tick=0; 当//当mode_num为1时,为年份改变位, TR0=0; //关闭Timer0 光标移到(1,4) if(mode_num==2) TMOD=0X01; //设置Timer0为 模式2,16位工作模式 gotoxy(1,7); //当mode_num为2时,为月 TL0=(TIMER0_COUNT & 0X00FF); 份改变位,光标移到(1,7) if(mode_num==3) //设置Timer0低八位数值 TH0=(TIMER0_COUNT >> 8); //设置gotoxy(1,10); //当mode_num为3时,为日Timer0高八位数值 期改变位,光标移到(1,10) if(mode_num==4) PT1=1; //设置Timer0的优先级为最高 ET0=1; //设置接受Timer0的中断 gotoxy(2,2); //当mode_num为4时,为 TR0=1; //启动Timer0 小时改变位,光标移到(2,2) if(mode_num==5) EA=1; //设置系统接受中断 } gotoxy(2,5); //当mode_num为5时,为 分钟改变位,光标移到(2,5) void write_time(uchar add,uchar number) if(mode_num==6) gotoxy(2,8);//写时间 //当mode_num为6时,为秒数改变位,{ 光标移到(2,8) gotoxy(2,add); if(mode_num==7) //当 display_data(number); mode_num为7时,退出修改模式 } { void write_riqi(uchar add,uchar number) mode_num=0; //非修//写日期 改模式时,将mode_num置零,有助于判断{ 是否为修改模式 gotoxy(1,add); write_com(0x0c); display_data(number); TR0=1; } } } void keyscan() //按键扫描程序 if(mode_num!=0) //为修改模式时,{ 加减键的处理子程序 { uchar mode_num; //设定mode_num, 来判断是哪一位要修改,当mode_num为零 if(plus==0) //当加键为零,即时,为非修改模式 外部触发一次时,进行以下处理 { if(mode==0) //“修改位”的选择 { delay(5); //延时 delay(5); //延时 if(plus==0) //延时再次判断 if(mode==0) //当外部按键mode加键,防止抖动 { 没按下一次时,都使mode_num自加,即 while(!plus); mode_num表示为按键mode按下的次数 { mode_num++; if(mode_num==1) while(!mode); //当为年份改变时,年份自加一,并且显示 TR0=0; 修改后的日期 write_com(0x0f); { } today.year++; if(mode_num==1) gotoxy(1,4); 12 write_riqi(1,today.year/100); //因为年份主要是最后两位在改变,所以除以100来计算年份值,除以100求得的商值为十位 write_riqi(3,today.year%100);//余数为个位 gotoxy(1,12); display_week(); //因为改变日期都会改变周数,所以要重新显示 gotoxy(1,4); } if(mode_num==2) //以下处理同上 { today.month++; if(today.month==13) today.month=1; //特别注意,月份不能超过12,当为13时,要将月份置1 write_riqi(6,today.month); gotoxy(1,12); display_week(); gotoxy(1,7); } if(mode_num==3) //以下处理同上 { today.day++; if(today.day>monthday(today.year,today.month)) //特别要判断每个月份的最大值,不能超过此数,超过后要将天数置一 today.day=1; write_riqi(9,today.day); gotoxy(1,12); display_week(); gotoxy(1,10); } if(mode_num==4) //以下处理同上 { 13 now.hour++; if(now.hour==24) now.hour=0; //小时不能超过24 write_time(1,now.hour); gotoxy(2,2); } if(mode_num==5) //以下处理同上 { now.minute++; if(now.minute==60) now.minute=0; write_time(4,now.minute); gotoxy(2,5); } if(mode_num==6) //以下处理同上 { now.second++; if(now.second==60) now.second=0; write_time(7,now.second); gotoxy(2,8); } } } if(dec==0) //减键的处理和加键处理处理相反 { delay(5); if(dec==0) { while(!dec); if(mode_num==1) { today.year--; write_riqi(1,today.year/100); write_riqi(3,today.year%100); gotoxy(1,12); display_week(); gotoxy(1,4); } if(mode_num==2) { today.month--; if(today.month==0) today.month=12; //要注意月份的最小值为1,当减为零时,要讲月份置为12 write_riqi(6,today.month); gotoxy(1,12); display_week(); gotoxy(1,7); } if(mode_num==3) { today.day--; if(today.day==0) //当天数减为0时,要重新赋值,为上一个月的最大值 today.day=monthday(today.year,today.month); write_riqi(9,today.day); gotoxy(1,12); display_week(); gotoxy(1,10); } if(mode_num==4) { now.hour--; if(now.hour<0) now.hour=23; //当小时数小于0时,要重新赋值,置为23 write_time(1,now.hour); gotoxy(2,2); } if(mode_num==5) { now.minute--; 14 if(now.minute<0) now.minute=59; //注意点同上 write_time(4,now.minute); gotoxy(2,5); } if(mode_num==6) { now.second--; if(now.second<0) now.second=59; //注意点同上 write_time(7,now.second); gotoxy(2,8); } } } } } void display_temp() //显示温度子程序 { uint wendu; //设置wendu变量来存放从18b20读取的温度 uchar A1,A2; //A1用来存放温度值的十位,A2存放个位 tmpchange(); //启动温度转换 wendu=tmp(); //读取温度值 A1=wendu/10; //求A1值 A2=wendu%10; //求A2值 gotoxy(2,10); display_data(A1); //显示A1值 display_string(\".\"); //用小数点分开个位和小数 write_date(int_to_char[A2]); //向lcd写数据,并且注意要转换A2数据类型 } void timer0(void) interrupt 1 { TR0=0; TL0=(TIMER0_COUNT & 0X00FF);//设置Timer0低八位数值 TH0=(TIMER0_COUNT >> 8);//设置Timer0高八位数值 TR0=1; count++; if(count==30) display_temp(); //延时,当count=30时,显示温度,并延时 if(count==200) //当count自加到200时,开始判断此时时、分、秒的值,并进行转换 { count=0; now.second++; if(now.second==60) //当秒达到60时,向分进位,且秒数置零 { now.second=0; now.minute++; if(now.minute==60) //当分达到60时,向时进位,且分数置零 { now.minute=0; now.hour++; if(now.hour==24) //当时达到24时,向天进位,且时数置零 { now.hour=0; today.day++; if(today.day>monthday(today.year,today.month)) 、//判断天数,当超过本月最大天数时,向月份进位,且天数置一 { today.day=1; today.month++; if(today.month==13) //判断月份,当月份超过12时,向年份进位,月份置一 { today.month=1; 15 today.year++; } write_riqi(6,today.month); 。//在位置6处,向lcd写月份 } write_riqi(9,today.day); //在位置9处,向lcd写天数 gotoxy(1,12); display_week(); } write_time(1,now.hour); //在位置1处,向lcd写时数 } write_time(4,now.minute); //在位置4处,向lcd写分数 } write_time(7,now.second); //在位置7处,向lcd写秒数 } } void main() { SYSTEMTIME CurrentTime; init_lcd(); //初始化lcd timer0_initialize(); //初始化内部定时器 Initial_DS1302(); //初始化DS1302 DateToStr(&CurrentTime); //从DS1302读取日期 TimeToStr(&CurrentTime); //从DS1302读取时间 gotoxy(1,1); display_string(CurrentTime.DateString); //显示日期 display_week(); //显示周几 gotoxy(2,1); display_string(CurrentTime.TimeString); //显示时间 gotoxy(2,10); display_string(\"00.0C\"); //显示温度 while(1) keyscan(); } 2、lcd1602头文件程序: #define uchar unsigned char #define uint unsigned int sbit rs=P2^0; 、//寄存器选择信号 sbit rw=P2^1; //读写控制信号线 sbit lcden=P2^2; //使能信号线 #define lcd_mode 0x38 #define display_cursor 0x0f #define undisplay_cursor 0x0c char code int_to_char[]=\"0123456789\"; //******************************************************** void delay(uint x) //延时程序 { uint a,b; for(a=x;a>0;a--) for(b=10;b>0;b--); } //******************************************************** void write_com(uchar com) //写命令 { P0=com; //写命令,记住时序问题 rs=0; //rs要为低电平 lcden=0; //使lcd使能端产生一个正脉冲 delay(10); lcden=1; delay(10); lcden=0; } //******************************************************** void write_date(uchar date) //写数据 16 { P0=date; //将数据送到P0口准备传送出 rs=1; //写数据时,rs要为高电平 lcden=0; //使lcd使能端产生一个正脉冲 delay(10); lcden=1; delay(10); lcden=0; } //******************************************************** void init_lcd(void) //初始化lcd1602 { delay(15); write_com(lcd_mode); //模式设置 delay(20); write_com(undisplay_cursor); //显示光标 delay(20); write_com(0x06); //光标移动 delay(20); write_com(0x01); //清屏 delay(20); } //******************************************************** void display_string(char *p) //写字符串 { while(*p) { write_date(*p); delay(20); p++; } } //******************************************************** void gotoxy(uchar x,uchar y) //设置移动坐标 { if(x==1) write_com(0x80+y); else write_com(0xc0+y); #define AM(X) X } #define PM(X) (X+12) //************************************// 转成24小时制 ******************** #define DS1302_SECOND 0x80 void display_data(char number) //显示数//时钟芯片的寄存器位置,存放时间,如0x80据 放的的秒数 { #define DS1302_MINUTE 0x82 uchar x,y; #define DS1302_HOUR 0x84 #define DS1302_WEEK 0x8A x=number/10; //将取得的数据转换成 #define DS1302_DAY 0x86 十进制 y=number%10; #define DS1302_MONTH 0x88 write_date(int_to_char[x]); #define DS1302_YEAR 0x8C write_date(int_to_char[y]); #define DS1302_RAM(X) (0xC0+(X)*2) } //用于计算 DS1302_RAM 地址的宏 RAM命令控制字地址:0xC0-0xFD void DS1302InputByte(unsigned char d) //3、ds1302头文件程序 #ifndef 实时时钟写入一字节(内部函数) _REAL_TIMER_DS1302_2003_7_21_ { #define unsigned char i; _REAL_TIMER_DS1302_2003_7_21_ ACC = d; for(i=8; i>0; i--) sbit DS1302_CLK = P1^6; { DS1302_IO = ACC0; //实时时钟时钟线引脚 sbit DS1302_IO = P1^7; IO 线上的数据存放在ACC中 // 将 DS1302_CLK = 1; //实时时钟数据线引脚 sbit DS1302_RST = P1^5; // 特别注意, DS1302写一数据时,CLK//实时时钟复位线引脚 要产生一负脉冲 sbit ACC0 = ACC^0; DS1302_CLK = 0; sbit ACC7 = ACC^7; ACC = ACC >> 1; //每次移位一位 typedef struct __SYSTEMTIME__ } { } unsigned char Second; unsigned char Minute; unsigned char DS1302OutputByte(void) // unsigned char Hour; 实时时钟读取一字节(内部函数) unsigned char Week; { unsigned char Day; unsigned char i; unsigned char Month; for(i=8; i>0; i--) unsigned char Year; { unsigned char DateString[9]; ACC = ACC >>1; unsigned char TimeString[9]; //将前一个下降沿读出的数据右移一位}SYSTEMTIME; //定义的时间类型 从而读出该次读出的数放入ACC7 17 ACC7 = DS1302_IO; if(flag) DS1302_CLK = 1; Write1302(0x8E,0x80); //0x8E 控 制字节地址,bit7=WP WP=1 禁止数据写入 DS1302_CLK = 0; DS1302 else //SCLK下降沿读出DS1302的数 } Write1302(0x8E,0x00); //WP=0 return(ACC); 允许写入DS1302 } } void Write1302(unsigned char ucAddr, void DS1302_SetTime(unsigned char Address, unsigned char ucDa) //其中Addr为 unsigned char Value) // 设置时间函DS1302地址, ucData为要写的数据 数 { { DS1302_RST = 0; DS1302_SetProtect(0); Write1302(Address, ((Value/10)<<4 | //写数据时,要注意RST产生一上升沿,CLK 为低电平 (Value%10))); //读写数据时要特别注意数据 DS1302_CLK = 0; 的处理,要写的数据要进行拼凑,将Value DS1302_RST = 1; 数据的十位取出,放入高四位,Value的个 DS1302InputByte(ucAddr); // 位放在低四位上 } 地址,命令 DS1302InputByte(ucDa); // 写1 void DS1302_GetTime(SYSTEMTIME 字节数据 DS1302_CLK = 1; *Time) DS1302_RST = 0; { } unsigned char ReadValue; //用来传递 读取1302的数据 unsigned char Read1302(unsigned char ReadValue = ucAddr) //读取DS1302某地址的数据 Read1302(DS1302_SECOND);?//读取秒{ 数,存放在ReadValue变量中 unsigned char ucData; Time->Second = DS1302_RST = 0; ((ReadValue&0x70)>>4)*10 + DS1302_CLK = 0; (ReadValue&0x0F); //将ReadValue数据 DS1302_RST = 1; 的第4、5、6位取出,实际上就是秒数的十 DS1302InputByte(ucAddr|0x01); 位,但是以二进制的形式表示,所以要进行// 地址,命令 移位,向右移四位,在乘以10,在加上 ucData = DS1302OutputByte(); ReadValue变量中秒数的,将十六进制的数// 读1字节数据 DS1302_CLK = 1; 化成十进制数表示,并放在Time->Second DS1302_RST = 0; 中 return(ucData); ReadValue = } Read1302(DS1302_MINUTE); Time->Minute = void DS1302_SetProtect(bit flag) //((ReadValue&0x70)>>4)*10 + 是否写保护 (ReadValue&0x0F);//同上 { ReadValue = 18 Read1302(DS1302_HOUR); Time->Hour = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //同上 ReadValue = Read1302(DS1302_DAY); Time->Day = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //同上 ReadValue = Read1302(DS1302_WEEK); Time->Week = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //同上 ReadValue = Read1302(DS1302_MONTH); Time->Month = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //同上 ReadValue = Read1302(DS1302_YEAR); Time->Year = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //同上 } void DateToStr(SYSTEMTIME *Time) //年份转换成可供lcd显示的字段子程序 { Time->DateString[0] = Time->Year/10 + '0'; //Time->DateString[0]中存放的就是要显示年的十位 Time->DateString[1] = Time->Year%10 + '0'; //Time->DateString[1]中存放的就是要显示年的个位 Time->DateString[2] = '-'; //年和月份之间用-分开 Time->DateString[3] = Time->Month/10 + '0'; //Time->DateString[3]中存放的就是要显示月份的十位 Time->DateString[4] = Time->Month%10 + '0'; //Time->DateString[4]中存放的就是要显示月份的个位 Time->DateString[5] = '-'; Time->DateString[6] = Time->Day/10 + 19 '0'; //Time->DateString[6]中存放的就是要 显示日的十位 Time->DateString[7] = Time->Day%10 + '0'; //Time->DateString[7]中存放的就是要显示日的个位 Time->DateString[8] = '\\0'; //记住,结束时要添加'\\0',以作为结束标志 } void TimeToStr(SYSTEMTIME *Time) //时间转换成可供lcd显示的字段子程序 { Time->TimeString[0] = Time->Hour/10 + '0'; Time->TimeString[1] = Time->Hour%10 + '0'; Time->TimeString[2] = ':'; Time->TimeString[3] = Time->Minute/10 + '0'; Time->TimeString[4] = Time->Minute%10 + '0'; Time->TimeString[5] = ':'; Time->TimeString[6] = Time->Second/10 + '0'; Time->TimeString[7] = Time->Second%10 + '0'; Time->TimeString[8] = '\\0'; } void Initial_DS1302(void) //DS1302的初始化程序 { unsigned char Second=Read1302(DS1302_SECOND); if(Second&0x80) //BIT7=CH CH=0振荡器允许工作,CH=1,振荡器停止 DS1302_SetTime(DS1302_SECOND,0); } #endif 4、ds18b20头文件 #include #define uchar unsigned char #define uint unsigned int uint temp; // 温度变量 sbit DS=P1^0; //定义数据传输接口 void delay1(uchar count) //延时 { while(count>0) count--; } void reset(void) //送初值和初始命令 { DS=0; delay1(100); DS=1; delay1(4); delay1(200); } bit read_bit(void) //读一比特,特别注意对于18b20,要读数据时,一定要产生DS正脉冲,然后才传送数据 { bit temp; DS=0; _nop_(); DS=1; _nop_(); temp=DS; delay1(200); return temp; } uchar read_byte(void) //读一字节的数据 { uchar i,byte=0; bit j; for(i=0;i<8;i++) { 20 byte=_cror_(byte ,1); //将byte数据向右循环一位,每次都取出最高位 j=read_bit(); //设定j为读取进来的一比特数 if(j==0) byte=byte|0x00; //当j=0时,即读取一字节数完毕,取出次字节数 else byte=byte|0x80; //每次取出最高位 } return byte; //返回字节数 } void write_byte(uchar command) //写一字节到18b20 { uchar i; for(i=0;i<8;i++) //用i来设定一字节的数据 { if((command & 0x01)==0) //取出最低位为零时,DS要产生一个负脉冲脉冲 { DS=0; delay1(8); DS=1; _nop_(); } else //否则产生一上升沿 { DS=0; _nop_(); DS=1; delay1(8); } command=_cror_(command,1); } } void tmpchange(void) //启动18b20 { reset(); write_byte(0xcc); //直接向18b20发送温度变换命令 write_byte(0x44); //启动18b20进行温度 转换 } uint tmp() //获取温度 { float tt; uchar a,b; reset(); write_byte(0xcc); //直接向18b20发送温度变换命令 write_byte(0xbe); //读取温度寄存器的温度值 a=read_byte();//读低八位 b=read_byte();//读高八位 temp=b; temp<<=8; //因为18b20处理接受到的数据要进行处理,先将高八位左移4位 temp=temp|a; //在和低八位相或,整合取得的数据 tt=temp*0.0625; temp=tt*10+0.5; return temp; } 21 因篇幅问题不能全部显示,请点此查看更多更全内容