基于单片机的电子万年历设计与实现
《嵌入式与单片机》课程设计报告
摘要
万年历作为日常生活中重要的时间显示设备,在人们的外出旅游和日常生活中都发挥了重要的作用。本文针对目前的万年历产品中存在的计时精度差、功能少和性价比低等问题,提出了一款由AT89C51单片机,DS1302时钟芯片和数码管为等器件设计的电子万年历的解决方案。并对硬件的时间采集,时间设置电路和时间显示电路等进行了详细的设计。硬件设计完成后,在KEIL4开发环境中利用C语言设计了时间读/写程序模块时间显示模块等控制程序,实现了对以上硬件的控制。软件和硬件设计完成后,根据硬件总体电路图在Proteus软件下设计了仿真原理图,对软硬件的设计输出进行了验证。经测试系统实现了倒计时模式、秒表模式、闹钟模式、万年历模式和时钟模式。本文研究设计的电子万年历,具有计时精准、功能完善和显示直观等优点,实现了对时间计量过程的科学管控,从而提升了设备的整体工作效率。
目录
《嵌入式与单片机》 1
摘要 2
1.概述 4
1.1应用背景 4
1.2本设计完成的主要功能 4
2.开发工具及相关技术 4
2.1开发工具keil介绍 4
2.2程序烧写工具 6
2.3仿真软件proteus介绍 7
2.4 51单片机 7
3.功能需求分析及硬件设计 8
3.1需求分析 8
3.2系统总体设计 9
3.3硬件总体设计方案 9
3.4系统硬件总体设计 9
4.软件设计与实现 12
4.1软件总体设计方案 12
4.2软件总体设计 13
4.3各软件模块的设计实现 13
5.系统仿真及测试 17
5.1防真过程及描述 17
5.2测试环境的搭建 17
5.3测试过程及结果 17
6.小结 20
附录A程序清单 21
1.概述
1.1应用背景
在现实我们生活中每个人都可能有自己的时钟,光阿在永不停息的流逝有了时钟人们就能随着时间有计划的过着每一天。然而现在绝大部分的时钟有的需要不断地更换电池,有些时钟需要外接电源,如果一旦电池没电或者外接电源无法供电,时钟就会停止计时了。而美国DALLAS公司的新型时钟因历芯片DS1302就能解决这一问题,该器件能提供实时时钟(RTC)/日历、定时闹钟。少于31天 的月份,同末目期可自动调整,其中包括闰年补偿。该器件还可以工作于24小时货代/PM指示的12小时格式。本时钟还具有环保、走时无噪音、低 功耗等非实时时钟不具有的功能。该实时时钟不但可以作为家用,而且更可以在公共场合使用,如车站、码头、商场等场所。
1.2本设计完成的主要功能
时钟模式:显示时-分-秒,按下S4一下进入时钟模式,再按S1可进入设置,按下S2、S3对其数字进行调节,S2增大,S3减小
万年历模式:显示年-月-日,按下S4俩下进入时钟模式,再按S1可进入设置,按下S2、S3对其数字进行调节,S2增大,S3减小
闹钟模式:显示指定闹钟时间,时-分-秒,按下S4三下进入时钟模式,再按S1可进入设置,按下S2、S3对其数字进行调节,S2增大,S3减小。
秒表模式:显示秒表计时,时- 分-秒,按下S4四下进入时钟模式,再按S1可暂停秒表,S2可清零并暂停,S3可继续开始计时。
倒计时模式:显示倒计时,时-分-秒,按下S4五下进入时钟模式,再按S1可进入设置调节下一位,按下S2、S3对其数字进行调节,S2增大,S3减小,倒数计时完后会发出警报声,给倒计时添加时间,警报声关闭。
倒计时模式秒表模式闹钟模式万年历模式时钟模式
2.开发工具及相关技术
2.1开发工具keil介绍
2.1.1Keil简介
Keil 是一款兼容单片机C语言软件开发系统,与汇编相比,C语言在功能上、结构性、可读性、可维护性上有明显的优势,[1]因而易学易用。Keil提供了包括C编译器、宏汇编、链接器、库管理和一个功能强大的仿真调试器等在内的完整开发方案,通过一个集成开发环境(μVision)将这些部分组合在一起。运行Keil软件需要WIN98、NT、WIN2000、WINXP等操作系统。如果你使用C语言编程,那么Keil几乎就是你的不二之选,即使不使用C语言而仅用汇编语言编程,其方便易用的集成环境、强大的软件仿真调试工具也会令你事半功倍。
2.1.2keil安装
(1))在http://www.downxia/downinfo/267041.html该网站中下载keil4(Keil C51 V9.00 )的安装包。
(2)打开它自带的注册软件,将keil4中的ID(File->License Management->Single-User License)复制上去获得新注册ID码
2.1.3Keil的使用
(1)在网上下载VDM51.dll,把文件复制到 D:\danpianji\C51\BIN 文件夹下。
(2)用记事本打开danpianji 根目录下的 TOOLS.INI 文件,加入 TDRV8=BIN\VDM51.DLL ("Proteus VSM Monitor-51 Driver" ) 。
(3)建立KEIL 工程后,单击(Project ->option for ta rget) ,弹出窗口,点击“Debug”按钮,在出现的对话框里在右栏 最上部 的下拉菜单里选中“Proteus VSM Monitor 一51 Driver”。并且还要点击一下“Use”前面表明选中的小圆点。点击seting还有ok就好了
2.2程序烧写工具
安装好CH340 驱动后,我们就可以下载程序了,在下载程序前先确认下开
发板上的USB 转 TTL 串口模块上的 P5 端子短接片是否短接好(即 P31T 与 URXD 连接,P30R 与 UTXD 连接),出厂的时候该短接片默认已经短接好,如下所示: 可以使用本公司开发的自动下载软件,软件在资料目录“\5--开发工具\3- 程序下载软件”内,可以看到里面有两个烧写软件,一个是“STC-ISP”下载软 件,另一个是“PZ-ISP”下载软件。STC-ISP 下载软件是需要冷启动,即先点击下载然后开启电源,操作较为复杂,不推荐使用该软件。而 PZ-ISP 是普中自主 研发的自动下载软件,可一键下载,操作非常简单,推荐大家使用 PZ-ISP 软件 下载程序。打开此软件选择对应的 51 单片机类型进行程序下载。具体操作如下:
1.打开“\5--开发工具\3-程序下载软件\PZ-ISP(推荐使用)”, 鼠标双击该软件,弹出如下界面:(注意软件的版本号)(注意:此时默认你已经安装好了 CH340 驱动,此时可以看到对应的串口号)
2.根据板载芯片型号是否含有RC 来选择芯片类型,比如板载芯片使用
STC89C52RC 或 STC89C52RD+或 STC89C516RC 或 STC89C516RD+,则选择芯片类型为“STC89C52xxx-RC”。如果使用 STC89C52,则芯片类型为“STC89C52xx”。
3.将波特率设置为“128000”(如果发现此波特率下载速度比较慢,可以
提高波特率,如果下载失败,可以把波特率降低,总之选择一个能下载的波特率),
4.其他的选项我们保持默认设置。下面点击“打开文件”,操作如下:
选择实验程序文件夹内.HEX 文件,点击“打开”即可选择好需要下载的程
序。
5.点击“程序下载”按钮即可完成程序下载。当程序下载完成会提示程序下载
成功。
2.3仿真软件proteus介绍
proteus是英国Labcenter公司研发的,是目前世界上最先进,最完整的嵌入式系统设计与仿真软件平台。它是一种可视化的支持多种型号单片机(如51,PIC,AVR,Motorola hcll等),并且支持当前流行的单片机开发环境(keil,MPLAB,IAR).是目前电子设计爱好者广泛是用的电子线路设计与仿真软件protel和Multisim功能的联合和进一步扩展。
它的功能不是单一的,它有强大的软件库,仿真功能可以和Multisin相媲美,并且有它没有的单片机仿真功能,并且还有pcb电路制版功能,可以和protel相媲美。
自我感觉到,他可以仿真模电,数点,单片机等重要的硬件环境非常难办到的事,还有就是自己设计的硬件环境可以运行的情况下,如果用此软件同样可以做出来效果,并且没有成本。非常实用。
2.4 51单片机
51单片机是对所有兼容Intel 8031指令系统的单片机的统称。[1]该系列单片机的始祖是Intel的8004单片机,后来随着Flash rom技术的发展,8004单片机取得了长足的进展,成为应用最广泛的8位单片机之一,其代表型号是ATMEL公司的AT89系列,它广泛应用于工业测控系统之中。很多公司都有51系列的兼容机型推出,今后很长的一段时间内将占有大量市场。51单片机是基础入门的一个单片机,还是应用最广泛的一种。需要注意的是51系列的单片机一般不具备自编程能力。
3.功能需求分析及硬件设计
3.1需求分析
随着电子技术的发展,人类不断研究,不断创新纪录。万年历目前已经不再局限于以书本形式出现。以电脑软件或者电子产品形式出现的万年历被称为电子万年历。与传统书本形式的万年历相比,电子万年历得到了越来越广泛的应用,采用电子时钟作为时间显示已经成为一种时尚。目前市场上各式各样的电子时钟数不胜数,但多数是只针对时间显示,功能单一不能满足人们日常生活需求
3.2系统总体设计
3.3硬件总体设计方案
用AT89C52作为主挖单片机,时钟模块选用DS1302作为时钟芯片,显示模块选用数码管,设置部分选用按键电路。
AT89C52与MCS-51单片机产品兼容8K字节在系统可编程Flash存储器1000次擦写周期、全静态操作:0Hz~33Hz、三级加密程序存储器、32个所编程1/012线、三个16位定时器/计数器八个中断源、全双1UART串行通道、低功耗空闲和掉电模式掉电后中断可唤醒、看门狗定时器、双数据指针、掉电标识符。
DS1302实时时钟芯片功能丰富,可以用来直接代替IBMPC上的时钟2历芯片DS12887,同时,它的管脚也和MC146818B、DS12887相兼容。由于DS1302能够自动产生世纪、年,月、目、时、分、秒等时间信息,其内部又增加了世纪寄存器,从而利用硬件电路解决子“千年”问题;DS1302自带有锂电池,外部掉电时,其内部时间信息还能够保持10年之久;对于一天内的时间记录,有12小时制和24小时制两种模式。用户还所对DS1302进行编程以实现多种方波输出,并可对其内部的三路中断通过软件进行屏蔽。
3.4系统硬件总体设计
3.4.1单片机最小系统
单片机最小系统包括单片机、复位电路、时钟电路构成。
STC89C52 单片机的工作电压范围:4V-5.5V,所以通常给单片机外界5V直流电源。连接方式为单片机中的40脚VCC接正极5V,而20脚VSS接电源地端。
复位电路就是确定单片机的工作起始状态,完成单片机的启动过程。单片机接通电源时产生复位信号,完成单片机启动确定单片机起始工作状态。当单片机系统在运行中,受到外界环境干扰出现程序跑飞的时候,按下复位按钮内部的程序自动从头开始执行。一般有上电自动复位和外部按键手动复位,单片机在时钟电路工作以后,在RESET端持续给出2个机器周期的高电平时就可以完成复位操作。本设计采用的是外部手动按键复位电路,需要接上上拉电阻来提高输出高电平的值。
时钟电路好比单片机的心脏,它控制着单片机的工作节奏。时钟电路就是振荡电路,是向单片机提供一个正弦波信号作为基准,决定单片机的执行速度。XTAL1和XTAL2分别为反向放大器的输入和输出,该反向放大器可以配置为片内振荡器。如采用外部时钟源驱动器件,XTAL2应不接。因为一个机器周期含有6个状态周期,而每个状态周期为2个振荡周期,所以一个机器周期共有12个振荡周期,如果外接石英晶体振荡器的振荡频率为12MHZ,一个振荡周期为1/12us。
3.4.2动态数码管模块
动态显示的特点是将所有位数码管的段选线并联在一起,由位选线控制是哪一位数码管有效。选亮数码管采用动态扫描显示。所谓动态扫描显示即轮流向各位数码管送出字形码和相应的位选,利用发光管的余辉和人眼视觉暂留作用,使人的感觉好像各位数码管同时都在显示。动态显示的亮度比静态显示要差一些,所以在选择限流电阻时应略小于静态显示电路中的。
3.4.3独立按键模块
4条输入线接到单片机的IO口上,当按键K1按下时,+5V通过电阻R1然后再通过按键K1最终进入GND形成一条通路,那么这条线路的全部电压都加到了R1这个电阻上,KeyIn1这个引脚就是个低电平。当松开按键后,线路断开,就不会有电流通过,那么KeyIn1和+5V就应该是等电位,是一个高电平。我们就可以通过KeyIn1这个IO口的高低电平来判断是否有按键按下。
3.4.4蜂鸣器
本设计里,我们采用有源蜂鸣器,由于蜂鸣器的工作电流一般比较大,以至于单片机的I/O 口是无法直接驱动的,所以要利用放大电路来驱动,我们使用三极管来放大电流,驱动蜂鸣器,此模块只要通过BELL(连接到到单片机P2.7)输入的PWM波既可以使蜂鸣器分出声音,我们设计的这款万年历可以在闹钟定时中作为声音提醒信号。
3.4.5 DS1302时钟模块
DS1302的工作原理和单片机的接口:
DS1302为美国DALLAS公司的一种实时时钟芯片,主要特点是采用串行数据传输,可为掉电保护电源提供可编程的充电功能,并且可以关闭充电功能。采用32.768Hz晶振。它可以对年、月、日、星期、时、分、秒进行计时,且具有闰年补偿等多种功能。DS1302 用于数据记录,特别是对某些具有特殊意义的数据点的记录上,能实现数据与出现该数据的时间同时记录。这种记录对长时间的连续测控系统结果的分析以及对异常数据出现的原因的查找有重要意义。
4.软件设计与实现
4.1软件总体设计方案
主程序开始初始化,并打开中断,然后执行扫描键盘及读取18B20值。当有S4键按下时,执行时钟设置,并当再次有S4按下时依次显示万年历、闹钟、秒表、倒计时(在时间到达时会有蜂鸣器进行提醒)等功能,S1为设置按键,S2、S3分别进行调节与确认,S4为功能选择按键。
4.2软件总体设计
4.3各软件模块的设计实现
4.3.1延时函数
/*
*********************************************************************
* 函 数 名 : delay
* 函数功能 : 延时函数,i=1时,大约延时10us
**********************************************************************/
void delay(u16 i)
{
while(i--);
}
4.3.2时间读取处理转换函数
/*
*********************************************************************
* 函 数 名 : datapros()
* 函数功能 : 时间读取处理转换函数
* 输 入 : 无
* 输 出 : 无
**********************************************************************/
void datapros(u8 a,u8 b,u8 c)
{
DisplayData[0] = smgduan[a/10%10]; //时
DisplayData[1] = smgduan[a%10];
DisplayData[2] = 0x40;
DisplayData[3] = smgduan[b/10%10]; //分
DisplayData[4] = smgduan[b%10];
DisplayData[5] = 0x40;
DisplayData[6] = smgduan[c/10%10]; //秒
DisplayData[7] = smgduan[c%10];
}
4.3.3数码管显示函数
/*
********************************************************************** 函数名 :DigDisplay()
* 函数功能 :数码管显示函数
* 输入 : 无
* 输出 : 无
**********************************************************************/
void DigDisplay() //显示函数
{
u8 i;
for(i=0;i<8;i++)
{
switch(i) //位选,选择点亮的数码管,
{
case(0):
LSA=0;LSB=0;LSC=0; break;//显示第0位
case(1):
LSA=1;LSB=0;LSC=0; break;//显示第1位
case(2):
LSA=0;LSB=1;LSC=0; break;//显示第2位
case(3):
LSA=1;LSB=1;LSC=0; break;//显示第3位
case(4):
LSA=0;LSB=0;LSC=1; break;//显示第4位
case(5):
LSA=1;LSB=0;LSC=1; break;//显示第5位
case(6):
LSA=0;LSB=1;LSC=1; break;//显示第6位
case(7):
LSA=1;LSB=1;LSC=1; break;//显示第7位
}
P0=DisplayData[7-i] ; //if(i==3) | 0x80输送段码的函数中对固定位显示小数点 判断
// else其他位置不需要小数点显示就照常显示
P0=DisplayData[7-i];//发送数据
delay(100); //间隔一段时间扫描
P0=0x00;//消隐
}
}
4.3.4按键函数
/*
*********************************************************************
* 函 数 名 : KEY
* 函数功能 : 按键函数
* 输 入 : 无
* 输 出 : 无
**********************************************************************/
4.3.5主函数
/*
*********************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
**********************************************************************/
void main()
{
TMOD=0x11;//设定定时气的工作方式
// TH0=0x3c;//设定初始值(高八位)
// TL0=0xb0;//设定T0初始值(低五位) 设定时间为50ms
TH0=(65536-2000)/256; //这是定时100us时高8位的值 2ms 1s=1000ms
TL0=(65536-2000)%256; //这是定时100us时低8位的值。
ET0=1; //内部中断
EA=1; //打开总中断
TR0=1; //启动定时器0
while(1)
{
KEY();
if(flag==1){
datapros(year,mon,day); //数据处理函数
DigDisplay();//数码管显示函数
} else if(flag==2){ //flag=2显示闹钟
datapros(B_hour,B_min,B_sec); //数据处理函数
DigDisplay();//数码管显示函数
}
else if(flag==3){ //flag=3 显示秒表
datapros(shi,fen,miao); //数据处理函数
DigDisplay();//数码管显示函数
}
else if(flag==4){ //flag=4 显示倒计时
datapros(B_shi,B_fen,B_miao); //数据处理函数
DigDisplay();//数码管显示函数
}
else
{
datapros(hour,min,sec); //数据处理函数
DigDisplay();//数码管显示函数 // flag=0 显示时钟
shi=0,fen=0,miao=0; //换回显示时钟需要将秒表值归零
}
}
}
4.3.6定时器中断函数
/*
*********************************************************************
* 函 数 名 : time1
* 函数功能 : 定时器中断函数
* 输 入 : 无
* 输 出 : 无
**********************************************************************/
5.系统仿真及测试
5.1防真过程及描述
先连接最小系统中的复位和晶振电路,再搭建蜂鸣器、时钟、按键和数码管电路。
5.2测试环境的搭建
(1)建立keil工程,选择单片机型号
(2)给工程创建源码文件,并以.c结尾,添加文件到源组里
(3)生成hex文件,点击魔术棒,在output中勾选Create HRX_File
(4)对代码进行编译,调试debug,点击USE,在下拉菜单里选中“Proteus VSM Monitor 一51 ”进行设置
(5)将程序导入到单片机中运行,双击单片机,选择生成的hex文件
(6)在keil中启动调试,开始运行,观察proteus中的电路
5.3测试过程及结果
测试编号 | 测试名称 | 测试过程 | 测试预期结果 | 测试实际结果 | 测试结论 |
1 | 时钟模式 | S2增大,S3减小 | 显示当前时间 | 22:29:14 | 显示成功 |
5.3.2万年历模式测试
测试编号 | 测试名称 | 测试过程 | 测试预期结果 | 测试实际结果 | 测试结论 |
2 | 万年历模式 | S2增大,S3减小 | 显示当前日期 | 22/06/29 | 显示成功 |
5.3.3闹钟模式测试
测试编号 | 测试名称 | 测试过程 | 测试预期结果 | 测试实际结果 | 测试结论 |
3 | 闹钟模式 | S2增大,S3减小 | 设置闹钟 | 设置了早上9点的闹钟 | 显示成功 |
5.3.4秒表模式测试
测试编号 | 测试名称 | 测试过程 | 测试预期结果 | 测试实际结果 | 测试结论 |
4 | 秒表模式 | S1可暂停秒表,S2可清零并暂停,S3可继续开始计时 | 随时间变化进行计时 | 随时间变化进行计时 | 计时成功 |
5.3.5倒计时模式测试
测试编号 | 测试名称 | 测试过程 | 测试预期结果 | 测试实际结果 | 测试结论 |
5 | 倒计时模式 | S2增大,S3减小 | 倒计时完成,蜂鸣器响 | 倒计时完成,蜂鸣器响 | 报警成功 |
6.小结
进行软硬件的结合与调试。通过下载将在电脑上已完成的程序下载到单片机芯片中。在调试中发现软件中存在的问题,及时解决问题,确保系统能正常工作并达到设计要求。通过反复的调试与实验,可以证明该系统能够较好地完成设计所需的基本要求。即能够正确的显示万年历。
在设计中,因为考虑到闹钟定时功能,我们希望我们设置的闹钟时刻不会因为系统的掉电而丢失,考虑到DS1302是有锂电池作为电源的,不会因为主系统掉电丢失内部数据,所以我们将闹钟的定时时刻放到了DS1302内的空余寄存器里面,像这些灵活的技巧就需要我们认真的阅读元件的数据手册,从中索取对自己有用的信息。
经测试,本作品完成设计所有要求。经过万年历的设计,让我学到了很多,让我认识到了学习基础知识的重要性,当设计完整的系统时,要考虑到硬件和软件两者的结合,有时硬件的不足,我们可以用软件程序来弥补,从而节约硬件成本,在设计软件程序时要模块化,可以提高程序的可读性。
附录A程序清单
#include "reg52.h" //此文件中定义了单片机的一些特殊功能寄存器
typedef unsigned int u16; //对数据类型进行声明定义
typedef unsigned char u8;
u8 flag_set=0; //设置变量:0正常工作 3调时 2 调分 1 调秒也就是按下设置时的工作方式
u8 flag = 0; //切换时钟和秒表显示的变量 0时钟 1日期 2闹钟 3秒表 4倒计时
u16 hour=10; //初始化显示的时分秒
u16 min = 0; //时钟的分
u16 sec=0; //时钟的秒
u16 shi=0,fen=0,miao=0; //秒表的时分秒
u16 B_hour=9,B_min=0,B_sec=0; //设置闹钟的时间
u16 year=21,mon=11,day=26; //定义年月日的变量
u16 B_miao=50,B_fen=10,B_shi=0; //定义倒计时的秒 分 时****注意顺序
bit noise=0; //闹钟开关变量 因为在某些时候需要关闭闹钟
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
sbit Beep=P1^5;
sbit Key1=P3^1; // 按键1,模式切换按键
sbit Key2=P3^0; // 按键2,倒计时增加
sbit Key3=P3^2; // 按键3,倒计时减少
sbit K4=P3^3; //设置设置按键引脚 即用K4表示单片机上的P3^6引脚 所有按键的定义相同
u8 DisplayData[8];
u8 code smgduan[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
/*
******************************************************************************
* 函 数 名 : delay
* 函数功能 : 延时函数,i=1时,大约延时10us
********************************************************************************/
void delay(u16 i)
{
while(i--);
}
/*
******************************************************************************
* 函 数 名 : datapros()
* 函数功能 : 时间读取处理转换函数
* 输 入 : 无
* 输 出 : 无
********************************************************************************/
void datapros(u8 a,u8 b,u8 c)
{
DisplayData[0] = smgduan[a/10%10]; //时
DisplayData[1] = smgduan[a%10];
DisplayData[2] = 0x40;
DisplayData[3] = smgduan[b/10%10]; //分
DisplayData[4] = smgduan[b%10];
DisplayData[5] = 0x40;
DisplayData[6] = smgduan[c/10%10]; //秒
DisplayData[7] = smgduan[c%10];
}
/*
******************************************************************************* 函数名 :DigDisplay()
* 函数功能 :数码管显示函数
* 输入 : 无
* 输出 : 无
********************************************************************************/
void DigDisplay() //显示函数
{
u8 i;
for(i=0;i<8;i++)
{
switch(i) //位选,选择点亮的数码管,
{
case(0):
LSA=0;LSB=0;LSC=0; break;//显示第0位
case(1):
LSA=1;LSB=0;LSC=0; break;//显示第1位
case(2):
LSA=0;LSB=1;LSC=0; break;//显示第2位
case(3):
LSA=1;LSB=1;LSC=0; break;//显示第3位
case(4):
LSA=0;LSB=0;LSC=1; break;//显示第4位
case(5):
LSA=1;LSB=0;LSC=1; break;//显示第5位
case(6):
LSA=0;LSB=1;LSC=1; break;//显示第6位
case(7):
LSA=1;LSB=1;LSC=1; break;//显示第7位
}
P0=DisplayData[7-i] ; //if(i==3) | 0x80输送段码的函数中对固定位显示小数点 判断
// else其他位置不需要小数点显示就照常显示
P0=DisplayData[7-i];//发送数据
delay(100); //间隔一段时间扫描
P0=0x00;//消隐
}
}
/*
******************************************************************************
* 函 数 名 : KEY
* 函数功能 : 按键函数
* 输 入 : 无
* 输 出 : 无
********************************************************************************/
void KEY() //按键函数
{
if(K4==0) //K4按键按下 可以切换显示的值具体如下
{
delay(10);
if(K4==0)
{
flag++; //flag标志位,0显示时钟数字,1显示日期2显示闹钟3显示秒表4显示倒计时
flag_set=0;
if(flag>4) //因为显示只有时钟和秒表 当flag大于1需要将其置0即显示时钟
{
flag=0;
}
} while(!K4);
}
if(Key1==0) //设置键按下时
{
delay(10);
if(Key1==0) //判断Key1是否按下
{
flag_set++; //设置变量++ 第一次设置按下调节秒/日 第二次分/月 第三次时/年
noise=1; //关闭蜂鸣器
if(flag_set==4)
{flag_set=0; //等于4就需要恢复工作 并重新开启蜂鸣器
noise=0;
TR0=1; //定时器也开启
}
}
while(!Key1); //按键释放
}
if(flag_set==1) //加减按钮只有在设置状态下才有效果
{
if(Key2==0) //加按键按下时
{
delay(10);
if(Key2==0) //调秒
{ if(flag==0){ //flag=0的时候显示时钟 按下Key2按键可以对秒钟加一
sec++; // sec值加一
if(sec>59){ //当秒钟等于60的时候需要置零
sec=0;
}
}
else if(flag==1){ //flag=1时显示的是日期 此时按下需要对日进行加一
day++;
if(day==32){ //假设最大日期31 超出则置1
day=1;
}
}
else if(flag==2){ //flag=2时显示的是闹钟 此时按下需要对闹钟的秒进行加一
B_sec++;
if(B_sec>59){ //秒等于60需要置零
B_sec=0;
}
}
else if(flag==3){ //flag=3时显示的是秒表 此时按下需要对秒表清零并停止
shi=0,fen=0,miao=0;
}
else{ //flag=4时显示的是倒计时 此时按下需要对倒计时的秒进行加一
B_miao++;
if(B_miao==60){
B_miao=0;
}
}
}while(!Key2);
}
if(Key3==0) //减按键按下时
{
delay(10);
if(Key3==0)
{ if(flag==0){ //flag=0时显示的是时钟 此时按下需要对时钟的秒进行减一
sec--;
if(sec==-1){ //当秒小于0时 秒需要赋值为59
sec=59;
}
}
else if(flag==1){ //flag=1时显示的是日期 此时按下需要对日进行减一
day--;
if(day<1){ //当日期小于1时需要赋值为假设的最大日期31
day=31;
}
}
else if(flag==2){ //flag=2时显示的是闹钟 此时按下需要对闹钟的秒进行减一
B_sec--; //
if(B_sec==-1){ //当秒小于0时 秒需要赋值为59
B_sec=59;
}
}
else if(flag==3){ //flag=时显示的是秒表 此时按下需要对秒表重新开始
flag_set=0;
}else{ //flag=4时显示的是倒计时 此时按下需要对倒计时的秒进行减一
B_miao--;
if(B_miao==-1){ //当秒小于0时 秒需要赋值为59
B_miao=59;
}
}
}while(!Key3);
}
}
if(flag_set==2)
{
if(Key2==0)
{
delay(10);
if(Key2==0)
{ if(flag==0){
min++; //flag=0的时候显示时钟 按下Key2按键可以对分钟加
if(min==60){ //当分钟钟等于60的时候需要置零
min=0;
}
}
else if(flag==1){ //flag=1时显示的是日期 此时按下需要对月进行加一
mon++;
if(mon==13){
mon=1;
}
}
else if(flag==2){ //flag=2时显示的是闹钟 此时按下需要对闹钟的分进行加一
B_min++;
if(B_min==60){ //分等于60需要置零
B_min=0;
}
}
else if(flag==3){ //flag=3时显示的是秒表 此时按下需要对秒表清零并停止
shi=0,fen=0,miao=0;
}
else{ //flag=4时显示的是倒计时 此时按下需要对倒计时的分进行加一
B_fen++;
if(B_fen==60){
B_fen=0;
}
}
}while(!Key2);
}
if(Key3==0) //减按键按下时
{
delay(10);
if(Key3==0)
{ if(flag==0){ //flag=0时显示的是时钟 此时按下需要对时钟的分进行减一
if(min>0)
min--;
else //当秒小于0时 秒需要赋值为59
min=59;
}
else if(flag==1){ //flag=1时显示的是日期 此时按下需要对月进行减一
if(mon>1)
mon--;
else //当月小于1时需要赋值为假设的最大12 没有0月嘛!
mon=12;
}
else if(flag==2){ //flag=2时显示的是闹钟 此时按下需要对闹钟的分进行减一
if(B_min>0)
B_min--;
else
B_min=59;
}
else if(flag==3){ //flag=3时显示的是秒表 此时按下需要对秒表重新开始
flag_set=0;
}else{
if(B_fen>0) //flag=4时显示的是倒计时 此时按下需要对倒计时的分进行减一
B_fen--;
else
B_fen=59;
}
}
while(!Key3);
}
}
if(flag_set==3)
{
if(Key3==0) //减按键按下时
{
delay(10);
if(Key3==0)
{ if(flag==0){ //flag=0时显示的是时钟 此时按下需要对时钟的是进行减一
if(hour>0)
hour--;
else //当时小于0时 秒需要赋值为23 24小时制
hour=23;
}
else if(flag==1){ //flag=1时显示的是日期 此时按下需要对年进行减一
year--;
}
else if(flag==2){ //flag=2时显示的是闹钟 此时按下需要对闹钟的时进行减一
if(B_hour>0)
B_hour--;
else
B_hour=23;
}
else if(flag==3){ //flag=时显示的是秒表 此时按下需要对秒表重新开始
flag_set=0;
}else{ //flag=4时显示的是倒计时 此时按下需要对倒计时的是进行减一
if(B_shi>0)
B_shi--;
else
B_shi=23;
}
}
while(!Key3);
}
if(Key2==0) //加按键按下时
{
delay(10);
if(Key2==0)
{ if(flag==0){
hour++; // 小时加一
if(hour==24){ //flag=0的时候显示时钟 按下Key2按键可以对时钟加一
hour=0;
}
}
else if(flag==1){ //flag=1时显示的是日期 此时按下需要对年进行加一
year++;
}
else if(flag==2){ //flag=2时显示的是闹钟 此时按下需要对闹钟的时进行加一
B_hour++;
if(B_hour==24){
B_hour=0;
}
}
else if(flag==3){ //flag=3时显示的是秒表 此时按下需要对秒表清零并停止
shi=0,fen=0,miao=0;
}
else{ //flag=4时显示的是倒计时 此时按下需要对倒计时时秒进行加一
B_shi++;
if(B_shi==24){
B_shi=0;
}
}
}
while(!Key2);
}
}
}
/*
******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
********************************************************************************/
void main()
{
TMOD=0x11;//设定定时气的工作方式
// TH0=0x3c;//设定初始值(高八位)
// TL0=0xb0;//设定T0初始值(低五位) 设定时间为50ms
TH0=(65536-2000)/256; //这是定时100us时高8位的值 2ms 1s=1000ms
TL0=(65536-2000)%256; //这是定时100us时低8位的值。
ET0=1; //内部中断
EA=1; //打开总中断
TR0=1; //启动定时器0
while(1)
{
KEY();
if(flag==1){
datapros(year,mon,day); //数据处理函数
DigDisplay();//数码管显示函数
} else if(flag==2){ //flag=2显示闹钟
datapros(B_hour,B_min,B_sec); //数据处理函数
DigDisplay();//数码管显示函数
}
else if(flag==3){ //flag=3 显示秒表
datapros(shi,fen,miao); //数据处理函数
DigDisplay();//数码管显示函数
}
else if(flag==4){ //flag=4 显示倒计时
datapros(B_shi,B_fen,B_miao); //数据处理函数
DigDisplay();//数码管显示函数
}
else
{
datapros(hour,min,sec); //数据处理函数
DigDisplay();//数码管显示函数 // flag=0 显示时钟
shi=0,fen=0,miao=0; //换回显示时钟需要将秒表值归零
}
}
}
/*
******************************************************************************
* 函 数 名 : time1
* 函数功能 : 定时器中断函数
* 输 入 : 无
* 输 出 : 无
********************************************************************************/
void time1() interrupt 1
{
u16 m;
TH0=(65536-2000)/256; //这是定时100us时高8位的值 2ms 1s=1000ms
TL0=(65536-2000)%256; //这是定时100us时低8位的值。
// TH0=0x3c;
// TL0=0xb0; //重新赋初值
m++;
if(((hour==B_hour)&&(min==B_min))&&(noise==0)&&(sec<9)) //判断是否到达闹钟时间并让闹钟只响十秒
{
Beep=~Beep;//蜂鸣器每隔1s响一次 beep=~beep;
}
else if((B_miao==0)&&(B_fen==0)&&(B_shi==0)){
Beep=~Beep;//蜂鸣器每隔1s响一次 beep=~beep;
}
else
Beep=1;//关闭蜂鸣器
if(m==500)
{ // 每50ms执行20次也就是一秒
m=0;
if(flag_set==0) //正常模式 默认为零 也就是时钟工作 如果这个改变时钟不工作
{ sec++; //每一秒数码管的显示同步数值加一
if(flag==3){ //flag等于3时需要启动秒表
miao++;
if(miao>59) // 工作时秒——>59清零 分加一 分到59之后清零 小时加一
{ //工作状态只会加法运算 不用管他们小于零的情况
miao=0;
fen++;
if(fen>59)
{
fen=0;
shi++;
if(shi>23)
{
shi=0;
}
}
}
}
if(flag==4){ //flag等于4时需要启动倒计时
B_miao--;
if(B_miao==-1) // 工作时秒——>0 分减一 分到0之后小时减一
{
if(B_fen>0){
B_miao=59;
B_fen--;
if((B_fen<0)&&(B_shi>0)){
B_fen=59;
B_shi--;
}
}
else if(B_shi>0){
B_shi--;
B_fen=59;
B_miao=59;
if(B_shi==0){
B_shi=0;
}
}
else {
B_miao=0;
}
}
}
if(sec>59) // 工作时秒——>59清零 分加一 分到59之后清零 小时加一
{ //工作状态只会加法运算 不用管他们小于零的情况
sec=0;
min++;
if(min>59)
{
min=0;
hour++;
if(hour>23)
{
hour=0;
}
}
}
}
}
}
更多推荐
基于单片机的电子万年历设计与实现
发布评论