51实现闹钟

前言

第一次写博客,使用CSDN也好几年了,第一次作为创作者的身份去写博客,以前都是在印象里面做做笔记这样的,今天把我这次51实验的程序作为博客生涯的第一篇吧,加油。

目标:

通过51单片机设计一个简单的闹钟,具体为:可以实现基本时钟功能,按键还能切换日期,和闹钟,且能自由设置。后续添加支持新建闹钟,理论支持无限添加新的闹钟。

所需元件:

硬件部分:51单片机、数码管、蜂鸣器。

原理图

实验板原理图
以上就是我们的硬件组成,关于基本的硬件原理、工作流程等相信大家耳熟能详,我这里不在赘述,而我们用到的k1-显示切换,k2-选中调整,k3按键加,k4-按键减。

软件部分:

基本的工程创建我就不介绍了,下面是工程目录结构,主要包含两个目录一个存放源文件,一个存放工具类文件,工具类文件夹里面是我在写其他的程序的时候,抽离出来的一些公共的方法,声明在头文件中,而头文件无需导入,只要在软件中指定其路径即可使用。

工程目录

工程目录

程序流程图

程序流程图
关于工程设计的基本思想,在我们的流程图中得到体现,读者可以仔细研磨,这对后面讲解代码部分有帮助,而我们的代码讲解也会按照流程图顺序来介绍。

软件代码:

1、 定时器初始化。使用两个定时器,T0和T1都用到了,这在之前本来是不需要的,但是后来发现了一点问题,先说现象:当我按下按键进行模式选择的时候或者其他的按键的时候,发现秒钟不准了,一按按键,速度就不正常了,原因:后来发现我在按键扫描的软件防抖的时候,用的和我们系统一样的定时器,那就有问题了,后来发现了,然后换成使用两个定时器,一个作为系统时钟,一个是软防抖的,代码就不贴了,比较简单,读者可自行编写。
2、选择默认显示的参数。这里本来不需要单独作为一个流程的,但是如果是这样,后面的现象都解释不清楚,那他很简单了,定义两个一维数组,用来存放我们的时间和日期,然后定义一个二维数组存放闹钟,还有一个数组指针。在我们需要显示的时候把我们的指针指向谁,然后把指针传递给我们数码管显示,也就是说我们不直接操作我们定义的任何一个数组,而是间接的通过指针来访问(本来指针是直接访问)。

u16   Time[3] = {10 , 23 , 0 };
u16   Date[3] = {20 , 12 , 1 };

u16  AlarmClock[3] = {10 , 23 ,10};

u16  *TimOrDatOrClock;

sbit beef = P1^5;

3、按键判断。按键判断我这里为了严谨,使用的上升沿检测,按键按下数值不变,手松开按键,才视为一次按键事件,具体如下:

/*做到上升沿检测*/
u8 key_scan(void)
{
	u8 status = null;
	status =(Serial_Key & 0x0f) ;
	
    if(status != 0x0f){ 
			 wait_ms(14); 
         if((Serial_Key & 0x0f)  == 0x0f) return status; 
     }

	return null;
}

注意:Serial_Key 我定义的是P3 口,因为P3口用到了串口和按键,这在原理图中可见,读者可返回参看。
4、检测闹钟时间是否到了。没有其他的,判断当前的时间是不是和闹钟的时间相等就可以做到,具体如下:
简单介绍:定义一个变量为3,当我们小时一致是减去1,再判断分钟一致时减去1,最后是秒,当我们的所有的全部一致时,我们的stat 应该是0。如果我们的stat是0的话应该和我们的is_beef 取反是一致的,is_beef 是我们用来标识蜂鸣器是不是在响,当我们有多个闹钟的时候,且时间在十秒之内,那就不会重复执行,tim是我们用来展示闹钟响铃的时间的,这里可以注意下,后面会讲到我们是如何用它来做一个逐渐急促的闹钟声的。
5、数码管显示。这个是我们比较重点的部分,因为我在这部分花费的时间是最多的,不是很难,但是结合控制闪烁,显示切换和其他一些要考虑的东西,那就显得十分繁琐了,基本思想我们清楚显示中有一部分不变,作为横杠的分割部分,下面直接看代码展示:

	void is_Colck()
	{
		 u8 stat = 3;
			for(i = 0; i < 3 ; i++)
			{ 
			  if(Time[i] == AlarmClock[i]) stat--;
			}
			
		if(	is_beef = !stat ) tim = 249;
	}
void Disp_Vue_Plus(u16 *vue)
{
	u8 status = 8;
	u8  j , i ;
	if(k >= 1) k--;
	
	for( j = 0 ; j < 3 ; j++)    // 8   0   0
	{
					map[j*3]     = (vue[j]/10) % 10;
		      map[j*3 + 1] =  vue[j] % 10;
  }
	
	for(i = 0; i < status ;i ++ )
	{
	    j = 350;
	 	Dis_chip_Sel   = chip_sele[status- i-1];
			if(i == 2 || i == 5) DISP = num[10];
			 else  //不是分割部分
			{
		    	if( IS != 0 )//没有任何值被选中
				{
				  if(i == (IS-2) || i == (IS-3))//判断是否选中当前值
				 	if(k < 80)
				    {
					  DISP = num[map[i]];
      				  if(k< 2) k = 200;  //控制闪烁关键部分
	    			} else continue;
				}
				DISP = num[map[i]];	//显示数值
			}
		 while(j--);
		 DISP = 0x00;
	}
}

读者是不是很困惑,对,使用了大量的IF判断,因为也是需要大部分的条件需要判断,具体是哪一些,听我一一道来。
首先是我们开头定义的 status 本来做为我们控制显示位数的变量的,后来放弃了这个想法,因为没有必要。然后走到第一个 For 循环,他的作用就是对我们的传入的时间日期进行分割,个位和十位分开用一个数组的连续两位来标识,方便后面我们在对其显示的时候取值。
第二个For循环,开始直接确定该哪个数码管显示,这是不需要犹豫的,然后是我们要对其所显示的值做判断了,首先判断当前显示的是不是第2位和第5位,规定了是显示“–”的。然后判断当前数码管是不是被选中,不是的话,也可以直接显示,不做判断;如果是的话,我们就要让他闪烁显示,再次判断它是不是我们真的显示的那两个数码管,减2是因为,我按下一次按键iS是加3的,加三的原因是3、6、9刚好可以和我们数码管在数组中定义的下标有一定的联系,他具体表现为 小时占据两位(6和7)、分钟占据两位(3和4),秒钟(0和1),按键数值减去3和2即可得到上述数码管的数组下标:时钟 = 9 - 3 和 9 - 2;如果以上条件均满足,那就是满足闪烁条件了,定义一个变量,理解:其他数刷新30遍,其才刷新1遍,30是假设,读者可分析,另外,此段代码可以精简,感兴趣的读者,可以尝试,欢迎和我讨论。
1 - 1、 按键扫描结果:之前讲到按键扫描,具体对他的处理,相信读者心中应该有一个宏图了,这很好,说明您是学习到了的,我们继续,代码如下:

		while(1)
		{
			CarryBit(Time ,2,2,2);	
			switch(key_scan())
			 {
				case Key_Mode: //调整
					IS += 3;
					 if(IS >= 10) IS = 0;
					break;
				case Key_:     //按键减
                    Decide(0);
					break;
				case Key_Add:
					Decide(1);
				     if(IS == 0) ;
					break;
				case Key_TXD:
				     Change(Tor);
				     Tor ++;
			       if(IS == 0) ;
			}
//			if( !Tor ) TimOrDatOrClock = Time;  else   TimOrDatOrClock = Date;
			
			Disp_Vue_Plus( TimOrDatOrClock );   //Tor
			is_Colck();
		}
	}

分析:用一个switch接收到我们得到的按键,分别是四个按键,我们把我们需要和硬件定义的值做了封装,便于后期更改,养成来良好的代码习惯很重要,这一点读者需要学习,这在以后的任何的开发中是十分常见的,定义在后面放出图片方便对比观看。
之前介绍到模式按键是对IS加3的操作,想一想我们之前将数码管的部分,想不起来?我来给读者复述一下:本来 IS 是 0 的,0 减去 2 和减去 3 不会对任何数码管选中;然后按下了一次,IS 为 3,判断 IS - 2 和 IS - 3 的值那就是 1和 0 了对应得数组下标的值的数码管就相当于选中,这个IS变量十分关键,贯穿了我们整个工程,后面我们判断是否有按键按下了(选中了),当前选中的是哪一个(通过 IS 减 1 或 2 得到),十分重要,需要理解。
Key_ 表示的是我们按键减按下执行的部分,这里使用的不是函数,而是宏定义(Decide(0))万物皆可宏定义,感兴趣的读者可以查阅资料。
细心的读者,应该发现我后面有一句if(IS == 0)未作任何处理,判断的就是有没有任何按键按下,有没有选中的值,判断的原因方便我们做后续功能拓展,比如说,我们没有按下模式,直接按下加或者减键那这个状态是不是富余出来的了,好的利用起来,在我们的程序流程图中已经做好了功能定义,但是作者未能有时间去实现,有精力的读者朋友可以帮我完善,欢迎改进、和我讨论。

#define Serial_Key    P3    //把串口和按键定义出来

#define Key_Mode      0x0e   
#define Key_TXD       0x0d
#define Key_          0x0b
#define Key_Add       0x07

#define Dis_chip_Sel  P2

还有我们宏定义部分:

#define    Decide(OV)    for( i = 0; i < 3; i++)                                    \
                           if(OV){                                                  \
                             if(IS == ((i+1)*3))                                 \
                                TimOrDatOrClock[i]++;                          \
                            }else {                                                 \
                           	  if(IS == ((i+1)*3) && TimOrDatOrClock[i] > 0)       \
                                TimOrDatOrClock[i]--;	  			                \
							}		

定义很简单,IS判断也作为了我们重要判断的依据之一,总的代码会循环3次表示全部判断一次,加和减的部分基本一致,需要注意的是语法:”\”宏定义不能分行用它来表示该行是不分段的,注意” \”后面不能有任何符号,空格也不行,否则会报错,逻辑简单,就不具体分析了,代码就代表我的思路。

最后是我们的模式切换按键了,分别显示时间、日期和闹钟


#define    Change(EX)   switch(EX)            {                                     \
														case 1:  TimOrDatOrClock  = 	AlarmClock;  break;       \
														case 2:  TimOrDatOrClock  = 	Time;        break;       \
														case 3:  TimOrDatOrClock  = 	Date;        break;       \
												    default: EX = 0;  }

EX为我们定义的一个值,每次按下加1,到3归0 ;

2-1 闹钟时间判断部分。之前讲到怎么判断闹钟时间到了,这次我们讲一下当我们时间到了的时候,怎么实现蜂鸣器做有急促的响声,代码如下:

	void is_Colck()
	{
		 u8 stat = 3;
			for(i = 0; i < 3 ; i++)
			{ 
			  if(Time[i] == AlarmClock[i]) stat--;
			}
			
		if(	is_beef = !stat ) tim = 249;
	}
	
	void irq_Time() interrupt 1
	{
		Tim_s();  //定时器赋予初值
		times++;
		if( times == 20)  //一秒时间
		{
				times = 0;	
				Time[2] ++;
		}
		if(tim < 250) { if((tim % (tim/40)) == 0)beef = !beef; tim--; }
		if(tim == 0) tim = 250;
	}

我在之前想到,每次定时器都有大量的中断,并且时间固定50ms,如果不不利用起来太可惜了,然后就加上了闹钟的功能,当然还可以有其他的操作可以实现,读者可以大发想象之门。tim在我们闹钟时间到了之后被赋值为250,可以改作为闹铃时间的长短,当它小于250表示闹钟时间到,蜂鸣器该响了,然后tim为40的倍数的时候取反一次,因为我们tim是在不断减小的,所以为零的情况会越来越多,表现出来就是蜂鸣器越来越急促的响声。
3、进位扫描。在我们while(1)循环中还有重要的一环,那就是进位检测,判断每一位的进位条件是否满足,代码如下:

/*   进位扫描  */
void CarryBit(u16 *arr , u16 MaxHeiBit ,u16 MaxConBit ,u16 MaxLowBit )
{
	u8  j = 0;
	if((MaxHeiBit | MaxConBit | MaxLowBit) == null)
	{  //还没实现以后有时间  ha 
		
	}
	else
	{
			for( j = 2 ; j > 0 ; j--)
				if(arr[j] >= 60) { arr[j] = 0; arr[j-1]++; }
	}

注意的是有一点:传入的参数,我开始的想法是做成一个模板,我解释一下,读者就明白了

参数作用
*arr需要做检测的主体
MaxHeiBit最高位的最大值, 例如:小时的最大位是 12
MaxConBit次高位的最大值,例如:分钟的最大位是 60
MaxLowBit低位的最大值, 例如:秒钟的最大位是 60

这样可以在主函数用一个变量做12小时制和24小时制的切换,遗憾的是我没有实现它。
现象:
闹钟
本次图片拍摄于,在我开始写这篇博客之前,显示的是日期;

本篇博客就到这里,有什么问题、疑问,欢迎在下方评论区留言讨论。

  • 8
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值