开头唠一唠:
趁着寒假的时间,也趁着课程设计正好是做一个万年历。就打算好好从头到尾来一遍。涨涨知识。首先说的是本人也是小白一颗,大神们能帮忙指正错误的话,不胜感激。写博客只是为了总结经验,要是帮到一部分人就更好了。我想是从硬件到软件都介绍的详细一点,还想说一说自己遇到的一些问题,可能要写的长一点。代码的话我会在后面上传。好,闲话不多说。进入正题。
概述:
首先说一下我用到的东西,硬件方面(电路都是自己拿万能板焊的):一片51单片机,一块12864液晶,一片ds1302时钟芯片,四个按键。还有些电容、电阻、晶振什么的,下面讲到的时候再说吧。主要的就这么多吧。再简单说一下按键的功能吧,假设按键分别是k1,k2,k3,k4。首先lcd主界面是显示的当前的日期时间和四路闹钟的时间。附图。k1,k2,k3,k4最开始被按下时分别对应的功能是k1:进入时间设定模式;k2:进入日期设定模式;k3:进入闹钟设定模式;k4:进入秒表计数模式。进入不同的模式后,四个按键有都有了新的功能,首先k4一直是退出,就是退出到最开始的选四种模式。k1,k2,k3对于日期和时间设定模式是一样的功能k1:数值加1,k2:数值减1,k3:更换调的是小时还是分钟抑或年份还是月份。对于闹钟模式,k1:数值加1,k2:更换调的是小时还是分钟,k3:更换调的是第几个闹钟。对于秒表模式,k1:第一次按是开始计数,然后再按就是记录一下当前是多少秒,最多可以记录9次。k2:暂停/开始,k3:重新计数。有点绕得慌,简单的的说就是有两重循环。要是还没理解,可以看后面的代码。
一:硬件电路
这部分怎么说,说简单也挺简单的。但其中有个梗我现在还没过去。就是最开始我打算自己焊个下载电路在上面的,结果总是下不进去程序。这部分算是题外话了,但还是想简单说一说。最开始打算用CH340芯片直接usb转uart的,结果芯片买回来发现好像没有直插的。自己腐板子什么的又嫌太麻烦。最后打算先用usb转九针串口转成rs232电平,再用max232转成uart电平的。照着电路图一顿焊,结果果然不出我所料,不可能一下就成功下进去程序。就找问题啊,找啊找,找啊找。好像是找到了一个,就是51下程序不是有一个断电在上电的过程吗?我是这样做的,但其中好像有问题,断的这个电应该只是单片机的电,而不包括max232的电。于是又改电路,改完还是不行。算了,这个我以后搞明白了再来说说吧。
其余的应该就不算什么难的了,找一个51最小系统原理图照着焊呗,没什么太大的问题的。法
对了,还有几个小的点,提一提吧。51的P0口是相当于集电极开路的门电路的,记得接上拉电阻。LCD屏导完程序时,最开始如果什么也不显示的话,记得调一下3脚接的电位器调一下背光。
二.软件设计
1.按键检测
这一部分在我最开始看来是没有什么大文章的,也没有什么可以值得写的,有点基础的人几分钟就可以把程序写出了。可是当自己正真写的时候,才知道自己不懂得太多,要学的也太多。单片机的IO口最普通的两种功能,输入和输出嘛。记得自己学stm32时,IO口的输入输出是要在最开始初始化的是定义的。也就是IO口在同一时刻只能有一种功能吧,总不能又输入有输出吧。可是51呢?让我懵逼,在任何地方,包括启动文件里都没有定义IO口是输入还是输出。这让我很郁闷,总不会我让一个IO口输出一个高电平后,还可以从IO口读输入吧,那样不一直应该读到的就是我输出的高电平吗。直到我好好研究了一波51IO口的内部电路,才明白其中的玄机。
这里是最简单的P1口的内部结构图。有点数电基础的人大概可以看明白。具体我就不讲了。你可以参考这里http://www.eeworld.com.cn/mcu/article_2017120236473_2.html
由上图可见,要正确地从引脚上读入外部信息,必须先使场效应管关断,以便由外部输入的信息确定引脚的状态。为此,在作引脚读入前,必须先对该端口写入l。具有这种操作特点的输入/输出端口,称为准双向I/O口。8051单片机的P1、P2、P3都是准双向口。P0端口由于输出有三态功能,输入前,端口线已处于高阻态,无需先写入l后再作读操作。弄懂IO口的内部结构之后。我就直接上程序了,慢慢研究吧。注释的和没有用到的部分大家就不要纠结了。
/************************************************************************************************* 程序说明:按键的检测程序(基于51单片机),现在只有独立按键检测函数 Author: xingcheng IO说明:按键接的 **************************************************************************************************/ #include"key.h" sbit KeyPort2=P1^5; sbit KeyPort0=P1^7; sbit KeyPort1=P1^6; sbit KeyPort3=P1^4; //自己焊的按键接的单片机引脚 //sbit KeyPort2=P1^2; //sbit KeyPort0=P1^0; //sbit KeyPort1=P1^1; //sbit KeyPort3=P1^3; /************************************************************************ 函数名称:key_scan() 函数功能:4个独立按键检测 输入参数:无 返回值:KeyV 通过返回值的不同来确定哪个按键被按下 *************************************************************************/ uchar key_scan() { uchar KeyV; KEYPORT=0xff; //从51IO口读数据,一般要先给锁存器写1, //具体请参考51IO口内部结构 if(KeyPort0==0||KeyPort1==0||KeyPort2==0||KeyPort3==0) //判断是否有按键按下 //这里改成if((P3&0xf0)!=0xf0)总是错,原因可能是P3读数据不是从引脚读的 //而是从锁存器读的,一直是0xff { delay_ms(10); //防止抖动(拿板子实验时,发现这里延不延时并无影响) if(KeyPort0==0) //判断哪个按键被按下// { KeyV=K1; } else if(KeyPort1==0) { KeyV= K2; } else if(KeyPort2==0) { KeyV=K3; } else if(KeyPort3==0) { KeyV=K4; } else { KeyV= 0; } //判断哪个按键被按下// if(KeyV != 0) //有按键按下时,进行松手检测 while(KeyPort0==0||KeyPort1==0||KeyPort2==0||KeyPort3==0); delay_ms(10); //延时消抖(拿板子实验,这里延时非常必要) } return KeyV; //返回KeyV来标识哪一个按键被按下 } /*****************************有时间再完善连按,长按等功能************************/ /* while((KEYPORT&0Xf0)!=NO_KEY) { delay_ms(15); PressCnt--; if(PressCnt==0) { PressCnt=SHORTCNT; return KeyV; } } } delay_ms(15); if((KEYPORT&0Xf0)==NO_KEY) { ReleaseCon=0; return KeyV; } */
#ifndef __KEY_H#define _KEY_H #include<reg52.h>#include"delay.h" #ifndef uchar #define uchar unsigned char#endif #define KEYPORT P3 // 四个按键接在了P3口的四个引脚#define NO_KEY 0xf0#define K1 0X01#define K2 0X02#define K3 0X03#define K4 0X04#define KEYSUB 0X02#define KEYADD 0X01#define KEYSET 0X04#define KEYNEXT 0X03 //K1,2,3,4,和这些是一样的,只是写.c文件时#define LONGCNT 150#define SHORTCNT 12 uchar key_scan(); #endif
2.lcd12864
这个就是真的没什么好说的了。就是记得调电位器调背光。对了,还有一个 好坑的地方,不知道各位有没有解决方法,就是那个光标(一闪一闪的那个)每次移动都是两个字两个字的移。上程序。
/*********************************************************************** 程序功能:12864液晶驱动程序 其他: 只包括基本的字符串显示功能 *************************************************************************/ #include <LCD12864.h> #define uchar unsigned char #define uint unsigned int #define LCD_data P0 //数据口 /******************************************************************* 函数名称:delay(int ms) 函数功能:延时 输入参数:ms 要延时的ms数 返回值: 无 *******************************************************************/ void delay(int ms) { while(ms--) { uchar i; for(i=0;i<250;i++) { ; ; ; ; } } } /******************************************************************* 函数名称:lcd_busy() 函数功能:检测LCD忙状态。 输入参数:无 返回值: result result为1时,忙等待;result为0时,闲,可写指令数据 *******************************************************************/ bit lcd_busy() { bit result; LCD_RS = 0; LCD_RW = 1; LCD_EN = 1; delay_ms(1); result = (bit)(LCD_data&0x80); LCD_EN = 0; return(result); } /*******************************************************************/ /*写指令数据到LCD */ /*RS=L,RW=L,E=高脉冲,D0-D7=指令码。 */ /*******************************************************************/ void lcd_wcmd(uchar cmd) { while(lcd_busy()); LCD_RS = 0; LCD_RW = 0; LCD_EN = 0; delay_ms(1); LCD_data = cmd; delay_ms(1); LCD_EN = 1; delay_ms(1); LCD_EN = 0; } /*******************************************************************/ /*写显示数据到LCD */ /*RS=H,RW=L,E=高脉冲,D0-D7=数据。 */ /*******************************************************************/ void lcd_wdat(uchar dat) { while(lcd_busy()); LCD_RS = 1; LCD_RW = 0; LCD_EN = 0; LCD_data = dat; delay_ms(1); LCD_EN = 1; delay_ms(1); LCD_EN = 0; } /*******************************************************************/ /* LCD初始化设定 */ /*******************************************************************/ void lcd_init() { LCD_PSB = 1; //并口方式 lcd_wcmd(0x34); //扩充指令操作 delay_ms(5); lcd_wcmd(0x30); //基本指令操作 delay_ms(5); lcd_wcmd(0x0C); //显示开,关光标 delay(5); lcd_wcmd(0x01); //清除LCD的显示内容 delay(5); } /*********************************************************/ /* 设定显示位置 X:行数 Y:列数 */ /*********************************************************/ void lcd_pos(uchar X,uchar Y) { uchar pos; if (X==0) {X=0x80;} else if (X==1) {X=0x90;} else if (X==2) {X=0x88;} else if (X==3) {X=0x98;} pos = X+Y ; lcd_wcmd(pos); //显示地址 } /*********************************************************/ /* 在设定位置显示字符(串) */ /*********************************************************/ void zifu_dis (uchar X,uchar Y,uchar *dis) { uchar i; lcd_pos(X,Y); i = 0; while(dis[i] != '\0') { //显示字符 lcd_wdat(dis[i]); i++; } }
/**************dis_12864.h***************/#ifndef __LCD12864_H__#define __LCD12864_H__ #include"delay.h" #include <reg52.h>#define uchar unsigned char #define uint unsigned int /*12864端口定义*/ #define LCD_data P0 //数据口 sbit LCD_RS = P2^3; //寄存器选择输入 sbit LCD_RW = P2^4; //液晶读/写控制 sbit LCD_EN = P2^5; //液晶使能控制 sbit LCD_PSB = P3^3; //串/并方式控制 /*函数声明*/ void delay(int ms); void lcd_init(); void beep(); void dataconv(); void lcd_pos(uchar X,uchar Y); //确定显示位置 void zifu_dis (uchar X,uchar Y,uchar *dis); #endif
3.ds1302时钟
直接给程序,相应的资料大家可以网上搜的。
/************************************************************************** THE REAL TIMER DS1302 DRIVER LIB COPYRIGHT (c) 2005 BY JJJ. -- ALL RIGHTS RESERVED -- File Name: DS1302.h Author: Jiang Jian Jun Created: 2003/7/21 Modified: NO Revision: 1.0 re ***************************************************************************/ #include"ds1302.h" /*************************************************************************** 函数名称:DS1302InputByte(unsigned char d) 函数功能:实时时钟写入一个字节(内部函数) 输入参数:d 要写入的数据 返回值:无 ***************************************************************************/ void DS1302InputByte(unsigned char d) { unsigned char i; ACC = d; for(i=8; i>0; i--) { DS1302_IO = ACC0; //相当于汇编中的 RRC DS1302_CLK = 1; DS1302_CLK = 0; ACC = ACC >> 1; } } /*************************************************************************** 函数名称:DS1302OutputByte(void) 函数功能:实时时钟读取一个字节(内部函数) 输入参数:无 返回值:ACC 读到的数据 ***************************************************************************/ unsigned char DS1302OutputByte(void) { unsigned char i; for(i=8; i>0; i--) { ACC = ACC >>1; //相当于汇编中的 RRC ACC7 = DS1302_IO; DS1302_CLK = 1; DS1302_CLK = 0; } return(ACC); } /*************************************************************************** 函数名称:Write1302(unsigned char ucAddr, unsigned char ucDa) 函数功能:往实时时钟指定地址写数据 输入参数:ucAddr 要写数据的地址 ucDa 要写入的数据 返回值:无 ***************************************************************************/ void Write1302(unsigned char ucAddr, unsigned char ucDa) { DS1302_RST = 0; DS1302_CLK = 0; DS1302_RST = 1; DS1302InputByte(ucAddr); // 地址,命令 DS1302InputByte(ucDa); // 写1Byte数据 // DS1302_CLK = 1; DS1302_RST = 0; } /*************************************************************************** 函数名称:Read1302(unsigned char ucAddr) 函数功能:读取ds1302某地址的数据 输入参数:ucAddr 要读数据的地址 返回值: ucData 读出的数据 ***************************************************************************/ unsigned char Read1302(unsigned char ucAddr) //读取ds1302某地址的数据 { unsigned char ucData; DS1302_RST = 0; DS1302_CLK = 0; DS1302_RST = 1; DS1302InputByte(ucAddr|0x01); // 地址,命令 ucData = DS1302OutputByte(); // 读1Byte数据 // DS1302_CLK = 1; DS1302_RST = 0; return(ucData); } /*************************************************************************** 函数名称:DS1302_SetProtect(bit flag) 函数功能:是否写保护 输入参数:flag 返回值: 无 其他:flag为1时,0x8E对应的control register最高位为1,写保护开启 ***************************************************************************/ void DS1302_SetProtect(bit flag) //是否写保护 { if(flag) Write1302(0x8E,0x80); else Write1302(0x8E,0x00); } /*************************************************************************** 函数名称:DS1302_SetTime(unsigned char Address, unsigned char Value) 函数功能:向指定寄存器写时间 输入参数:Address 寄存器地址 Value 要写入的时间(hex码) 返回值: 无 其他:可以先用宏定义定义好year,month,hour等的地址 ***************************************************************************/ void DS1302_SetTime(unsigned char Address, unsigned char Value) // 设置时间函数 { DS1302_SetProtect(0); Write1302(Address, ((Value/10)<<4 | (Value%10))); //将hex码转化为BCD码 } /*************************************************************************** 函数名称:DS1302_GetTime(SYSTEMTIME *Time) 函数功能:读出日期和时间,将它们存入Time这个结构体中 输入参数:*Time 要存日期和时间的结构体的地址 返回值: 无 ***************************************************************************/ void DS1302_GetTime(SYSTEMTIME *Time) { unsigned char ReadValue; ReadValue = Read1302(DS1302_SECOND); Time->Second = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //八进制转为十进制 ReadValue = Read1302(DS1302_MINUTE); Time->Minute = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); ReadValue = Read1302(DS1302_HOUR); Time->Hour = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); ReadValue = Read1302(DS1302_DAY); Time->Day = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); ReadValue = Read1302(DS1302_WEEK); Time->Week = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); ReadValue = Read1302(DS1302_MONTH); Time->Month = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); ReadValue = Read1302(DS1302_YEAR); Time->Year = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); } /*************************************************************************** 函数名称:DateToStr(SYSTEMTIME *Time) 函数功能:将读出的日期变成便于显示的字符形式 输入参数:*Time 要存字符的结构体 返回值:无 ***************************************************************************/ void DateToStr(SYSTEMTIME *Time) { Time->DateString[0] = Time->Year/10+0x30 ; //·分离个位和十位 Time->DateString[1] = Time->Year%10+0x30 ; Time->DateString[2] = '-'; Time->DateString[3] = Time->Month/10+0x30; Time->DateString[4] = Time->Month%10+0x30 ; Time->DateString[5] = '-'; Time->DateString[6] = Time->Day/10+0x30 ; Time->DateString[7] = Time->Day%10+0x30 ; //用LCD显示,要变成ascii码所以加了0x30,用数码管显示的话就不用加了 Time->DateString[8] = '\0'; } /*************************************************************************** 函数名称:TimeToStr(SYSTEMTIME *Time) 函数功能:将读出的时间变成便于显示的字符形式 输入参数:*Time 要存字符的结构体 返回值:无 ***************************************************************************/ void TimeToStr(SYSTEMTIME *Time) { Time->TimeString[0] = Time->Hour/10+0x30 ; Time->TimeString[1] = Time->Hour%10+0x30 ; Time->TimeString[2] = ':'; Time->TimeString[3] = Time->Minute/10+0x30 ; Time->TimeString[4] = Time->Minute%10+0x30 ; Time->TimeString[5] = ':'; Time->TimeString[6] = Time->Second/10+0x30; Time->TimeString[7] = Time->Second%10+0x30 ;//用LCD显示,要变成ascii码所以加了0x30,用数码管显示的话就不用加了 Time->DateString[8] = '\0'; } /*************************************************************************** 函数名称:Initial_DS1302(void) 函数功能:初始化ds1302 输入参数:无 返回值:无 ***************************************************************************/ void Initial_DS1302(void) { unsigned char Second=Read1302(DS1302_SECOND); if(Second&0x80) DS1302_SetTime(DS1302_SECOND,0); } /******************************************************************************** void BurstWrite1302(unsigned char *pWClock) //往ds1302写入时钟数据(多字节方式) { unsigned char i; Write1302(0x8e,0x00); // 控制命令,WP=0,写操作 DS1302_RST = 0; DS1302_CLK = 0; DS1302_RST = 1; DS1302InputByte(0xbe); // 0xbe:时钟多字节写命令 for (i = 8; i>0; i--) //8Byte = 7Byte 时钟数据 + 1Byte 控制 { DS1302InputByte(*pWClock); // 写1Byte数据 pWClock++; } DS1302_CLK = 1; DS1302_RST = 0; } void BurstRead1302(unsigned char *pRClock) //读取ds1302时钟数据(时钟多字节方式) { unsigned char i; DS1302_RST = 0; DS1302_CLK = 0; DS1302_RST = 1; DS1302InputByte(0xbf); // 0xbf:时钟多字节读命令 for (i=8; i>0; i--) { *pRClock = DS1302OutputByte(); // 读1Byte数据 pRClock++; } DS1302_CLK = 1; DS1302_RST = 0; } void DS1302_TimeStop(bit flag) // 是否将时钟停止 { unsigned char Data; Data=Read1302(DS1302_SECOND); DS1302_SetProtect(0); if(flag) Write1302(DS1302_SECOND, Data|0x80); else Write1302(DS1302_SECOND, Data&0x7F); } ********************************************************************************/
#ifndef _DS1302_H#define _DS1302_H #include<reg52.h>#include"delay.h"#include<intrins.h> #ifndef uchar #define uchar unsigned char#endifsbit DS1302_CLK = P3^6; //实时时钟时钟线引脚sbit DS1302_IO = P3^5; //实时时钟数据线引脚sbit DS1302_RST = P3^4; //实时时钟复位线引脚//sbit DS1302_CLK = P3^6; //实时时钟时钟线引脚//sbit DS1302_IO = P3^4; //实时时钟数据线引脚//sbit DS1302_RST = P3^5; //实时时钟复位线引脚 sbit ACC0 = ACC^0;sbit ACC7 = ACC^7; typedef struct __SYSTEMTIME__{ unsigned int Second; unsigned char Minute; unsigned char Hour; unsigned char Week; unsigned char Day; unsigned char Month; unsigned char Year; unsigned char DateString[9]; unsigned char TimeString[9]; }SYSTEMTIME; //定义的时间类型 #define AM(X) X#define PM(X) (X+12) // 转成24小时制#define DS1302_SECOND 0x80#define DS1302_MINUTE 0x82#define DS1302_HOUR 0x84 #define DS1302_WEEK 0x8A#define DS1302_DAY 0x86#define DS1302_MONTH 0x88#define DS1302_YEAR 0x8C#define DS1302_RAM(X) (0xC0+(X)*2) //用于计算ds1302RAM地址的宏 void DS1302InputByte(unsigned char d) ;unsigned char DS1302OutputByte(void) ;void Write1302(unsigned char ucAddr, unsigned char ucDa);void DS1302_SetProtect(bit flag);unsigned char Read1302(unsigned char ucAddr);void DS1302_SetTime(unsigned char Address, unsigned char Value);void DS1302_GetTime(SYSTEMTIME *Time);void DateToStr(SYSTEMTIME *Time);void TimeToStr(SYSTEMTIME *Time);void Initial_DS1302(void);#end
主要的程序模块到这里基本上就算准备好了。完整的程序我压缩一下上传到资源吧,(没办法想赚点积分,理解理解),其实到这步,大家应该把完整的程序写出来也不是问题了。
再来说说其他的吧。在使用keil软件时,总是报这样的错误*** ERROR L107: ADDRESS SPACE OVERFLOW。也是多方查找才找到问题所在。就是我们所定义变量是定义在51的RAM里的,而且供变量存储的只有256或者128个字节(看型号吧),这里看网上说在变量前面加idata,然而并不管用。还是尽量节省RAM吧。只读的数组定义前面加上code,全局变量尽量少点。不行就只能换单片机了毕竟51是一个资源很少 的单片机,不适合一些大工程。最后加上张效果图
算了,我还是把,所有的程序也贴上来吧,也不在乎那几个积分啦 。
下面的是按键处理程序(这个才是核心程序),和主函数。我从KEIL上复制过来的时候改了一下把edit configuration里的Encode in ANSI 改成了Chinese GB2312.要不然复制过来时中文是乱码。你复制到自己的工程里时应该要改回来吧。
#include"keyProcess.h" void array2show(ARRAY2SHOW *arrayshow0,uchar wch); //函数声明// void sec2show(SYSTEMTIME *secshow); SYSTEMTIME showtime; extern SYSTEMTIME CurrentTime; extern ARRAY2SHOW Alarmandshow; /**************************************************************************************************** 函数名称:key_process(uchar mode) 函数功能:按键处理函数(调节日期,时间,秒表,闹钟) 输入参数:mode 用来选择模式,是修改日期,时间还是闹钟 返回值:无 ****************************************************************************************************/ void key_process(uchar mode) { uchar Wch=0; uchar flag=0; uchar AlarmWch=0; uchar HourSecWch=0; uchar temp=0; switch(mode) //在最外层循环中检测按键,确定要设置什么 { DS1302_GetTime(&CurrentTime); case MODE0: //设置时间 showtime=CurrentTime; while(1) { DateToStr(&CurrentTime); zifu_dis(1,0,&CurrentTime.DateString[0]); //修改时间不影响从1302读日期显示 //(麻烦的思想)TArray3=show2array3(&CurrentTime.TimeString[0]); //将显示的字符形式变成可以直接加1的形式 if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4)//检测有没有按键按下,有按键按下才执行操作 { switch(key_scan()) //再次检测按键 { case K3: //K3按下,选择时间的哪一位被更改 Wch++; if(Wch==3) Wch=0; break; case K1: //K1按下,数字加一 //(麻烦的思想)TArray3[TimeWch]++; //转化成单个字符形式显示 if(Wch==0) { showtime.Hour++; if(showtime.Hour==24) showtime.Hour=0; } else if(Wch==1) { showtime.Minute++; if(showtime.Minute==60) showtime.Minute=0; } else if(Wch==2) { showtime.Second++; if(showtime.Second==60) showtime.Second=0; } TimeToStr(&showtime); zifu_dis(0,0,&showtime.TimeString[0]); break; case K2: //K2按下,数字减一 //(麻烦的思想)TArray3[TimeWch]--; //(麻烦的思想)zifu_dis(1,0,array32show(TArray3)); if(Wch==0) { showtime.Hour--; if(showtime.Hour==0xff) showtime.Hour=0; } else if(Wch==1) { showtime.Minute--; if(showtime.Minute==0xff) showtime.Minute=0; } else if(Wch==2) { showtime.Second--; if(showtime.Second==0xff) showtime.Second=0; } TimeToStr(&showtime); zifu_dis(0,0,&showtime.TimeString[0]); break; case K4: //K4按下,确定修改, flag=1;break; } } if(flag==1) //flag为1时,确定修改,将1302里的时间重置,并退到最初的模式检测 { DS1302_SetTime(DS1302_HOUR,showtime.Hour); DS1302_SetTime(DS1302_MINUTE,showtime.Minute); DS1302_SetTime(DS1302_SECOND,showtime.Second); Wch=0; flag=0; break; } } break; case MODE1: //设置日期 showtime=CurrentTime; while(1) { DS1302_GetTime(&CurrentTime); TimeToStr(&CurrentTime); zifu_dis(0,0,&CurrentTime.TimeString[0]); //修改日期,不影响从1302读时间显示 //(麻烦的思想)DArray3=show2array3(&CurrentTime.DateString); //将显示的字符形式变成可以直接加1的形式 if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4)//检测有没有按键按下,有按键按下才执行操作 { switch(key_scan()) //再次检测按键 { case K3: //K3按下,选择日期的哪一位被更改 Wch++; if(Wch==3) Wch=0; break; case K1: //K1按下,数字加一 //(麻烦的思想)DArray3[DateWch]=DArray3[DateWch]+1; //(麻烦的思想)zifu_dis(0,0,array32show(DArray3)); if(Wch==0) showtime.Year++; else if(Wch==1) { showtime.Month++; if(showtime.Month==13) showtime.Month=1; } else if(Wch==2) { showtime.Day++; if(showtime.Month==1||showtime.Month==3||showtime.Month==5||showtime.Month==7||showtime.Month==8||showtime.Month==10||showtime.Month==12) if(showtime.Day==32) showtime.Day=0; else if(showtime.Month==2) if(showtime.Day=30) showtime.Day=0; else if(showtime.Day==31) showtime.Day=0; } DateToStr(&showtime); zifu_dis(1,0,&showtime.DateString[0]); break; case K2: //K2按下,数字减一 //(麻烦的思想)DArray3[DateWch]--; //(麻烦的思想)zifu_dis(0,0,array32show(DArray3)); if(Wch==0) showtime.Year--; else if(Wch==1) showtime.Month--; else if(Wch==2) showtime.Day--; DateToStr(&showtime); zifu_dis(1,0,&showtime.DateString[0]); break; case K4: //K4按下退出此循环,回到模式检测循环 flag=1; break; } } if(flag==1) //flag为1时,确定修改,将1302里的日期重置,并退到最初的模式检测 { DS1302_SetTime(DS1302_YEAR,showtime.Year); DS1302_SetTime(DS1302_MONTH,showtime.Month); DS1302_SetTime(DS1302_DAY,showtime.Day); flag=0; Wch=0; break; } } break; case MODE2: //设置闹钟 while(1) { DS1302_GetTime(&CurrentTime); DateToStr(&CurrentTime); TimeToStr(&CurrentTime); zifu_dis(0,0,&CurrentTime.TimeString[0]); //在设置闹钟时不让时间的显示停下 if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4)//检测有没有按键按下,有按键按下才执行操作 { switch(key_scan()) //再次检测按键 { case K3: //K1按下,选择哪一个闹钟被更改 AlarmWch++; if(AlarmWch==4) AlarmWch=0; break; case K2: //K2按下,选择闹钟的小时还是秒被更改 HourSecWch++; if(HourSecWch==2) HourSecWch=0; break; case K1: //K3按下,数字加1 Alarmandshow.Alarm[AlarmWch][HourSecWch]++; if(Alarmandshow.Alarm[AlarmWch][HourSecWch]==60) Alarmandshow.Alarm[AlarmWch][HourSecWch]=0; array2show(&Alarmandshow,AlarmWch); zifu_dis(2+AlarmWch%2,2+AlarmWch/2*3,&Alarmandshow.showstring[0]); break; case K4: //K4按下退出此循环,回到模式检测循环 flag=1;break; } } if(flag==1) { AlarmWch=0; HourSecWch=0; //最好要将AlarmWch,HourSecWch清零,后面要用 flag=0; break; } } break; case MODE3: //秒表 while(1) //此层循环用来显示秒表的初始界面 { temp=0; showtime.Second=0; lcd_init(); zifu_dis(0,3,"00.0"); if(key_scan()==K1) //K1按下,秒表开始计时 { while(1) //此层循环是秒表开始后的循环 { delay_ms(73); //再算上程序执行的时间,一共为100ms sec2show(&showtime); zifu_dis(0,3,&showtime.TimeString[0]); if(flag==0) showtime.Second++; //每过100ms,Second++, switch(key_scan()) { case K1: zifu_dis(temp/3+1,temp*3%9,&showtime.TimeString[0]); temp++; //读一下秒表,记录下 if(temp==9) temp=0; break; case K2: flag=~flag; break; case K3: flag=2; break; case K4: flag=1; break; } if(flag==2||flag==1) { if(flag==2) flag=0; break; } } } if(flag==1) { flag=0; break; } } lcd_init(); for(;AlarmWch<4;AlarmWch++) { //arrayshow.array2[AlarmWch][HourSecWch]=0; array2show(&Alarmandshow,AlarmWch); zifu_dis(AlarmWch/2+2,AlarmWch%2*3+2,&Alarmandshow.showstring[0]); } zifu_dis(2,0,"闹钟"); AlarmWch=0; HourSecWch=0; break; } } /*********************************************************************************************** 函数名称:array32show(uchar *array3) 函数功能:将存在array[3]里的小时,分钟,秒转换成可以直接显示的形式 输入参数:*array3 array[3]的首地址 返回值: show show[9]的首地址,可以直接用来显示 *************************************************************************************************/ /*uchar *array32show(uchar *array3) { uchar show[5]; show[0] = *array3/10+0x30 ; show[1] = *array3++%10+0x30 ; show[2] = ':'; show[3] = *array3/10+0x30 ; show[4] = *array3%10+0x30 ;//用LCD显示,要变成ascii码所以加了0x30,用数码管显示的话就不用加了 show[5] = '\0'; return show; } *///没有用到 /*********************************************************************************************** 函数名称:show2array3(uchar *show) 函数功能:将存在show[]里的可直接显示的字符转换成可以直接加一的array[3] 输入参数:*show show数组的首地址 返回值: array3 array数组的首地址,可以直接用来做加一操作 *************************************************************************************************/ /*uchar *show2array3(uchar *show) { uchar array3[3]; array3[0]=(show[0]-0x30)*10+(show[1]-0x30); array3[1]=(show[3]-0x30)*10+(show[4]-0x30); array3[2]=(show[6]-0x30)*10+(show[7]-0x30); return array3; }*/ void array2show(ARRAY2SHOW *arrayshow0,uchar wch) { arrayshow0->showstring[0] = arrayshow0->Alarm[wch][0]/10+0x30 ; arrayshow0->showstring[1] = arrayshow0->Alarm[wch][0]%10+0x30 ; arrayshow0->showstring[2] =':'; arrayshow0->showstring[3] = arrayshow0->Alarm[wch][1]/10+0x30 ; arrayshow0->showstring[4] = arrayshow0->Alarm[wch][1]%10+0x30 ; //用LCD显示,要变成ascii码所以加了0x30,用数码管显示的话就不用加了 arrayshow0->showstring[5] = '\0'; } void sec2show(SYSTEMTIME *secshow) { secshow->TimeString[0]=secshow->Second/100+0x30; secshow->TimeString[1]=secshow->Second%100/10+0x30; secshow->TimeString[2]='.'; secshow->TimeString[3]=secshow->Second%10+0x30; secshow->TimeString[4]='\0'; }
#ifndef __KEYPROCESS_H #define _KEYPROCESS_H #include<reg52.h> #include<stdio.h> #include"delay.h" #include"key.h" #include"ds1302.h" #include"LCD12864.h" #ifndef uchar #define uchar unsigned char #endif typedef struct _ARRAYSHOW_ { unsigned char showstring[6]; unsigned char Alarm[4][2]; }ARRAY2SHOW; typedef struct _SHOW_ { unsigned char showstring[6]; unsigned char array2[4][2]; }show; #define MODE0 0X00 #define MODE1 0X01 #define MODE2 0X02 #define MODE3 0X03 #define TIMESET MODE0 #define DATESET MODE1 #define ALARMSET MODE2 #define SECCON MODE3 void key_process(uchar mode); #endif
这个是蜂鸣器要用到的,就是一个IO口拉高拉低。/*********************************************************************************** 程序说明:利用12864液晶和ds1302配合按键实现 万年历,四路可调闹钟,秒表(基于51单片机) 作者:哈尔滨工程大学 黄子炫 ***********************************************************************************/ #include <reg52.h> #include<stdio.h> #include"delay.h" #include"ds1302.h" #include"LCD12864.h" #include"key.h" #include"buzzer.h" #include"keyProcess.h" SYSTEMTIME CurrentTime; //存储当前从ds1302中读到的时间日期等 ARRAY2SHOW Alarmandshow; //存储闹钟的时间,和用于闹钟显示的字符串 char code table[7][20]={{"星期壹"},{"星期贰"},{"星期叁"},{"星期肆"},{"星期伍"},{"星期陆"},{"星期日"}}; sbit led=P1^7; void main() { uchar mode; Initial_DS1302(); //ds1302初始化 // DS1302_SetTime(DS1302_HOUR,10); // DS1302_SetTime(DS1302_MINUTE,0); // DS1302_SetTime(DS1302_SECOND,0);//向ds1302中写初始时间 // DS1302_SetTime(DS1302_YEAR,17); // DS1302_SetTime(DS1302_MONTH,1); // DS1302_SetTime(DS1302_DAY,16); //向ds1302中写初始日期 DS1302_SetTime(DS1302_WEEK,3); lcd_init(); //lcd12864初始化 zifu_dis(2,0,"闹钟"); zifu_dis(2,2,"00:00"); zifu_dis(2,5,"00:00"); zifu_dis(3,2,"00:00"); zifu_dis(3,5,"00:00"); //设置闹钟的初始显示 while(1) { if(key_scan()==K1||key_scan()==K2||key_scan()==K3||key_scan()==K4) { switch (key_scan()) { case K1: mode=MODE0;break; //MODE0设置时间 case K2: mode=MODE1;break; //MODE0设置日期 case K3: mode=MODE2;break; //MODE0设置闹钟 case K4: mode=MODE3;break; //MODE0设置秒表 } key_process(mode); //按键处理函数 } DS1302_GetTime(&CurrentTime); DateToStr(&CurrentTime); TimeToStr(&CurrentTime); zifu_dis(0,0,&CurrentTime.TimeString[0]); zifu_dis(1,0,&CurrentTime.DateString[0]); //读出ds1302里的时间,在lcd上显示 zifu_dis(1,4,table[CurrentTime.Week]); if((CurrentTime.Hour==Alarmandshow.Alarm[0][0]&&CurrentTime.Minute==Alarmandshow.Alarm[0][1])|| (CurrentTime.Hour==Alarmandshow.Alarm[1][0]&&CurrentTime.Minute==Alarmandshow.Alarm[1][1])|| (CurrentTime.Hour==Alarmandshow.Alarm[2][0]&&CurrentTime.Minute==Alarmandshow.Alarm[2][1])|| (CurrentTime.Hour==Alarmandshow.Alarm[3][0]&&CurrentTime.Minute==Alarmandshow.Alarm[3][1])) //检查所设的闹钟时间和现在的时间是否一致,是则响蜂鸣器。 buzzer_delay(); } }
#include"buzzer.h" void buzzer_on(void) { BuzzerPort=0; } void buzzer_off(void) { BuzzerPort=1; } void buzzer_delay(void) { BuzzerPort=0; delay_ms(400); BuzzerPort=1; delay_ms(400); }
#ifndef __BUZZER_H #define _BUZZER_H #include<reg52.h> #include"delay.h" #ifndef uchar #define uchar unsigned char #endif sbit BuzzerPort=P2^2; void buzzer_on(void); void buzzer_off(void); void buzzer_delay(void); #endif
完工~
基于51单片机的万年历(包含闹钟,秒表)实现
最新推荐文章于 2024-10-28 20:24:36 发布