基于单片机的多功能电子时钟设计.doc
单片机课程设计设计题目:电子时钟设计电子学 院_ 物理与光电工程学院 专 业_ 电子科学与技术 年级班别 08级电子一班 学 号 学生姓名_ 陈罗武 指导教师_ 何榕礼_ 2010年 12 月 18 日目录一、 设计目的、设计题目二、 设计任务、功能描述.三、 硬件原理分析.四、 软件设计.五、 程序清单.一、设计目的、设计题目本设计的目的是设计一个实用的电子时钟,该时钟能够实现日常的时钟显示,它可以对年、月、日、周日、时、分、秒进行计时,具有闰年补偿功能,同时具有时钟调整、闹钟等功能。二、 设计任务、功能描述整体设计任务:本电子时钟设计首要的工作是结合以往所学的单片机程序编写理论和编写规则来编写电子时钟的软件部分,编写时要结合所配的STM8S105芯片的管脚功能和其他硬件电路,如DS1302时钟芯片和LCD1602液晶显示屏,该部分运用STVD单片机软件来完成。在编写完软件并检测完正确后用仿真硬件检测运行检测程序是否正确,并调试。待这一切工作做好后再利用系统板硬件电路来实践实现软件功能与硬件的结合。三、硬件原理分析电路设计分析电路原理设计是基于小系统包括电源电路、复位电路、按键电路、时钟电路、LCD液晶显示驱动电路、输出控制电路。电源部分是用直流电源经变压来提供的5V,晶体振荡器采用的是16MHz的石英晶体振荡器。部分硬件设计:(1)本设计采用DS1302实时时钟芯片来进行计时,并将1302的数据输入单片机进行处理,送入LCD1602液晶显示屏进行显示。(2)用四个电位按键来实现对电子时钟的调试工作,当按第一下总控键时进入中断程序,即时钟的调整状态,有两个电位按键分别来调整时钟的加和减;第四个按键用来调整时分秒日月年,按一下调整秒,连按两下调整分,按三下调整时,四下调整日,五下调整月,六下调整年,七下退出时间设置。(3)用一个按键和其他部件组成的复位电路与芯片连接来实现整个程序及硬件的重新复位功能。部分电路图如下:1.1复位电路本次设计采用按键电平复位,按键电平复位相当于按复位键后,复位端通过电阻与VDD电源接通,如下图:1.2振荡器和时钟电路设计本次设计采用常用的内部时钟方式接法:通过在引脚OSCIN和OSCOUT两端跨接晶体或陶瓷谐振器,再利用芯片内部的振荡电路,就构成了稳定的自激振荡器,其发出的脉冲直接送入内部时钟电路,如图所示,外接晶振时,C1和C2的值通常选择为20-30pf。C1、C2对频率有微调作用,影响振荡的稳定性和起振速度。所采用的晶体或陶瓷谐振器得频率选择0-24MHz。为了减小寄生电容,更好的保证振荡器稳定、可靠的工作,谐振器和电容赢尽可能与单片机芯片靠近安装。1.3电源因为该电路所需电压为5v,可直接用电池供应或也可以用直流电源供应,但需经过变压才行。1.4 DS1302实时时钟芯片DS1302 是美国DALLAS公司推出的一种高性能、低功耗、带RAM的实时时钟电路,它可以对年、月、日、周日、时、分、秒进行计时,具有闰年补偿功能,工作电压为2.5V5.5V。采用三线接口与CPU进行同步通信,并可采用突发方式一次传送多个字节的时钟信号或RAM数据。DS1302内部有一个31×8的用于临时性存放数据的RAM寄存器。DS1302是DS1202的升级产品,与DS1202兼容,但增加了主电源/后背电源双电源引脚,同时提供了对后背电源进行涓细电流充电的能力。DS1302的引脚排列中Vcc1为后备电源,VCC2为主电源。在主电源关闭的情况下,也能保持时钟的连续运行。DS1302由Vcc1或Vcc2两者中的较大者供电。当Vcc2大于Vcc10.2V时,Vcc2给DS1302供电。当Vcc2小于Vcc1时,DS1302由Vcc1供电。X1和X2是振荡源,外接32.768kHz晶振。RST是复位/片选线,通过把RST输入驱动置高电平来启动所有的数据传送。RST输入有两种功能:首先,RST接通控制逻辑,允许地址/命令序列送入移位寄存器;其次,RST提供终止单字节或多字节数据的传送手段。当RST为高电平时,所有的数据传送被初始化,允许对DS1302进行操作。如果在传送过程中RST置为低电平,则会终止此次数据传送,I/O引脚变为高阻态。上电运行时,在Vcc2.5V之前,RST必须保持低电平。只有在SCLK为低电平时,才能将RST置为高电平。I/O为串行数据输入输出端(双向),后面有详细说明。SCLK始终是输入端。电路图如下:1.4 LCD1602液晶屏显示电路1602是一种字符型液晶显示模块,专门用于显示字母、数字、符号等点阵式LCD,目前常用16*1,16*2,20*2和40*2行等的模块。下面以某公司的1602字符型液晶显示器为例,介绍其用法。一般1602字符型液晶显示器实物如图1602LCD分为带背光和不带背光两种,基控制器大部分为HD44780,带背光的比不带背光的厚,是否带背光在应用中并无差别,两者尺寸差别如下图1602LCD采用标准的14脚(无背光)或16脚(带背光)接口,各引脚接口说明如下:第1脚:VSS为地电源。第2脚:VDD接5V正电源。第3脚:VL为液晶显示器对比度调整端,接正电源时对比度最弱,接地时对比度最高,对比度过高时会产生“鬼影”,使用时可以通过一个10K的电位器调整对比度。第4脚:RS为寄存器选择,高电平时选择数据寄存器、低电平时选择指令寄存器。第5脚:R/W为读写信号线,高电平时进行读操作,低电平时进行写操作。当RS和R/W共同为低电平时可以写入指令或者显示地址,当RS为低电平R/W为高电平时可以读忙信号,当RS为高电平R/W为低电平时可以写入数据。第6脚:E端为使能端,当E端由高电平跳变成低电平时,液晶模块执行命令。第714脚:D0D7为8位双向数据线。第15脚:背光源正极。第16脚:背光源负极1.5输出控制电路将PB口用于单片机和LCD显示器的数据传输通道,如下图所示,通过控制LCD的读写控制端将1302的数据显示出来。1.6整体布局图四、 软件设计分析1、地址空间及端口分配_Bool rs PC_ODR:1; /PC作为LCD的控制端,设置为输入_Bool rw PC_ODR:2;_Bool en PC_ODR:3;_Bool sclk PA_ODR:4; /串行时钟,输入输出控制_Bool dio PA_ODR:5; /三线接口时的数据线_Bool id PA_IDR:5;_Bool ce PA_ODR:6; /作为复位端口_Bool ID1 PG_IDR:0; /将PD6引脚设置为外部中断的输入引脚2、总体架构说明该时钟程序设计思路如下:(1)、在程序头将设计中的一些IO口进行分配并注释。(2)、初始化程序,对单片机的IO口设置为输入输出等,对1302,1602的参数进行设置,如设定时钟运行为24小时制;将一些未到时钟设计功能的功能端口关闭等。(3)开始主程序进行程序扫描,先扫初始化字子程序初始化DS1302和LCD1602,启动时钟芯片进行计时,初始化1602的显示方式,将分配好的时分秒等数字调入到显示子程序中。>显示子程序通过分配的空间对应口将DS1302时钟芯片的数据显示相应的时分秒,年月日等。>进入按键扫描,判断按键是否有变动,若有就根据设计程序对相应的按键按入次数而进行实现相应的功能。如进入时间调整,中断进入、退出等。>最后返回主程序头重新依次扫描。显示过程设有消隐,按键通过软件去斗。(4)、子程序部分。在主程序的调用下依据不同的子程序工能而编写子程序,有多级嵌套。这些子程序包括显示子程序、延时子程序、时间计数子程序、按键处理子程序、调时间子程序等。3、各子程序功能显示子程序:将时钟芯片1302内的时钟数据送入单片机进行处理再送入LCD1602内进行显示,显示格式为:年/月/日/时/分/秒/星期。延时子程序:该子程序是为了实现1302及1602在读写过程中的上升下降沿让芯片有足够的读写时间,以及字符有足够的显示时间,同时作为按键开关扫描去抖的延迟时间。时间计数子程序:该程序功能是调动DS1302的初始化程序、读写程序,并将其内部数据送入单片机内进行处理。DS1302选择24小时工作方式,秒计数,走到59后进位分,后变00从新开始计时,分走到59后进位时,分从新开始00计时,时走到23,分走到59,秒走到59后进入从新日期周期计时。按键处理子程序:该程序进行按键处理的功能判断分别对时分秒、年月日等进行调整已经中断的进入与退出等。4、程序清单#include "stm8s105C_S.h"uchar n1,n2,n3;uchar u0,u1,u2,u3;uchar table="20 - - " uchar table1=" : : "uchar table2='0','1','2','3','4','5','6','7','8','9'uchar table3="MonTueWedThuFriSatSun"uchar table4=" Happy birthday " /16个字符uchar table5="<<<<<to you>>>>>"#define uint unsigned int #define uchar unsigned char #define PB PB_ODR#define Write_second 0X80 /秒写寄存器地址#define Write_minute 0X82/分写寄存器地址#define Write_hour 0X84/时写寄存器地址#define Write_day 0X86/日写寄存器地址#define Write_month 0X88/月写寄存器地址#define Write_week 0X8A/周写寄存器地址#define Write_year 0X8C/年写寄存器地址#define Write_protect 0X8E/写保护字节写寄存器地址#define Write_chongdian 0x90 /慢充电寄存器地址#define Read_second 0X81/秒读寄存器地址#define Read_minute 0X83/分读寄存器地址#define Read_hour 0X85/时读寄存器地址#define Read_day 0X87/日读寄存器地址#define Read_month 0X89/月读寄存器地址#define Read_week 0X8B/周读寄存器地址#define Read_year 0X8D/年读寄存器地址_Bool rs PC_ODR:1; /pc作为lcd的控制端,设置为输入_Bool rw PC_ODR:2;_Bool en PC_ODR:3;_Bool sclk PA_ODR:4; /串行时钟,输入输出控制_Bool dio PA_ODR:5; /三线接口时的数据线_Bool id PA_IDR:5;_Bool ce PA_ODR:6; /复位_Bool da_od PA_DDR:5;_Bool da_CR1 PA_CR1:5;_Bool da_CR2 PA_CR2:5;_Bool ID1 PG_IDR:0; /将Pd6引脚设置我外部中断的输入引脚_Bool ID2 PD_IDR:5;_Bool ID3 PG_IDR:1;_Bool ID4 PD_IDR:6;/键盘的输入/shfm_key(void) uchar lshi;uchar an=0,n=0;doif(!ID1)delay(6);if(!ID1)while(!ID1);lshi=1;an+;if(an=1)Write_ds1302(Write_second,0x80); /先暂停时钟秒u2=Read_ds1302(Read_second );u2=(u2&0x70)>>4)*10+(u2&0x0F);write_sfm(6,u2); lcd_wcom(0x80+0x40+7);lcd_wcom(0x38);lcd_wcom(0x0f); /显示功能开,有光标if(an=2)u1=Read_ds1302(Read_minute);u1=(u1&0x70)>>4)*10+(u1&0x0F);write_sfm(3,u1); lcd_wcom(0x80+0x40+4); /显示分lcd_wcom(0x38);lcd_wcom(0x0f); /显示功能开,有光标if(an=3)u0=Read_ds1302(Read_hour);u0=(u0&0x70)>>4)*10+(u0&0x0F);write_sfm(0,u0); lcd_wcom(0x80+0x40+1); /显示时lcd_wcom(0x38);lcd_wcom(0x0f); /显示功能开,有光标if(an=4)n3=Read_ds1302(Read_day);n3=(n3&0x70)>>4)*10+(n3&0x0F);write_sfm1(8,n3); lcd_wcom(0x80+9); /显示日lcd_wcom(0x38);lcd_wcom(0x0f); /显示功能开,有光标if(an=5)n2=Read_ds1302(Read_month);n2=(n2&0x70)>>4)*10+(n2&0x0F);write_sfm1(5,n2); lcd_wcom(0x80+6); /显示月lcd_wcom(0x38);lcd_wcom(0x0f); /显示功能开,有光标 if(an=6)n1=Read_ds1302(Read_year);n1=(n1&0x70)>>4)*10+(n1&0x0F);write_sfm1(2,n1); lcd_wcom(0x80+3); /显示年lcd_wcom(0x38);lcd_wcom(0x0f); /显示功能开,有光标if(an=7)lshi=0;an=0;Write_ds1302(Write_second,0x00); /从新开启时钟lcd_wcom(0x38);lcd_wcom(0x0c);n=1; switch(an)case1:if(ID2=0)delay(4);if(ID2=0) while(!ID2);_asm("NOP");jia_key(Write_second,Read_second,6,1);_asm("NOP");if(ID3=0) /秒的减 delay(4);if(ID3=0) while(!ID3);_asm("NOP");jia_key(Write_second,Read_second,6,0);_asm("NOP");break;case 2 :if(ID2=0) /分的加delay(4);if(ID2=0)while(!ID2);_asm("NOP");jia_key(Write_minute ,Read_minute,3,1);_asm("NOP");if(ID3=0) /分的减delay(4);if(ID3=0) while(!ID3);_asm("NOP");jia_key(Write_minute ,Read_minute,3,0);_asm("NOP"); break;case 3 :if(ID2=0) /时的加 delay(4);if(ID2=0) while(!ID2);_asm("NOP");hjia_key(Write_hour,Read_hour,0,1);_asm("NOP"); if(ID3=0) /小时的减 delay(4);if(ID3=0) while(!ID3);_asm("NOP");hjia_key(Write_hour,Read_hour,0,0);_asm("NOP"); break;case 4: if(ID2=0) /日的加 delay(4);if(ID2=0) while(!ID2);_asm("NOP");rjia_key(Write_day,Read_day,8,1);_asm("NOP");if (ID3=0) /日的减 delay(4);if(ID3=0) while(!ID3);_asm("NOP");rjia_key(Write_day,Read_day,8,0);_asm("NOP");break;case 5: if(ID2=0) /月的加delay(4);if(ID2=0) while(!ID2);_asm("NOP");yjia_key(Write_month ,Read_month,5,1);_asm("NOP"); if(ID3=0) /月的减 delay(4);if(ID3=0) while(!ID3);_asm("NOP");yjia_key(Write_month ,Read_month,5,0);_asm("NOP"); break;case 6:if(ID2=0) /年的加 delay(4);if(ID2=0) while(!ID2);_asm("NOP");njia_key(Write_year,Read_year,2,1);_asm("NOP"); if(ID3=0) /年的减delay(4);if(ID3=0) while(!ID3);_asm("NOP");njia_key(Write_year,Read_year,2,0);_asm("NOP"); break; while(lshi=1);_asm("NOP");return(n);return(n); void Write_ds1302(uchar addr,uchar dat)uchar i ;PC_DDR |=0xFF;PC_CR1 |=0xFF;PC_CR2 |=0X00;ce = 0; /先复位sclk = 0; /清零,保持io口的低电平,以待数据的写入ce = 1; /置1,开始写数据/发送地址for(i =0;i<8;i+)dio= addr&0x01; /将一位bcd码的数据送给io口addr=addr >>1; /右移一位sclk =1; /产生上升沿,发送数据sclk =0;/发送数据for(i=0;i<8;i+) dio= dat&0x01; /将一位bcd码的地址数据送给io口dat =dat>>1;sclk=1; /产生上升沿,发送数据delay(1);sclk=0;ce = 0;/读操作/Read_ds1302(uchar addr) uchar i;uchar k;uchar dat;da_od=0;da_CR1=0;da_CR2=0;ce=0;sclk=0;ce=1;/发送地址for(i=0;i<8;i+)sclk=0;delay(10);if(addr&0x01)dio=1;else dio=0;addr>>=1;sclk=1;delay(2);da_od=0;da_CR1=0;da_CR2=0;/读取数据/for(k=0;k<8;k+) dat>>=1;sclk=1;delay(2);sclk=0;delay(2); if(id=0)dat&=0x7f;else dat |= 0x80 ;sclk=1;ce=0;return(dat);/IO口的初始化/void GPIO_Init(void) PC_DDR |=0X0F;PC_CR1 |=0X0F;PC_CR2 |=0X00;PG_DDR |=0X00;PG_CR1 |=0X03;PG_CR2 |=0X00;PD_DDR |=0X1C;PD_CR1 |=0X0F;EXTI_CR1|=0X00;ITC_SPR2|=0X30;PD_CR2 |=0X40;PA_DDR |=0XFF;PA_CR1 |=0XFF;PA_CR2 |=0X00;PB_DDR |=0XFF;PB_CR1 |=0XFF;PB_CR2 |=0X00;/延时函数/void delay(uint n) uint x,y; for(x=n;x>0;x-) for(y=110;y>0;y-); /1602写命令函/void lcd_wcom(uchar com)rs=0; /选择指令寄存器 rw=0; /选择写 PB=com; /把命令字送入PBdelay(5); /延时一小会儿,让1602准备接收数据 en=1; /使能线电平变化,命令送入1602的8位数据口 en=0; /1602写数据函数 /void lcd_wdat(uchar dat) rs=1; /选择数据寄存器 rw=0; /选择写 PB=dat; /把要显示的数据送入P2 delay(5); /延时一小会儿,让1602准备接收数据 en=1; /使能线电平变化,数据送入1602的8位数据?en=0; /1602初始化函数/void lcd_init(void) uchar a,k;delay(15); /等待电源稳定lcd_wcom(0x38); /8位数据,双列,5*7字形 lcd_wcom(0x0c); /开启显示屏,关光标,光标不闪烁 lcd_wcom(0x06); /显示地址递增,即写一个数据后显示位置右移一位 lcd_wcom(0x01); /清屏 /显示第一行/lcd_wcom(0x80);for(a=0;a<15;a+)lcd_wdat(tablea);delay(5);/显示第二行/lcd_wcom(0x80+0x40);for(k=0;k<8;k+) lcd_wdat(table1k);delay(5);/年月日时分秒输入的子函数/秒分的加减子函数/输入/void jia_key(uchar rd,uchar wd,uchar fd,uchar fen)u1=Read_ds1302(wd);_asm("nop");u1=(u1&0x70)>>4)*10+(u1&0x0F);if(fen=1) u1+;if(u1=60) u1=0;else u1-;if(u1+1)=0) u1=59;if(u1>59) u1=59;Write_ds1302(rd,(u1/10)<<4 | (u1%10);/秒write_sfm(fd,u1); lcd_wcom(0x80+0x40+fd);/显示分lcd_wcom(0x38);lcd_wcom(0x0f);/显示功能开,有光标/时的加减子函数/输入/void hjia_key(uchar rd,uchar wd,uchar fd,uchar shi)u0=Read_ds1302(wd);u0=(u0&0x70)>>4)*10+(u0&0x0F);if(shi=1) u0+;if(u0=24)u0=0;else u0-;if(u0+1)=0) u0=23; if(u0>23) u0=23; Write_ds1302(rd,(u0/10)<<4 | (u0%10); /秒_asm("nop");write_sfm(fd,u0);lcd_wcom(0x80+0x40+fd); /显示分_asm("nop");lcd_wcom(0x38);lcd_wcom(0x0f); /显示功能开,有光标lcd_wcom(0x80+0x40+fd);/日的加减子函数/输入/void rjia_key(uchar rd,uchar wd,uchar fd,uchar day) n3=Read_ds1302(wd); n3=(n3&0x70)>>4)*10+(n3&0x0F);if (day=1) n3+;if(n3=32) n3=0;elsen3-;if(n3+1)=0)n3=31;_asm("nop");if(n3>31) n3=31;Write_ds1302(rd,(n3/10)<<4 | (n3%10);_asm("nop");write_sfm1(fd,n3); _asm("nop");lcd_wcom(0x80+fd); /显示lcd_wcom(0x38);lcd_wcom(0x0f); /显示功能开,有光标/ 月的子函数/输入/void yjia_key(uchar rd,uchar wd,uchar fd,uchar yue)n2=Read_ds1302(wd);n2=(n2&0x70)>>4)*10+(n2&0x0F);_asm("nop");if(yue=1) n2+;if(n2=13) n2=0; elsen2-;if(n2+1)=0) n2=12;if(n2>12) n2=12; Write_ds1302(rd,(n2/10)<<4 | (n2%10);_asm("nop");write_sfm1(fd,n2); _asm("nop");lcd_wcom(0x80+fd);_asm("nop");lcd_wcom(0x38);lcd_wcom(0x0f);/显示功能开,有光标/ 年的子函数/输入/ void njia_key(uchar rd,uchar wd,uchar fd,uchar nian) n1=Read_ds1302(wd); n1=(n1&0xF0)>>4)*10+(n1&0x0F); if (nian=1) n1+;if(n1=100) n1=0; else n1-; if(n1+1)=0) n1=99; if(n1>99) n1=99; Write_ds1302(rd,(n1/10)<<4 | (n1%10);write_sfm1(fd,n1); lcd_wcom(0x80+fd); /显示lcd_wcom(0x38);lcd_wcom(0x0f); /显示功能开,有光标lcd_wcom(0x80+fd);/初始化1302,设置开始时候的时分秒void Init_DS1302(void)Write_ds1302(Write_protect,0x00);Write_ds1302(Write_year,0x0A);/年Write_ds1302(Write_month,0XC); /月Write_ds1302(Write_day,0X22); /日Write_ds1302(Write_hour,0X23); /小时Write_ds1302(Write_minute,0X59); /分Write_ds1302(Write_second,0X00); /秒Write_ds1302(Write_protect,0x80); /关闭寄存器操作;void write_sfm(uchar add,uchar dat) uchar shi,ge;shi=dat/10;ge=dat%10;lcd_wcom(0x80+0x40+add);lcd_wdat(table2shi); lcd_wdat(table2ge);void write_sfm1(uchar add,uchar dat) uchar shi,ge;shi=dat/10;ge=dat%10;lcd_wcom(0x80+add);lcd_wdat(table2shi);lcd_wdat(table2ge);void write_sfm2(uchar add,uchar dat) uchar k,ge;ge=dat%10;lcd_wcom(0x80+add);for(k=(ge*3-3);k<(ge*3);k+) lcd_wdat(table3k);delay(5);void lcd_init1(void) /显示祝福语 uchar i,k=0;PD_ODR=0x11;lcd_wcom(0x80);for(i=0;i<16;i+) lcd_wdat(table4i); lcd_wcom(0x80+0x40);for(k=0;k<16;k+) lcd_wdat(table5k);delay(100); PD_ODR=0X18;void naozhong(uchar hour1,uchar day1,uchar month1) if(hour1=00) /判断是否时相等 if(day1=23) /判断日是否相等if(month1=12) /判断月是否相等lcd_init1();lcd_wcom(0x01); /清屏 lcd_init();PD_ODR=0X00;return; void read_shzh(void) u2=Read_ds1302(Read_second ); /把读取的数据暂时储存在u2 u2=(u2&0x70)>>4)*10+(u2&0x0F); /转十进制的bcd码换成十进制 write_sfm(6,u2); u1=Read_ds1302(Read_minute ); /分 u1=(u1&0xF0)>>4)*10+(u1&0x0F); /转换成十进制 write_sfm(3,u1); /显示分 u0=Read_ds1302(Read_hour ); /时 u0=(u0&0xF0)>>4)*10+(u0&0x0F); /转换成十进制 write_sfm(0,u0); /显示小时 u3=Read_ds1302(Read_week ); /周 u3=u3&0x0F; /转换成十进制 write_sfm2(11,u3); /显示星期 n1=Read_ds1302(Read_year ); n1=(n1&0xF0)>>4)*10+(n1&0x0F); /转换成十进制 write_sfm1(2,n1); /显示年 n3=Read_ds1302(Read_day ); n3=(n3&0x30)>>4)*10+(