基于单片机万年历设计

来源:美国留学 发布时间:2021-03-30 点击:

基于单片机的万年历设计 二、实验要求 设计一个万年历,将时钟显示在LCD1602的显示屏上并且可以进行年、月、日以及时、分、秒的设置。此外还可以通过按键进行闹钟设置以及事件提醒功能,用蜂鸣器进行闹铃提醒。最后附加一个温湿度检测的功能,用温湿度传感器检测室内的温湿度并将温湿度数据在显示屏上显示出来。

三、实验设备和仪器 1.用 STC89C52芯片作为系统板的主控芯片 2.DHT11温湿度传感器 3.DS1302时钟芯片 4.LCD1602显示屏 四、实验各模块原理介绍 4.1 STC89C52单片机 STC89C52是STC公司生产的一种低功耗、高性能CMOS8位微控制器,具有8K字节系统可编程Flash存储器。STC89C52使用经典的MCS-51内核,具有传统51单片机不具备的功能。在单芯片上,拥有灵巧的8 位CPU 和在系统可编程Flash,使得STC89C52为众多嵌入式控制应用系统提供高灵活、超有效的解决方案。

(1)主要特性 8K字节程序存储空间;

512字节数据存储空间;

内带4K字节EEPROM存储空间;

可直接使用串口下载;

(2)器件参数 1. 增强型8051单片机,6时钟/机器周期和12时钟/机器周期可以任意选择,指令代码完全兼容传统8051。

2. 工作电压:5.5V~3.3V(5V单片机)/3.8V~2.0V(3V 单片机)
3.工作频率范围:0~40MHz,相当于普通8051的0~80MHz,实际工作频率可达48MHz 4. 用户应用程序空间为8K字节 5. 片上集成512字节RAM 6. 通用I/O 口(32个),复位后为:P1/P2/P3 是准双向口/弱上拉,P0口是漏极开路输出,作为总线扩展用时,不用加上拉电阻,作为I/O口用时,需加上拉电阻。

7. ISP(在系统可编程)/IAP(在应用可编程),无需专用编程器,无需专用仿真器,可通过串口(RXD/P3.0,TXD/P3.1)直接下载用户程序,数秒即可完成一片。

8. 具有EEPROM 功能 9. 共3个16位定时器/计数器。即定时器T0、T1、T2。

10.外部中断4 路,下降沿中断或低电平触发电路,Power Down 模式可由外部中断低电平触发中断方式唤醒。

11. 通用异步串行口(UART),还可用定时器软件实现多个UART。

12. 工作温度范围:-40~+85℃(工业级)/0~75℃(商业级)
13. PDIP封装 1、STC89C52单片机引脚图 图4.1 STC89C52单片机引脚图 ① 主电源引脚(2根)
VCC(Pin40):电源输入,接+5V电源
GND(Pin20):接地线 ②外接晶振引脚(2根)
XTAL1(Pin19):片内振荡电路的输入端
XTAL2(Pin20):片内振荡电路的输出端 ③控制引脚(4根)
RST/VPP(Pin9):复位引脚,引脚上出现2个机器周期的高电平将使单片机复位。

  ALE/PROG(Pin30):地址锁存允许信号
PSEN(Pin29):外部存储器读选通信号
EA/VPP(Pin31):程序存储器的内外部选通,接低电平从外部程序存储器读指令,如果接高电平则从内部程序存储器读指令。

④可编程输入/输出引脚(32根)
STC89C52单片机有4组8位的可编程I/O口,分别位P0、P1、P2、P3口,每个口有8位(8根引脚),共32根。

PO口(Pin39~Pin32):8位双向I/O口线,名称为P0.0~P0.7 P1口(Pin1~Pin8):8位准双向I/O口线,名称为P1.0~P1.7 P2口(Pin21~Pin28):8位准双向I/O口线,名称为P2.0~P2.7 P3口(Pin10~Pin17):8位准双向I/O口线,名称为P3.0~P3.7。

2、STC89C52单片机最小系统 STC89C52单片机最小系统主要由复位电路和时钟电路组成。复位功能通过外部电路来实现,按下按键K1控制单片机的复位。时钟电路主要通过晶振来实现,为单片机提供运行时钟。根据不同的需求选用的晶振频率会有差异,在本次课程设计中采用11.0592MHZ的晶振频率。单片机复位电路如图4.2所示,晶振电路如图4.3所示。

图4.2 复位电路 图4.3晶振电路 4.2 DS1302芯片 DS1302是DALLAS公司出的一款实时时钟芯片,它广泛应用于电话、传真、便携式仪器等产品领域,主要性能指标如下:
1、DS1302是一个实时时钟芯片,可以提供秒、分、小时、日期、月、年等信息,具有软件自动调整的能力,可以通过配置AM/PM来决定采用24小时格式还是12小时格式。

2、拥有31字节数据存储RAM。

3、串行I/O通信方式。

4、DS1302的工作电压比较宽,在2.0V~5.5V范围内均可正常工作。采用双电源供电,当主电源比备用电源高0.2V时,由主电源供电,否则采用备用电源,一般是一个纽扣电池。

5、DS1302实时时钟芯片的功耗很低,当工作电压为2.0V时,工作电流小于300nA。

6、DS1302共有8个引脚,有两种封装形式,一种是DIP-8封装,芯片宽度(不含引脚)是300mil,一种是SOP-8封装,有两种宽度,一种是150mil,一种是208mil。

(1)
DS1302引脚及其功能 图 4.4 DS1302芯片引脚图 (1)Vcc2:主电源引脚,当Vcc2比Vcc1高0.2v以上时,DS1302由Vcc2供电,当Vcc2低于Vcc1时,由Vcc1供电。

(2、3)X1、X2:这两个引脚需要接一个32.768k的晶振,为了给DS1302提供一个基准。但是该晶振的引脚负载电容必须为6pF,若使用有源晶振,接到X1上即可,X2则悬空。

(4)GND:接地 (5)CE:DS1302的输入引脚。该引脚内部有一个40k的下拉电阻,当该引脚为高电平,对DS1302进行读写。

(6)I/O:该引脚是一个双向通信引脚,且内部含有一个40k的下拉电阻,可进行数据的读写。

(7)SCLK:该引脚是输入引脚,且内部含有一个40k的下拉电阻,作为通信的时钟信号。

(8)Vcc1:备用电源引脚。

(2) DS1302与单片机接线图 DS1302引脚与单片机接线图如图4.5所示,从图4.5可看出,引脚X1与X2之间接一个32.768K的晶体正振荡器,Vcc1通过一个纽扣能电池再接地。

图4.5 DS1302与单片机连接图 4.3 LCD1602液晶显示屏 LCD1602液晶屏能够能够同时显示32个字符,价格便宜,编程简单而且稳定可靠。LCD1602液晶屏是一种图形点阵显示器,显示原理简单易懂,都是液晶屏内部的液晶材料变化而显示不同的字符,因为液晶是具有流动特性的物质,所以只需外加很微小的力量即可使液晶分子运动。

(1)LCD1602引脚 1602LCD采用标准的14脚(无背光)或16脚(带背光)接口,各引脚接口图如图4.6所示:
图4.6 LCD1602引脚图 (a) 各个引脚说明 1脚:VSS为地电源 2脚:VDD接5V正电源 3脚:V0为液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比 对比度过高时会产生“鬼影”,使用时可以通过一个10K的电位器调整。

4脚:RS为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器 5脚:R/W为读写信号线,高电平时进行读操作,低电平时进行写操作。当RS R/W共同为低电平时可以写入指令或者显示地址,当RS为低电平,R/W为高电平;

RS为高电平,R/W为低电平时可以写入数据。

6脚:E端为使能端,当E端由高电平跳变成低电平时,液晶模块执行命令。

7~14脚:D0~D7为8位双向数据线。

15脚:背光源正极 16脚:背光源负极 (b)LCD1602主要技术参数:
显示容量:32个字符;

芯片工作电压:4.5-5.5V;

工作电流:2.0mA(5.0V);

模块最佳工作电压:5.0V;

(2)LCD1602与单片机接线图 图4.7 LCD1602与单片机接线图 LCD1602与单片机接线图如4.7所示,背光源正极(5)与VDD(2)均接电源,将液晶显示器接地保证对比度最强,可使用一个10K的电位器进行调整。

4.4 温湿度传感器DHT11 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它采用专用的数字模块采集技术、温湿度传感技术,确保产品的可靠性、长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。因此,该产品具有品质卓越、超快响应、抗干扰能力强、性价比极高等优点。

每个DHT11传感器都在极为精确的湿度校验室中进行校准。校准系数以程序的形式储存在OTP内存中,传感器内部在检测信号的处理过程中要调用这些校准系数。单线制串行接口,使系统集成变得简易快捷。超小的体积、极低的功耗,信号传输距离可达20米以上,使其成为各类应用甚至最为苛刻的应用场合的最佳选则。产品为4针单排引脚封装,连接方便,特殊封装形式可根据用户需求而提供。

(1)DHT11引脚及其功能 DHT11的引脚图如图4.8所示,一般情况下它有四个引脚,分别为:VCC、DOUT、NC、GND。各引脚以及功能如表4-1所示。

图4.8 DHT11引脚图 表4-1 DHT11引脚及其功能 pin 名称 注释 1 VDD 供电3-5.5VDC 2 DATA 串行数据,单总线 3 NC 空脚,请悬空 4 GND 接地,电源负极 1.电源引脚 DHT11的供电电压为3-5.5V。传感器上电后,要等待 1s 以越过不稳定状态在此期间无需发送任何指令。电源引脚(VDD,GND)之间可增加一个100nF 的电容,作用在于去耦滤波。

2.串行接口(单线双向) DATA是用于微处理器与DHT11之间的通信和同步的串行双向接口,采用单总线数据格式。每次通信都是以高位先出的顺序传输40位数据,用时约为4 ms。

数据格式为:
8位湿度整数数据+8位湿度小数数据+8位温度整数数据+8位温度小数数据+8位校验和数据。数据分小数部分和整数部分,当前小数部分用于以后扩展,现读出为零。数据传送正确时,校验和数据等于“8位湿度整数数据+8位湿度小数数据+8位温度整数数据+8位温度小数数据”所得结果的末8位。

(2)DHT11与单片机接线图 DHT11与单片机的接线图如图4.9所示,由图4.9中可看出,DHT11第三个引脚接单片机的P2.2引脚,第一个引脚接电阻和发光二极管。

图4.9 DHT11与单片机接线图 4.5 蜂鸣器 蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、复印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中作发声器件。蜂鸣器可分为无源蜂鸣器和有源蜂鸣器两种,此处我们用到的是有源蜂鸣器。有源蜂鸣器工作的理想信号是直流电,通常标示为VDC、VDD等。因为蜂鸣器内部有一简单的振荡电路,能将恒定的直流电转化成一定频率的脉冲信号,从而带动钼片振动发音。

有源蜂鸣器主要靠压电效应的原理来发声的,且内部带有多谐振荡器,可产生1.5-2.5kHZ 的电压信号。由此有源蜂鸣器才能发声。有源蜂鸣器在单片机中的电路图较为简单,图4.10为蜂鸣器与单片机的接线图。

图4.10 蜂鸣器与单片机的接线图 有源蜂鸣器直接接上额定电源就可连续发声,I/O口输出后接一个1K的电阻,目的在于基极限流,以低电平方式,启动蜂鸣器发声。

五、流程图 (1) 主程序流程图 图5.1是系统主程序流程图,系统开始时先进行初始化,然后再调整时间和闹钟,再通过串口屏将时间显示出来。

图5.1 主程序流程图 (2)子程序流程图 系统从主程序开始执行,分别调用各个子程序,其中子程序包括DS1302子程序,DHT11子程序和LCD1602子程序等。

(1)DS1302芯片子程序流程图 DS1302为实时时钟芯片,可以提供秒、分、时、日、月、年及星期等信息,具有软件自动调整的能力,可以通过配置AM/PM来决定采用24小时格式还是12小时格式。从DS1302中可以读出年、月、日、时、分、秒等信息。图5.2为DS1302芯片的子程序流程图:
图5.2 DS1302子程序流程图 (2)DHT11子程序流程图 DHT11为温湿度传感器,可以实时显示环境中的温湿度。其数据由8位湿度整数数据,8位湿度小数数据,8位温度整数数据和8位温度小数数据以及8位校验和数据。DHT11子程序流程图如5.3所示:
图5.3 DHT11子程序流程图 (3)LCD1602子程序流程图 LCD1602显示时,也要先进行初始化,紧接着读指令,读完之后写指令,再对数据进行读写,然后显示出来,其流程图如图5.4所示。

图5.4 LCD1602显示屏子程序流程图 六、实验目的 1.学会并掌握可keil软件的使用;

2.学会并掌握Altium Designer软件的使用;

3.实现万年历的基本功能;

4.通过实验巩固单片机相关知识和检验自身动手能力 七、实验内容 本次实验使用STC89C52单片机作为主控芯片,使用DS1302芯片作为时钟芯片,在LCD1602显示屏上显示,并可以设置闹钟和事件提醒功能,此外还可以进行室内温湿度的检测和显示,最终完成万年历的设计。

八、实验步骤 1.查询资料,确定主控芯片、温湿度传感器、时钟芯片以及LCD1602显示屏的使用说明。

2.根据功能要求进行硬件电路的设计,使用Altium Designer软件设计电路。整个电路分为主控芯片控制模块、LCD1602显示模块、按键设置模块、蜂鸣器电路模块以及DHT11温湿度传感器模块。硬件电路图如图1所示:
图8.1:硬件电路图 3.根据电路图编写C语言代码:
代码如下:
#include <reg52.h> #define uchar unsigned char #define uint unsigned int #define lcddata P0 #define readalarmsecond 0xc7 //定义从 DS1302 中读闹钟秒值的命令字节 #define writealarmsecond 0xc6 //定义往 DS1302 中写闹钟秒值的命令字节 #define readalarmminute 0xc5 //定义从 DS1302 中读闹钟分值的命令字节 #define writealarmminute 0xc4 //定义往 DS1302 中写闹钟分值的命令字节 #define readalarmhour 0xc3 //定义从 DS1302 中读闹钟小时值命令字节 #define writealarmhour 0xc2 //定义往 DS1302 中写闹钟小时值命令字节 #define readeventday 0xd7 //定义从 DS1302 中读事件提醒日期值的命令字节 #define writeeventday 0xd6 //定义往 DS1302 中写事件提醒日期值的命令字节 #define readeventmonth 0xd5 //定义从 DS1302 中读事件提醒月份值的命令字节 #define writeeventmonth 0xd4 //定义往 DS1302 中写事件提醒月份值的命令字节 #define readeventyear 0xd3 //定义从 DS1302 中读事件提醒年份值的命令字节 #define writeeventyear 0xd2 //定义往 DS1302 中写事件提醒年份值的命令字节 sbit lcdrs=P2^7; //LCD 数据 /命令选择端 (H/L) sbit lcdrw=P2^6; //LCD 读/写选择端 (H/L) sbit lcden=P2^5; //LCD 使能控制 sbit beep=P1^3; //蜂鸣器控制端 sbit DQ = P1^4; //温湿度传感器· sbit kmenu=P3^2; //功能键 sbit kup=P3^3; //增大键 sbit kdown=P3^4; //减小键 sbit kalarm=P3^5; //闹钟查看键 Ring: 显示闹钟需要调到的时间 sbit kevent=P3^6; //事件查看键 Ding: 显示事件提醒需要调到的时间 //sbit kmode=P3^7; //切换温湿度显示 sbit dsclk=P2^2; //ds1302 的时钟信号端 sbit dsdata=P2^1; //ds1302 的数据 I/O 端 sbit dsrst=P2^0; //ds1302 的复位端 sbit ACC7=ACC^7; //定义累加器的最高和最低位。

sbit ACC0=ACC^0; bit flagalarm,flagseta,flagset; // 分别定义闹钟响标志位,闹钟设置标志位,时间设置标志位 bit flagevent,flagsseta; // 分别定义事件提醒标志位,事件提醒设置标志位 uchar FLAG,k; uchar temp; uchar T_data_H,T_data_L,RH_data_H,RH_data_L,checkdata; uchar T_data_H_temp,T_data_L_temp,RH_data_H_temp,RH_data_L_temp,checkdata_temp; uchar comdata; uchar kmenunum,s,m,h,dd,mm,yy; //功能键被按次数的计数器 . char second,minute,hour,year,month,day,week; uchar inittime[7]={0x59,0x59,0x11,0x05,0x07,0x04,0x18}; //初始化后设置为:
//2018 年 7 月 5 日 星期四, 11 点 59 分 59 秒, //从左往右的数据依次是,秒,分,时,日,月,星期,年。

// 年月日星期:
2018-07-05 THU // 时间:
11:59:59 uchar code t1[]=“ - - “; //液晶固定显示部分。

uchar code t2[]=“ : : “; //各函数声明 void delay(uint z); //毫秒级延时函数。

void di(); //蜂鸣器发声函数。

void writecom(uchar com); //液晶写命令函数。

void writedata(uchar dat); //液晶写数据函数。

void writetime(uchar add,uchar dat); // 液晶刷新时分秒。

void writenyr(uchar add,uchar dat); // 液晶刷新日期。

void writeweek(uchar week); // 液晶星期几显示函数。

void keyscan(); // 键盘扫描函数。

void dswritebyte(uchar d);// 往 DS1302 写入一个字节。

uchar dsreadbyte(); //从 DS1302 读出一个字节。

void dswrite(uchar add, uchar dat); // 向 DS1302 的指定地址写入一个字节 uchar dsread(uchar add); //从 DS1302 的指定地址读出一个字节 void init1302time(uchar *pClock); //DS1302 时间初始化函数 void init(); //初始化函数 void Delay_100us(uint j);//延时100us void Delay_10us(void);//延时10us void COM(void); void Read_DHT11(void); void delay(uint z) //毫秒级延时函数。

{ uint x,y; for(x=z;x>0;x--) for(y=110;y>0;y--); } void di() //蜂鸣器发声函数。

{ beep=0; delay(100); beep=1; } void writecom(uchar com) //液晶写命令函数。

{ lcdrs=0;// 置为写入命令 lcddata=com;// 送入数据 delay(1); lcden=1;// 拉高使能端 delay(1); lcden=0;// 完成高脉冲 } void writedata(uchar dat) //液晶写数据函数。

{ lcdrs=1;// 置为写入数据 lcddata=dat;// 送入数据 delay(1); lcden=1; delay(1); lcden=0; } void writetime(uchar add,uchar dat) // 液晶刷新时分秒。

{ //3为时的开始位置,6 为分, 9 为秒。

uchar shi,ge; shi=dat/10; ge=dat%10; writecom(0x80+0x40+add); writedata(0x30+shi); writedata(0x30+ge); } void writenyr(uchar add,uchar dat) // 液晶刷新日期。

{ //0 为年, 3 为月, 6 为日。

uchar shi,ge; shi=dat/10; ge=dat%10; writecom(0x80+add); writedata(0x30+shi); writedata(0x30+ge); } void writeweek(uchar week) // 液晶星期几显示函数。

{ writecom(0x80+9); //从第一行隔十三个字符后开始写星期 switch(week) { case 1: writedata('M'); writedata('O'); writedata('N'); break; case 2: writedata('T'); writedata('U'); writedata('E'); break; case 3: writedata('W'); writedata('E'); writedata('D'); break; case 4: writedata('T'); writedata('H'); writedata('U'); break; case 5: writedata('F'); writedata('R'); writedata('I'); break; case 6: writedata('S'); writedata('A'); writedata('T'); break; case 7: writedata('S'); writedata('U'); writedata('N'); break; } } void keyscan() // 键盘扫描函数。

{ if((flagalarm==1)||(flagevent==1)) // 如果闹钟在响或者有事件提醒,任意键停止闹钟响或者取消事件提醒 { if((kmenu==0)||(kup==0)||(kdown==0)||(kalarm==0)||(kevent==0)) { delay(5); if((kmenu==0)||(kup==0)||(kdown==0)||(kalarm==0)||(kevent==0)) { while(!(kmenu&&kup&&kdown&&kalarm&&kevent)); di(); flagalarm=0; // 清除闹钟标志。

flagevent=0; // 清除事件提醒标志。

} } } if(kmenu==0) //检测功能键。

{ delay(5); if(kmenu==0) { kmenunum++; // 记录功能键按下次数。

if(flagseta==1) // 检测是否在进行闹钟设置。

if(kmenunum==4) kmenunum=1; // 因为进行闹钟设置时,只调节时分秒,所以, kmenunum 只能等于 1,2,3。

if(flagsseta==1) // 检测是否在进行事件提醒设置。

if(kmenunum==8) kmenunum=5; // 因为进行事件提醒设置时,只调节年月日,所以, kmenunum 只能等于 5,6,7 flagset=1; // 设置标志位,表示在进行各种时间设置。

while(!kmenu); di(); switch(kmenunum) //定位光标闪烁点。

{ case 1: writecom(0x80+0x40+10); // 秒闪烁。

writecom(0x0f); // 开光标闪烁。

break; case 2: writecom(0x80+0x40+7); // 分闪烁。

break; case 3: writecom(0x80+0x40+4); // 时闪烁。

break; case 4: writecom(0x80+10); //星期闪烁 . break; case 5: writecom(0x80+7); //日闪烁 . break; case 6: writecom(0x80+4); //月闪烁 . break; case 7: writecom(0x80+1); //年闪烁 . break; case 8: kmenunum=0; writecom(0x0c); // 取消光标闪烁。

flagset=0; //取消时间调节标志。

dswrite(0x8e,0x00); /* 允许写操作 */ dswrite(0x80,second/10*16+second%10); dswrite(0x82,minute/10*16+minute%10); dswrite(0x84,hour/10*16+hour%10); dswrite(0x8a,week/10*16+week%10); dswrite(0x86,day/10*16+day%10); dswrite(0x88,month/10*16+month%10); dswrite(0x8c,year/10*16+year%10); dswrite(0x8e,0x80); /* 禁止写操作 */ break; } } } if(kmenunum!=0) //只有当功能键按下时 ,才检测增大 ,减小键 . { if(kup==0) { delay(5); if(kup==0) { while(!kup); di(); switch(kmenunum) //根据功能键被按下的次数,调节相应数值。

{ case 1: second++; if(second==60) second=0; writetime(9,second); writecom(0x80+0x40+10); // 因为,上面送液晶显示 break; //一次 ,光标后移一位 ,所以要将光标复位 . case 2: minute++; if(minute==60) minute=0; writetime(6,minute); writecom(0x80+0x40+7); break; case 3: hour++; if(hour==24) hour=0; writetime(3,hour); writecom(0x80+0x40+4); break; case 4: week++; if(week==8) week=1; writeweek(week); writecom(0x80+11); break; case 5: day++; if(day==32) day=1; writenyr(6,day); writecom(0x80+7); break; case 6: month++; if(month==13) month=1; writenyr(3,month); writecom(0x80+4); break; case 7: year++; if(year==100) year=0; writenyr(0,year); writecom(0x80+1); break; } } } if(kdown==0) { delay(5); if(kdown==0) { while(!kdown); di(); switch(kmenunum) //根据功能键被按次数调节相应数值 . { case 1: second--; if(second==-1) second=59; writetime(9,second); writecom(0x80+0x40+10); break; case 2: minute--; if(minute==-1) minute=59; writetime(6,minute); writecom(0x80+0x40+7); break; case 3: hour--; if(hour==-1) hour=23; writetime(3,hour); writecom(0x80+0x40+4); break; case 4: week--; if(week==0) week=7; writeweek(week); writecom(0x80+11); break; case 5: day--; if(day==0) day=31; writenyr(6,day); writecom(0x80+7); break; case 6: month--; if(month==0) month=12; writenyr(3,month); writecom(0x80+4); break; case 7: year--; if(year==-1) year=99; writenyr(0,year); writecom(0x80+1); break; } } } } if(kalarm==0) // 检测闹钟调节键是否按下,此条if 语句与 if(kmenunum!=0) { // 在同一个层次。

delay(5); if(kalarm==0) { flagseta=~flagseta; while(!kalarm); di(); if(flagseta==0) //此时,退出闹钟设置,保存各相关数值。

{ flagset=0; //清除时间设置标志,在 if(kalarm==0) 这个 if 语句writecom(0x80+0x40)中,不用 flagset=1 这条语句来设置 //flagset 标志位,因为如果进行了闹钟时间的设置就会执行前面 if(kmenunum==0) 语句中的 flagset=1 这条语句。

writedata(' '); // 清除液晶上的 “ Ri闹钟调节标志。

” writedata(' '); writecom(0x0c); //取消光标闪烁 dswrite(0x8e,0x00); /* 允许写操作 */ dswrite(writealarmsecond,second/10*16+second%10); // 往 DS1302 中保存闹钟的时分秒值。

dswrite(writealarmminute,minute/10*16+minute%10); dswrite(writealarmhour,hour/10*16+hour%10); dswrite(0x8e,0x80); /* 禁止写操作 */ } else //进入闹钟设置。

{ s=dsread(readalarmsecond)/16*10+dsread(readalarmsecond)%16; /* 读取DS1302 中保存的闹钟时分秒原始值,转化为十进制数存进 s,m,h 保存,用以后面判断闹钟时间是否到达。*/ m=dsread(readalarmminute)/16*10+dsread(readalarmminute)%16; h=dsread(readalarmhour)/16*10+dsread(readalarmhour)%16; second=s; //把闹钟时间的十进制数据传送给变量 second,minute,hour,用以调节其值。

minute=m; hour=h; writecom(0x80+0x40); writedata('R'); // 液晶显示闹钟调节标志。

writedata('i'); writetime(3,hour); // 送液晶显示闹钟时间。

writetime(6,minute); writetime(9,second); } } } if(kevent==0) // 检测事件提醒调节键是否按下,此条 if 语句与 if(kmenunum!=0) { // 在同一个层次。

delay(5); if(kevent==0) { flagsseta=~flagsseta; while(!kevent); di(); if(flagsseta==0) //此时,退出事件提醒设置,保存各相关数值。

{ flagset=0; //清除时间设置标志,在 if(kevent==0) 这个 if 语句 writecom(0x80+9);// 中,不用 flagset=1 这条语句来设置 //flagset 标志位,因为 ,如果进行了事件提醒时间的设置 //,就会执行前面 if(kmenunum==0) 语句中的 flagset=1 这条语句。

writedata(' '); // 清除液晶上的 “ Ti 事件提醒调节标志。

” writedata(' '); writedata(' '); writecom(0x0c); //取消光标闪烁 dswrite(0x8e,0x00); /* 允许写操作 */ dswrite(writeeventyear,year/10*16+year%10); // 往 DS1302 中保存事件提醒的年月日值。

dswrite(writeeventmonth,month/10*16+month%10); dswrite(writeeventday,day/10*16+day%10); dswrite(0x8e,0x80); /* 禁止写操作 */ } else //进入事件提醒设置。

{ yy=dsread(readeventyear)/16*10+dsread(readeventyear)%16; /* 读取DS1302 中保存的事件提醒年月日原始值,转化为十进制数存进 yy,mm,dd 保存,用以后面判断事件提醒时间是否到达。*/ mm=dsread(readeventmonth)/16*10+dsread(readeventmonth)%16; dd=dsread(readeventday)/16*10+dsread(readeventday)%16; year=yy; //把事件提醒时间的十进制数据传送给变量 hour,day,month,week用以调节其值。

month=mm; day=dd; writecom(0x80+9); writedata('T'); // 液晶显示事件提醒设置标志。

writedata('i'); writedata(' '); writenyr(0,year);// 送液晶显示事件提醒时间。

writenyr(3,month); writenyr(6,day); } } } } void dswritebyte(uchar d)// 往 DS1302 写入一个字节。

{ uchar i; ACC=d; for(i=8;i>0;i--) { dsdata=ACC0; dsclk=1; /*为什么这里时钟先为 1 后为 0,因为 dsclk 初始化为 0,而,每次循环 最后 ,都将其置 0,这样就形成一个从 0 开始的上升沿 ,恰好满足 ds1302 写数据在上升沿, 但是 , 时钟必须从 0 开始的要求 */ dsclk=0; ACC=ACC>>1; } } uchar dsreadbyte() //从 DS1302 读出一个字节。

{ uchar i; for(i=8;i>0;i--) { ACC=ACC>>1; ACC7=dsdata; dsclk=1; // 读数据也一样 ,虽然是下降沿 ,但是 ,也要从 0 开始 .这样设置 dsclk 刚好满足要求。

dsclk=0; } return ACC; } void dswrite(uchar add,uchar dat) // 向 DS1302 的指定地址写入一个字节。

{ dsrst=0; dsclk=0; dsrst=1; dswritebyte(add); /* 先写入地址 ,命令字节 */ dswritebyte(dat); /* 再写 1Byte 数据 */ dsclk=1; dsrst=0; } uchar dsread(uchar add) //从 DS1302 的指定地址读出一个字节 . { uchar dat; dsrst=0; dsclk=0; dsrst=1; dswritebyte(add); /* 先写入地址 ,命令字节 */ dat=dsreadbyte(); /* 再读 1Byte 数据 */ dsclk=1; dsrst=0; return dat; } void init1302time(uchar *pClock) //DS1302 时间初始化函数 . { uchar i; uchar add=0x80; dswrite(0x8e,0x00); /* 允许写操作 */ for(i=7;i>0;i--) { dswrite(add,*pClock); /* 依次写入秒 分 时 日 月 星期 年 */ pClock++; add+=2; //因为 DS1302 同一个时钟寄存器占两个地址,最低位为 0,是 } //用于写的地址 ,最低位为 1,是用于读的地址 .所以 add 加 2. dswrite(0x8e,0x80); /* 禁止写操作 */ } void init() //初始化函数。

{ uchar num; //变量初始化 . flagset=0; // 时间设置标志位置 0,表示未进行时间设置 flagseta=0; //闹钟设置标志位置 0. flagalarm=0;// 闹钟标志位置 0. kmenunum=0; //功能键被按次数计数器置 0. lcdrw=0; //DS1302 初始化。

dswrite(0x8e,0x00); /* 允许写操作 */ dswrite(0x80,0x00);// 打开 DS1302 晶振,使其开始工作。

dswrite(0x84,0x00);// 设置为 24 小时制。

init1302time(inittime);// 初始化时间为:
//2018 年 7 月 5 日 星期四, 11 点 59 分 59 秒, //从左往右的数据依次是,秒,分,时,日,月,星期,年。

dswrite(0x8e,0x80); /* 禁止写操作 */ //1602 液晶初始化。

writecom(0x38);//开显示 writecom(0x0c);//开显示不显示光标 writecom(0x06);//写一个指针加1 writecom(0x01);//清屏 writecom(0x80);//设置数据指针起点 for(num=0;num<16;num++) //写入液晶固定显示部分。

{ writedata(t1[num]); delay(1); } writecom(0x80+0x40); // 换到液晶第二行。

for(num=0;num<16;num++) { writedata(t2[num]); delay(1); } } void Delay_100us(uint j)//延时100us { uchar i; for(;j>0;j--) for(i=0;i<27;i++);//STC89C52RC } void Delay_10us(void)//延时10us { uchar i; i--;i--;i--;i--;i--;i--;//STC89C52RC } void COM(void) { uchar i; for(i=0;i<8;i++) { FLAG=2; while((!DQ)&&FLAG++); Delay_10us(); Delay_10us(); Delay_10us(); temp=0; if(DQ)temp=1; FLAG=2; while((DQ)&&FLAG++); //超时则跳出for循环 if(FLAG==1) break; //判断数据位是0还是1 // 如果高电平高过预定0高电平值则数据位为 1 comdata<<=1; comdata|=temp; //0 }//rof } //-------------------------------- //-----湿度读取子程序 ------------ //-------------------------------- //----以下变量均为全局变量-------- //----温度高8位== T_data_H------ //----温度低8位== T_data_L------ //----湿度高8位== RH_data_H----- //----湿度低8位== RH_data_L----- //----校验 8位 == checkdata----- //----调用相关子程序如下---------- //---- Delay();, Delay_10us();,COM(); //-------------------------------- void Read_DHT11(void) { //主机拉低18ms DQ=0; Delay_100us(180); DQ=1; //总线由上拉电阻拉高 主机延时20us Delay_10us(); Delay_10us(); Delay_10us(); Delay_10us(); //主机设为输入 判断从机响应信号 DQ=1; //判断从机是否有低电平响应信号 如不响应则跳出,响应则向下运行 if(!DQ) //T ! { FLAG=2; //判断从机是否发出 80us 的低电平响应信号是否结束 while((!DQ)&&FLAG++); FLAG=2; //判断从机是否发出 80us 的高电平,如发出则进入数据接收状态 while((DQ)&&FLAG++); //数据接收状态 COM(); RH_data_H_temp=comdata; COM(); RH_data_L_temp=comdata; COM(); T_data_H_temp=comdata; COM(); T_data_L_temp=comdata; COM(); checkdata_temp=comdata; DQ=1; //数据校验 temp=(T_data_H_temp+T_data_L_temp+RH_data_H_temp+RH_data_L_temp); if(temp==checkdata_temp) { RH_data_H=RH_data_H_temp; RH_data_L=RH_data_L_temp; T_data_H=T_data_H_temp; T_data_L=T_data_L_temp; checkdata=checkdata_temp; }//fi }//fi } void main() { uchar temp; init(); while(1) { keyscan(); if((flagalarm==1)||(flagevent==1)) // 当闹钟时间到时,执行此段程序,启动蜂鸣器发声。

{ di(); delay(100); di(); delay(500); } if(flagset==0&&flagseta==0&&flagsseta==0) // 没有进行时间设置, 和, 闹钟设置 和 事件提醒设置时, 执行此段程序。

{ keyscan(); Read_DHT11();//读取 DHT11 的数据。

//读取 DS1302 的数据。

temp=dsread(0x81); //读秒数据,送 temp 暂时保存。

second=temp/16*10+temp%16; // 将 BCD 码转换成十进制数 . temp=dsread(0x83); minute=temp/16*10+temp%16; temp=dsread(0x85); hour=temp/16*10+temp%16; temp=dsread(0x87); day=temp/16*10+temp%16; temp=dsread(0x89); month=temp/16*10+temp%16; temp=dsread(0x8b); week=temp/16*10+temp%16; temp=dsread(0x8d); year=temp/16*10+temp%16; // 年月日时分秒数据送液晶显示 writetime(3,hour); writetime(6,minute); writetime(9,second); writenyr(0,year); writenyr(3,month); writenyr(6,day); writeweek(week); //显示湿度 writecom(0x80+13); writedata(RH_data_H/10+'0'); writedata(RH_data_H%10+'0'); writedata('%'); //显示温度 writecom(0x80+0x40+12); writedata(T_data_H/10+'0'); writedata(T_data_H%10+'0'); writedata('.'); writedata(T_data_L%10+'0'); //闹钟和事件提醒判断 if((second==s)&&(minute==m)&&(hour==h)) flagalarm=1; if((year==yy)&&(month==mm)&&(day==dd)) flagevent=1; } } } 4.实物验证结果验证 根据硬件电路图进行实物搭建与焊接,并对硬件进行调试。将写好的程序烧录到单片机中,得到硬件实物图如图8.2所示:
图8.2 硬件实物图 从上图8.2中可以看出,LCD1602第一行显示年、月、日,及当下环境中的湿度;
第二行显示时、分、秒及当下环境的温度。此外,依据功能键、加一键、减一键、闹钟键及事件键的组合使用,可实现对闹钟的设置、事件的提醒设置、时间的设置以及温湿度的显示这四种功能。

九、心得体会 通过本次实验培养了我们综合运用所学知识,发现、提出、分析和解决实际问题,锻炼实践能力的重要环节,回顾此次单片机实验,我们感觉受益良多,其中编程,仿真,调试开发板等步骤。这次课程设计使我们懂得理论与实际相结合是很重要的,只有理论知识是远远不够的,只有把所学知识和实践相结合起来,从理论中得出结论,才能真正让所学的知识服务于我们的生活,服务于我们的社会,从而提高我们的实际动手能力和独立思考的能力,同时加强了与同学的合作和与老师的沟通,收获很多大!这次实验也让我们发现了自身的不足,在以后学习研究中我们会巩固加强专业知识的学习,为今后的研究工作打下坚实的基础。

仅供参考

推荐访问:
上一篇:年社会工会工作总结
下一篇:部编版数学六年级下册第二单元测试卷

Copyright @ 2013 - 2018 优秀啊教育网 All Rights Reserved

优秀啊教育网 版权所有