课设系列:51单片机制作智能时钟闹钟

操作演示视频

51单片机制作智能时钟闹钟



课设的基本要求

1)按键灵敏,做好消抖准备;
2)数码管显示正常,无残影;
3)在任意界面,时钟走时准确,无暂停;


一、硬件框图

在这里插入图片描述

二、功能描述

2.1.基本功能

1) 用数码管显示时钟和闹钟两个功能界面;
2) 通过独立按键实现界面切换、参数设置等功能;
3) 通过串口接收来自单片机的信息;

2.2.设计要求

1) 显示界面切换时间:≤0.3 秒
2) 按键反应时间:≤0.2秒

2.3(1)时钟显示功能

S8820822
提示符熄灭熄灭20分熄灭22秒

(2)闹钟显示界面

P8820822
提示符熄灭熄灭20分熄灭22秒

2.4按键功能

1)按键1:定义为“界面切换”,按下按键1,切换选择时钟和闹钟界面;
2)按键2:按下2号键时,第一下为时间暂停,接着按为分钟、秒之间的选择。
3)按键3:实现数据累加功能,有界限回滚;
4)按键4:实现数据递减功能,有界限回滚;

2.5串口功能

当闹钟的时间到了,单片机给电脑发送字符串“TIME IS UP”;

2.6初始状态说明

初始状态上电默认为处于时钟显示界面,默认显示数据11分11秒

S8811811
提示符熄灭熄灭11分熄灭11秒

三、评分标准

1).上电初始化界面显示正常——10分;
2) 按键1功能正常——5分;
3) 按键2功能正常——5分;
4) 按键3功能正常——5分;
5) 按键4功能正常——5分;
6) 时钟界面显示正常——20分;
7) 闹钟界面显示正常——20分;
8) 串口功能实现——20分;
9) 代码编写整洁、清晰——10分;

四、 程序编写

独立按键程序的编写

定义三个局部变量, k1_flag, k4_flag,k5_flag,进行数据存储。
详细注释,看代码,我有标注。

void KeyScan()//按键扫描
{
 	if(k1==0)
	{
	 	delay(1000);//按键消抖
	 	if(k1==0)   
   		{  
   			k1_flag=~k1_flag;//变量取反,判断按键1按下次数
			if(k1_flag==0)
				{ET0=1;} //开启定时器中断
   		}
		while(k1==0);//再次判断
	}
	if(k2==0)
	{
	 	delay(1000);
		if(k2==0)  
   		{
   			ET0=0;//关闭定时器中断
   			if(k1_flag==0)//时钟的移位操作
			{
   				k4_flag++; //数码管移位
   				if(k4_flag==4)
   				{ET0=1;k4_flag=0;}//开启中断;重新计数操作
   			}
			if(k1_flag==1)//闹钟的移位操作
			{
   				k5_flag++;//闹钟数码管移位
   				if(k5_flag==4)//数据累积到4
   				{k5_flag=0;}//清零操作	
  			}		
		}
		while(k2==0);
	}
	if(k3==0)/*对时钟、闹钟进行加法滚动*/
	{
	 	delay(1000);//按键消抖
		if(k3==0)
		{
		if(k4_flag==2)//按第一下为暂停,第二下开始对分钟开始加法运算
			{minute++;if(minute==60){minute=0;}}
		if(k4_flag==3)//第三下开始对秒进行加法运算
			{second++;if(second==60){second=0;}}
		if(k5_flag==2)//对闹钟的分中进行加法运算
			{minute1++;if(minute1==60){minute1=0;}}
		if(k5_flag==3)//对闹钟的秒进行加法运算
			{second1++;if(second1==60){second1=0;}}
		} while(k3==0);
	}
	if(k4==0) /*对时钟、闹钟进行减法滚动*/
	{
	 	delay(1000);
		if(k4==0)
		{
		if(k4_flag==2)//第一下暂停,第二下对时钟的分钟进行减法运算
			{minute--;if(minute==-1){minute=59;}}
		if(k4_flag==3)//第三下对时钟的秒进行减法运算
			{second--;if(second==-1){second=59;}}
		if(k5_flag==2)//第二下对闹钟的分钟进行减法运算
			{minute1--;if(minute1==-1){minute1=59;}}
		if(k5_flag==3)//第三下对闹钟的秒进行减法运算
			{second1--;if(second1==-1){second1=59;}}
		} while(k4==0);
	}
}

初始化程序的编写

我是把时钟初始化程序和闹钟初始化程序分别建立了两个函数。
直接对minute和second进行编写即可改变初值。

时钟初始化;
在这里插入图片描述

void clock_init()
{
 	TempData[0]=0x6d;
	TempData[1]=0x00;
	TempData[2]=0x00;
	TempData[3]=dofly_DuanMa[minute/10];
	TempData[4]=dofly_DuanMa[minute%10];
	TempData[5]=0x00;
	TempData[6]=dofly_DuanMa[second/10];
	TempData[7]=dofly_DuanMa[second%10];
}

闹钟初始化:
在这里插入图片描述

void alarm_init()
{
 	TempData[0]=0x73;
	TempData[1]=0x00;
	TempData[2]=0x00;
	TempData[3]=dofly_DuanMa[minute1/10];
	TempData[4]=dofly_DuanMa[minute1%10];
	TempData[5]=0x00;
	TempData[6]=dofly_DuanMa[second1/10];
	TempData[7]=dofly_DuanMa[second1%10];	
}

时间程序编写

这里我是使用定时器进实现的时间流逝,定时器的好处就是对秒的计算比较准确,而且比死循环延时函数更加稳定。
代码如下所示:

void Init_Timer0(void)//开启定时器 0
{
 	TMOD|=0x01;
	TH0=(65536-2000)/256;//给初值。
	TL0=(65536-2000)%256;
	EA=1;//总中断
	ET0=1;//定时器中断
	TR0=1;//定时器开关
}
/*中断*/
void Timer0_isr(void) interrupt 1
{
	static unsigned int num;
	TH0=(65536-2000)/256;//给初值。
	TL0=(65536-2000)%256;
	num++;
	if(num==500) //50ms
	{
	 	num=0;
		second++;
			if(second==60)
			{
			 second=0;
			 minute++;
			 	if(minute==60)
				{
				 	minute=0;
				}
			}
	}
	if((minute==minute1)&&(second==second1))//相等判断条件
	{
		 	BEEP=!BEEP;
			delay(800);
			UART_init();
			UART_send_string(Buf);
			delay(2000);
//			TR1=0;
	}
	else
		BEEP=0;
}

细心的朋友就会发现,我这里不止包括了一个定时器,还有一个定时器UART_init();它是用来实现当闹钟响起时,对电脑发送字符串的。下面我们就来讲一下它。

串口通信程序

得先说一下串口相关寄存器的基本知识(参考普中开发攻略):
(1)串口控制寄存器 SCO
在这里插入图片描述
SM0 和 SM1 为工作方式选择:
在这里插入图片描述
SM2(暂时咱们用不到):多机通信控制位,主要用于方式 2 和方式 3。当 SM2=1 时可以利用收到
的 RB8 来控制是否激活 RI(RB8=0 时不激活 RI,收到的信息丢弃;RB8=1 时收
到的数据进入 SBUF,并激活 RI,进而在中断服务中将数据从 SBUF 读走)。当
SM2=0 时,不论收到的 RB8 为 0 和 1,均可以使收到的数据进入 SBUF,并激活 RI
(即此时 RB8 不具有控制 RI 激活的功能)。通过控制 SM2,可以实现多机通信)
REN:允许串行接收位。由软件置 REN=1,则启动串行口接收数据;若软件置
REN=0,则禁止接收。
TB8:在方式 2 或方式 3 中,是发送数据的第 9 位,可以用软件规定其作用。
可以用作数据的奇偶校验位,或在多机通信中,作为地址帧/数据帧的标志位。
在方式 0 和方式 1 中,该位未用到。
RB8:在方式 2 或方式 3 中,是接收到数据的第 9 位,作为奇偶校验位或地
址帧/数据帧的标志位。在方式 1 时,若 SM2=0,则 RB8 是接收到的停止位。
TI:发送中断标志位。在方式 0 时,当串行发送第 8 位数据结束时,或在其
它方式,串行发送停止位的开始时,由内部硬件使 TI 置 1,向 CPU 发中断申请。
在中断服务程序中,必须用软件将其清 0,取消此中断申请。
RI:接收中断标志位。在方式 0 时,当串行接收第 8 位数据结束时,或在其
它方式,串行接收停止位的中间时,由内部硬件使 RI 置 1,向 CPU 发中断申请。
也必须在中断服务程序中,用软件将其清 0,取消此中断申请。
如何单片机单方面向PC端发送字符串:
参考以下代码:

u8 Buf[]="TIME IS UP!\n";//串口发送的字符串。

/*开启定时器1*/
void UART_init(void)
{
    SCON = 0x50; //串口方式1
 
    TMOD|= 0x20; // 定时器使用方式2自动重载
    TH1 = 0xFD; //9600波特率对应的预设数,定时器方式2下,TH1=TL1
    TL1 = 0xFD;
 
    TR1 = 1;//开启定时器,开始产生波特率
}

void UART_send_byte(u8 dat) //仅是发送一个字符
{
	SBUF = dat;       //把数据放到SBUF中
	while (TI == 0);//未发送完毕就等待
	TI=0;    //发送完毕后,要把TI重新置0
}

void UART_send_string(u8 *buf)//发送一个字符串
{
	while (*buf != '\0')
	{
		UART_send_byte(*buf++);
	}
}

全部代码汇总

/*课题:用51单片机制作时钟闹钟
	独立按键k1为切换时钟和闹钟界面
	k2为通过移位对时钟闹钟是分钟还是秒进行选择
	k3为对分钟进行有界限的增加、k4为对秒进行有界限的减少
	时钟和闹钟时间相等时,蜂鸣器开始报警,串口发送“TIME IS UP!”给PC*/
#include <REGX52.H>
#define DatPort P0//数码管数据端口宏定义。
#define KeyPort P3//独立按键端口宏定义。
#define u16 unsigned int 
#define u8 unsigned char

unsigned char TempData[8];
bit k1_flag=0;//对位进行定义。
u8 k4_flag=0,k5_flag=0;
unsigned char minute,second,minute1,second1;//定义分、秒

u8 minute=11,second=11;//时钟初始化;
u8 minute1=20,second1=22;//闹钟初始化;
  
unsigned char code dofly_DuanMa[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//0-9
unsigned char code dofly_WeiMa[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
u8 code smgduan[17]={0X3F,0X06,0X5B,0X4F,0X66,0X6D,0X7D,0X07,0X7F,0X6F,0X77,0X7C,0X39,0X5E,0X79,0X71};//数码管时钟初始化;

bit UpdateTimeFlag;//读时间标志;

sbit BEEP=P2^5;

sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;

sbit k1=P3^1;
sbit k2=P3^0;
sbit k3=P3^2;
sbit k4=P3^3;
/*各工作函数*/
void DigDisplay();
void KeyScan();
void Init_Timer0(void);
void Timer1_display(void);
void clock_init();
void alarm_init();


u8 Buf[]="TIME IS UP!\n";//串口发送的字符串。

void delay(u16 i)
{
 	while(i--);
}
/*开启定时器1*/
void UART_init(void)
{
    SCON = 0x50; //串口方式1
 
    TMOD|= 0x20; // 定时器使用方式2自动重载
    TH1 = 0xFD; //9600波特率对应的预设数,定时器方式2下,TH1=TL1
    TL1 = 0xFD;
 
    TR1 = 1;//开启定时器,开始产生波特率
}

void UART_send_byte(u8 dat) //仅是发送一个字符
{
	SBUF = dat;       //把数据放到SBUF中
	while (TI == 0);//未发送完毕就等待
	TI=0;    //发送完毕后,要把TI重新置0
}

void UART_send_string(u8 *buf)//发送一个字符串
{
	while (*buf != '\0')
	{
		UART_send_byte(*buf++);
	}
}
/*时钟界面*/
void clock_init()
{
 	TempData[0]=0x6d;
	TempData[1]=0x00;
	TempData[2]=0x00;
	TempData[3]=dofly_DuanMa[minute/10];
	TempData[4]=dofly_DuanMa[minute%10];
	TempData[5]=0x00;
	TempData[6]=dofly_DuanMa[second/10];
	TempData[7]=dofly_DuanMa[second%10];
}
/*闹钟界面*/
void alarm_init()
{
 	TempData[0]=0x73;
	TempData[1]=0x00;
	TempData[2]=0x00;
	TempData[3]=dofly_DuanMa[minute1/10];
	TempData[4]=dofly_DuanMa[minute1%10];
	TempData[5]=0x00;
	TempData[6]=dofly_DuanMa[second1/10];
	TempData[7]=dofly_DuanMa[second1%10];	
}
/*开启定时器0*/
void Init_Timer0(void)
{
 	TMOD|=0x01;
	TH0=(65536-2000)/256;//给初值。
	TL0=(65536-2000)%256;
	EA=1;//总中断
	ET0=1;//定时器中断
	TR0=1;//定时器开关
}

void main(void)
{
 	Init_Timer0();
    while(1)
	{
		if(k1_flag==0)
	  	{
  	  		clock_init();
	  	}
   		else if(k1_flag==1)
		{
	  		alarm_init();
		}
	 	KeyScan();
		DigDisplay();
	}
}

void Timer0_isr(void) interrupt 1
{
	static unsigned int num;
	TH0=(65536-2000)/256;//给初值。
	TL0=(65536-2000)%256;
	num++;
	if(num==500) //50ms
	{
	 	num=0;
		second++;
			if(second==60)
			{
			 second=0;
			 minute++;
			 	if(minute==60)
				{
				 	minute=0;
				}
			}
	}
	if((minute==minute1)&&(second==second1))//相等判断条件
	{
		 	BEEP=!BEEP;
			delay(800);
			UART_init();
			UART_send_string(Buf);
			delay(2000);
//			TR1=0;
	}
	else
		BEEP=0;
}

void DigDisplay()
{
 	u8 i=0;
	for(i;i<8;i++)
	{
	 	switch(i)
		{
		 	case(7):LSA=0;LSB=0;LSC=0;break;//0
			case(6):LSA=1;LSB=0;LSC=0;break;//1
			case(5):LSA=0;LSB=1;LSC=0;break;//2
			case(4):LSA=1;LSB=1;LSC=0;break;//3
			case(3):LSA=0;LSB=0;LSC=1;break;//4
			case(2):LSA=1;LSB=0;LSC=1;break;//5
			case(1):LSA=0;LSB=1;LSC=1;break;//6
			case(0):LSA=1;LSB=1;LSC=1;break;//7
		}
		P0=TempData[i];//传送段选数据;
		delay(100);
		P0=0x00;//消隐
	}
}

void KeyScan()//按键扫描
{
 	if(k1==0)
	{
	 	delay(1000);
	 	if(k1==0)   
   		{  
   			k1_flag=~k1_flag;//取反判断按键1按下次数
			if(k1_flag==0)
				{ET0=1;} //开启定时器中断
   		}
		while(k1==0);
	}
	if(k2==0)
	{
	 	delay(1000);
		if(k2==0)  
   		{
   			ET0=0;//关闭定时器中断
   			if(k1_flag==0)
			{
   				k4_flag++; //数码管移位
   				if(k4_flag==4)
   				{ET0=1;k4_flag=0;}
   			}
			if(k1_flag==1)
			{
   				k5_flag++;
   				if(k5_flag==4)
   				{k5_flag=0;}	
  			}		
		}
		while(k2==0);
	}
	if(k3==0)/*对时钟、闹钟进行加法滚动*/
	{
	 	delay(1000);
		if(k3==0)
		{
		if(k4_flag==2)
			{minute++;if(minute==60){minute=0;}}
		if(k4_flag==3)
			{second++;if(second==60){second=0;}}
		if(k5_flag==2)
			{minute1++;if(minute1==60){minute1=0;}}
		if(k5_flag==3)
			{second1++;if(second1==60){second1=0;}}
		} while(k3==0);
	}
	if(k4==0) /*对时钟、闹钟进行减法滚动*/
	{
	 	delay(1000);
		if(k4==0)
		{
		if(k4_flag==2)
			{minute--;if(minute==-1){minute=59;}}
		if(k4_flag==3)
			{second--;if(second==-1){second=59;}}
		if(k5_flag==2)
			{minute1--;if(minute1==-1){minute1=59;}}
		if(k5_flag==3)
			{second1--;if(second1==-1){second1=59;}}
		} while(k4==0);
	}
}

总结

用51单片机制作智能时钟闹钟对初学者来说算是一个高级实验例程了,对编写程序的人要求细心,比如我在编写串口通信时,就发生了串口和定时器冲突,刚开始秒很正常,可是当闹钟铃声过去,串口发送完字符串后,秒数频率变得非常快,我检查了好久,最后发现TMOD没有写上与‘|’运算符!改变了在该定时器预存的值,后面的定时器配置会覆盖前一个定时器TMOD的状态值。
这就是细心问题,还有串口使用的定时器不要和时钟定时器用的一样,不然会发生冲突,导致数据读写出现问题,

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bitter tea seeds

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值