基于51单片机的万年历(包含闹钟,秒表)实现

开头唠一唠:

          趁着寒假的时间,也趁着课程设计正好是做一个万年历。就打算好好从头到尾来一遍。涨涨知识。首先说的是本人也是小白一颗,大神们能帮忙指正错误的话,不胜感激。写博客只是为了总结经验,要是帮到一部分人就更好了。我想是从硬件到软件都介绍的详细一点,还想说一说自己遇到的一些问题,可能要写的长一点。代码的话我会在后面上传。好,闲话不多说。进入正题。

概述:

         首先说一下我用到的东西,硬件方面(电路都是自己拿万能板焊的):一片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

/***********************************************************************************
程序说明:利用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();
	}		
}
												
这个是蜂鸣器要用到的,就是一个IO口拉高拉低。
#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

 完工~

 

 

 

 

 

 

 

 

 

 

   

 

 

 

 

     

 

 

         

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章