超声波测距系统

文章目录

  • 前言
  • 一、功能描述
    • 一、界面一
    • 二、界面二
    • 三、界面三
    • 四、界面四
    • 五、初始界面
  • 二、编程实现


前言

  具有测距、温度补充、实时时钟、记忆、阈值警报、串口数据发送等等功能,通过LCD1602显示,按键进行相关操作。

一、功能描述

  LCD1602显示共有五个界面,按键一用于切换显示界面。

一、界面一

  此界面测距为连续测距模式,LCD1602不间断刷新测量距离和温度,一旦测量距离小于设置的阈值,单片机将会发出警报。此界面按下按键4可进入与上位机连接状态,当上位机发送1至单片机后,mcu立刻将测量数据发送至上位机。

二、界面二

  此界面为阈值设置界面,按键二用于改变设置位(上限还是下限),设置位会闪烁显示,以此提示。按键三四用于加减。

三、界面三

  此界面测距为单次测距模式,按键二按下测量一次,按键三按下则将当前测量距离和测量时间(如23:59:45时测量距离为45cm)保存到EEPROM里面,最多可以记录十次,第十一次将会覆盖第一次数据。

四、界面四

  此界面测距为回放模式,按下按键二三切换回放序号,LCD1602会显示在界面三时记录的距离和测量时间。

五、初始界面

  上电后,LCD1602会显示DS1302的初始时间设置,此时需要通过按键二三四进行时间设置。设置完成后,按下按键一,系统进入界面一,开始工作。

二、编程实现

  核心代码main.c如下,项目工程见超声波测距系统
  各模块原理和代码见我的CSDN博客专栏单片机

#include "main.h"
typedef unsigned char u8;
//P2用于选择P0输出通道,P0输出数据
#define outputp0(y,x) P0=x,P2&=0x1f,P2=y,P2&=0x1f;
//按键值,按键值缓存值
u8 kbdnum=0,kbdtemp=0;
//显示页面值,距离阈值设置选择位,保存次数值,时间设置选择位,测量数据保存起始地址;
u8 show_page=0,distance_set_flag=0,save_times=1,set_time_flag=0,save_distance_addr=0x04;
//测量阈值上限,测量阈值下限,LCD闪烁位
int distance_h=350,distance_l=6,LCD_showflag=0;
//测量阈值上下限数组,十六进制,便于进行保存到EEPROM或从EEPROM读取的操作
u8 distance_limit[4]={0};
//测量温度
float temper=0;
//时间保存中间量
u8 time_s_temp=0;
//主函数延时函数,t ms
void main_Delay1ms(int t)	//@11.0592MHz
{
	unsigned char data i, j;
	while(t--)
	{
		_nop_();
		_nop_();
		_nop_();
		i = 11;
		j = 190;
		do
		{
			while (--j);
		} while (--i);
	}
}

/*
  *  @brief     按键值读取工作函数
  *  @param    
  *  @reval      
  *  @note:    
*/

void work_kbd()
{
	if(kbdnum)   //如果有按键按下
	{
		if(kbdnum==13)   //key1按下
		{
			show_page++;
			show_page%=4;   //切换显示界面
			
			if(show_page==0)    //如果为界面0,连续测距模式
			{
				TR0=0;
				temper=rd_temper();
				TR0=1;
				work_ultra();  //界面0

				LCD_ShowString(1,1,"Distance:   . CM");  //显示相应界面
				LCD_ShowString(2,1,"Temper:    .  C ");
			}
			else if(show_page==1)   //如果为界面1,测量阈值设置模式
			{
				outputp0(0xa0,0x00);    //关闭蜂鸣器,防止在界面0警报状态下进入界面1导致蜂鸣器一直响
				distance_set_flag=0;LCD_showflag=0;    //复位距离阈值设置位和LCD闪烁位
				LCD_ShowString(1,1,"High:   CM      ");  //显示相应界面
				LCD_ShowString(2,1,"Low:   CM       ");
			}
			else if(show_page==2)  //如果为界面2,逐次测量模式
			{			
				save_distance_addr=4;save_times=1;  //复位数据保存起始地址和保存次数
				LCD_ShowString(1,1,"Distance:   . CM");   //显示相应界面
				LCD_ShowString(2,1,"Time:  -  -     ");
			}
			else if(show_page==3)   //如果为界面3,回放模式
			{
				save_distance_addr=4;save_times=1;  //复位数据保存起始地址和保存次数
				LCD_ShowString(1,1,"Save_Dis:   . CM");  //显示相应界面
				LCD_ShowString(2,1,"Time:  -  -     ");
				TR0=0;
				at24c02_read_multi(read_time,save_distance_addr,3);
				distance=at24c02_read(save_distance_addr+3)*256+at24c02_read(save_distance_addr+4);
				TR0=1;   //将第一次保存数据读取出来
			}

		}
		else if(kbdnum==14)  //key2按下
		{
			if(show_page==1)   //如果是界面1,测量阈值设置模式
				distance_set_flag=!distance_set_flag;  //改变距离阈值设置位,用于切换选择设置上限或下限
			else if(show_page==2)  //如果是界面2,逐次测量模式
			{				
				TR0=0;
				temper=rd_temper();
				TR0=1;
				work_ultra();    //执行一次距离测量
			}	
			else if(show_page==3)  //如果是界面3,回放模式
			{
				if(save_distance_addr<=48)  //用于选取下一个数据保存地址
				{
					save_distance_addr+=5;
					save_times++;
				}
				else if(save_distance_addr>48)  //超出数据保存最大地址,则返回最小地址
				{
					save_distance_addr=4;
					save_times=1;
				}
				TR0=0;     //将保存数据读取出来
				at24c02_read_multi(read_time,save_distance_addr,3);
				distance=at24c02_read(save_distance_addr+3)*256+at24c02_read(save_distance_addr+4);	
				TR0=1;				
			}	
		}
		else if(kbdnum==15) //key3按下
		{
			if(show_page==1)  //如果是界面1,测量阈值设置模式
			{
				if(distance_set_flag==0)  //如果是设置距离上限
				{
					distance_h++;    //加一
					if(distance_h>350)   //防超过最大值
						distance_h--;
				}
				else if(distance_set_flag==1)  //如果是设置距离下限
				{
					distance_l++;     //加一
					if(distance_l>=distance_h)  //防超过距离上限
						distance_l--;
				}	
			}
			else if(show_page==2)  //如果是界面2,逐次测量模式,将测量数据进行保存
			{
				TR0=0;     
				time_s_temp=read_time[0];read_time[0]=read_time[2]; //用于DS1302读取时间顺序为秒分时,而人习惯为时分秒,因此将秒与时交换位置
				read_time[2]=time_s_temp;
				at24c02_write_multi_page(read_time,save_distance_addr,3); //保存测量时间
				save_distance_addr+=3;
				at24c02_write(save_distance_addr++,distance/256);  //保存测量距离
				at24c02_delay5ms();
				at24c02_write(save_distance_addr++,distance%256);	 //保存测量距离
				TR0=1;				
				if(save_distance_addr>=54)  //数据读取地址达到最大,返回最小地址
					save_distance_addr=4;
				save_times++;     //读取的数据对应的保存次序
				if(save_times>10)  //如果次序超过最大次数10,则返回第一次
					save_times=1;
			}	
			else if(show_page==3)  //如果是界面3,回放模式
			{
				if(save_distance_addr>=9)   
				{
					save_distance_addr-=5;  //读取上一次保存数据
					save_times--;
				}
				else if(save_distance_addr<9)  //如果读取数据地址达到最小地址,则返回最大地址
				{
					save_distance_addr=49;
					save_times=10;
				}
				TR0=0;    //将数据读取出来
				at24c02_read_multi(read_time,save_distance_addr,3);
				distance=at24c02_read(save_distance_addr+3)*256+at24c02_read(save_distance_addr+4);	
				TR0=1;				
			}
		}
		else if(kbdnum==16)  //key4按下
		{
			if(show_page==0)    //如果是界面0,连续测距模式
			{
				LCD_ShowString(1,1,"Please connect  ");   //等待与上位机连接
				LCD_ShowString(2,1,"  to computer!  ");
				while(receivebit==0);										  //一旦连接
				for(save_distance_addr=4;save_distance_addr<=53;save_distance_addr++)
				{
					sendbit(at24c02_read(save_distance_addr));  //将保存数据全部发送至上位机
				}
				LCD_ShowString(1,1,"Datas sent  OK! ");   //提示发送完成
				LCD_ShowString(2,1,"                ");	
				main_Delay1ms(3000);
				LCD_ShowString(1,1,"Distance:   . CM");   //回到连续测距显示
				LCD_ShowString(2,1,"Temper:    .  C ");	
				receivebit=0;	   //复位接收值
			}
			else if(show_page==1)    //如果是界面1,测量阈值设置模式
			{
				if(distance_set_flag==0)  //如果是设置距离上限
				{
					distance_h--;   //减一
					if(distance_h<=distance_l)   //防小于距离下限
						distance_h++;
				}
				else if(distance_set_flag==1)  //如果是设置距离下限
				{
					distance_l--;			//减一
					if(distance_l<6)   //防止小于最小值
						distance_l++;
				}				
			}
			else if(show_page==2)  //如果是界面2,逐次测距模式
			{
				show_page=0;  //切换回界面0,连续测距模式
				LCD_ShowString(1,1,"Distance:   . CM");   //显示相应界面
				LCD_ShowString(2,1,"Temper:    .  C ");				
			}		
		}
		if((kbdnum==15||kbdnum==16)&&show_page==1)  //如果在测量阈值设置模式改变了距离上限或下限的值,则将新值进行保存
		{
			distance_limit[0]=distance_h/256;  //将距离上下限转换为16进制
			distance_limit[1]=distance_h%256;
			distance_limit[2]=distance_l/256;
			distance_limit[3]=distance_l%256;
			TR0=0;
			at24c02_write_multi(distance_limit,0x00,4);  //一次写入多个数据
			TR0=1;
		}
	kbdnum=0;  //清零按键值
	}
}

/*
  *  @brief     界面0,连续测距模式
  *  @param    
  *  @reval      
  *  @note:    
*/

void page_0()
{	
		TR0=0;
		temper=rd_temper();  //测量温度
		TR0=1;
		if(tultra>=60)  //每60ms测量一次
		{
			work_ultra();  //测量距离
		}
		if(distance>=(distance_l*10)&&distance<=(distance_h*10))   //如果在测量范围之内
		{
			LCD_ShowString(1,1,"distance:");  //正常显示测量距离
			LCD_ShowNum(1,10,distance/10,3);
			LCD_ShowNum(1,14,distance%10,1);
			LCD_ShowChar(1,13,'.');
			LCD_ShowString(1,15,"CM");		
			
			outputp0(0xa0,0x00);  //蜂鸣器关
		}
		else if(distance<(distance_l*10)||distance>(distance_h*10))  //如果超出测量范围
		{
			LCD_ShowString(1,1,"Error!          ");  //测量距离显示“ERROR!”
			outputp0(0xa0,0x40);  //蜂鸣器开
		}
		LCD_ShowSignedNum(2,8,temper,3);
		LCD_ShowNum(2,13,(int)(temper*100)%100,2);	//显示温度
}

/*
  *  @brief     界面1,测量阈值设置模式
  *  @param    
  *  @reval      
  *  @note:    
*/

void page_1()
{
	if(LCD_showflag==0)   //如果LCD闪烁位为0,正常显示
	{
		LCD_ShowNum(1,6,distance_h,3);
		LCD_ShowNum(2,5,distance_l,3);
	}
	else if(LCD_showflag==1)  //如果LCD闪烁位为1,则清空相应数据显示位置
	{
		if(distance_set_flag==0)LCD_ShowString(1,6,"   ");
		else if(distance_set_flag==1)LCD_ShowString(2,5,"   ");
	}
}

/*
  *  @brief     界面2,逐次测距模式
  *  @param    
  *  @reval      
  *  @note:    
*/

void page_2()
{
	TR0=0;
	DS1302_read(DS1302_write_addr);  //读取时间
	TR0=1;
	
	LCD_ShowNum(1,10,distance/10,3);   //显示相应数据
	LCD_ShowNum(1,14,distance%10,1);
	LCD_ShowNum(2,6,read_time[2]/16*10+read_time[2]%16,2);
	LCD_ShowNum(2,9,read_time[1]/16*10+read_time[1]%16,2);
	LCD_ShowNum(2,12,read_time[0]/16*10+read_time[0]%16,2);
	LCD_ShowNum(2,15,save_times,2);
}

/*
  *  @brief     界面3,回放模式
  *  @param    
  *  @reval      
  *  @note:    
*/

void page_3()
{
	LCD_ShowNum(1,10,distance/10,3);  //显示相应数据
	LCD_ShowNum(1,14,distance%10,1);
	LCD_ShowNum(2,6,read_time[0]/16*10+read_time[0]%16,2);  //由于数据以16进制保存,因此需要转换为10进制
	LCD_ShowNum(2,9,read_time[1]/16*10+read_time[1]%16,2);
	LCD_ShowNum(2,12,read_time[2]/16*10+read_time[2]%16,2);
	LCD_ShowNum(2,15,save_times,2);	
}

/*
  *  @brief     界面显示
  *  @param    
  *  @reval      
  *  @note:    
*/

void page_show()
{
	switch(show_page)
	{
		case 0:page_0();break;
		case 1:page_1();break;
		case 2:page_2();break;
		case 3:page_3();break;
	}
}

/*
  *  @brief     上电时间初始化
  *  @param    
  *  @reval      
  *  @note:    
*/

void set_time()
{
	outputp0(0x80,0xff);  //关闭8位LED
	outputp0(0xa0,0x00);		//关闭蜂鸣器继电器等
	LCD_Init();	     //LCD1602初始化
	rd_temper();   //由于DS18B20上电默认+85°,为了防止温度有极小时间错误显示,因此上电后即让DS18B20测量一次温度
	main_Delay1ms(1000);  //系统上电后延时一秒,确保各单位准备好工作
	Timer0_Init();  //定时器0初始化
	LCD_ShowString(1,1,"Please set time!");  //设置时间提示
	LCD_ShowString(2,1,"Time:  -  -     ");

	while(kbdnum!=13)   //如果key1按下,则退出时间设置
	{	
		if(kbdnum==14)   //如果key2按下
		{
			set_time_flag++;set_time_flag%=3; //时间设置选择位
			kbdnum=0;		  //清零按键值
		}
		else if(kbdnum==15) //如果key3按下
		{
			DS1302_write_time[set_time_flag]++;  //相应数据加一
			if(DS1302_write_time[set_time_flag]==24&&set_time_flag==2) //防止秒分时超出各自上限
				DS1302_write_time[set_time_flag]=0;
			else if(DS1302_write_time[set_time_flag]==60)
				DS1302_write_time[set_time_flag]=0;
			kbdnum=0;		  //清零按键值
		}
		else if(kbdnum==16) //如果key4按下
		{
			if(DS1302_write_time[set_time_flag]>=1)
				DS1302_write_time[set_time_flag]--;    //相应数据减一
			else
				DS1302_write_time[set_time_flag]=59*(set_time_flag<2)+23*(set_time_flag==2); //防止秒分时小于各自下限
			kbdnum=0;		  //清零按键值
		}	
		if(LCD_showflag==0)   //如果LCD闪烁位为0,正常显示
		{
			LCD_ShowNum(2,6,DS1302_write_time[2],2);
			LCD_ShowNum(2,9,DS1302_write_time[1],2);
			LCD_ShowNum(2,12,DS1302_write_time[0],2);
		}
		else if(LCD_showflag==1)    //如果LCD闪烁位为1,则清空相应数据显示位置
		{
			switch(set_time_flag)
			{
				case 0:LCD_ShowString(2,12,"  ");break;
				case 1:LCD_ShowString(2,9,"  ");break;
				case 2:LCD_ShowString(2,6,"  ");break;	
			}	
		}
	}
	if(kbdnum==13)  //key1按下
	{
		TR0=0;	
		for(set_time_flag=0;set_time_flag<3;set_time_flag++) //将设置时间从10进制转换为16进制
			DS1302_write_time[set_time_flag]=DS1302_write_time[set_time_flag]/10*16+DS1302_write_time[set_time_flag]%10;
		DS1302_set(DS1302_write_addr,DS1302_write_time);  //设置DS1302时间
		TR0=1;
		kbdnum=0;	  //清零按键值
	}	
}

/*
  *  @brief     系统初始化
  *  @param    
  *  @reval      
  *  @note:    
*/

void init()
{
	Timer1_Init();  //定时器0初始化
	Uart1Init();    //串口1初始化
	LCD_ShowString(1,1,"distance:   CM  "); //上电默认界面0,显示相应界面
	LCD_ShowString(2,1,"Temper:    .  C ");
	//将保存的距离上下限读取并设置为距离阈值
	at24c02_read_multi(distance_limit,0x00,4);
	distance_h=distance_limit[0]*256+distance_limit[1];
	distance_l=distance_limit[2]*256+distance_limit[3];
	
	distance=distance_l*10+1;  //防止未开始测量即报警
}
void main()
{
	set_time();  //设置时间
	init();   //系统初始化
	receivebit=0;  //清零接收位
	while(1)
	{
		work_kbd();  //按键读取
		page_show();   //界面显示
	}
	
}

void Timer0_Isr(void) interrupt 1
{
	static u8 kbd_longflag=0;      //矩阵键盘长按标志位,1长按0短按
	static int kbd_short_t=0,kbd_t=0,kbd_long_t=0,tlcd=0;
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	
	kbd_short_t++;          //矩阵键盘按键短按时间计数
	if(kbd_short_t>=(500*kbd_longflag+25*(!kbd_longflag)))   //短按每25ms检测一次按键
	{
		if(!(colu1&colu2&colu3&colu4))     //长按检测,每25ms检测矩阵键盘按键是否仍在按下,如果是
		{
			kbd_t++;						//矩阵键盘按键按下时间计数(以25ms为单位)
			if(kbd_t>=80)       //如果持续按下2s
			{
				kbd_t=80;         //防止溢出
				kbd_longflag=1;           //切换为长按模式
			}
		}
		else if(colu1&colu2&colu3&colu4)		//长按检测,每25ms检测按键是否仍在按下,如果不是
		{
			kbd_t=0;							//清零按键按下时间计数
			kbd_longflag=0;								//切换为短按模式
		}
		
		kbd_short_t=0;             //清零短按时间计数
		if(kbd_longflag==0)         //如果是短按模式
		{
			kbdtemp=kbd_send(0);
			if(kbdtemp)   //按键以短按模式检测
				kbdnum=kbdtemp;
		}
	}
	
	kbd_long_t++;           //按键长按时间计数
	if(kbd_long_t>=500)     //长按时每500ms检测一次按键
	{
		kbd_long_t=0;						  //清零按键长按时间计数
		if(kbd_longflag==1)         //如果是长按模式
		{
			
			if(kbd_send(1))			  //按键以长按模式检测
				kbdnum=kbd_send(1);
		}
	}
	
	tultra++; //超声波工作时间间隔计数
	
	tlcd++;   //LCD闪烁位时间计数
	if(tlcd>=500)
	{
		tlcd=0;
		LCD_showflag=!LCD_showflag;  //实现LCD1602闪烁显示
	}
}

你可能感兴趣的:(单片机,单片机,蓝桥杯,c语言,mcu,51单片机)