开头唠一唠:

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

概述:

首先说一下我用到的东西,硬件方面(电路都是自己拿万能板焊的):一片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 12uchar 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 LIBCOPYRIGHT (c)   2005 BY JJJ.--  ALL RIGHTS RESERVED  --File Name:       DS1302.hAuthor:          Jiang Jian JunCreated:         2003/7/21Modified:       NORevision:         1.0re
***************************************************************************/
#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;            //相当于汇编中的 RRCDS1302_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;                  //相当于汇编中的 RRCACC7 = 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);elseWrite1302(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);elseWrite1302(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;elseif(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按下,数字加1Alarmandshow.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
#endiftypedef 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      MODE3void 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
#endifsbit BuzzerPort=P2^2;void buzzer_on(void);
void buzzer_off(void);
void buzzer_delay(void);#endif

完工~

搜索

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

  1. 基于51单片机的万年历(算法实现)

    基于51单片机的万年历,用到了单片机独立键盘.数码管.LED灯模块实现. 想要简单还是DS1302好用. 1 /******************************************** ...

  2. 基于51单片机的LCD1602电子钟闹钟proteus仿真设计

    本设计是基于51单片机的LCD1602电子钟闹钟proteus仿真设计 源码+仿真+原理图+器件清单 仿真软件版本:proteus 7.8 程序编译器:keil 4/keil 5 编程语言:C语言 编 ...

  3. 【036】基于51单片机的电子时钟与秒表Proteus仿真设计

    一.压缩包资料内容 (1).基于51单片机的电子时钟与秒表proteus仿真设计一份: (2).基于51单片机的电子时钟与秒表proteus仿真设计keli源代码一份: (3).基于51单片机的电子时 ...

  4. 51单片机项目设计:基于51单片机时钟万年历

    文章目录 一.项目功能 二.材料选择 三.原理图设计 四.PCB设计 五.程序设计 哔哩哔哩视频链接: https://www.bilibili.com/video/BV1EF411z7im/ 资料链 ...

  5. 基于51单片机的万年历proteus仿真原理图方案设计

    (末尾附文件) 系统框图 原理图 仿真图 最小系统电路 STC89C52的最小系统如图3-3所示,整个最小系统由三个部分组成,晶振电路部分.复位电路部分.电源电路等三个部分组成. 晶振电路包括2个30 ...

  6. 基于51单片机的数码管闹钟设计

    系统功能:利用定时器实现时钟,时钟可以通过独立按键设置,设有一个闹钟,闹钟时间可通过按键进行设置,时钟界面和闹钟界面可通过按键切换,当时钟和闹钟时间相同,蜂鸣器响,提示闹钟时间到等. 此系统重点在于: ...

  7. 51单片机开发实例 基于51单片机的万年历

    一.系统设计 通过DS1302时钟模块进行时间数据采集,通过LCD1602显示模块进行显示,通过按键可以修改实时时间.STC89C52单片机作为万年历系统的主控,将DS1302时钟模块得到的时间数据后 ...

  8. 基于51单片机的万年历(带温湿度)带闹钟功能proteus仿真原理图PCB

    功能介绍: 0.本系统采用STC89C52作为单片机 1.LCD1602液晶显示当前时间和温湿度 2.按键可切换页面,显示农历,显示闹钟设置 3.当时间到达设定闹钟时间时,蜂鸣器报警 原理图: PCB ...

  9. 基于51单片机的万年历可显示农历带闹钟整点报送功能proteus仿真原理图PCB

    功能介绍: 0.本系统采用STC89C52作为单片机 1.LCD1602液晶显示当前时间 2.按键可切换页面,显示农历,显示闹钟设置 3.当时间到达设定闹钟时间时,蜂鸣器报警 4.具备整点报时功能 原 ...

最新文章

  1. 共享思维导图,协作型思维导图Leangoo
  2. 基于corosync+pacemaker实现主从高可用集群
  3. MySQL: load data infile 需要注意的点
  4. Rust从入门到放弃(1)—— hello,world
  5. AUTOSAR从入门到精通100讲(八十二)-AutoSAR之基础篇CanNM
  6. Pycharm最新版本安装教程
  7. 转Java调用C/C++编写的第三方dll动态链接库(非native API)--- JNI
  8. 【毕设】ASP.net校友录毕业设计(源代码+论文+开题报告+答辩PPT)
  9. 0910下eclipse 语言包的插件安装
  10. 飞思卡尔16位单片机(四)——GPIO输入功能测试
  11. 怎样删除usb计算机连接网络打印机驱动,打印机USB连接安装设置及常见问题处理...
  12. ROS 基础知识(一)
  13. Ubuntu设置开机自动启动脚本/程序的各种方法
  14. POI之Excel字体样式
  15. 深搜(不撞南墙不回头),迷宫问题
  16. 怎样使用计算机上的高级共享设置密码,怎么设置电脑共享密码
  17. 计算机辅助英语教学 教材,英语教学课中计算机辅助
  18. 【Zabbix】Zabbix微信告警配置演示
  19. 深度解读BN、LN、WN、CN
  20. 关于医保你可能不了解的

热门文章

  1. 羊车门问题的python代码_作业:羊车门问题
  2. C++ 获取个位数十位数等
  3. 程序员开发指南!金三银四Android面试的一些感受,这原因我服了
  4. 零信任能彻底解决邮件安全难题
  5. (Node+Vue+微信公众号开发)企业级产品全栈开发速成周末班
  6. 正则验证邮箱,手机号
  7. MOOS程序解析记录(6)uSimMarine解析1
  8. 2020金融科技领域最具商业合作价值企业盘点
  9. MATLAB | 生成视频文件
  10. 用伪类添加翘边阴影::before和::after