单片机-基于单片机的闹钟万年历(带超声波模块)

一、设计要求

(1)基本部分:作品要利用CPU最小系统外至少5部分硬件电路以同时实现功能,包括但不限于LED、KB、BP、LCD、EEPROM、UART;
(2)创优部分:包括但不限于驱动外接模块、实现算法逻辑、自创硬件;
二、作品介绍
该电子万年历使用AT89C52为核心,采用LCD1602液晶屏显示,动态显示时间,同时以蜂鸣器作为闹钟声音,融合超声波测距模块(HC-SRO4)实现对闹钟的关闭;具体功能如下:
1、显示年月日周,显示格式为“年-月-日 周”;
2、显示时分秒,显示格式为“时-分-秒”;
3、通过按键控制来设置时间以及闹钟,并显示闹钟;
4、显示超声波测距的结果,当闹钟响起,只需要手掌靠近单片机10cm以内即可关闭闹钟;
主体功能原理简介:
1、闹钟模块(蜂鸣器):
在这里插入图片描述
充当电子万年历中的闹铃,接通电源后振荡器产生音频信号通过电磁线圈,使电磁线圈产生磁场,振动膜片在电磁线圈和磁铁的相互作用下,周期性振动发声;可通过改变频率控制蜂鸣器音调;
2、控制模块(独立按键):
在这里插入图片描述
这里使用独立按键来对电子万年历进行控制,按下K3停止计时进入设置状态,K1移动设置点,K2使设置点所在的时间加一,K4使用设置点的时间单位来设置闹钟,比如K1移动到小时,按下K2小时数加一,按下K4会设置一个1小时的闹钟,再次按下K3恢复计时;
独立按键的原理是按下接通电路,松开断开电路,但这个过程的信号波形并不是完美的矩形波,在按下与松开这俩个过程均存在一定的抖动;
3、液晶显示模块(LCD1602):
在这里插入图片描述
这里使用LCD1602液晶显示模块来显示时间,闹钟,以及超声波测距的距离,并实时更新变化;
在液晶模块的初始化中需要先设置其显示模式,并且在液晶模块显示字符时光标是自动右移的,无须人工干预;要显示字符时要求先输入显示字符的地址,并且液晶显示屏中第一个字符的地址为40H,若要输入第二行,则需要将需要显示字符的地址位置加80H,即第二行第一个字符应该写入的数据是(40H+80H);
在其初始化过程中,若要写入命令,则需要将使能端RS电平拉低,若是写入数据,则将RS电平设置为高电平;
LCD1602一些关键指令设置:
在这里插入图片描述
4、时钟模块(DS1302):
在这里插入图片描述
这里时钟模块主要负责计时,按照开始设定的时间,通电后就开始走动;内含31个字节静态RAM;采用串行数据传送方式,数据以BCD的形式传送,因此在程序中要将字符数据转换成BCD码的形式,即加上0X30;在I/O数据输入输出过程中,控制指令输入下一个SCLK时钟的上升沿时,数据被写入DS1302数据输入从0开始;也同样在控制指令字之后的下一个SCLK脉冲的下降沿读取DS1302的数据,读取也是从低位0到高位7;
并且需要注意的是,一次读写操作至少读写俩个字符,一个是控制字节,一个是命令,告诉单片机是读还是写;
5、超声波模块(HC-SRO4):
在这里插入图片描述
超声波模块主要负责测试开发板与周围障碍物距离,便于控制闹钟的关闭,而不需要使用按键去控制,这样显得更加方便和智能;
给它提高一个10uS以上得脉冲触发信号该模块内部将发出8个40kHZ周期电平并检测回波;然后通过发送信号与收到回波的时间间隔来计算距离;
C语言代码:

#include"reg52.h"
#include"intrins.h"
#超声波接线说明 Trig -------  P2^1;Echo -------  P2^0;VCC  -------- +5v; GND  -------- GND
typedef unsigned int u16;	  //对数据类型进行声明定义
typedef unsigned char u8;
//---秒分时日月周年 最低位读写位;-------//
u8 code READ_RTC_ADDR[7] = {
    
    0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d}; 
u8 code WRITE_RTC_ADDR[7] = {
    
    0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};

//---DS1302时钟初始化2013年1月1日星期二12点00分00秒。---//
//---存储顺序是秒分时日月周年,存储格式是用BCD码---//
u8 TIME[7] = {
    
    1, 0, 0x18, 0x26, 0x12, 0x00, 0x20};
u8 CLOCK=0;
unsigned char code ASCII[15] = {
    
    '0','1','2','3','4','5','6','7','8','9','.','-','M'};
//PIN口定义
#define LCD1602_DATAPINS P0
sbit LCD1602_E=P2^7;
sbit LCD1602_RW=P2^5;
sbit LCD1602_RS=P2^6;
//---定义ds1302使用的IO口---//
sbit DSIO=P3^4;
sbit RST=P3^5;
sbit SCLK=P3^6;
//超声波接口
sbit Trig = P2^1;
sbit Echo = P2^0;
//管脚接线
sbit beep=P1^5;	 
sbit K1=P3^1;
sbit K2=P3^0;
sbit K3=P3^2;
sbit K4=P3^3;	 
u8 SetState,SetPlace;
u16  dis=0;
u8 noise_time0,noise_time1;
unsigned long S=0;
bit flag =0;
u8 disbuff[4] ={
    
     0,0,0,0};
//闹钟标志
u8 rang_flag=0;  
u8 r_flag=0; 
u8 r1_flag=0;
//延时函数
void delay(u16 i)  //当i=1,大约延时10us
{
    
    
	while(i--);
}

//DS1302写入命令(地址加数据)
void Ds1302Write(u8 addr, u8 dat)
{
    
    
	u8 n;
	RST = 0;
	_nop_();

	SCLK = 0;//先将SCLK置低电平。
	_nop_();
	RST = 1; //然后将RST(CE)置高电平。
	_nop_();

	for (n=0; n<8; n++)//开始传送八位地址命令
	{
    
    
		DSIO = addr & 0x01;//数据从低位开始传送
		addr >>= 1;
		SCLK = 1;//数据在上升沿时,DS1302读取数据
		_nop_();
		SCLK = 0;
		_nop_();
	}
	for (n=0; n<8; n++)//写入8位数据
	{
    
    
		DSIO = dat & 0x01;
		dat >>= 1;
		SCLK = 1;//数据在上升沿时,DS1302读取数据
		_nop_();
		SCLK = 0;
		_nop_();	
	}	
		 
	RST = 0;//传送数据结束
	_nop_();
}
//DS1302读取一个地址数据
int Ds1302Read(u8 addr)
{
    
    
	u8 n,dat,dat1;
	RST = 0;
	_nop_();

	SCLK = 0;//先将SCLK置低电平。
	_nop_();
	RST = 1;//然后将RST(CE)置高电平。
	_nop_();

	for(n=0; n<8; n++)//开始传送八位地址命令
	{
    
    
		DSIO = addr & 0x01;//数据从低位开始传送
		addr >>= 1;
		SCLK = 1;//数据在上升沿时,DS1302读取数据
		_nop_();
		SCLK = 0;//DS1302下降沿时,放置数据
		_nop_();
	}
	_nop_();
	for(n=0; n<8; n++)//读取8位数据
	{
    
    
		dat1 = DSIO;//从最低位开始接收
		dat = (dat>>1) | (dat1<<7);
		SCLK = 1;
		_nop_();
		SCLK = 0;//DS1302下降沿时,放置数据
		_nop_();
	}

	RST = 0;
	_nop_();	//以下为DS1302复位的稳定时间,必须的。
	SCLK = 1;
	_nop_();
	DSIO = 0;
	_nop_();
	DSIO = 1;
	_nop_();
	return dat;	
}

//初始化DS1302
void Ds1302Init()
{
    
    
	u8 n;
	Ds1302Write(0x8E,0X00);		 //禁止写保护,就是关闭写保护功能
	for (n=0; n<7; n++)//写入7个字节的时钟信号:分秒时日月周年
	{
    
    
		Ds1302Write(WRITE_RTC_ADDR[n],TIME[n]);	
	}
	Ds1302Write(0x8E,0x80);		 //打开写保护功能
}
//读取时钟信息
void Ds1302ReadTime()
{
    
    
	u8 n;
	for (n=0; n<7; n++)//读取7个字节的时钟信号:分秒时日月周年
	{
    
    
		TIME[n] = Ds1302Read(READ_RTC_ADDR[n]);
	}
		
}

//LCD写入命令
void LcdWriteCom(u8 com)	  //写入命令
{
    
    
	LCD1602_E = 0;     //使能
	LCD1602_RS = 0;	   //选择发送命令
	LCD1602_RW = 0;	   //选择写入
	
	LCD1602_DATAPINS = com;     //放入命令
	delay(100);		//等待数据稳定

	LCD1602_E = 1;	          //写入时序
	delay(500);	  //保持时间
	LCD1602_E = 0;
}
//LCD写入数据
void LcdWriteData(u8 dat)			//写入数据
{
    
    
	LCD1602_E = 0;	//使能清零
	LCD1602_RS = 1;	//选择输入数据
	LCD1602_RW = 0;	//选择写入

	LCD1602_DATAPINS = dat; //写入数据
	delay(100);

	LCD1602_E = 1;   //写入时序
	delay(500);   //保持时间
	LCD1602_E = 0;
}
//LCD屏幕初始化
void LcdInit()						  //LCD初始化子程序
{
    
    
 	LcdWriteCom(0x38);  //开显示
	LcdWriteCom(0x0c);  //开显示不显示光标
	LcdWriteCom(0x06);  //写一个指针加1
	LcdWriteCom(0x01);  //清屏
	LcdWriteCom(0x80);  //设置数据指针起点
}
//外部中断0
void Int0Configuration()
{
    
    
	//设置INT0
	IT0=1;//跳变沿出发方式(下降沿)
	EX0=1;//打开INT0的中断允许。
	EA=1;//打开总中断	
}
void Int0() interrupt 0		 
{
    
    
	delay(1000);
	if(K3==0)
	{
    
    
		SetState=~SetState;
		SetPlace=0;
		Ds1302Init();	
	}
}

void Conut(void)
{
    
    
	 dis=TH0*256+TL0;
	 TH0=0;
	 TL0=0;
	
	 S=(dis*1.7)/100;     //算出来是CM
	 if((S>=700)||flag==1) //超出测量范围显示“-”
	 {
    
    	 
	 	flag=0;
		LcdWriteCom(0x89+0x40);
		LcdWriteData('-');
		LcdWriteData('-');
		LcdWriteData('M');
	 }
	 else
	 {
    
    
	  	disbuff[0]=S%1000/100;
	  	disbuff[1]=S%1000%100/10;
	  	disbuff[2]=S%1000%10 %10;
		LcdWriteCom(0x89+0x40);
		LcdWriteData('0'+disbuff[0]/16);				//时
		LcdWriteData('.');				//时
		LcdWriteData('0'+(disbuff[1]&0x0f));				//时
		LcdWriteData('0'+(disbuff[2]&0x0f));
		LcdWriteData('M');
	 }
}
//T0中断用来计数器溢出,超过测距范围
void zd0() interrupt 1 		 
{
    
    
	flag=1;							 //中断溢出标志
}
void  StartModule() 		         //启动模块
{
    
    
	Trig=1;			                     //启动一次模块
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_();
	_nop_(); 
	_nop_(); 
	_nop_(); 
	_nop_();
	Trig=0;
  }
//显示函数
void LcdDisplay()
{
    
    
	LcdWriteCom(0x80+0X40);
	LcdWriteData('0'+TIME[2]/16);				//时
	LcdWriteData('0'+(TIME[2]&0x0f));				 
	LcdWriteData('-');
	LcdWriteData('0'+TIME[1]/16);				//分
	LcdWriteData('0'+(TIME[1]&0x0f));	
	LcdWriteData('-');
	LcdWriteData('0'+TIME[0]/16);				//秒
	LcdWriteData('0'+(TIME[0]&0x0f));

	LcdWriteCom(0x80);
	LcdWriteData('2');
	LcdWriteData('0');
	LcdWriteData('0'+TIME[6]/16);			//年
	LcdWriteData('0'+(TIME[6]&0x0f));
	LcdWriteData('-');
	LcdWriteData('0'+TIME[4]/16);			//月
	LcdWriteData('0'+(TIME[4]&0x0f));
	LcdWriteData('-');
	LcdWriteData('0'+TIME[3]/16);			//日
	LcdWriteData('0'+(TIME[3]&0x0f));		 
	LcdWriteCom(0x8D);
	LcdWriteData('0'+(TIME[5]&0x07));
	LcdWriteCom(0x8b);
	LcdWriteData('0'+(CLOCK&0x07));
}
//闹钟函数
void noise()
{
    
    
	if(TIME[SetPlace]-noise_time1==0)
	{
    
    
		rang_flag=1;
	}
	if(rang_flag==1) beep=~beep;
	if(S<10) 
	{
    
    	
		CLOCK=0;
		r_flag=0;
		LcdWriteCom(0x8b);
		LcdWriteData('0'+(CLOCK&0x07));	
	}
}

//主函数
void main(void)
{
    
    
	u8 i;
	TMOD=0x01;		   //设T0为方式1,GATE=1;
	TH0=0;
	TL0=0;          
	ET0=1;             //允许T0中断	
	Int0Configuration();
	LcdInit();
	Ds1302Init();
	while(1)
	{
    
    
		StartModule();
	    while(!Echo);		//当RX为零时等待
	    TR0=1;			    //开启计数
	    while(Echo);			//当RX为1计数并等待
	    TR0=0;				//关闭计数
        Conut();			//计算
		delay(10000);
		if(SetState==0)
		{
    
    
			Ds1302ReadTime();
		}
		else
		{
    
    
			if(K1==0)		//检测按键K1是否按下
			{
    
    
				delay(10000);	//消除抖动
				if(K1==0)
				{
    
    
					SetPlace++;
					if(SetPlace>=7)
						SetPlace=0;					
				}

				while((i<50)&&(K1==0))	 //检测按键是否松开
				{
    
    
					delay(10000);
					i++;
				}
				i=0;
			}
			if(K2==0)		//检测按键K2是否按下
			{
    
    
				delay(10000);	//消除抖动
				if(K2==0)
				{
    
    
					TIME[SetPlace]++;
					if((TIME[SetPlace]&0x0f)>9)					 //换成BCD码。
					{
    
    
						TIME[SetPlace]=TIME[SetPlace]+6;
					}
					if((TIME[SetPlace]>=0x60)&&(SetPlace<2))		//分秒只能到59
					{
    
    
						TIME[SetPlace]=0;
					}
					if((TIME[SetPlace]>=0x24)&&(SetPlace==2))		//小时只能到23
					{
    
    
						TIME[SetPlace]=0;
					}
					if((TIME[SetPlace]>=0x32)&&(SetPlace==3))		//日只能到31
					{
    
    
						TIME[SetPlace]=0;	
					}
					if((TIME[SetPlace]>=0x13)&&(SetPlace==4))		//月只能到12
					{
    
    
						TIME[SetPlace]=0;
					}	
					if((TIME[SetPlace]>=0x7)&&(SetPlace==5))		//周只能到7
					{
    
    
						TIME[SetPlace]=1;
					}		
				}
				
				while((i<50)&&(K2==0))	 //检测按键是否松开
				{
    
    
					delay(10000);
					i++;
				}
				i=0;
			}
			if(K4==0)
			{
    
    
				r_flag=1;
				CLOCK++;
				if((CLOCK&0x0f)>9)					 //换成BCD码。
				{
    
    
					CLOCK=CLOCK+6;
				}
				noise_time1=TIME[SetPlace]++;		
			}		
		}
		LcdDisplay();
		if(r_flag==1)
		{
    
    
			noise();
		}	
	}				
}

作品附图:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_46837674/article/details/113029998