基于51单片机的可调时钟和闹钟(STC15F2K60S2)

(一)概述

开发板是蓝桥杯的开发版,主控芯片是IAP15FK60S2,不是89C52,是看着江科大的视频学的.主要是很多文章都是89C52的,没有15FK60S2的,所以就有了这篇,也当是练练手.

显示用的是数码管不是LCD1602,买蓝桥杯板子的时候可没配有1602,用数码管确实会复杂许多,没有买液晶屏的朋友可能看着视频学起来比较难受,包括有相当一部分东西和89C52是不一样的,比如说想把代码直接写进去运行都不行.

江科大视频的时钟是按下1后选择设置时间还是显示时间,按下2后选择设置年月日时分秒中的哪一位,按3 是增加,4是减少.

但是用数码管显示的话,首先年月日时分秒就要分成两部分显示,需要一个按键切换,我用了按键4;选择设置年月日还是时分秒,我用了按键5;选择设置哪一位需要一个按键,我用了按键6;增加我用了按键7,减少我用了按键8.越界判断没有变,我只是用了一些switch改写,相当牛逼的判断.9是确认键.

值得一提,这里使用的数码管和按键都是后面讲过的定时器扫描.标志位闪烁的实现是通过在定时器每隔500ms翻转一次闪烁标志变量,然后在设置数码管显示的函数调用时通过三目运算符判断是显示值还是什么都不显示.

此外,我还为这个时钟系统添加了闹钟功能,在按键10选择设置闹钟后,使用和调时间同样的按键调闹钟即可,按键11是取消或关闭闹钟;按键12是把上次闹钟值在设置界面显示.按键13是关闭或打开按键声音.

如果觉得前面的话看着繁琐,文末有这个时钟系统的说明书

(二)代码

main.c

值得注意的是,在while中使用if判断成立再执行数码管设置函数会导致数码管显示不了,所以我采用了if判断后设置标志位,然后switch根据标志位选择显示的内容.

#include <head.h>
unsigned char mode1,mode2,mode,value,i;
unsigned char viewflag,flashflag1=1,flashflag2=1,flashflag3=1;
unsigned char vioceflag=0,alarmflag=0,soundflag=0,numberfeedback=0,ringflag=0;
char time[]={30,58,17,31,1,3,24},time3[7]={0,0,15,31,7,5,21},time4[7]={0,0,18,30,1,7,24},time5[7];//初始时间
void timeset()//选择调的时间
{ 
		switch(mode2)
		{
			case 0://调时分秒
				viewflag=6;
				break;
				
			case 1://调年月日
				viewflag=7;
				break;
		}
}

void timeshow(unsigned char i)//读取当前时间显示
{
	readTime0();
	switch(i)
	{
		
		case 0://时分秒
			smg_set(0,time1[2]);
			smg_set(1,time0[2]);
			smg_set(2,16);
			smg_set(3,time1[1]);
			smg_set(4,time0[1]);		
			smg_set(5,16);
			smg_set(6,time1[0]);
			smg_set(7,time0[0]);
			break;
		
		case 1://年月日
			smg_set(0,time1[6]);
			smg_set(1,time0[6]);
			smg_set(2,16);
			smg_set(3,time1[4]);
			smg_set(4,time0[4]);		
			smg_set(5,16);
			smg_set(6,time1[3]);
			smg_set(7,time0[3]);	
			break;
	}
}

void timeget()//把当前时间写入设置界面
{
	for(i=0;i<7;i++)
	{
		time[i]=time0[i]+time1[i]*10;					
	}
}

void alarm()//对比现在时间和设置时间
{
	unsigned char buzzflag=1;
	static unsigned int k=0;
	for(i=0;i<7;i++)
		{
			time4[i]=time0[i]+time1[i]*10;			
		}
	for(i=0;i<7;i++)
	{
		if(time4[i]!=time3[i])
		{
			buzzflag=0;
			break;
		}
	}
	if(buzzflag==1)
	{
		ringflag=1;
	}
	if(soundflag==1&&ringflag==1)
	{
		P2andP1(0xA0,0x40,0);
		k++;
	}
	if(k>20000)//响一段时间可以自动关
	{
		k=0;
		ringflag=0;
		P2andP1(0x80,0xFF,0);
	}
}

void view()//数码管显示内容选择
{
	switch(viewflag)
		{
			case 0://上电就显示时分秒
				timeshow(0);
				break;
			
			case 4://控制显示时分秒还是年月日
				if(mode1==0)timeshow(0);
				if(mode1==1)timeshow(1);
				break;
			
			case 6://调时分秒时显示时分秒
				smg_set(0,(flashflag3==0)?17:time[2]/10);
				smg_set(1,(flashflag3==0)?17:time[2]%10);

				smg_set(2,16);
				smg_set(3,(flashflag2==0)?17:time[1]/10);
				smg_set(4,(flashflag2==0)?17:time[1]%10);
				
				smg_set(5,16);
				smg_set(6,(flashflag1==0)?17:time[0]/10);
				smg_set(7,(flashflag1==0)?17:time[0]%10);
				
				break;
			
			case 7://调年月日时显示年月日
				smg_set(0,(flashflag3==0)?17:time[6]/10);
				smg_set(1,(flashflag3==0)?17:time[6]%10);
				smg_set(2,16);
				smg_set(3,(flashflag2==0)?17:time[4]/10);
				smg_set(4,(flashflag2==0)?17:time[4]%10);		
				smg_set(5,16);
				smg_set(6,(flashflag1==0)?17:time[3]/10);
				smg_set(7,(flashflag1==0)?17:time[3]%10);
				break;							
		}
}

void voice()//按键声音
{
	vioceflag=1;
	if(vioceflag==1&&numberfeedback==0)P2andP1(0xA0,0x40,0);
}

void main()//初始化和按键逻辑判断
{
	P2andP1(0xA0,0,0);//关闭蜂鸣器继电器
	Timer0_Init();//定时器0初始化(1ms)
	init_ds1302();//1302初始化
	setTime0();//设置初始时间
	while(1)
	{	
		value=Keys();			
		switch(value) //按键功能控制
		{
			case 4://选择显示的时间是年月日还是时分秒
				voice();
				mode1++;
				mode1=mode1%2;
				viewflag=4;
				break;
			
			case 5://选择需要设置的时间
				voice();
				mode2++;
				mode2=mode2%2;
				viewflag=5;	
				timeset();
				break;
			
			case 6://选择设置时分秒还是年月日
				voice();
				if(viewflag==6||viewflag==7)
				{
					mode++;
					mode=mode%3;
				}
				break;
				
			case 7://+				
				voice();
				if(viewflag==6)
				{
					switch(mode)
					{
						case 0://控制秒
							time[0]++;
							if(time[0]>59)time[0]=0;
							break;
						
						case 1://控制分
							time[1]++;
							if(time[1]>59)time[1]=0;
							break;
						
						case 2://控制时
						time[2]++;	
						if(time[2]>23)time[2]=0;
							break;
					}
				}	
				if(viewflag==7)//+
				{
					switch(mode)
					{
						case 0://控制日
							time[3]++;
							if(time[4]==1||time[4]==3||time[4]==5||time[4]==7||time[4]==8||time[4]==10||time[4]==12)
							{
								if(time[3]>31)time[3]=1;
							}
							else if(time[4]==4||time[4]==6||time[4]==9||time[4]==11)
							{
								if(time[3]>30)time[3]=1;
							}
							else if(time[4]==2)
							{
								if(time[6]%4==0)
								{
									if(time[3]>29)time[3]=1;
								}
								else 
									if(time[3]>28)time[3]=1;			
							}
							break;
						
						case 1://控制月
							time[4]++;
							if(time[4]>12)time[4]=1;
							break;
						
						case 2://控制年
						time[6]++;	
						if(time[6]>99)time[6]=0;
							break;
					}
				}
				break;			
			
			case 8:
				voice();
				if(viewflag==6)
				{
					switch(mode)
					{
						case 0://控制秒
							time[0]--;
							if(time[0]<0)time[0]=59;
							break;
						
						case 1://控制分
							time[1]--;
							if(time[1]<0)time[1]=59;
							break;
						
						case 2://控制时
							time[2]--;
							if(time[2]<1)time[2]=23;
							break;
					}
				}
				if(viewflag==7)//-
				{
					switch(mode)
					{
						case 0://控制日
							time[3]--;
							if(time[4]==1||time[4]==3||time[4]==5||time[4]==7||time[4]==8||time[4]==10||time[4]==12)
							{
								if(time[3]<1)time[3]=31;
								if(time[3]>31)time[3]=1;
							}
							else if(time[4]==4||time[4]==6||time[4]==9||time[4]==11)
							{
								if(time[3]<1)time[3]=30;
								if(time[3]>30)time[3]=1;
							}
							else if(time[4]==2)
							{
								if(time[6]%4==0)
								{
									if(time[3]<1)time[3]=29;
									if(time[3]>29)time[3]=1;
								}
								else
                                {
								    if(time[3]<1)time[3]=28;
									if(time[3]>28)time[3]=1;
                                }									
							}
							break;
						
						case 1://控制月
							time[4]--;
							if(time[4]<1)time[4]=12;
							break;
						
						case 2://控制年
						time[6]--;	
						if(time[6]<0)time[6]=99;
							break;
					}
				}
				break;
			
			case 9://确认更改
				voice();
				if(alarmflag==1)
				{
					for(i=0;i<7;i++)
					{
						time3[i]=time[i];
						time5[i]=time[i];
					}
					timeget();
					alarmflag=0;
					P2andP1(0x80,0xFE,0);//有闹钟时指示灯提示
				}			
				else	setTime0();
				break;
			
			case 10://闹钟
				voice();
				timeget();
				alarmflag=1;	
				break;
			
			case 11://取消或关闭闹钟
				voice();	
				ringflag=0;
				for(i=0;i<7;i++)
				{
					time3[i]=0;
				}
				timeget();
				P2andP1(0x80,0xFF,0);
				break;
			
			case 12://把上次闹钟值在设置界面显示
				voice();
				for(i=0;i<7;i++)
				{
					time[i]=time5[i];
				}
			
			case 13://关闭或打开按键声音
				voice();
				numberfeedback++;
				numberfeedback=numberfeedback%2;
		}
		alarm();
		view();					
	}
}

void waterled() interrupt 1
{
	static unsigned int T0=0,T1=0,T2=0,T3=0;
	T0++;T1++;T2++;T3++;
	if(T0>=20)//按键扫描
	{
		T0=0;
		key_loop();
		
	}
	if(T1>=2)//数码管扫描
	{
		T1=0;
		smg_loop();	
	}
	if(T2>=500)//显示位闪烁
	{
		T2=0;
		switch(mode)
		{
			case 0:flashflag1=!flashflag1;flashflag2=1;flashflag3=1;break;//其它位设置1是为了防止切换位之后原来的位还在闪
			case 1:flashflag2=!flashflag2;flashflag1=1;flashflag3=1;break;
			case 2:flashflag3=!flashflag3;flashflag2=1;flashflag1=1;break;
		}
	}
		if(T3>250)//控制发声
	{
		T3=0;
		
		if(vioceflag==1)
		{
			vioceflag=0;
			P2andP1(0xA0,0,0);
		}
		if(soundflag==1)soundflag=0;
		else if(soundflag==0)soundflag=1;P2andP1(0xA0,0,0);		
	}
}

每个变量的作用

1. `unsigned char mode1, mode2, mode, value, i`: 
   - `mode1` 和 `mode2` 用于表示当前的时间显示模式或设置模式(如时分秒/年月日)。
   - `mode` 用于在调时间时进一步细分要调整的是小时、分钟还是秒或者年、月、日。
   - `value` 存储从按键扫描函数得到的按键值。
   - `i` 是一个通用循环索引变量,在循环结构中使用。

2. `unsigned char viewflag, flashflag1, flashflag2, flashflag3`: 
   - `viewflag` 用于控制数码管显示的内容,比如显示当前时间还是进入设置界面。
   - `flashflag1`, `flashflag2`, `flashflag3` 在设置时间时用作闪烁效果标志位,决定哪个数字位置进行闪烁提示。

3. `unsigned char vioceflag, alarmflag, soundflag, numberfeedback, ringflag`:
   - `vioceflag` 用于触发按键音效。
   - `alarmflag` 标记是否正在设置闹钟时间。
   - `soundflag` 控制蜂鸣器发声状态。
   - `numberfeedback` 用于控制按键声音反馈的开关。
   - `ringflag` 表示闹钟是否正在响起。

4. `char time[]` 及相关数组(`time3[7]`, `time4[7]`, `time5[7]`):
   - `time` 数组存储了读取到的当前时间信息。
   - `time3[7]` 存储预设的闹钟时间。
   - `time4[7]` 临时存储当前时间,用于与闹钟时间对比。
   - `time5[7]` 保存上次设定过的闹钟时间。

 程序的实现逻辑

1. **初始化阶段**:
   - 初始化单片机端口和定时器(如`P2andP1`函数控制蜂鸣器继电器以及`Timer0_Init`初始化定时器0)。
   - 初始化实时时钟芯片DS1302(`init_ds1302`)并设置初始时间(`setTime0`)。

2. **按键处理循环**:
   - 主循环中持续读取按键值(`value=Keys()`)并对不同的按键动作做出响应。
   - 按键对应的功能包括切换显示内容(年月日与时分秒)、选择需要调整的时间部分(小时/分钟/秒或年/月/日)、增减数值以及确认更改等。 

3. **时间显示与调整**:
   - `timeshow()` 函数用于在数码管上正确显示当前时间或日期。
   - 当进入时间设定模式时(`viewflag==6` 或 `viewflag==7`),`view()` 函数会根据闪烁标志(`flashflag1, flashflag2, flashflag3`)来实现数字的闪烁效果,并显示待调整的时间或日期。
   - 用户通过按键增加或减少所选时间字段的值。

4. **闹钟功能**:
   - 通过按键操作可以设定闹钟时间(存储在`time3`数组中),并在之后的循环中用`alarm()`函数比较当前时间和预设的闹钟时间。
   - 当闹钟时间到时,`ringflag`被置为1,触发蜂鸣器发出铃声,响铃一段时间后自动停止。

5. **中断服务程序 waterled()**:
   - 定期执行一些周期性任务,如按键扫描、数码管扫描以实现动态显示效果,以及控制发声反馈的开关。

各函数解析

2. `timeset()` 函数根据`mode2`的选择来设置查看(或调整)时分秒或年月日的时间。
3. `timeshow()` 函数用于在数码管上显示当前时间,参数决定显示时分秒还是年月日。
4. `timeget()` 函数将当前读取到的时间合并存储至数组中。
5. `alarm()` 函数用于对比当前时间和预设的闹钟时间,并在匹配时触发蜂鸣器报警。
6. `view()` 函数负责控制数码管显示内容,根据不同的`viewflag`值切换显示时分秒、年月日或者处于调时状态下的闪烁时间数字。
7. `voice()` 函数处理按键声音反馈。
8. `main()` 函数是主程序,初始化相关硬件并循环检测按键输入,根据按键值执行相应的功能,如切换显示模式、设置时间、调整时间、确认更改、设置闹钟等。
9. `waterled()` 是定时器中断服务函数,用于周期性地进行按键扫描、数码管扫描以及实现显示位的闪烁和按键音效等功能。

head.h

这只是一个小程序,所以所有的函数就放在一起声明了,当然这不是一个好习惯.

#ifndef __HEAD_H__
#define __HEAD_H__
#include <STC15F2K60S2.H>
#include <intrins.h>
extern unsigned char time0[],time1[];					//时间储存,十位和个位
extern char time[];                           //传给1302的要写入的时间
void P2andP1(unsigned char p2,p0,p42);				//P2P0P42控制										
void Timer0_Init();                           //定时器0初始化
void init_ds1302();                           //时钟初始化
void settime();                               //写入时钟
void readtime();                              //读取时钟
unsigned char key_loop();                     //按键扫描
void smg_loop();                              //数码管扫描
void smg_set(unsigned char location,number);  //显示内容设置
unsigned char Keys();                         //按键读取
#endif

timer.c

STC-ISP生成的 

#include <head.h>
void Timer0_Init()		//1毫秒@12.000MHz
{
	AUXR &= 0x7F;			//定时器时钟12T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0x18;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;
	ET0 = 1;				//使能定时器0中断
	EA=1;
	PT0=0;
	PX0=0;
}

P2andP1andP42.c

应该不用多解释为什么这三个端口的处理放在一起吧

#include <head.h>
void P2andP1(unsigned char p2,p0,p42)
{
	P42=p42;
	P0=p0;
	P2=p2;
	P2=0;
}

smg.h

主程序中要调用定时器扫描smg_loop(),这是共阴极数码管

#include <head.h>
code unsigned char Seg_Table[] = 
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0x88, //A  10
0x83, //b  11
0xc6, //C  12
0xa1, //d  13
0x86, //E  14
0x8e, //F  15
0xBF,  //-  16
0xFF	//none 17
};
unsigned char SMG[8]={17,17,17,17,17,17,17,17};//记住从0开始
void smg_set(unsigned char location,number)
{
	SMG[location]=number;

}
void smg(unsigned char loc,word)
{		
	P42=1;P0=0;
	switch(loc)
	{
		
		case 0:P2andP1(0xc0,0x01,0);break;
		case 1:P2andP1(0xc0,0x02,0);break;
		case 2:P2andP1(0xc0,0x04,0);break;
		case 3:P2andP1(0xc0,0x08,0);break;
		case 4:P2andP1(0xc0,0x10,0);break;
		case 5:P2andP1(0xc0,0x20,0);break;
		case 6:P2andP1(0xc0,0x40,0);break;
		case 7:P2andP1(0xc0,0x80,0);break;
		
	}
	P2andP1(0xe0,Seg_Table[word],0);
	
}
void smg_loop()
{
	static unsigned char i=0;
	
	smg(i,SMG[i]);
	SMG[i]=17;
	i++;
	
	if(i>=8)i=0;

}

解析

`Seg_Table[]` 数组存储了数码管显示每个数字和符号的段码,用于驱动共阴极数码管。例如,数组中的第一个元素 `0xc0` 对应于数码管显示数字 '0' 时各段的亮灭状态。

`SMG[8]` 数组初始化为17(表示不显示任何有效数字或符号),用于保存数码管各位置要显示的段码对应的索引值。

`smg_set(unsigned char location, number)` 函数将指定位置(location)的数码管设置为显示对应 `number` 索引处的字符。

`smg(unsigned char loc, word)` 函数用于实际更新数码管的显示内容:
- 首先通过 `P42` 和 `P0` 设置相关的控制线状态。
- 根据输入的 `loc` 参数选择要操作的数码管位置,调用 `P2andP1()` 函数设定相应的位选信号。
- 最后,根据输入的 `word` 参数从 `Seg_Table[]` 数组中获取对应的段码,并通过 `P2andP1()` 函数输出到数码管的段选线上。

`smg_loop()` 函数是一个循环显示函数
- 它包含一个静态变量 `i` 用于记录当前要显示的数码管位置。
- 在每次调用时,首先调用 `smg(i, SMG[i])` 更新当前数码管位置的内容,并将该位置的显示内容恢复为无效(即17,代表无有效显示)。
- 然后递增 `i`,当 `i` 达到8时重置为0,实现循环显示数码管上的所有位置。

arrkeys.c

定时器版的矩阵键盘,同样也需要主程序调用扫描.和视频中的键盘稍有不同,keynumber设置为局部变量,这样定时器每一次扫描都会重置为0,而不是在keys中重置.

key变量必须定义为局部变量!!!否则在按下某同样的按键切换不同功能时会因为key不能及时重置为0导致仿佛那个键一直在按着.(当时可是懵逼了好几天)

#include <head.h>
unsigned char matrixkey()
{
	unsigned char key=0;
	P34=0;P35=1;P42=1;P44=1;
	if(P30==0){key=19;}
	if(P31==0){key=18;}
	if(P32==0){key=17;}
	if(P33==0){key=16;}
	
	P35=0;P34=1;P42=1;P44=1;
	if(P30==0){key=15;}
	if(P31==0){key=14;}
	if(P32==0){key=13;}
	if(P33==0){key=12;}
	
	P42=0;P35=1;P34=1;P44=1;
	if(P30==0){key=11;}
	if(P31==0){key=10;}
	if(P32==0){key=9;}
	if(P33==0){key=8;}
	
	P44=0;P42=1;P35=1;P34=1;
	if(P30==0){key=7;}
	if(P31==0){key=6;}
	if(P32==0){key=5;}
	if(P33==0){key=4;}
	
	return key;

}
unsigned char key_loop()
{
	static unsigned char nowstate=0,laststate=0;
	unsigned char keynumber=0;
	laststate=nowstate;
	nowstate=matrixkey();
	if(laststate==0&&nowstate==4)
	{
		keynumber=4;
	}
	if(laststate==0&&nowstate==5)
	{
		keynumber=5;
	}
	if(laststate==0&&nowstate==6)
	{
		keynumber=6;
	}
	if(laststate==0&&nowstate==7)
	{
		keynumber=7;
	}
		if(laststate==0&&nowstate==8)
	{
		keynumber=8;
	}
		if(laststate==0&&nowstate==9)
	{
		keynumber=9;
	}
		if(laststate==0&&nowstate==10)
	{
		keynumber=10;
	}
		if(laststate==0&&nowstate==11)
	{
		keynumber=11;
	}
		if(laststate==0&&nowstate==12)
	{
		keynumber=12;
	}
		if(laststate==0&&nowstate==13)
	{
		keynumber=13;
	}
		if(laststate==0&&nowstate==14)
	{
		keynumber=14;
	}
		if(laststate==0&&nowstate==15)
	{
		keynumber=15;
	}
		if(laststate==0&&nowstate==16)
	{
		keynumber=16;
	}
		if(laststate==0&&nowstate==17)
	{
		keynumber=17;
	}
		if(laststate==0&&nowstate==18)
	{
		keynumber=18;
	}	
		if(laststate==0&&nowstate==19)
	{
		keynumber=19;
	}
	return keynumber;	

}
unsigned char Keys()
{
	unsigned char Temp=0;
	Temp=key_loop();
	return Temp;
}

解析

1. `unsigned char matrixkey()` 函数:
   - 该函数通过设置不同组合的行线(P34、P35)和列线(P42、P44)为高或低电平,依次检测每行的所有按键是否被按下。
   - 如果某个按键按下(对应IO口读取到低电平),则返回对应的按键值(0-19)。

2. `unsigned char key_loop()` 函数
   - 定义两个静态变量`nowstate`和`laststate`分别存储当前状态和上一状态的按键值。
   - 调用`matrixkey()`获取当前按键状态,并与上一次按键状态进行比较。
   - 只有当上次按键状态为0(未按下任何键)且当前按键状态不为0(表示有一个键被按下)时,才将当前按键值转换为数字键号并返回。

3. `unsigned char Keys()` 函数:
   - 直接调用`key_loop()`函数获取按键值并返回。

ds1302.c

相比视频代码所做的改进是设置时间函数和读取时间函数采用了for循环执行,更加精练,设置时间函数的要写入数据还可以统一处理,注意要把宏定义的东西用一个数组来储存,顺序不要错.

因为是数码管显示,所以采用个位和十位分开两个数组来储存,因此在读取时间的时候获得十位数时整除16即可不需要乘10.

#include <head.h>
sbit sclk=P1^7;
sbit io=P2^3;
sbit ce=P1^3;//定义位变量方便操作
unsigned char temp;
unsigned char time0[7],time1[7],time2[7];//储存时间的数组

#define SECOND  0X80
#define MINUTE  0X82
#define HOUR    0X84
#define DATE    0X86
#define MONTH   0X88
#define DAY     0X8A
#define YEAR    0X8C
#define WP      0X8E//用于文本替换
unsigned char define[]={SECOND,MINUTE,HOUR,DATE,MONTH,DAY,YEAR};
void init_ds1302()//初始化函数
{
	ce=0;
	sclk=0;

}	
void write_ds1302(char com,Data)
{
	unsigned char i=0;
			ce=1;//前后开关ce
	for(i=0;i<8;i++)//操作某个时间的读
	{
		io=com&(0x01<<i);//一位一位与
		sclk=1;
		sclk=0;
	
	}
	for(i=0;i<8;i++)//写具体数据
	{
		io=Data&(0x01<<i);
		sclk=1;
		sclk=0;
	
	}
		ce=0;

}
unsigned char read_ds1302(char com)
{
	unsigned char i=0,Datas=0x00;
	ce=1;
	com|=0x01;//写的最低位置一就是读
	for(i=0;i<8;i++)
	{
		io=com&(0x01<<i);
		sclk=0;
		sclk=1;
	
	}
	for(i=0;i<8;i++)
	{ 
		sclk=1;//1001是为了统一8个循环
		sclk=0;
		if(io)//如果某个位有值才执行写入
			Datas|=(0x01<<i);//妙
	
	}
	ce=0;
	io=0;//关闭
	
	return Datas;

}
void setTime0()
{
	unsigned char i=0;
	write_ds1302(WP,0x00);//关闭写保护
	for(i;i<7;i++)
	{
		time2[i]=time[i]/10*16+time[i]%10;
		write_ds1302(define[i],time2[i]);
	
	}
}
void readTime0()
{
	unsigned char i=0;for(i;i<7;i++)
	{
		temp=read_ds1302(define[i]);
		time1[i]=temp/16;time0[i]=temp%16;
	
	}
}

解析

1. 定义了与DS1302连接的单片机引脚,包括sclk(时钟信号)、io(数据线)和ce(使能信号)。

2. 定义了3个存储时间的数组:`time0[7]`、`time1[7]` 和 `time2[7]`。其中,`time0[7]` 和 `time1[7]` 分别用于存储读取到的时间的十位和个位数字,`time2[7]` 用于临时存放要写入DS1302的时间数据(将十位和个位合并成一个字节)。

3. 定义了一系列常量对应DS1302内部寄存器地址,如秒、分钟、小时等。

4. `init_ds1302()` 函数负责初始化DS1302,主要是将CE拉低,确保在进行通信前DS1302处于待命状态。

5. `write_ds1302(com, Data)` 函数用来向DS1302写入指令和数据。首先通过IO口一位一位发送指令(com),然后同样方式发送具体的数据(Data)。最后释放CE信号完成写入过程。

6. `read_ds1302(com)` 函数用于从DS1302读取指定寄存器的内容。首先发送带有读标志的指令,接着接收8位数据并将其组合成最终结果返回。

7. `setTime0()` 函数用于设置DS1302的当前时间。先关闭写保护功能,然后对每一个时间字段进行处理,将十位数乘以16加上个位数得到一个字节,再调用 `write_ds1302()` 将其写入DS1302相应的寄存器中。

8. `readTime0()` 函数用于从DS1302读取当前时间,并分别将读取到的每个字节拆分成十位和个位,存入对应的 `time1[7]` 和 `time0[7]` 数组中。

(三)说明书

(为了显得有仪式感一点)

产品说明书:

标题: 实时时钟与闹钟系统


一、产品概述

本产品是一款基于C51单片机开发的实时时钟与闹钟系统,具备时分秒和年月日显示、时间调整、闹钟设置与触发、声音反馈以及数码管显示等功能。用户可以通过按键进行各项操作,系统通过中断服务程序实现低功耗、高效率的运行。


二、功能模块说明

  1. 时间显示
    • 上电后默认显示当前的时分秒。
    • 用户可通过按键切换至年月日显示。
  2. 时间设置
    • 用户可选择设置时分秒或年月日。
    • 通过"+"和"-"键可以递增或递减当前选择的时间单位(如秒、分、小时、日期等)。
    • 系统自动处理大小月和闰年的规则。
  3. 闹钟功能
    • 用户可以设定闹钟时间和日期,并在到达指定时刻时触发蜂鸣器响铃。
    • 响铃持续一段时间后自动停止,并支持手动关闭。
    • 可保存和恢复上次设置的闹钟时间。
  4. 数码管显示
    • 数码管采用动态扫描方式显示当前时间和设置时间,且在设置过程中对应位置的数字会闪烁提示用户。
    • 支持显示内容的闪烁效果。
  5. 声音反馈
    • 按键操作时有声音反馈,用户可根据需求开启或关闭此功能。

三、操作指南

1. **模式切换键(按键4**

   功能:用于切换显示内容。每按一次此键,数码管将从显示当前时分秒切换至显示年月日,反之亦然。

2. **时间设置选择键(按键5**

   功能:用于选择需要调整的时间类型。每次按下该键时,将在设置时分秒年月日之间切换,并进入相应的设置界面。

3. **单位切换键(按键6**

   功能:在调整时分秒年月日时,通过此键可以逐级切换调整的单位。例如,在调整时分秒时,依次切换为秒、分钟、小时;在调整年月日时,依次切换为日、月、年。

4. **增加键(按键7**

   功能:在设置时间状态时,按下此键可使选定的时间单位递增。如在调整秒数时,按一次增加一秒;调整日期时,按一次增加一天等。

5. **减少键(按键8**

   功能:与增加键相反,在设置时间状态时,按下此键会使选定的时间单位递减。例如,调整秒数时,按一次减少一秒;调整日期时,按一次减少一天等。

6. **确认键(按键9**

   功能:完成对当前时间的设定后,按下此键以保存更改并退出设置模式。若已启用闹钟功能,则会将当前设置作为新的闹钟时间存储,并在有闹钟设置时点亮相关指示灯。

7. **闹钟开启/关闭键(按键10**

   功能:按下此键可启动或关闭闹钟功能。当闹钟开启时,系统会在预设的时间到达时触发提醒。

8. **取消闹钟键(按键11**

   功能:用于取消当前已设置的闹钟,同时停止正在响起的闹钟提示音,并清除闹钟设置。

9. **获取当前时间键(按键12**

   功能:在设置界面下,按下此键可以立即读取当前实际时间和日期,并在设置界面上显示出来。

10. **查看上次闹钟键(按键13**

    功能:在设置界面中,按下此键将自动加载并显示最后一次设定的闹钟时间。

11. **声音开关键(按键14**

    功能:用于开启或关闭按键音效反馈。每次按下此键,按键声音功能的状态将在开启和关闭间切换。

请按照上述说明进行操作,确保正确无误地设置和使用您的数字时钟。同时请注意,本时钟在处理日期时会自动考虑大小月及闰年的天数差异。如有任何疑问,请查阅完整的产品说明书。

  • 30
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值