基于51单片机的万年历的设计(共25页).doc
精选优质文档-倾情为你奉上单片机课程实训SCM PRACTICAL TRAINING实训设计题目Title Of Training 万年历的设计 分院(系别)Department 专业Speciality 班 级Class 设计作者Author 完成日期Date 组 别Team 指导教师Advisor 专心-专注-专业目 录第一部分 课程设计任务书一、课程设计题目用中小规模集成芯片设计制作万年历。二、课程设计时间五天三、实训提交方式提交实训设计报告电子版与纸质版四、设计要求(1)显示年、月、日、时、分、秒和星期,并有相应的农历显示。(2)可通过键盘自动调整时间。 (3)具有闹钟功能。 (4)能够显示环境温度,误差小于±1 (5)计时精度:月误差小于20秒。 第二部分 课程设计报告一、单片机发展概况单片机诞生于20世纪70年代末,它的发展史大致可分为三个阶段: 第一阶段(1976-1978):初级单片机微处理阶段。该时期的单片机具有 8 位CPU,并行 I/O 端口、8 位时序同步计数器,寻址范围 4KB,但是没有串行口。 第二阶段(1978-1982):高性能单片机微机处理阶段,该时期的单片机具有I/O 串行端口,有多级中断处理系统,15 位时序同步技术器,RAM、ROM 容量加大,寻址范围可达 64KB。 第三阶段(1982-至今)位单片机微处理改良型及 16 位单片机微处理阶段民用电子产品、计算机系统中的部件控制器、智能仪器仪表、工业测控、网络与通信的职能接口、军工领域、办公自动化、集散控制系统、并行多机处理系统和局域网络系统。 二、MCS-51单片机系统简介 MCS-51系列单片机产品都是以Intel公司最早的典型产品8051为核心构成的。MCS-51单片机由CPU 、RAM 、ROM 、I/O接口、定时器/计数器、中断系统、内部总线等部件组成。8051单片机的基本性能有:u 8位CPU;u 布尔代数处理器,具有位寻址能力;u 128B内部RAM,21个专用寄存器;u 4KB内部掩膜ROM;u 2个16位可编程二进制加1定时器/计数器;u 32个(4×8位)双向可独立寻址的I/O口;u 1个全双工UART(异步串行通信口);u 5个中断源,两级中断结构 ;u 片内振荡器及时钟电路 ,晶振频率为1.2MHz12MHz;u 外部程序/数据存储器寻址空间均为64KB;u 111条指令,大部分为单字节指令;u 单一+5V电源供电,双列直插40引脚DIP封装。三、设计思想 整体设计以单片机技术为核心,采用C语言进行软件设计,增加了程序的可读性和可移植性,为了便于扩展和更改,软件的设计采用模块化结构。程序先向LCD更新时钟芯片的时间与温度传感器的时间,然后进行初始化工作。程序由一个主函数,两个定时器中断程序,一个时钟设置子程序,一个农历设置子程序,一个温度设置子程序,一个延时子程序,一个调时子程序,一个显示子程序构成。程序通过按键扫描程序来确定是否调用中断程序来对时间进行调整。用一子程序完成时分的调整,通过循环扫描四个按键的电平变化来判断对应按键是否按下,并带有去抖动功能,四个按键分别有增加,减小,退出与功能选择的作用。通过功能选择时钟设置与闹钟设置,使用加或减按键进行预置,完成后可点退出键完成操作。可分为以下几个功能模块:1)主程序:定时器中断初始化、时钟与温度更新程序与键盘监控。2)计时:为定时器中断服务子程序,完成刷新计时缓冲区的功能。3)农历:由阴历换算对照表得出阳历并显示。4)闹钟:采用定时器中断方式实现闹钟与整点报时。5) 温度:由温度传感器将温度传送到LCD显示。6)设置:由按键设置闹钟时间或时钟时间。7)键盘扫描:判断是否有键按下,并确定键号。 8)LCD显示:完成8位动态显示。四、硬件电路设计1. 总体设计 系统包括单片机主控模块,温度传感器采集模块,日历时钟模块,按键调整模块,蜂鸣器模块,闹钟模块。如图1所示为系统设计图。 日历时钟芯片 DS1302 按键调整 电路 AT89C51 单片机温度传感器 DS18B20LCD12864蜂鸣器闹钟图1 系统设计图如图2所示为系统仿真图。图2 系统仿真图2. 晶振电路 如图3所示,51单片机的内部有一个用于构成振荡器的高增益反相放大器,它的输入端为XTAL1引脚,输出端为XTAL2引脚,两个跨接石英晶体及两个电容就可以构成稳定的自激振荡器。电容器通常取30pF左右。 图3 晶振电路 图4 复位电路3. 复位电路 往单片机的复位引脚上输入24个时钟周期以上的高电平,即执行复位操作。按键复位是指系统在运行时,按下一个开关,就能在RST引脚产生一段时间的高电平,使系统复位,常见的按键复位电路如图4所示。对12MHz晶振频率而言,电路中C取10pF,R取1K。4. DS1302时钟电路DS1302是一种高性能、低功耗、带RAM的实时时钟电路,它可以对年、月、日、周日、 时、分、秒进行计时,具有闰年补偿功能,工作电压为2.5V5.5V。采用三线接口与CPU进行同步通信,并可采用突发方式一次传送多个字节的时钟信号或RAM数据。DS1302内部有一个31×8的用于临时性存放数据的RAM寄存器。DS1302是DS1202的升级产品,与DS1202兼容,但增加了主电源/后背电源双电源引脚,同时提供了对后背电源进行涓细电流充电的能力。图5示出DS1302的引脚排列,其中Vcc1为后备电源,Vcc2为主电源。在主电源关闭的情况下,也能保持时钟的连续运行。DS1302由Vcc1或Vcc2两者中的较大者供电。当Vcc2大于Vcc1+0.2V时,Vcc2给DS1302供电。当Vcc2小于Vcc1时,DS1302由Vcc1供电。X1和X2是振荡源,外接32.768KHz晶振。RST是复位/片选线,通过把RST输入驱动置高电平来启动所有的数据传送。RST输入有两种功能:首先,RST接通控制逻辑,允许地址/命令序列送入移位寄存器;其次,RST提供终止单字节或多字节数据的传送手段。当RST为高电平时,所有的数据传送被初始化,允许对DS1302进行操作。如果在传送过程中RSTS置为低电平,则会终止此次数据传送,I/O引脚变为高阻态。上电动行时,在Vcc大于等于2.5V之前,RST必须保持低电平。中有在SCLK为低电平时,才能将RST置为高电平,I/O为串行数据输入端(双向)。SCLK始终是输入端。 图5 DS1302时钟芯片 图6 温度采集系统电路5. 温度采集系统电路 在本万年历当中温度的采集采用数字温度传感器DS18B20。它属于单总线器件,具有线路简单,体积小的特点。因此用它来组成一个测温系统,具有线路简单,在一根通信线,可以挂很多这样的数字温度计,十分方便。 具有如下的经济特点:(1)只要求一个端口即可实现通信。(2)在DS18B20中的每个器件上都有独一无二的序列号。(3)实际应用中不需要外部任何元器件即可实现测温。(4)测量温度范围在55。C到125。C之间。(5)数字温度计的分辨率用户可以从9位到12位选择。(6)内部有温度上、下限告警设置。如图6所示。6. 按键调整电路 按键采用4个独立的按键,一个功能键、一个退出键、一个加按键、一个减按键通过这四个按键可以来合理的设置时钟的调整和闹铃的设置等。如图7所示与51单片机的P0.0P0.3的连接示意图。图7 按键调整电路7. 闹钟提示电路 当到达整点时或者当前的时间等于51单片机中设置闹钟时间时蜂鸣器便会发出声音进行提示。与单片机P0.5引脚的连接电路如图8所示。 图8 闹钟提示电路 图9 LCD显示电路8. LCD显示电路 在本万年历当中12864液晶显示当前的实时时间重要的阴阳历节日等功能。12864液晶具有如下的特性: 1)提供8位,4位并行接口及串行接口可选2)并行接口适配M6800时序3)自动电源启动复位功能4)内部自建振荡源 64×16位字符显示RAM(DDRAM最多16字符×4行,LCD显示范围16×2行)(改为半角输入) 2M位中文字型ROM(CGROM),总共提供8192个中文字型(16×16点阵)16K位半宽字型ROM(HCGROM),总共提供126个西文字型(16×8点阵)64×16位字符产生RAM(CGRAM) 15×16位总共240点的ICONRAM(ICONRAM)其与单片机的连接电路如图9所示。五、软件设计框图1. 主程序流程图:2. 阴阳历转换流程图:六、程序源代码1. 主程序#include < reg52.h >#include < nongli.h >#include < lcd.h >#include < shezhi.h >#include < time.h>#include < wendu.h >#include < key.h >#define uchar unsigned char#define uint unsigned int/*sbit bell = P2 0; /定义蜂鸣器端口/* 定时器设置整点报时*/void Timer0_Service() interrupt 1 static uchar count = 0; static uchar flag = 0; /记录鸣叫的次数 count = 0; TR0 = 0; /关闭Timer0 TH0 = 0x3c; TL0 = 0XB0; /延时 50 ms TR0 = 1 ; /启动Timer0 count +; if( count = 20 ) /鸣叫 1 秒 bell = bell; count = 0; flag +; if( flag = 6 ) flag = 0; TR0 = 0; /关闭Timer0 /* 中断服务程序 整点报时 一分钟*/uchar HexNum_Convert(uchar HexNum)/将16进制数转换成十进制数uchar Numtemp;Numtemp=(HexNum>>4)*10+(HexNum&0X0F);return Numtemp;/* 函数名称:main()* 功 能:* 入口参数:* 出口参数:*/void main( void ) uchar clock_time6 = 0X00,0X59,0X23,0X09,0X04,0X11; /定义时间变量 秒 分 时 日 月 年uchar alarm_time2 = 10, 06; /闹钟设置 alarm_time0: 分钟 alarm_time1 :小时uchar temperature2; /定义温度变量 temperature0 低8位 temperature1 高8位 Lcd_Initial(); /LCD初始化Clock_Fresh( clock_time ); /时间刷新Clock_Initial( clock_time ); /时钟初试化 /*中断初始化*/ EA = 1; /开总中断 ET0 = 1; /Timer0 开中断ET2 = 1; /Timer2 开中断 TMOD = 0x01 ; /Timer0 工作方式 1RCAP2H = 0x3c; RCAP2L = 0xb0; /Timer2 延时 50 ms while( 1 ) switch( Key_Scan() )/按键扫描 case up_array: Key_Idle(); /检测按键松开 break; case down_array: Key_Idle(); /检测按键松开 break; case clear_array: Key_Idle(); /检测按键松开 break; case function_array: Key_Function( clock_time, alarm_time ); case null: Clock_Fresh( clock_time ); /时间刷新 Lcd_Clock( clock_time ); /时间显示 Sensor_Fresh( temperature ); /温度更新 Lcd_Temperture( temperature ); /温度显示 Calendar_Convert( 0 , clock_time ); Week_Convert( 0, clock_time ); /整点报时if( ( * clock_time = 0x59 ) && ( * ( clock_time + 1 ) = 0x59 ) ) bell = 0; TR2 = 1; /启动Timer2 /闹钟报警if( * alarm_time = HexNum_Convert(* ( clock_time + 1 ) )/分钟相吻if( * ( alarm_time + 1 ) = HexNum_Convert(*( clock_time + 2 ) ) /小时相吻合 bell = 0;TR2 = 1; /启动Timer2 break; 2. 温度控制程序#ifndef _SENSOR#define _SENSOR#define uchar unsigned char#define uint unsigned int/*DS18B20管脚配置*/sbit dq = P2 1; /*DS18B20软件延时专用*/void Sensor_Delay(uchar count)/延时函数 while(count-);/*从DS18B20读一个字节*/uchar Sensor_Read_Byte(void) uchar i = 0; uchar temp = 0; for(i=8;i>0;i-) dq = 0; / 给脉冲信号 temp >>= 1; dq = 1; / 给脉冲信号 if(dq) temp |= 0x80; Sensor_Delay(20); return (temp);/*向DS18B20写一个字节*/void Sensor_Write_Byte(uchar temp) uchar i = 0; for(i=8;i>0;i-) dq = 0; dq = temp&0x01; Sensor_Delay(20); dq = 1; temp>>=1; /*DS18B20初始化*/uchar Sensor_Initial(void) uchar i = 0; dq = 1; / DQ复位 Sensor_Delay(1); / 稍做延时 dq = 0; / 单片机将DQ拉低 Sensor_Delay(100); / 精确延时,大于480us dq = 1; / 拉高总线 Sensor_Delay(6); / 稍做延时后 i = dq; / 若x=0则初始化成功,若x=1则初始化失败 Sensor_Delay(130); return (i);/*读取并显示温度*/void Sensor_Fresh(uchar * temperature ) Sensor_Initial(); Sensor_Write_Byte( 0xCC ); / 跳过读序号列号的操作 Sensor_Write_Byte( 0x44 ); / 启动温度转换 Sensor_Initial(); Sensor_Write_Byte( 0xCC ); / 跳过读序号列号的操作 Sensor_Write_Byte( 0xBE ); / 读取温度寄存器 temperature 0 = Sensor_Read_Byte();/低位 temperature 1 = Sensor_Read_Byte(); /高位#endif3. 日历设置程序#ifndef _SUN_MOON#define _SUN_MOON/*/#define uchar unsigned char#define uint unsigned int#include < shezhi.h >#include < lcd.h >/* 功能: 读取数据表中农历的大月或小月 ,如果大月返回1, 小月返回0*/bit get_moon_day( uchar month_p,uint calendar_address ) uchar temp,temp1;temp1=(month_p+3)/8;temp=0x80>>(month_p+3)%8);temp=year_codecalendar_address+temp1&temp;if(temp=0)return(0);elsereturn(1);/* 功能: 输入BCD的阳历数据, 输出BCD阴历数据( 1901 - 2099 ) c_flag:阳历的世纪标志 clock_time: 时钟地址* 说明: c_flag = 0 :21世纪 c_flag = 1 :19世纪 */void Calendar_Convert( uchar c_flag, uchar * clock_time ) bit flag_month, flag_year; uchar year, month, day, month_point; /定义 年 月 天 uchar temp1, temp2, temp3; uint calendar_address; /定义农历地址 uint day_number; uchar clock_moon3; /定义阴历 clock_time += 3; /指向日 day = ( * clock_time >> 4 ) * 10 + ( *clock_time & 0x0f ); /BCD转换十进制 clock_time +; /指向月 month = ( * clock_time >> 4 ) * 10 + ( * clock_time & 0x0f ); /BCD转换十进制 clock_time +; /指向年 year = ( * clock_time >> 4 ) * 10 + ( * clock_time & 0x0f ); /BCD转换十进制 /定位日历地址 if( c_flag = 0 ) calendar_address = ( year + 99 ) * 3; else calendar_address = ( year - 1 ) * 3; /春节(正月初一)所在的阳历月份 temp1 = year_code calendar_address + 2 & 0x60; /Bit6Bit5:春节所在的阳历月份 temp1 >>= 5 ; /春节(正月初一)所在的阳历日期 temp2 = year_code calendar_address + 2 & 0x1f; /Bit4Bit0:春节所在的阳历日期 /计算春节(正月初一)离当年元旦 1月1日(阳历) 的天数;春节只会在阳历的1月 或 2月 /*if( temp1 = 1 ) temp3 = temp2 - 1; else temp3 = temp2 + 31 - 1; */temp3=temp2-1;if(temp1!=1) temp3+=0x1f; /计算阳历月离当年元旦 1月1日(阳历) 的天数 if( month < 10 ) day_number = day_code1 month - 1 + day ; else day_number = day_code2 month - 10 + day ; /如果阳历的月大于2 且该年的2月为闰月,天数加1 /闰年指的就是阳历有闰日或阴历有闰月的一年; /阳历四年一闰,在二月加一天,这一天叫做闰日: /农历三年一闰,五年两闰,十九年七闰,每逢闰年所加的一个月叫做闰月。 if( ( month <= 2 ) | ( year % 0x04!= 0) ) day_number-=1; / day_number +; / if (month<2)|(year%0x04!=0) / day_number-=1; /判断阳历日 在春节(正月初一) 之前 还是 之后 if( day_number >= temp3 ) /阳历在春节之后 或者 春节当日 day_number -= temp3; month = 1; month_point = 1; / month_point 为月份指向,阳历日在春季前就是春季 flag_month = get_moon_day( month_point, calendar_address ); /检查该阴历月的大小 大月返回1 小月返回0 flag_year = 0; /* if( flag_month ) temp1 = 30; /大月30天 else temp1 = 29; /小月29天 */ if (flag_month=0) temp1=29; elsetemp1=30; /闰月所在的月分 temp2 = year_code calendar_address & 0xf0; temp2 >>= 4; /提取高四位 假如是0 表示没有闰月 while( day_number >= temp1 ) day_number -= temp1; month_point +; if( month = temp2 ) flag_year = flag_year; if( flag_year = 0 ) month +=1; else month + ; flag_month = get_moon_day( month_point, calendar_address ); if( flag_month ) temp1 = 30; else temp1 = 29; day = day_number + 1; else /阳历在春节之前使用以下代码进行运算 temp3 -= day_number; if( year = 0 ) year = 0xe3; c_flag = 1; else year -= 1; calendar_address -= 3; month = 0xc; temp2 = year_code calendar_address & 0xf0; temp2 >>= 4; /提取高4位 flag_year=0; if( temp2 = 0 ) month_point = 12; else month_point = 13; /flag_year = 0; flag_month = get_moon_day( month_point, calendar_address ); if( flag_month ) temp1 = 30; else temp1 = 29; while( temp3 > temp1 ) temp3 -= temp1; month_point -; if( flag_year = 0 ) month -=1; if( month = temp2 ) flag_year = flag_year; flag_month = get_moon_day( month_point, calendar_address ); if( flag_month ) temp1 = 0x1e; else temp1 = 0x1d; day = temp1 - temp3 + 1; /HEX->BCD ,运算结束后,把数据转换为BCD数据 temp1 = year / 10; temp1 <<= 4; clock_moon2 = temp1 | ( year % 10 ); temp1 = month / 10; temp1 <<= 4; clock_moon1 = temp1 | ( month % 10 ); temp1 = day / 10; temp1 <<= 4; clock_moon0 = temp1 | ( day % 10 ); Lcd_Lunar_Calendar( clock_moon );/*算法: ( 日期 + 年份 + 所过闰年 + 月校正 ) / 7 的余数就是星期 如果是闰年又不到 3 月份上述之和 要减一天 再*