一、设计题目
基于单片机的数字时钟
二、设计任务
以51单片机、实时时钟芯片DS1307(或DS1302)和液晶显示LCD1602为主体,设计一款简易数字时钟,主要功能如下:
基本功能 | 1. 能够准确显示时间(显示格式为时时:分分:秒秒,12//24小时制切换); 2. 可进行时间调整; 3. 具有闹钟时间设置、闹钟开/关。 |
扩展功能 | 1. 可设置多个闹钟。 2. 其它功能(如音乐闹钟、温度显示灯)。 |
电路设计 | 1. 画出电路框图; 2. 画出整机电路工作原理图。 |
软件设计 | 1. 根据功能和电路编写程序流程图; 2. 编写配套的子程序和主程序,实现任务要求的功能。 |
软硬件调试 | 1. 调试和测试项目完整,有记录、有统计和分析结果; 2. 能根据结果提出改进措施。 |
附件3 软件设计变……………………………………………………………………………………24
附件4 程序模块及参数…………………………………………………………………………....25
绪论
本次课程设计的目的是为我们更好的掌握单片机的应用知识,有助于提高我们的职业素质,同时加深对知识的了解,本次设计也是为我们的毕业设计打基础,加强知识的综合应用。
本设计以ATC51单片机为核心,以实时时钟芯片DS1307和液晶显示LCD1602为主体设计了一款简易数字时钟。该时钟系统主要由单片机最小系统、时钟模块、闹钟模块、液晶显示模块、键盘控制模块组成。系统具有简单清晰的操作界面,能够准确显示时间(显示格式为时时:分分:秒秒,24小时制),可随时进行时间调整,具有闹钟时间设置、闹钟开/关。设计以硬件软件化为指导思想,充分发挥单片机功能,大部分功能通过软件编程来实现,电路简单明了,系统稳定性高。同时,该时钟系统还具有功耗小、成本低的特点,具有很强的实用性。由于系统所用元器件较少,单片机所被占用的I/O口不多,因此系统具有一定的可扩展性。软件程序均采用C语言编写,便于移植与升级。报告详细介绍了整个系统的硬件组成结构、工作原理和系统的软件程序设计。
通过一个阶段的对知识点的了解更加明确,同时也了解了做项目不是件容易的事,需要多方面的知识,还需要大量的查阅资料,再把自己的所学知识综合应用,这样才有可能做出项目来。
第1章 设计方案
1.1 方案论证与比较
方案一:基于FPGA的系统总体设计方法。为了实现:(1)显示年、月、日、时、分、秒、星期,并且可以进行调整时间;(2)可以设定闹钟和整点报时的功能,数字时钟在总体上主要分为三大部分:输入人机界面部分、FPGA核心功能部分和输出界面部分,其系统设计框图如图1-1所示。
图1-1 基于FPGA的系统设计方框图
方案二:采用单片机技术来实现数字钟的功能。系统以ATC51单片机为核心控制器件,它除了具备微机CPU的数值计算功能外,还具有灵活强大的控制功能,以便实时检测系统的输入量、控制系统的输出量,实现自动控制。与传统机械表相比,它具有走时精确,显示直观等特点。它的计时周期为24小时,显满刻度为“23时59分59秒”,另外具有校时功能,断电后有记忆功能,恢复供电时可实现计时同步等特点。外围主要有串行通信器件实时时钟芯片DS1307等,使得系统线路简单可靠性高。系统结构框图如图1-2所示。
图1-2 基于单片机实现的数字时钟系统结构框图
单片机最小系统:其作用是和外围的时钟芯片通信,并控制数据传输的过程,采集时间信息并予以处理。
键盘模块:键盘模块可以设置时间信息,通过单片机写入时钟芯片,以更新时间;也可以设置闹钟,由单片机存入其内部RAM中。
时钟模块:此模块由专用的实时时钟芯片构成,由它提供实时的日历时钟信息。
液晶显示模块:单片机读取时钟芯片DS1307中的信息,通过液晶显示器实时显示。采用LCD作为显示器,具有界面友好、功耗低的优点。
闹钟模块:单片机主控模块读取日历芯片中的时间信息,与所设置的闹钟时间相比较,若相同时,闹钟模块工作闹钟模块。
电源模块:用220V市电经整流、滤波、稳压后,输出稳定的+5V的直直流电为其供电。
1.2 方案确定
综合考虑以上两种方案的优缺点以及题目的基本要求和发挥要求,在本设计中,我采了第二种方案,即采用单片机来实现数字时钟的功能。
第2章 硬件设计
2.1 单片机最小系统
单片机最小系统以ATC51单片机为核心,由单片机、时钟电路、复位电路等组成如图2-1所示。主要负责各个模块的初始化工作;读取并处理时间;处理按键响应;控制液晶实时显示等。
图2-1 单片机最小系统
主控制器ATC51单片机与MCS51系列单片机产品兼容,内部自带有4KB的Flash存储器及256KB RAM单元,不需另外扩展EEPROM及静态RAM,可以在线下载程序,易于日后的升级。
图中,P2.0、P2.1、P2.2及P1口为单片机与液晶显示器连接的控制和通信的数据端口;P2.6和P2.7为单片机与时钟芯片DS1307通信的端口;P2.3为闹钟的控制端口;P0.0、P0.1、P0.2、P0.3为按键模块的接口。
时钟电路是由XTAL1和XTAL2之间跨接的晶体振荡器和微调电容构成。时钟电路中晶体振荡器的频率高则系统的时钟频率就高,所以该系统采用12M晶振;
复位电路有两种形式:手动按键复位和上电复位,在本系统中采用的是手动按键复位。如图2-1所示,R1、R2、C3和SW组成系统手动按键复位电路。
2.2 时钟模块
系统采用DS1307时钟芯片。DS1307是美国DALLAS公司推出的一种高性能、低功耗的时钟芯片,它是一款I2C总线接口的时钟芯片,采用两线与CPU进行通信,片内含有8个特殊功能寄存器和56bit的SRAM。
DS1307的主要技术指标:具有秒、分、时、日、星期、月、年的计数功能;12小时制和24小时制两种计数模式;可自动调整每月的天数,具有闰年自动修正、掉电保护和上电复位功能。
2.2.1 DS1307的引脚功能
DS1307采用8引脚双列直插DIP封装形式,引脚分布如图2-2所示。相应功能如下:
Vcc:主电源;
Vbat:备用电源。当Vbat>Vcc+0.2V时,由Vcc向DS1307供电,当Vbat SCL:I2C总线时钟线; SDA:I2C总线数据线; SQW/OUT:方波/输出驱动器。 图2-2 DS1307引脚分布图 2.2.2 DS1307的内部结构 1.内部结构 DS1307芯片由晶体振荡器、电源控制器、I2C总线控制、分频处理、逻辑控制、RAM存储、多路选择器、时钟/日历寄存器、缓冲器组成,内部结构如图2-3所示。 1Hz 图2-3 DS1307的内部结构 2寄存器 DS1307有关日历、时间的寄存器共有12个,其中有7个寄存器(读时81H~8DH,写时80H~8CH),存放的数据格式为BCD码形式,如表2-1所示。 表2-1 DS1307的日历、时间寄存器 PM:00-23 56×8 (2)秒寄存器(81H、80H)的位7定义为时钟暂停标志(CH)。当该位置为1时,时钟振荡器停止,DS1307处于低功耗状态;当该位置为0时,时钟开始运行。 (3)控制寄存器(8FH、8EH)的位7是写保护位(WP),其它7位均置为0。在任何的对时钟和RAM的写操作之前,WP位必须为0。当WP位为1时,写保护位防止对任一寄存器的写操作。 2.2.3 DS1307的读写操作 DS1307是基于I2C总线接口的时钟芯片,软件上与I2C总线完全兼容。 1.I2C总线的驱动 I2C总线在传送数据时,必须确认传送数据的开始和结束。而且每传送一个字节,要发送一个应答位(0);在一个周期发送结束后,要发送一个应答位(1)。具体如图2-4所示,三种信号的格式如下: (1)启动信号:当时钟总线SCL为高电平时,数据线SDA由高电平跳变为低电平定义为“启动”信号。 (2)停止信号:当时钟总线SCL为高电平时,数据线SDA由低电平跳变为高电平定义为“结束”信号。 (3)应答位:当主器件发送完一字节的数据后,后面必须跟一个应答位(ACK)。在时钟高电平期间,如果数据线SDA为低电平代表一个字节的传送结束,并准备下一个要传送的字节;在时钟高电平期间,如果数据线SDA为低电平代表一个传送周期结束,准备下一个传送周期。 图2-4 I2C总线的数据传送格式 2.DS1307的写控制 图2-5是DS1307的写控制格式,首先发送启动信号,然后发送的第一个字节是用来控制芯片的地址以及读写控制位(D0:0–写),之后是应答位,然后发送其它字节数据,在最后发送一个结束标志的应答位,紧跟着是停止信号。 XXXX XXXX XXXX XXXX 3.DS1307的读控制 图2-6是DS1307的读控制格式,首先发送启动信号,然后发送的第一个字节是用来控制芯片的地址以及读写控制为(D0:1–读),之后是应答位,然后发送其它字节数据,在最后发送一个结束标志的应答位,紧跟着是停止信号。 XXXX XXXX XXXX XXXX 2.2.4 DS1307硬件电路设计 DS1307采用与CPU进行通信,电路连接简单。DS1307的内部振荡电路结构如图2-7所示,在芯片内部连接有两个电容,目的是为了使晶振起振,所以在电路设计 中就不需要另外再加电容了。 图2-7 DS1307的内部振荡电路 时钟模块电路如图2-8所示,其中晶振采用的是32.768kHz,经内部电路分频后可获得一个标准的秒脉冲信号;电阻R3、R4是I2C总线的上拉电阻。 图2-8 DS1307的电路连接 2.3 闹铃模块 系统采用蜂鸣器作为闹铃输出,电路连接如图2-9所示。电路中采用PNP管Q1来控制蜂鸣器的开关,由图可以看出:当P2.3引脚为高电平时,PNP管截止,蜂鸣器不工作;当P2.3引脚为低电平时,PNP管导通,蜂鸣器工作。其中R9为限流电阻。 图2-9 闹铃电路 2.4 键盘模块 键盘模块设置了四个按键:KEY1、KEY2、KEY3、KEY4。其中KEY1为模式切换键,KEY2为设定值上升键,用KEY3为设定值减小键,KEY4是返回键。 电路连接如图2-10所示。4个上拉电阻可以保证在没有按键输入时,进入单片机四个I/O口的按键状态均为高电平,防止干扰产生;当有按键按下时,相应的端口线状态转为低电平。 图2-10 键盘电路 2.5.1 LCD1602的引脚功能 LCD1602模块的引脚如图2-11所示。 图2-11 LCD1602模块 其引脚功能如下: RS:数据和指令选择控制端,RS=0命令状态;RS=1数据; R/W:读写控制线,R/W=0写操作;R/W=1读操作; A:背光控制正电源; K:背光控制地; E:数据读写操作控制位,E线向LCD模块发送一个脉冲,LCD模块与单片机间将进行一次数据交换; DB0~DB7:数据线,可用8位连接,也可只用高4位连接,节约单片机资源; VDD:电源端; VEE:亮度控制端(1-5V); VSS:接地端。 2.5.2 LCD1602的显示操作 1.四种基本操作 LCD有四种基本操作,具体如表2-2所示。 表2-2 LCD与单片机之间有四种基本操作 执行读状态字操作,如表2-2所示须满足RS=0、R/W=1。根据管脚功能,当为有效电平时,状态命令字可从LCD模块传输到数据总线。同时可以保持一段时间,从而实现读状态字的功能。如结束 图2-12所示为读入状态字流程图。 图2-12 读入状态字流程图 (2)写命令操作 由表2-2可知当RS=0,R/W=0时,才可以通过单片机或用户指令把数据即命令,写到LCD模块,此时就对LCD进行调制。可采用查询方式:先读入状态字,再判断忙标志,最后写命令字。 1)命令字 表2-3所示为命令字,其主要介绍了指令名称、控制信号及控制代码。其指令名称是指要实现的功能,控制代号是采用的十六进制的数值表示的。 a.清零操作是指输入某命令字后即能将整个屏幕显示的内容全部清除; b.归home位:将光标送到初始位;其中的*号为任意,高低电平均可; c.输入方式:设光标移动方向并指定整体显示,是否移动。I/D=0:减量方式,S=1:移位,S=0:不移位; d.显示状态:D指设置整体显示开关;C指设置光标显示开关;B指设置光标的字符闪耀; e.光标画面滚动:R/L指右移或左移;S/C指移动总体或光标; f.功能设置:DL接口数位,L指显示行数,F显示字型; g.CGRAM地址设制:相当于一个数据库,可以在其中选择所需要的符号; h.DDRAM地址设制:显示定位; i.读BF和AC:B为最高位忙的标志,F为标志位; j.写数据:将数据按要求写入到对应的单元; k.读数据:读相应单元内的数据。 表2-3 命令字 返回 图2-13 写命令字流程图 3)定义光标位置 把显示数据要某个位置,就是把显示数据写在相应的DDRAM地址中,DDRAM地址占7位。Set DDRAM address命令如表2-3所示。光标定位,写入一个显示字符后,DDRAM地址会自动加1或减1,加或减由输入方式设置。 表2-4 SetDDRAMaddress命令 表2-5 DDRAM地址 从通电开始通过延时,先经过判忙后再进行功能设置,过一段时间后可以设制显示状态(如设置行、位或阵列)再经过延时后清屏后再可以设置输入方式,具体如图2-14所示。 返回 图2-14 LCD初始化流程图 2.LCD显示程序设计 LCD显示程序的设计一般先要确定LCD的初始化、光标定位、确定显示字符后,LCD就可以按如图2-15显示。 返回 图2-15 LCD显示程序流程图 第3章 软件设计 系统的软件设计可以分为几个部分,首先编写各个模块的底层驱动程序,而后是系统联机调试,编写上层系统程序。本系统软件程序主要包括:液晶LCD1602的底层驱动模块、时钟芯片DS1307的底层驱动模块、键盘扫描及键值处理模块、闹钟模块等。 3.1 系统流程图 系统的软件主流程图如图3-1所示。 Y 图3-1 主程序流程图 3.2 键盘处理模块流程图 系统设制了四个按键,分别是功能设置、定值上升、定值下降和返回键。整个系统的软件设计均采用C语言开发,采用查询的方式对按键状态进行扫描,确保系统的实时性。因此这些器件的底层程序均可以移植到其它系统中,这就是采用C语言开发的最大的优点。键盘处理流程图分别如图3-2、3-3、3-4、3-5所示。 设置分状态:+、-不变 图3-2 时间设置模块 设置月态:+、-不变 图3-3日期设置流程图 设置星期状态:+、-不变 图3-4 星期设置流程图 N A\ Y Y Y Y Y N N N N N N N N N N N N N N N N N N N Y Y Y 图3-5 闹钟设置流程图 第4章 系统测试 本设计的核心采用是ATC51单片机,以时钟芯片DS1307和液晶LCD1602作为外围元件,构成了一个多功能的数字时钟系统。 在设计中出现了很多问题,有的问题解决了,而有的问题不能解决。 在设计中遇到了按键不能正常工作,有的案件不灵活,有时候不能实现调时间,经过多次试验,在不同的计算机上验证,最后证实是因为软件的版本问题,和设置问题,通过与同学的讨论找到了原因所在,并修改。 还有就是软件错误,这是个大麻烦,比较难找出错误,不过在大家的努力下最终把错误改正,使其正常工作。 这个设计的不足是不能实现12/24小时的切换,由于程序太过复杂,要修改时相当的困难,所以没有实现这一功能。 总结 本设计以功能齐全适用于大众为指导思想,选取ATC51单片机为控制核心。通过外围元件实时时钟芯片DS1307构成了一个具有实时时间与日历显示、按键调时、闹铃定时功能的数字时钟。系统采用液晶LCD1602作为显示器,软件程序采用均采用C语言编写,便于移植与升级。 系统经组装、调试后,可以稳定运行。同时可以对时间、日期、星期和闹钟进行设置。系统采用串行器件具有线路简单、体积小、价格低等优点。报告详细介绍了整个系统的硬件组成结构、工作原理和系统的软件程序设计。 在本次设计的全过程中,我对二年所学的知识有了一个比较系统的认识和理解。涉及了各方面的知识,大大扩展了我的知识面,同时使我学会了如何用所学的知识去解决一些实际的问题。 在设计中我深知自己掌握的知识还远远不够,掌握的一些理论知识应用到实践中去,总会出现这样或那样的问题,不是理论没有掌握好,而是光知道书本上的知识是远远不够的,一定要把理论知识和实践结合起来。把学到的知识应用到实践中去,多做多练,才可以把理论的精华发挥出来。知识不是知道、了解就好,一定要去应用它,发展它,让它在现实生活中得到充分的应用,从而解决一些问题,这才是学习的根本目的。在设计阶段,通过对课题的深入分析与研究,使我对技术有了一定的了解。在遇到问题时,得到了指导老师与同学的悉心帮助,使我感受到集体的力量是无穷的。 通过这次设计,我学会了和别人配合工作,因为一个人所学的知识不可能面面俱到的, 只有通过合作,发挥自己的优点,体现团队精神,才能使工作做得更为出色。通过这 次设计,我学到了许多书本上学不到的知识,增强了自己的动手能力。即将毕业的我十分珍惜这次锻炼的机会,我按部就班的完成了自己的设计任务,但由于自己的知识水平有限,仍然存在很多的不足之处,恳请老师多多指教!当今的社会是竞争的社会,而人才的竞争则是竞争的焦点,课程设计对于我们即将离校的同学来说,是离校前很好的一次锻炼,使我们各方面的能力都有了很大的提高,为我们踏出校门,走上社会增强了能力与自信。 参考文献 [1] 白驹衍, 《单片计算机及应用》北京:电子工业出版社, 1999.2 [2]李朝青.单片机原理及接口技术(修订版).北京:北京航空航天大学出版社,1998 [3] 李广弟.单片机基础.北京:北京航空航天大学出版社,1992 [4] 何立民.单片机应用技术大全.北京:北京航空航天大学出版社, 1994 [5]张毅刚. 单片机原理及接口技术.哈尔滨:哈尔滨工业大学出版社,1990 [6] 谭浩强.单片机课程设计. 北京:清华大学出版社,19 [7] 谢自美,《电子线路设计、实验、测试 》武汉:华中理工大学出版2000 [8] 何书森、何华斌《实用数字电路原理与设计速成》福州:福建科学技术出版社,2000.6 附录1 系统仿真电路图 附录2 主要源程序 /***************************定义头文件开始***************************/ #include #include #include #include /****************************义头文件结束****************************/ /*************************第一部分:预定义变量************************/ /*************************0.LCD1602模块开始**************************/ /***************************0.1 输入方式控制*************************/ #define LCD_AC_AUTO_INCREMENT 0x06 //数据读、写操作后,AC自动增一,不移位 #define LCD_AC_AUTO_DECREASE 0x04 //数据读、写操作后,AC自动减一,不移位 #define LCD_MOVE_ENABLE 0x05 //数据读、写操作,画面平移 #define LCD_MOVE_DISENABLE 0x04 //数据读、写操作,画面不动 /***************************0.2 光标画面归位*************************/ #define LCD_GO_HOME 0x02 //AC=0,光标、画面回HOME位 /***************************0.3 显示状态设置*************************/ #define LCD_DISPLAY_ON 0x0C //显示开:整体显示,光标显示关,光标位的字符无闪耀 #define LCD_DISPLAY_OFF 0x08 //显示关 #define LCD_CURSOR_ON 0x0A //光标显示 #define LCD_CURSOR_OFF 0x08 //光标不显示 #define LCD_CURSOR_BLINK_ON 0x09 //光标闪烁 #define LCD_CURSOR_BLINK_OFF 0x08 //光标不闪烁 /***************************0.4 光标画面滚动*************************/ //光标、画面移动,不影响DDRAM #define LCD_LEFT_MOVE 0x18 //LCD显示左移一位 #define LCD_RIGHT_MOVE 0x1C //LCD显示右移一位 #define LCD_CURSOR_LEFT_MOVE 0x10 //光标左移一位 #define LCD_CURSOR_RIGHT_MOVE 0x14 //光标右移一位 /****************************0.5 功能设置****************************/ #define LCD_DISPLAY_DOUBLE_LINE 0x38 //两行显示 #define LCD_DISPLAY_SINGLE_LINE 0x30 //单行显示 /******************************0.6 清屏******************************/ #define LCD_CLEAR_SCREEN 0X01 //清屏 /***********************0.7 LCD1602地址相关**************************/ #define LINE1_HEAD 0x80 // 第一行DDRAM起始地址 #define LINE2_HEAD 0xc0 // 第二行DDRAM起始地址 #define LINE_LENGTH 16 //每行的最大字符长度 /********************0.8 LCD1602接线引脚定义*************************/ #define LCDIO P1 //定义P2口与LCD1602的数据口相接 sbit LCD_RS=P2^0; sbit LCD_RW=P2^1; sbit LCD_EN=P2^2; sbit LCD_BUSY=LCDIO^7; /*******************0.9函数申明及相关定义****************************/ #define uchar unsigned char #define uint unsigned int void delay_ms(uint m,uint n); /*******************0.10 显示字符串编码定义**************************/ unsigned char code str[]={0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x03a,0}; /*************************0.LCD1602模块结束**************************/ /**************************1.DS1307模块开始**************************/ /*****************************1.1 引脚定义***************************/ sbit SCLK=P2^6; //DS1307串行时钟信号输入端口 pin 6 sbit SDA=P2^7 ; //DS1307串行数据输入端口 pin 5 /*****************************1.2 变量定义***************************/ code unsigned char rtc_address[7]={0x00,0x01,0x02,0x03,0x04,0x05,0x06}; unsigned char read_rtc_code[7]={0,0,0,0,0,0,0}; /**************************1.DS1307模块结束**************************/ /**************************3.闹钟响应模块开始************************/ sbit Alarm_On=P2^3; unsigned int Alarm_On_Flag=0; /**************************3.闹钟响应模块结束************************/ /**************************4.时钟设置模块开始************************/ unsigned int DD=90000000; //unsigned char Time_Init[]={0,0,12,2,4,12,7}; //设置时间时的初始值:2007-12-4,星期二,12:00:00 unsigned char Set_Alarm_Init[]={0,0,0}; //闹钟初始时间 /*************************第一部分:预定义变量************************/ /****************************函数声明*******************************/ unsigned int Key_Scan(void); //按键扫描程序 void Set_Time_Second(unsigned int Key_On_Number_Flag); //设置时间秒函数 void Set_Time_Minute(unsigned int Key_On_Number_Flag); //设置时间分函数 void Set_Time_Hour(unsigned int Key_On_Number_Flag); //设置时间时函数 void Set_Time(void); //设置时间函数 void Set_Time_Date(unsigned int x); //设置日期 void Set_Date_Day(unsigned int Key_On_Number_Flag,unsigned int Month); //设置日期日函数 void Set_Date_Month(unsigned int Key_On_Number_Flag); //设置日期月函数 void Set_Date_Year(unsigned int Key_On_Number_Flag); //设置日期年函数 void Set_Date(void); //设置时间函数 void Set_Week(unsigned int x); //设置星期函数 void Set_Alarm_Second(unsigned int Key_On_Number_Flag); //设置闹钟秒函数 void Set_Alarm_Minute(unsigned int Key_On_Number_Flag); //设置闹钟分函数 void Set_Alarm_Hour(unsigned int Key_On_Number_Flag); //设置闹钟时函数 void Set_Alarm(void); //设置闹钟函数 void Set_Time_Display(unsigned int x,unsigned int y); //设置时间时的显示时间函数 void Alarm_Ring(void); //闹钟铃声函数 unsigned int Alarm_Compare(void); //闹钟时间比较函数 void Alarm_Ack(void); //闹钟响应函数 /****************************函数声明*******************************/ /**********************第二部分:各模块底层驱动程序*******************/ /*************************1:LCD1602显示模块开始**********************/ /*********************1.1 检测LCD忙闲状态函数:开始*******************/ void LCD_check_busy(void) { do{ LCD_EN=0; //使能端无效 LCD_RS=0; LCD_RW=1; //读命令字状态,与LCD_RS配合使用,LCD_RS=0、LCD_RW=1时读命令 LCDIO=0xff; //设置LCDIO(单片机与LCD的数据接口)为输入状态 LCD_EN=1; //使能端有效,读LCD状态入LCDIO }while(LCD_BUSY==1); //当(LCDIO^7)=1,表示LCD为忙状态,可延时一段时间后再读 LCD_EN=0; //LCD空闲时停止读状态,将使能端清0,置无效状态 } /*********************检测LCD忙闲状态函数:结束*********************/ /**************1.2 单片机向LCD1602发送控制命令函数:开始***********/ /*********参数说明:command为单片机向LCD1602发送的控制命令*********/ void LCD_send_command(uchar command) { LCD_check_busy(); //检查LCD忙闲状态 LCD_RS=0; LCD_RW=0; //写命令字状态,与LCD_RS配合使用,二者同为低电平时写命令 LCD_EN=1; //使能端有效,单片机向LCD发送控制命令 LCDIO=command; LCD_EN=0; //写命令结束,恢复状态 } /****************单片机向LCD1602发送控制命令函数:结束*************/ /***************1.3 单片机向LCD1602发送数据函数:开始**************/ /*******************参数说明:dat为待发送字符数据******************/ void LCD_send_data(uchar dat) { LCD_check_busy(); //检查LCD忙闲状态 LCD_RS=1; LCD_RW=0; //写数据状态,与LCD_RS配合使用,LCD_RS=1、LCD_RW=0时写数据 LCD_EN=1; LCDIO=dat; //使能端有效,单片机向LCD发送数据,送LCDIO LCD_EN=0; //发送数据结束,恢复状态 } /*****************单片机向LCD1602发送数据函数:结束****************/ /******************1.4 单片机读LCD1602数据函数:开始***************/ unsigned char LCD_read_data(void) { unsigned char dat; LCD_check_busy(); //检查LCD忙闲状态 LCD_RS=1; LCD_RW=1; //读数据状态,与LCD_RS配合使用,LCD_RS=1、LCD_RW=1时读数据 LCD_EN=1; dat=LCDIO; //使能端有效,单片机读LCD数据,送dat LCD_EN=0; //读数据结束,恢复状态 return(dat); } /********************单片机读LCD1602数据函数:结束*****************/ /**********************1.5 LCD1602初始化:开始*********************/ void LCD_init(void) { LCD_send_command(LCD_DISPLAY_DOUBLE_LINE); //功能设置 delay_ms(2,300); //延时37us LCD_send_command(LCD_DISPLAY_ON); //显示状态设置 delay_ms(1,300); //延时37us LCD_send_command(LCD_CLEAR_SCREEN); //清屏 delay_ms(1,2000); //延时1.5ms LCD_send_command(LCD_AC_AUTO_INCREMENT); //输入方式设置 delay_ms(1,300); } /*************************LCD1602初始化:结束**********************/ /*********************1.6 LCD1602显示字符串:开始******************/ /*******参数说明:x为列数,y为行数,Data为待显示的字符串指针*******/ void LCD_disp_string(uchar x,uchar y,uchar *Data) { if(y==0) //显示第一行字符串 { if(x LCD_send_command(LINE1_HEAD+x); //DDRAM首行起始地址加x for(;x LCD_send_data(*(Data++)); //连续发送数据 } if(*Data!='\\0') //第一行显示完,若字符串未显示完,则转至第二行显示 { x=0; //列地址归home位 y=1; //转至第二行显示 } } } if(y==1) //显示第二行字符串 { LCD_send_command(LINE2_HEAD+x); //DDRAM第二行起始地址加x for(;x LCD_send_data(*(Data++)); //连续发送数据 } } } /***********************LCD1602显示字符串:结束********************/ /**********************1.7 LCD1602显示字符:开始*******************/ /**********参数说明:x为列数,y为行数,Data为待显示的字符********** void LCD_disp_string1(uchar x,uchar y,uchar Data) { if(y==0&&x LCD_send_command(LINE1_HEAD+x); //DDRAM首行起始地址加x LCD_send_data(Data); //连续发送数据 } if(y==1&&x LCD_send_command(LINE2_HEAD+x); //DDRAM第二行起始地址加x LCD_send_data(Data); //连续发送数据 } } ************************LCD1602显示字符:结束*********************/ /************************1.8 设置显示地址开始**********************/ void LCD_set_add(unsigned char row,unsigned char col) { if(row==0&&col else if(row==1&&col } /************************1.8 设置显示地址结束**********************/ /*************************1.9 延时函数:开始***********************/ /***************参数说明:改变m,n的值可改变延时时间长短************/ void delay_ms(uint m,uint n) { uint i,j; for(i=0;i } /****************************延时函数:结束************************/ /************************1:LCD1602显示模块结束*********************/ /***************************2:I2C总线模块开始**********************/ /***********************2.1 I2C延时程序开始************************/ void I2C_delay(void) //I2C延时程序 { _nop_(); //空操作指令 _nop_(); _nop_(); _nop_(); } /***********************2.1 I2C延时程序结束************************/ /***********************2.2 I2C启动程序开始************************/ void I2C_start(void) //I2C启动函数 { SDA=1; //数据为1 _nop_(); //保持 SCLK=1; //设置时钟为高电平 _nop_(); //保持 SDA=0; //时钟为高电平时,让数据SDA由1→0,启动I2C总线 _nop_(); //保持 SCLK=0; //设置时钟为低电平 _nop_(); //保持 } /***********************2.2 I2C启动程序结束************************/ /***********************2.3 I2C停止程序开始************************/ void I2C_stop(void) //I2C停止函数 { SDA=0; //数据为0 _nop_(); SCLK=1; //设置时钟为高电平 _nop_(); SDA=1; //时钟为高电平时,让数据SDA由0→,停止I2C总线 _nop_(); SCLK=0; _nop_(); } /***********************2.3 I2C停止程序结束************************/ /***********************2.4 I2C应答程序开始************************/ void I2C_send_ack(bit k) //I2C总线应答函数 { SDA=k; //数据为0或1,若为0,表示接下来发送下一字节数据;若为1,表示发送下一周期数据 I2C_delay(); //延时,准备数据 SCLK=1; //设置时钟为高电平,发送响应信号 I2C_delay(); SCLK=0; //设置时钟为低电平 } /***********************2.4 I2C应答程序结束************************/ /*****************2.4 单片机向I2C发送数据dat程序开始***************/ void I2C_write_byte(unsigned char dat) //单片机向I2C总线写数据dat { unsigned char i; //设置每次写的二进制数的各位:8个,即每次发送一个字节数据 for (i=0;i<8;i++) { SCLK=0; //设置时钟为低电平 I2C_delay(); SDA=(bit)(dat&0x80); //将dat与二进制数1000 0000B相与,取最首位送SDA(数据线) dat<<=1; //数据dat左移一位,以便取下一位数 I2C_delay(); SCLK=1; //设置始终为高电平,发送数据SDA I2C_delay(); } SCLK=0; //设置时钟为低电平 } /*****************2.4 单片机向I2C发送数据dat程序结束***************/ /*******************2.5 单片机读I2C数据dat程序开始*****************/ unsigned char I2C_read_byte(void) //CPU读I2C总线数据 { unsigned char i,dat; dat=0; SDA=1; for (i=0;i<8;i++) { SCLK=0; //首先设置时钟位低电平,准备读数据SDA dat=dat<<1; //dat左移1位 SCLK=1; //设置时钟为高电平,取数据 I2C_delay(); if(SDA==1) //如果SDA=1,则dat加1 { dat++; } SCLK=0; } return (dat); } /*******************2.5 单片机读I2C数据dat程序结束*****************/ /***************************2:I2C总线模块结束**********************/ /***************************3:DS1307模块开始***********************/ /****************3.1 单片机向DS1307发送数据程序开始****************/ unsigned char Write1307(unsigned char add,dat)//add为目标地址,dat为待发送数据 { unsigned char temp; //定义变量temp temp=dat/10; //将待输出的十进制数字转换为DS1307内部可识别和存放的BCD码 temp<<=4; //比如10应转换为00010000即10H存放,执行结果依次为: temp=dat%10+temp; //temp=1;temp=00010000B;temp=10%10+00010000B=00010000B=10H I2C_start(); I2C_write_byte(0xD0); //CPU向I2C总线发写命令,设置为写模式 I2C_send_ack(0); //发应答信号,等待发下一个数据 I2C_write_byte(add); //CPU向I2C总线发送地址信息 I2C_send_ack(0); //发应答信号,等待发下一个数据 I2C_write_byte(temp); //CPU向I2C总线发数据 I2C_send_ack(1); //发送完毕,等待下一周期 I2C_stop(); return (0); } /****************3.1 单片机向DS1307发送数据程序结束****************/ /****************3.2 单片机读DS1307发送数据程序开始****************/ unsigned char Read1307(unsigned char add) //add为源地址,返回值为dat { unsigned char temp,dat; I2C_start(); I2C_write_byte(0xD0); I2C_send_ack(0); I2C_write_byte(add); I2C_send_ack(1); I2C_stop(); I2C_start(); I2C_write_byte(0xD1); I2C_send_ack(0); dat=I2C_read_byte(); I2C_send_ack(1); I2C_stop(); temp=dat/16; dat=dat%16; dat=dat+temp*10; return (dat); } /****************3.2 单片机读DS1307发送数据程序结束****************/ /******************3.3 单片机读DS1307寄存器程序开始****************/ void Read_RTC(void) { unsigned char i,*p; p=rtc_address; for(i=0;i<7;i++) { read_rtc_code[i]=Read1307(*p); p++; } } /******************3.3 单片机读DS1307寄存器程序结束****************/ /***********************3.4 DS1307初始化程序开始*******************/ void Set_RTC(void) { unsigned char i,*p; p=rtc_address; for(i=0;i<7;i++) { Write1307(*p,read_rtc_code[i]); p++; } } /***********************3.4 DS1307初始化程序结束*******************/ /***************************3:DS1307模块结束***********************/ /***************************4: 显示模块开始************************/ /***********************4.1 选择星期程序开始***********************/ void chose(void) { unsigned char x; x=read_rtc_code[3]-2; if(x>6) x=x-6; else if(x<0) x=x+6; switch (x) { case 0: LCD_disp_string(0,0,"Mon."); break; case 1: LCD_disp_string(0,0,"Tue."); break; case 2: LCD_disp_string(0,0,"Wed."); break; case 3: LCD_disp_string(0,0,"Thu."); break; case 4: LCD_disp_string(0,0,"Fri."); break; case 5: LCD_disp_string(0,0,"Sat."); break; case 6: LCD_disp_string(0,0,"Sun."); break; } } void chose_set(void) { unsigned char x; x=read_rtc_code[3]-2; if(x>6) x=x-6; else if(x<0) x=x+6; switch (x) { case 0: LCD_disp_string(0,1,"Mon."); break; case 1: LCD_disp_string(0,1,"Tue."); break; case 2: LCD_disp_string(0,1,"Wed."); break; case 3: LCD_disp_string(0,1,"Thu."); break; case 4: LCD_disp_string(0,1,"Fri."); break; case 5: LCD_disp_string(0,1,"Sat."); break; case 6: LCD_disp_string(0,1,"Sun."); break; } } /***********************4.1 选择星期程序结束***********************/ /***************************4.2 显示程序开始***********************/ void Display(void) { LCD_set_add(1,8); //设置显示的初始位置 LCD_send_data((read_rtc_code[6]/10)|0x30); //显示年 LCD_send_data((read_rtc_code[6]%10)|0x30); LCD_send_data('-'); LCD_send_data((read_rtc_code[5]/10)|0x30); //显示月 LCD_send_data((read_rtc_code[5]%10)|0x30); LCD_send_data('-'); LCD_send_data((read_rtc_code[4]/10)|0x30); //显示日 LCD_send_data((read_rtc_code[4]%10)|0x30); LCD_set_add(0,8); LCD_send_data((read_rtc_code[2]/10)|0x30); //显示时 LCD_send_data((read_rtc_code[2]%10)|0x30); LCD_send_data(':'); LCD_send_data((read_rtc_code[1]/10)|0x30); //显示分 LCD_send_data((read_rtc_code[1]%10)|0x30); LCD_send_data(':'); LCD_send_data((read_rtc_code[0]/10)|0x30); //显示秒 LCD_send_data((read_rtc_code[0]%10)|0x30); chose(); //显示星期 } /***************************4.2 显示程序结束***********************/ /***************************4: 显示模块结束************************/ /****************************5 键盘模块开始***********************/ /*************************按键扫描函数******************************/ unsigned int Key_Scan(void) { unsigned int Key_On_State=0; //按键状态标记 unsigned int Key_On_Number=0; //按键号 P0=0x0f; //设置P0低4位为输入状态 Key_On_State=P0&0x0f; //读P0口低4位/按键状态 if(Key_On_State!=0x0f) //如果有按键按下 { Key_On_State=P0&0x0f; delay_ms(400,40); if(Key_On_State==0x0e) Key_On_Number=1; else if(Key_On_State==0x0d) Key_On_Number=2; else if(Key_On_State==0x0b) Key_On_Number=3; else if(Key_On_State==0x07) Key_On_Number=4; else Key_On_Number=0; } while(Key_On_State!=0x0f) { delay_ms(100,40); //Key_On_State=P0&0x0f; Key_On_State=P0; } return Key_On_Number; } /*************************按键扫描函数******************************/ /**************************设置秒函数*******************************/ void Set_Time_Second(unsigned int Key_On_Number_Flag) { unsigned int Key_On_Number_Flag1; unsigned int y=DD; L1: switch(Key_On_Number_Flag) //判断秒是加1还是减1 { case 0: break; case 1: read_rtc_code[0]++; //秒加1 if(read_rtc_code[0]>60) read_rtc_code[0]=read_rtc_code[0]-60; //如果加到60以上,则减60 break; case 2: read_rtc_code[0]--; //秒减1 if(read_rtc_code[0]<0) read_rtc_code[0]=read_rtc_code[0]+60; //如果减到0以下,则加60 break; } while(y--) { Set_Time_Display(0,3); //显示刚设置更新的时间 Key_On_Number_Flag1=Key_Scan(); //扫描按键状态,求键值 switch(Key_On_Number_Flag1) { case 0: break; //无按键按下,则跳出循环,计数值减1,继续扫描 case 4: case 1: LCD_send_command(LCD_CLEAR_SCREEN); Set_Time(); return; //按键4和按键1按下,返回 case 2: Key_On_Number_Flag=1; //按键2和按键3按下,设置时间秒 y=DD; goto L1; case 3: Key_On_Number_Flag=2; y=DD; goto L1; } } } void Set_Time_Minute(unsigned int Key_On_Number_Flag) { unsigned int Key_On_Number_Flag1; unsigned int y=DD; L2: switch(Key_On_Number_Flag) //判断秒是加1还是减1 { case 0: break; case 1: ++read_rtc_code[1]; //秒加1 if(read_rtc_code[1]>59) read_rtc_code[1]=read_rtc_code[1]-60; //如果加到60以上,则减60 break; case 2: --read_rtc_code[1]; //秒减1 if(read_rtc_code[1]<0) read_rtc_code[1]=read_rtc_code[1]+60; //如果减到0以下,则加60 break; } while(y--) { Set_Time_Display(0,2); //显示刚设置更新的时间 Key_On_Number_Flag1=Key_Scan(); //扫描按键状态,求键值 switch(Key_On_Number_Flag1) { case 0: break; //无按键按下,则跳出循环,计数值减1,继续扫描 case 4: LCD_send_command(LCD_CLEAR_SCREEN); return; //按键4按下,返回 case 1: Set_Time_Second(0); //按键1按下,调用设置秒函数 y=DD; break; case 2: Key_On_Number_Flag=1;//按键2和按键3按下,设置时间分 y=DD; goto L2; case 3: Key_On_Number_Flag=2; y=DD; goto L2; } } } /**************************设置分函数*******************************/ /*************************设置小时函数******************************/ void Set_Time_Hour(unsigned int Key_On_Number_Flag) { unsigned int Key_On_Number_Flag1; unsigned int y=DD; L3: switch(Key_On_Number_Flag) //判断秒是加1还是减1 { case 0: break; case 1: read_rtc_code[2]++; //秒加1 if(read_rtc_code[2]>23) read_rtc_code[2]=read_rtc_code[2]-24; //如果加到60以上,则减60 break; case 2: --read_rtc_code[2]; //秒减1 if(read_rtc_code[2]<0) read_rtc_code[2]=read_rtc_code[2]+24; //如果减到0以下,则加60 break; } while(y--) { Set_Time_Display(0,1); //显示刚设置更新的时间 Key_On_Number_Flag1=Key_Scan(); //扫描按键状态,求键值 switch(Key_On_Number_Flag1) { case 0: break; //无按键按下,则跳出循环,计数值减1,继续扫描 case 4: LCD_send_command(LCD_CLEAR_SCREEN); return; //按键4按下,返回 case 1: Set_Time_Minute(0); //按键1按下,调用设置分函数 y=DD; break; case 2: Key_On_Number_Flag=1;//按键2和按键3按下,设置时函数 y=DD; goto L3; case 3: Key_On_Number_Flag=2; y=DD; goto L3; } } } /*************************设置小时函数******************************/ /**************************设置日函数*******************************/ void Set_Date_Day(unsigned int Key_On_Number_Flag,unsigned int Month) { unsigned int Key_On_Number_Flag1; unsigned int y=DD; unsigned int Month_Days_Flag; switch(Month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: Month_Days_Flag=31; break; case 4: case 6: case 9: case 11: Month_Days_Flag=30; break; case 2: if(((read_rtc_code[0])%4==0)&&((read_rtc_code[0]%100)!=0)) Month_Days_Flag=29; else Month_Days_Flag=28; } L4: switch(Key_On_Number_Flag) //判断秒是加1还是减1 { case 0: break; case 1: read_rtc_code[4]++; //秒加1 if(read_rtc_code[4]>Month_Days_Flag) read_rtc_code[4]=read_rtc_code[4]-Month_Days_Flag; //如果加到60以上,则减60 break; case 2: read_rtc_code[4]--; //秒减1 if(read_rtc_code[4]<0) read_rtc_code[4]=read_rtc_code[4]+Month_Days_Flag; //如果减到0以下,则加60 break; } while(y--) { Set_Time_Display(1,5); //显示刚设置更新的时间 Key_On_Number_Flag1=Key_Scan(); //扫描按键状态,求键值 switch(Key_On_Number_Flag1) { case 0: break; //无按键按下,则跳出循环,计数值减1,继续扫描 case 4: case 1: LCD_send_command(LCD_CLEAR_SCREEN); return; //按键4和按键1按下,返回 case 2: Key_On_Number_Flag=1;//按键2和按键3按下,设置时间秒 y=DD; goto L4; case 3: Key_On_Number_Flag=2; y=DD; goto L4; } } } /**************************设置日函数*******************************/ /**************************设置月函数*******************************/ void Set_Date_Month(unsigned int Key_On_Number_Flag) { unsigned int Key_On_Number_Flag1; unsigned int y=DD; L5: switch(Key_On_Number_Flag) //判断秒是加1还是减1 { case 0: break; case 1: read_rtc_code[5]++; //秒加1 if(read_rtc_code[5]>12) read_rtc_code[5]=read_rtc_code[5]-12; //如果加到60以上,则减60 break; case 2: read_rtc_code[5]--; //秒减1 if(read_rtc_code[5]<0) read_rtc_code[5]=read_rtc_code[5]+12; //如果减到0以下,则加60 break; } while(y--) { Set_Time_Display(1,6); //显示刚设置更新的时间 Key_On_Number_Flag1=Key_Scan(); //扫描按键状态,求键值 switch(Key_On_Number_Flag1) { case 0: break; //无按键按下,则跳出循环,计数值减1,继续扫描 case 4: LCD_send_command(LCD_CLEAR_SCREEN); return; //按键4按下,返回 case 1: Set_Date_Day(0,read_rtc_code[1]); //按键1按下,调用设置秒函数 y=DD; break; case 2: Key_On_Number_Flag=1;//按键2和按键3按下,设置时间分 y=DD; goto L5; case 3: Key_On_Number_Flag=2; y=DD; goto L5; } } } /**************************设置月函数*******************************/ /*************************设置年函数******************************/ void Set_Date_Year(unsigned int Key_On_Number_Flag) { unsigned int Key_On_Number_Flag1; unsigned int y=DD; L6: switch(Key_On_Number_Flag) //判断秒是加1还是减1 { case 0: break; case 1: read_rtc_code[6]++; //秒加1 if(read_rtc_code[6]>2100) read_rtc_code[6]=read_rtc_code[6]-2100; //如果加到60以上,则减60 break; case 2: read_rtc_code[6]--; //秒减1 if(read_rtc_code[6]<0) read_rtc_code[6]=read_rtc_code[6]+2100; //如果减到0以下,则加60 break; } while(y--) { Set_Time_Display(1,7); //显示刚设置更新的时间 Key_On_Number_Flag1=Key_Scan(); //扫描按键状态,求键值 switch(Key_On_Number_Flag1) { case 0: break; //无按键按下,则跳出循环,计数值减1,继续扫描 case 4: LCD_send_command(LCD_CLEAR_SCREEN); return; //按键4按下,返回 case 1: Set_Date_Month(0); //按键1按下,调用设置分函数 y=DD; break; case 2: Key_On_Number_Flag=1;//按键2和按键3按下,设置时函数 y=DD; goto L6; case 3: Key_On_Number_Flag=2; y=DD; goto L6; } } } /*************************设置小时函数******************************/ ///////////////////////////////////////////////////////////////////// /************************设置闹钟秒函数*****************************/ void Set_Alarm_Second(unsigned int Key_On_Number_Flag) { unsigned int Key_On_Number_Flag1; unsigned int y=DD; L7: switch(Key_On_Number_Flag) //判断秒是加1还是减1 { case 0: break; case 1: Set_Alarm_Init[0]++; //秒加1 if(Set_Alarm_Init[0]>60) Set_Alarm_Init[0]=Set_Alarm_Init[0]-60; //如果加到60以上,则减60 break; case 2: Set_Alarm_Init[0]--; //秒减1 if(Set_Alarm_Init[0]<0) Set_Alarm_Init[0]=Set_Alarm_Init[0]+60; //如果减到0以下,则加60 break; } while(y--) { Set_Time_Display(3,12); //显示刚设置更新的时间 Key_On_Number_Flag1=Key_Scan(); //扫描按键状态,求键值 switch(Key_On_Number_Flag1) { case 0: break; //无按键按下,则跳出循环,计数值减1,继续扫描 case 4: case 1: LCD_send_command(LCD_CLEAR_SCREEN); return; //按键4和按键1按下,返回 case 2: Key_On_Number_Flag=1;//按键2和按键3按下,设置时间秒 y=DD; goto L7; case 3: Key_On_Number_Flag=2; y=DD; goto L7; } } } /************************设置闹钟秒函数*****************************/ /************************设置闹钟分函数*****************************/ void Set_Alarm_Minute(unsigned int Key_On_Number_Flag) { unsigned int Key_On_Number_Flag1; unsigned int y=DD; L8: switch(Key_On_Number_Flag) //判断秒是加1还是减1 { case 0: break; case 1: Set_Alarm_Init[1]++; //秒加1 if(Set_Alarm_Init[1]>60) Set_Alarm_Init[1]=Set_Alarm_Init[1]-60; //如果加到60以上,则减60 break; case 2: Set_Alarm_Init[1]--; //秒减1 if(Set_Alarm_Init[1]<0) Set_Alarm_Init[1]=Set_Alarm_Init[1]+60; //如果减到0以下,则加60 break; } while(y--) { Set_Time_Display(3,11); //显示刚设置更新的时间 Key_On_Number_Flag1=Key_Scan(); //扫描按键状态,求键值 switch(Key_On_Number_Flag1) { case 0: break; //无按键按下,则跳出循环,计数值减1,继续扫描 case 4: LCD_send_command(LCD_CLEAR_SCREEN); return; //按键4按下,返回 case 1: Set_Alarm_Second(0); //按键1按下,调用设置秒函数 y=DD; break; case 2: Key_On_Number_Flag=1; //按键2和按键3按下,设置时间分 y=DD; goto L8; case 3: Key_On_Number_Flag=2; y=DD; goto L8; } } } /************************设置闹钟分函数*****************************/ /***********************设置闹钟小时函数****************************/ void Set_Alarm_Hour(unsigned int Key_On_Number_Flag) { unsigned int Key_On_Number_Flag1; unsigned int y=DD; L9: switch(Key_On_Number_Flag) //判断秒是加1还是减1 { case 0: break; case 1: Set_Alarm_Init[2]++; //秒加1 if(Set_Alarm_Init[2]>24) Set_Alarm_Init[2]=Set_Alarm_Init[2]-24; //如果加到60以上,则减60 break; case 2: Set_Alarm_Init[2]--; //秒减1 if(Set_Alarm_Init[2]<0) Set_Alarm_Init[2]=Set_Alarm_Init[2]+24; //如果减到0以下,则加60 break; } while(y--) { Set_Time_Display(3,10); //显示刚设置更新的时间 Key_On_Number_Flag1=Key_Scan(); //扫描按键状态,求键值 switch(Key_On_Number_Flag1) { case 0: break; //无按键按下,则跳出循环,计数值减1,继续扫描 case 4: LCD_send_command(LCD_CLEAR_SCREEN); return; //按键4按下,返回 case 1: Set_Alarm_Minute(0); //按键1按下,调用设置分函数 y=DD; break; case 2: Key_On_Number_Flag=1; //按键2和按键3按下,设置时函数 y=DD; goto L9; case 3: Key_On_Number_Flag=2; y=DD; goto L9; } } } /***********************设置闹钟小时函数****************************/ /*************************设置闹钟函数******************************/ void Set_Alarm(void) { unsigned int x; unsigned int y=DD; LCD_send_command(LCD_CLEAR_SCREEN); while(y--) { Set_Time_Display(3,9); x=Key_Scan(); switch(x) { case 0: break; case 4: LCD_send_command(LCD_CLEAR_SCREEN); y=DD; return; case 1: Set_Alarm(); y=DD; break; case 2: Set_Alarm_Hour(1); y=DD; break; case 3: Set_Alarm_Hour(2); y=DD; break; } } LCD_send_command(LCD_CLEAR_SCREEN); return; } /*************************设置闹钟函数******************************/ /*************************设置星期函数******************************/ void Set_Week(unsigned int Key_On_Number_Flag) { unsigned int Key_On_Number_Flag1; unsigned int y=DD; L7: switch(Key_On_Number_Flag) //判断秒是加1还是减1 { case 0: break; case 1: ++read_rtc_code[3]; //秒加1 if(read_rtc_code[3]>7) read_rtc_code[3]=read_rtc_code[3]-7; //如果加到60以上,则减60 break; case 2: --read_rtc_code[3]; //秒减1 if(read_rtc_code[3]<0) read_rtc_code[3]=read_rtc_code[3]+7; //如果减到0以下,则加60 break; } LCD_send_command(LCD_CLEAR_SCREEN); while(y--) { Set_Time_Display(2,8); //显示刚设置更新的时间 Key_On_Number_Flag1=Key_Scan(); //扫描按键状态,求键值 switch(Key_On_Number_Flag1) { case 0: break; //无按键按下,则跳出循环,计数值减1,继续扫描 case 4: return; //按键4按下,返回 case 1: Set_Alarm(); //按键1按下,调用设置分函数 y=DD; break; case 2: Key_On_Number_Flag=1; //按键2和按键3按下,设置时函数 y=DD; goto L7; case 3: Key_On_Number_Flag=2; y=DD; goto L7; } } LCD_send_command(LCD_CLEAR_SCREEN); return; } /*************************设置星期函数******************************/ /*************************设置日期函数******************************/ void Set_Date(void) { unsigned int x; unsigned int y=DD; LCD_send_command(LCD_CLEAR_SCREEN); while(y--) { Set_Time_Display(1,4); x=Key_Scan(); switch(x) { case 0: break; case 4: Set_RTC(); LCD_send_command(LCD_CLEAR_SCREEN); y=DD; return; case 1: Set_Week(0); y=DD; break; case 2: Set_Date_Year(1); y=DD; break; case 3: Set_Date_Year(2); y=DD; break; } } LCD_send_command(LCD_CLEAR_SCREEN); return; } /*************************设置日期函数******************************/ /*************************设置时间函数******************************/ void Set_Time(void) { unsigned int x; unsigned int y=DD; x=Key_Scan(); if(x!=1) return; else { LCD_send_command(LCD_CLEAR_SCREEN); while(y--) { Set_Time_Display(0,0); x=Key_Scan(); switch(x) { case 0: break; case 4: Set_RTC(); LCD_send_command(LCD_CLEAR_SCREEN); y=DD; return; case 1: Set_Date(); y=DD; break; case 2: Set_Time_Hour(1); y=DD; break; case 3: Set_Time_Hour(2); y=DD; break; } } } LCD_send_command(LCD_CLEAR_SCREEN); return; } /*************************设置时间函数******************************/ /*************************闹钟响应函数******************************/ void Alarm_Ring(void) { unsigned int x=50; switch(Alarm_On_Flag) { case 0: break; case 1: while(x--) { Alarm_On=0; delay_ms(5,5); Alarm_On=1; delay_ms(5,5); } break; } return; } /*************************闹钟响应函数******************************/ /*************************闹钟时间比较函数**************************/ unsigned int Alarm_Compare(void) { unsigned int x=50; if((Set_Alarm_Init[1]==read_rtc_code[1])&&(Set_Alarm_Init[2]==read_rtc_code[2])) Alarm_On_Flag=1; else Alarm_On_Flag=0; return(Alarm_On_Flag); } /*************************闹钟时间比较函数**************************/ /***************************闹钟响应函数****************************/ void Alarm_Ack(void) { unsigned x; x=Alarm_Compare(); switch(x) { case 0: break; case 1: Alarm_Ring(); break; } return; } /***************************闹钟响应函数****************************/ /*************************显示时间函数******************************/ void Set_Time_Display(unsigned int x,unsigned int y) { unsigned char *Display; switch(y) { case 0: Display="Set_Time"; break; case 1: Display="Set_Time_Hour"; break; case 2: Display="Set_Time_Min."; break; case 3: Display="Set_Time_Sec."; break; case 4: Display="Set_Date"; break; case 5: Display="Set_Date_Day"; break; case 6: Display="Set_Date_Mon."; break; case 7: Display="Set_Date_Year"; break; case 8: Display="Set_Week"; break; case 9: Display="Set_Alarm"; break; case 10: Display="Set_Alarm_Hour"; break; case 11: Display="Set_Alarm_Min."; break; case 12: Display="Set_Alarm_Sec."; break; case 13: Display="A_ON"; break; case 14: Display="A_OFF"; break; } switch(x) { case 0: LCD_disp_string(0,0,Display); LCD_set_add(1,0); //设置显示的初始位置 LCD_send_data((read_rtc_code[2]/10)|0x30); //显示时 LCD_send_data((read_rtc_code[2]%10)|0x30); LCD_send_data(':'); LCD_send_data((read_rtc_code[1]/10)|0x30); //显示分 LCD_send_data((read_rtc_code[1]%10)|0x30); LCD_send_data(':'); LCD_send_data((read_rtc_code[0]/10)|0x30); //显示秒 LCD_send_data((read_rtc_code[0]%10)|0x30); break; case 1: LCD_disp_string(0,0,Display); LCD_set_add(1,0); LCD_send_data((read_rtc_code[6]/10)|0x30); //显示年 LCD_send_data((read_rtc_code[6]%10)|0x30); LCD_send_data('-'); LCD_send_data((read_rtc_code[5]/10)|0x30); //显示月 LCD_send_data((read_rtc_code[5]%10)|0x30); LCD_send_data('-'); LCD_send_data((read_rtc_code[4]/10)|0x30); //显示日 LCD_send_data((read_rtc_code[4]%10)|0x30); break; case 2: LCD_disp_string(0,0,Display); chose_set(); break; case 3: LCD_disp_string(0,0,Display); LCD_set_add(1,0); //设置显示的初始位置 LCD_send_data((Set_Alarm_Init[2]/10)|0x30); //显示时 LCD_send_data((Set_Alarm_Init[2]%10)|0x30); LCD_send_data(':'); LCD_send_data((Set_Alarm_Init[1]/10)|0x30); //显示分 LCD_send_data((Set_Alarm_Init[1]%10)|0x30); LCD_send_data(':'); LCD_send_data((Set_Alarm_Init[0]/10)|0x30); //显示秒 LCD_send_data((Set_Alarm_Init[0]%10)|0x30); break; } } /*************************显示时间函数******************************/ /****************************5 键盘模块结束***********************/ /***************************8.主函数:开始*************************/ void main(void) { uint i; uchar *cp; //定义字符串指针 LCD_init(); //LCD初始化 while(1) { delay_ms(20,100); //延时 Read_RTC(); //读时间信息 delay_ms(100,100); Display(); //液晶实时显示 delay_ms(100,100); Set_Time(); //键盘处理 Alarm_Ack(); //闹钟处理 } } /****************************主函数:结束**************************/ 附录3 软件设计变量表
(1)小时寄存器(85H、84H)的位7用于定义DS1307是运行于12小时模式还是24小时模式。当为高电平时,选择12小时模式。在12小时模式时,位D5是,当为1时,表示PM。在24小时模式时,位5是第二个10小时位。地址 D7 D6 D5 D4 D3 D2 D1 D0 功能 取值范围 81H CH 十位 个位 秒 00-59 83H 0 十位 个位 分 00-59 85H 0 12 十位 十位 个位 时 AM:1-12 24 8B 0 0 0 0 0 星期 星期 01-07 87H 0 0 十位 个位 日 01-31 H 0 0 0 十位 个位 月 01-12 8DH 十位 Year 年 00-99 8FH OUT 0 0 SOWE 0 0 RS1 RS0 控制字 - 91H RAM 00H-FFH
图2-5 CPU写数据模式S 1101000 0 A XXXX A XXXX A XXXX A XXXX A P
图2-6 CPU读数据模式S 1101000 1 A XXXX A XXXX A XXXX A XXXX A P
(1)读状态操作RS R/W 操作 0 0 写命令操作(初始化,光标定位等) 0 1 读状态操作(读忙标志位) 1 0 写数据操作(要显示内容) 1 1 读数据操作(可以把显示存储区中的数据反读出来)
2)如图2-13为写命令字的流程图。指令名称 控制信号 控制代码 RS RW D7 D6 D5 D4 D3 D2 D1 D0 清屏 0 0 0 0 0 0 0 0 0 1 归HOME位 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 RL * * 功能设置 0 0 0 0 1 DL N F * * CGRAM地址设制 0 0 0 1 A5 A4 A3 A2 A1 A0 DDRAM地址设制 0 0 1 A6 A5 A4 A3 A2 A1 A0 读BF和AC 0 1 BF AC6 AC5 AC4 AC3 AC2 AC1 AC0 写数据 1 0 数 据 读数据 1 1 数 据
第1行DDRAM地址与第2行DDRAM地址并不连续。如表2-4所示。RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 0 0 1 AC6 AC5 AC4 AC3 AC2 AC1 AC0
4)LCD初始化row 1 2 3 4 5 … 14 15 16 line1 80H 81H 82H 83H 84H … 8dH 8eH 8fH line2 0c0H 0c1H 0c2H 0c3H 0c4H … 0cdH 0ceH 0cfH
附录4 程序模块及参数序号 变量名 含义 1 LCD_AC_AUTO_INCREMENT 数据读、写操作后,AC自动增一,不移位 2 LCD_AC_AUTO_DECREASE 数据读、写操作后,AC自动减一,不移位 3 LCD_MOVE_ENABLE 数据读、写操作,画面平移 4 LCD_MOVE_DISENAB 光标画面归位 5 LCD_GO_HOME AC=0,光标、画面回HOME位 6 LCD_DISPLAY_ON 显示开:整体显示,光标显示关,光标位的字符无闪耀 7 LCD_DISPLAY_OFF LE 数据读、写操作,画面不动 8 LCD_CURSOR_ON 光标显示 9 LCD_CURSOR_OFF 光标不显示 10 LCD_CURSOR_BLINK_ON 光标闪烁 11 LCD_CURSOR_BLINK_OFF 光标不闪烁 12 LCD_LEFT_MOVE LCD显示左移一位 13 LCD_RIGHT_MOVE LCD显示右移一位 14 LCD_CURSOR_LEFT_MOVE 光标左移一位 15 LCD_CURSOR_RIGHT_MOVE 光标右移一位 16 LCD_DISPLAY_DOUBLE_LINE 两行显示 17 LCD_DISPLAY_SINGLE_LINE 单行显示 18 LCD_CLEAR_SCREEN 清屏 19 LINE1_HEAD 第一行DDRAM起始地址 20 LINE2_HEAD 第二行DDRAM起始地址 21 LINE_LENGTH 每行的最大字符长度 22 LCDIO 定义P1口与LCD1602的数据口相接 23 unsigned char code str[] 显示字符串编码定义 24 SCLK P2^6DS1307串行时钟信号输入端口 25 SDA P2^7 DS1307串行数据输入端口 26 LCD_RS P2^0 27 LCD_RW P2^1 28 LCD_EN P2^2 29 LCD_BUSY LCDIO^7 30 Alarm_On P2^3 序号 子程序名 入口参数 出口参数 功能 1 LCD_check_busy 无 无 检测LCD忙闲状态函数 2 LCD_send_command command 无 单片机向LCD1602发送的控制命令 3 LCD_send_data dat 无 单片机向LCD1602发送数据函数 4 LCD_read_data 无 dat 1.4 单片机读LCD1602数据函数 5 LCD_init 无 无 1.5 LCD1602初始化 6 LCD_disp_string uchar x,uchar y,uchar *Data 无 LCD1602显示字符串 7 LCD_disp_string1 uchar x,uchar y,uchar Data 无 LCD1602显示字符 8 LCD_set_add char row, char col 无 设置显示地址 9 delay_ms m,n 无 延时函数 10 I2C_delay 无 无 I2C延时程序开始 11 I2C_start 无 无 I2C启动程序 12 I2C_stop 无 无 I2C停止程序 13 I2C_send_ack k 无 I2C应答程序 14 I2C_write_byte char dat 无 单片机向I2C发送数据dat程序 15 I2C_read_byte 无 dat 单片机读I2C数据dat程序 16 Write1307 char add,dat 0 单片机向DS1307发送数据程序 17 Read1307 add dat 单片机读DS1307发送数据程序 18 Read_RTC 无 无 单片机读DS1307寄存器程序 19 Set_RTC 无 无 DS1307初始化程序 20 chose 无 无 选择星期程序 21 Display 无 无 显示程序 22 Key_Scan 无 Key_On_Number 按键扫描函数 23 Set_Time_Second key_On_Number_Flag 无 设置秒函数 24 Set_Time_Minute Key_On_Number_Flag 无 设置分函数 25 Set_Time_Hour Key_On_Number_Flag 无 设置小时函数 26 Set_Date_Day Key_On_Number_Flag, 无 设置日函数 27 Set_Date_Month Key_On_Number_Flag 无 设置月函数 28 Set_Date_Year Key_On_Number_Flag 无 设置年函数 29 Set_Alarm_Second Key_On_Number_Flag 无 设置闹钟秒函数 30 Set_Alarm_Minute Key_On_Number_Flag 无 设置闹钟分函数 31 Set_Alarm_Hour Key_On_Number_Flag 无 设置闹钟小时函数 32 Set_Alarm 无 无 设置闹钟函数 33 Set_Week Key_On_Number_Flag) 无 设置星期函数 34 Set_Date 无 无 设置日期函数 35 Set_Time 无 无 设置时间函数 36 Alarm_Ring 无 无 闹钟响应函数 37 Alarm_Ack 无 无 闹钟响应函数 38 Alarm_Compare 无 Alarm_On_Flag 闹钟时间比较函数 39 Set_Time_Display int x int y 无 显示时间函数