华侨大学单片机课设实验报告 - 图文

更新时间:2024-04-20 17:21:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

单片机实验报告

题 目 基于单片机的简单万年历 学 院 专 业 班 级 姓 名 信息科学与工程学院 通信工程 10级A班 学 号 2012 年 12 月 18 日 一.实验目的

本作品使用STC89C52单片机作为控制核心,在学习板的基础上自行拓展了DS1302时钟芯片以及LCD1602,蜂鸣器等器件,设计了一款具有多个功能的万年历。

二.可实现的功能

该作品基于STC89C52单片机并利用液晶LCD1602显示以及DS1302时钟芯片,通过程序控制实现了以下功能:

1. 液晶上显示年、月、日、星期、时、分、秒,并且按秒实时更新显示。 2. 具有闹铃设定及到时报警功能。

3. 能够使用板上的按键随时调节各个参数,4个有效键,分别为功能选择键、数值增大键、数值减小键和闹钟查看键。

三. 工作原理

本系统的工作原理是: 单片机通过对DS1302时钟芯片内的时间信息进行采集,并将其显示于液晶上实现基本时间显示功能;单片机根据用户按键输入的信号通过程序控制实现时间更新功能及闹钟、报警功能。

四. 硬件部分

此系统所包括的模块如图所示: 闹铃输出 按键输入 液晶显示 单 片 机 主 控

系统硬件设计

1、LCD显示模块设计

液晶显示模块具有体积小、功耗低、显示内容丰富、超薄轻巧等优点,在袖珍式仪表和低功耗应用系统中得到广泛的应用。目前液晶显示模块已经是单片机应用设计中最常用的信息显示器件。其中LCD1602液晶显示模块是常用的选择,它可以显示两行,每行16个字符,采用单+5V电源供电,外围电路配置简单,价格便宜,具有很高的性价比。考虑到本系统设计中友好的人机界面,相对采用多个LED作为显示模块,LCD1602更合适。 1.1 引脚描述

1602采用标准的16脚接口,如下图所示,其中:

第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脚:LEDA为背光接5V正电源。 第16脚:LEDK为背光地电源。

1.2 LCDI602控制指令

如表1所示,LCD1602的读写操作、屏幕和光标的操作都是通过指令编程来实现的。 ·指令 1:清显示。指令码01H,光标复位到地址00H位置。 ·指令 2:光标复位。光标返回到地址00H。

·指令 3:光标和显示模式设置,I/D:光标移动方向,高电平右移,低电平左移 ;S:屏幕

上所有文字是否左移或者右移。高电平表示有效,低电平则无效。

·指令 4:显示开关控制。D:控制整体显示的开与关,高电平表示开显示,低电平表示关

显示 C:控制光标的开与关,高电平表示有光标,低电平表示无光标 B:控制光标是否闪烁,高电平闪烁,低电平不闪烁

·指令5:光标或显示移位。S/C:高电平时移动显示的文字,低电平时移动光标 。 ·指令 6: 功能设置命令 。DL:高电平时为4位总线,低电平时为8位总线; N:低电平

时为单行显示,高电平时双行显示;F:低电平时显示5x7的点阵字符,高电平时显示5x10的点阵字符。

·指令 7:字符发生器RAM地址设置 。 ·指令 8:DDRAM地址设置 。

·指令9:读忙信号和光标地址。BF:为忙标志位,高电平表示忙,此时模块不能接收命令

或者数据,如果为低电平表示不忙。

·指令10:写数据。 ·指令11:读数据。

指 令 1清显示 2光标返回 3置输入模式 4显示开/关控制 5光标或字符移位 6置功能

7置字符发生存储器地址 8置数据存储器地址 9读忙标志或地址

10写数CGRAM或DDRAM 11从CGRAM或DDRAM读数

RS R/W D7 D6 D5 D4 D3 D2 D1 D0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 * 0 0 0 0 0 0 0 1 I/D S 0 0 0 0 0 0 1 D C B 0 0 0 0 0 1 S/C R/L * * 0 0 0 0 1 DL N F * * 0 0 0 1 字符发生存储器地址 0 0 1 显示数据存储器地址 0 1 BF 计数器地址 1 0 要写的数 1 1 读出的数据

表1 LCD1602的控制指令表

1.3 LCDI602读写控制时序

LCD1602的读写控制时序如表2所示,使用LCD1602显示时要严格按照时序要求,否则LCD1602显示会出现问题。

表2 LCD1602的读写控制时序表

RS R/W E 功能 0 0 下降沿 写指令代码 0 1 高电平 读忙标志和AC码 1 0 下降沿 写数据 1 1 高电平 读数据

1.4 LCDI602与单片机接口电路

2、DS1302模块设计

DS1302通过三根口线实现与单片机的通信,因DS1302功耗很小,即使电源掉电后通过3V的纽扣电池仍能维持DS1302精确走时。

(1) DS1302特性介绍

DS1302是美国DALLAS公司推出的一种高性能、低功耗的实时时钟芯片,附加31字节静态RAM,采用SPI三线接口与CPU进行通信,并可采用突发方式一次传送多个字节的时钟信号和RAM数据。实时时钟可提供秒、分、时、日、星期、月和年,一个月小与31天时可以自动调整,且具有闰年补偿功能。工作电压宽达2.5~5.5V。采用双电源供电(主电源和备用电源),可设置备用电源充电方式,提供了对后备电源进行涓细电流充电的能力。

(2) DS1302引脚介绍

各引脚的功能为:

8 、Vcc1:备用电池端; 1、Vcc2:5V电源。当Vcc2>Vcc1+0.2V时, 由Vcc2向DS1302供电,当Vcc2< Vcc1时, 由Vcc1向DS1302供电。

7、 SCLK:串行时钟,输入; 6、I/O:数据输入输出口; 5、CE/RST:复位脚

2 3、X1、X2 是外接晶振脚 (32.768KHZ的晶振) 4、地(GND)

(4)DS1302有关日历、时间的寄存器

寄存器的说明如下:

1、秒寄存器(81h、80h)的位7定义为时钟暂停标志(CH)。当初始上电时该位置为1, 时钟振荡器停止,DS1302处于低功耗状态;只有将秒寄存器的该位置改写为0时,时钟才能开 始运行。 2、小时寄存器(85h、84h)的位7用于定义DS1302是运行于12小时模式还是24小时模式。当为

高时,选择12小时模式。在12小时模式时,位5是 ,当为1时,表示PM。在24小时模式时,

位5是第二个10小时位

3、控制寄存器(8Fh、8Eh)的位7是写保护位(WP),其它7位均置为0。在对任何的时钟和

RAM的写操作之前,WP位必须为0。当WP位为1时,写保护位防止对任一寄存器的写操作。 也就是说在电路上电的初始态WP是1,这时是不能改写上面任何一个时间寄存器的, 只有首先将WP改写为0,才能进行其它寄存器的写操作。

(5)DS1302控制字介绍

控制字的最高有效位(位7)必须是逻辑1,如果它为0,则不能把数据写入到DS1302中。 位6:如果为0,则表示存取日历时钟数据,为1表示存取RAM数据; 位5至位1(A4~A0):指示操作单元的地址; 位0(最低有效位):如为0,表示要进行写操作,为1表示进行读操作。 读数据:

读数据时在紧跟8位的控制字指令后的下一个SCLK脉冲的下降沿,读出DS1302的数据, 读出的数据是从最低位到最高位。 写数据:

控制字总是从最低位开始输出。在控制字指令输入后的下一个SCLK时钟的上升沿时, 数据被写入DS1302,数据输入也是从最低位(0位)开始。 位0(最低有效位):为1表示进行读操作。 如为0,表示要进行写操作, 控制字后 SCLK 下降沿 读数据 SCLK上升沿写数据

(6)DS1302单字节读写时序介绍

DS1302的数据读写是通过I/O串行进行的。当进行一次读写操作时最少得读写两个字节,第一个字节是控制字节,就是一个命令,告诉DS1302是读还是写操作,是对RAM还是对CLOK寄存器操作,以及操作的址。第二个字节就是要读或写的数据了。我们先看

单字节写:在进行操作之前先得将CE(也可说是RST)置高电平,然后单片机将控制字的位0放到I/O上,当I/O的数据稳定后,将SCLK置高电平,DS1302检测到SCLK的上升沿后就将I/O上的数据读取,然后单片机将SCLK置为低电平,再将控制字的位1放到I/O上,如此反复,将一个字节控制字的8个位传给DS1302。接下来就是传一个字节的数据给DS1302,当传完数据后,单片机将CE置为低电平,操作结束。

单字节读操作的一开始写控制字的过程和上面的单字节写操作是一样,但是单字节读操作在

写控制字的最后一个位,SCLK还在高电平时,DS1302就将数据放到I/O上,单片机将SCLK置为低电平后数据锁存,

单机机就可以读取I/O上的数据。如此反复,将一个字节的数据读入单片机。

读与写操作的不同就在于,写操作是在SCLK低电平时单片机将数据放到IO上,当SCLK上升沿时,DS1302读取。而读操作是在SCLK高电平时DS1302放数据到IO上,将SCLK置为低电平后,单片机就可从IO上读取数据。

(7)DS1302操作指令介绍

操作说明:

1 首先要通过8EH将写保护去掉,将日期,时间的初值写时各个寄存器。

2 然后就可以对80H、82H、84H、86H、88H、8AH、8CH进行初值的写入。同时也通过秒寄存器将位7的CH值改成0,这样DS1302就开始走时运了。 3 将写保护寄存器再写为80H,防止误改写寄存器的值。

4 不断读取80H-8CH的值,将它们格式化后显示到1602LCD液晶上

(8)DS1302与单片机接口电路

3、键盘模块设计

按键在单片机系统中是一个很重要的部件。为了输入数据、查询和控制系统的工作状态,都要用到按键。本实验用到按键进行时间设置与调整作用。

4、闹铃模块设计

系统采用蜂鸣器作为闹铃输出。闹铃电路连接如图所示。

系统软件设计

软件设计是本设计的关键,软件程序编写的好坏直接影响着系统运行情况的良好。因本程序涉及的模块较多,所以程序编写也采用模块化设计,C语言具有编写灵活、移植方便、便于模块化设计的特点,所以本系统的软件采用C51编写。 程序框图如下:

程序如下:

#include

#include \

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) //液晶刷新时分秒。

{ //4为时的开始位置,7为分,10为秒。 uchar shi,ge; shi=dat/10; ge=dat;

writecom(0x80+0x40+add); writedata(0x30+shi); writedata(0x30+ge);

}

void writenyr(uchar add,uchar dat) //液晶刷新日期。 { //3为年,6为月,9为日。 uchar shi,ge; shi=dat/10; ge=dat;

writecom(0x80+add); writedata(0x30+shi); writedata(0x30+ge); }

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

writecom(0x80+12); //从第一行隔十二个字符后开始写星期 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() //键盘扫描函数。 {

key=0;

if(flagalarm==1) //如果闹钟在响,任意键停止闹钟响。 {

if((kmenu==0)||(kup==0)||(kdown==0)||(kalarm==0)) {

delay(5);

if((kmenu==0)||(kup==0)||(kdown==0)||(kalarm==0)) {

while(!(kmenu&&kup&&kdown&&kalarm)); di();

flagalarm=0; //清除闹钟标志。 } } }

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

delay(5); if(kmenu==0) {

kmenunum++; //记录功能键按下次数。

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

if(kmenunum==4) kmenunum=1; //因为进行闹钟设置时,只调节 //时分秒,所以,kmenunum 只能等于1,2,3。 flagset=1; //设置标志位,表示在进行各种时间设置。 while(!kmenu); di();

switch(kmenunum) //定位光标闪烁点。 {

case 1: writecom(0x80+0x40+11); //秒闪烁。 writecom(0x0f); //开光标闪烁。 break;

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

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

break;

case 4: writecom(0x80+14); //星期闪烁. break;

case 5: writecom(0x80+10); //日闪烁. break;

case 6: writecom(0x80+7); //月闪烁. break;

case 7: writecom(0x80+4); //年闪烁. break;

case 8: kmenunum=0;

writecom(0x0c); //取消光标闪烁。 flagset=0; //取消时间调节标志。

dswrite(0x8e,0x00); /*允许写操作*/

dswrite(0x80,second/10*16+second); //退出时间设置功能时,往DS1302

dswrite(0x82,minute/10*16+minute); //中保存所有时间变量的值。

dswrite(0x84,hour/10*16+hour); dswrite(0x8a,week/10*16+week); dswrite(0x86,day/10*16+day); dswrite(0x88,month/10*16+month); dswrite(0x8c,year/10*16+year);

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(10,second);

writecom(0x80+0x40+11); //因为,上面送液晶显示

break; //一次,光标后移一位,所以要将光标复位. case 2: minute++;

if(minute==60) minute=0;

writetime(7,minute); writecom(0x80+0x40+8); break; case 3: hour++;

if(hour==24) hour=0;

writetime(4,hour);

writecom(0x80+0x40+5); break; case 4: week++; if(week==8) week=1;

writeweek(week); writecom(0x80+14); break; case 5: day++;

if(day==32) day=1;

writenyr(9,day); writecom(0x80+10); break; case 6: month++;

if(month==13) month=1;

writenyr(6,month); writecom(0x80+7); break; case 7: year++;

if(year==100) year=0;

writenyr(3,year); writecom(0x80+4); break; } } }

if(kdown==0) {

delay(5); if(kdown==0)

{

while(!kdown); di();

switch(kmenunum) //根据功能键被按次数调节相应数值. {

case 1: second--;

if(second==-1) second=59;

writetime(10,second); writecom(0x80+0x40+11); break; case 2: minute--;

if(minute==-1) minute=59;

writetime(7,minute); writecom(0x80+0x40+8); break; case 3: hour--;

if(hour==-1) hour=23;

writetime(4,hour); writecom(0x80+0x40+5); break; case 4: week--; if(week==0) week=7;

writeweek(week); writecom(0x80+14); break; case 5: day--; if(day==0) day=31;

writenyr(9,day); writecom(0x80+10); break; case 6: month--; if(month==0) month=12;

writenyr(6,month); writecom(0x80+7); break; case 7: year--;

if(year==-1) year=99;

writenyr(3,year); writecom(0x80+4); break; } } } }

if(kalarm==0) //检测闹钟调节键是否按下,此条if语句与 if(kmenunum!=0) { //在同一个层次。 delay(2); 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); //往DS1302中保存闹钟的时分秒值。

dswrite(writealarmminute,minute/10*16+minute); dswrite(writealarmhour,hour/10*16+hour); dswrite(0x8e,0x80); /*禁止写操作*/ }

else //进入闹钟设置。 {

s=dsread(readalarmsecond)/16*10+dsread(readalarmsecond); //读取DS1302中保存的闹钟时分秒原始值,转化为十进制数存进s,m,h保存,用以后面判断闹钟时间是否到达。

m=dsread(readalarmminute)/16*10+dsread(readalarmminute); h=dsread(readalarmhour)/16*10+dsread(readalarmhour);

second=s; //把闹钟时间的十进制数据传送给变量second,minute,hour,用以调节其值。 minute=m;

hour=h; writecom(0x80+0x40);

writedata('R'); //液晶显示闹钟调节标志。 writedata('i');

writecom(0x80+0x40+3);

writetime(4,hour); //送液晶显示闹钟时间。 writetime(7,minute);

writetime(10,second); } }

} }

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; //lcdrs=0;

//lcden=0; //液晶使能端置0,液晶能够工作了. dula=0; //关闭数码管 wela=0;

//DS1302初始化。

dswrite(0x8e,0x00); /*允许写操作*/

dswrite(0x80,0x00);//打开DS1302晶振,使其开始工作。 dswrite(0x84,0x00);//设置为24小时制。 init1302time(inittime);//初始化时间为:

//12年8月12日星期日,11点59分59秒,

//从左往右的数据依次是,秒,分,时,日,月,星期,年。 dswrite(0x8e,0x80); /*禁止写操作*/

//1602液晶初始化。

writecom(0x38); writecom(0x0c); writecom(0x06); 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 main() {

uchar temp; init(); while(1) {

程序。

keyscan();

if(flagalarm==1) //当闹钟时间到时,执行此段程序,启动蜂鸣器发声。 {

di();

delay(100); di();

delay(500); }

if(flagset==0&&flagseta==0) //没有进行时间设置,和,闹钟设置时,执行此段{

keyscan();

//读取DS1302的数据。

temp=dsread(0x81); //读秒数据,送temp暂时保存。 second=temp/16*10+temp; //将BCD码转换成十进制数.

temp=dsread(0x83);

minute=temp/16*10+temp;

temp=dsread(0x85);

hour=temp/16*10+temp;

temp=dsread(0x87);

day=temp/16*10+temp;

temp=dsread(0x89);

month=temp/16*10+temp;

temp=dsread(0x8b);

week=temp/16*10+temp;

temp=dsread(0x8d);

year=temp/16*10+temp;

writetime(4,hour); //各数据送液晶显示. writetime(7,minute);

writetime(10,second); writenyr(3,year); writenyr(6,month);

writenyr(9,day); writeweek(week);

}

if((second==s)&&(minute==m)&&(hour==h)) flagalarm=1; } }

五、结论

通过对基于单片机的万年历的研究和设计,完成了一种能够显示日期、星期、时间的多功能万年历,该万年历还可以定时闹钟。

设计期间我查看了单片机和各个芯片之间的通信协议,液晶的读写时序,按键的防抖。本系统基本完成了设计指标的所有要求,所有的程序分块编写,通过main函数来调用,增加了程序的易修改性和易读性,方便他人的使用和改进。不过这次的作品还有很多的不足,例如:调时间很麻烦,不能按照人为习惯自由调整;还出现了一些错误,4月居然可以调到31号,不过正常走时不会走到。错误在所难免,关键是你真正学到了知识,我想这才是老师布置题目的真正用意。

附1:电路原理图及PCB图

附2:实物图

本文来源:https://www.bwwdw.com/article/7btp.html

Top