基于51单片机超声波液位控制器设计

1、设计需求及目标

测量液位高度并显示,设置液位范围,超过液位范围报警,自动控制液位。

2、设计思路

用单片机做水位控制这个设计核心是传感器,将信号传送到单片机,单片机再将信号输出给电机,来完成设计的要求工作。 主要分三块:单片机本身一块;超声波传感器;电机控制一块。 

3、设计方案

当水位低时,起动水泵给水,水位上升到水位上限时,停止供水。当水箱水位低于水位下限时,起动水泵。 报警控制如下: 当水位高与水位上限的时候,由传感器发送信号给单片机,单片机控制水泵停止运作,系统水位高报警。当水位低于水位下限的时候,由传感器发送信号,单片机控制水泵开始运作,系统水位低报警。 硬件电路如图所示。

4、程序主函数

#include <reg51.h>
#include <intrins.h>		// 包含循环移位:_cror_
#include "main.h"     
//----------------------------------------------------------------------

uchar code TabNumASCII[10] =    {'0','1','2','3','4','5','6','7','8','9'};

bool	g_flag = isNo;		//用于标记超时(65.536ms) 	  
bool	g_flag05s = isNo;	//用于标记0.52秒  
uchar 	ucCount = 0;			//用于计数0.52秒     
    
uint	uiH = 80;			//设定的最高报警水位 H
uint	uiL = 30;			//设定的最低报警水位 L
uint	uiD = 100; 			//检测探头到水库底部的距离 D 

bool	g_flagSwitch = isNo;		//控制阀门连续开启间隔延时(保护)标志
bool	g_flagBeepTimer = isNo;	//定时提醒标志
		

//-----------------------------------------------------------------------
// 延时10us
void delay10us(void)		//@12MHz
{
	unsigned char i;

	_nop_();
	i = 2;
	while (--i);
}

// 延时100us
void delay100us(void)		//@12MHz
{
	uchar i;

	_nop_();
	i = 47;
	while (--i);
}

// 延时125us
void delay125us(void)		//@12MHz
{
	unsigned char i;
	i = 60;
	while (--i);
}

// 延时5ms
void delay5ms(void)		//@12.000MHz
{
	unsigned char i, j;

	i = 10;
	j = 183;
	do
	{
		while (--j);
	} while (--i);
}

// 延时500ms
void delay500ms(void)		//@12MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 4;
	j = 205;
	k = 187;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

//-----------------------------------------------------------------------
//初始化IO端口			
void initIO(void)					
{
	P0 = 0xff;
	P1 = 0xff;
	P2 = 0xff;
	P3 = 0xff;
}

// 初始化定时器0,定时器时钟12T模式 模式1,16位 @12.000MHz
void initTimer0(void)		
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0;				//定时器初值清零
	TH0 = 0;				//定时器初值清零
	//TR0 = 1;   			//开定时器0
    ET0 = 1;  			//开定时器0中断
    EA = 1;     			//开总中断    	
	
}

// 初始化定时器1,定时器时钟12T模式 模式1,16位 @12.000MHz
void initTimer1(void)		//50毫秒@12.000MHz
{	
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x10;		//设置定时器模式
	TL1 = 0xB0;		//设置定时初值
	TH1 = 0x3C;		//设置定时初值	
	TR1 = 1;		//定时器1开始计时
	ET1 = 1;  	//开定时器0中断
}

//-----------------------------------------------------------------------
//定时器0中断
void zd0(void) interrupt 1 		 
{
	
	g_flag = isYes;							//中断溢出标志,g_flag = isYes超过测距范围
	if(++ucCount >= 8)
	{
		ucCount = 0;
		g_flag05s = isYes;					//g_flag05s = isYes定时0.52秒到,用于测量周期延时
	}
	TL0 = 0;		//设置定时初值
	TH0 = 0;		//设置定时初值
	
}


//定时器1中断 定时50ms
void tm1_isr() interrupt 3 using 1
{
	static uchar count = DATA_switchTime;	//50ms的200倍 = 10S
	static uchar uiCount = 1200;				//			= 1分钟		
	static uint uiCount_BeepTimer = DATA_BeepTimer;

	TL1 = 0xB0;		//设置定时初值
	TH1 = 0x3C;		//设置定时初值

	if (g_flagSwitch == isNo)
	{
		if (count-- == 0)               //50ms * 200 -> 10s
		{
			count = DATA_switchTime;
			g_flagSwitch = isYes;
			// TR1 = 0;
		}
	}	
	
	if(g_flagBeepTimer == isNo)
	{
		if (uiCount-- == 0)               //= 1分钟
		{
			uiCount = 1200;
			if(uiCount_BeepTimer-- == 0)
			{
				uiCount_BeepTimer = DATA_BeepTimer; 
				g_flagBeepTimer = isYes;
				// TR1 = 0;
			}			
			
		}

	}
}

//-----------------------------------------------





//外部中断1
void exint1() interrupt 2 
{
    EX1 = 0; 			//关闭当前中断
	TR0 = 0;   			//关闭时器0
	
}
//-----------------------------------------------------------------------
  
//读LCD忙状态并等待忙状态结束
void LCD_waitNotBusy(void)
{
	IO_LCD_Data = 0xFF; 
	io_LCD_RS = 0;
	io_LCD_RW = 1;
	io_LCD_E = 0;
	_nop_();
	_nop_();
	io_LCD_E = 1;
	while(IO_LCD_Data & 0x80); //检测如果是忙信号,一直等到不忙
}

//给LCD写指令
void LCDWriteCommand(uchar command,bool ifReadBusy) //ifReadBusy = 1 时先进行忙检测
{
	if (ifReadBusy == isReadBusy) LCD_waitNotBusy(); //根据需要检测忙
	IO_LCD_Data = command;
	io_LCD_RS = 0;
	io_LCD_RW = 0;	
	io_LCD_E = 0;
	_nop_();
	_nop_();
	io_LCD_E = 1;	
}

//给LCD写数据
void LCDWriteData(uchar dat) 
{
	LCD_waitNotBusy(); //等到不忙
	IO_LCD_Data = dat;
	io_LCD_RS = 1;
	io_LCD_RW = 0;
	io_LCD_E = 0; 
	_nop_();
	_nop_();
	io_LCD_E = 1;
}




// 初始化LCD1602液晶显示屏
void initLCD1602(void) 
{
	uchar	i;	
	IO_LCD_Data = 0;								// 数据端口清零
	for(i = 0; i < 3; i++)						// 设置三次显示模式
	{
		LCDWriteCommand(0x38,isNotReadBusy);	// 不检测忙信号
		delay5ms();
	}
	
	LCDWriteCommand(0x38,isReadBusy); // 设置显示模式,检测忙信号
	LCDWriteCommand(0x08,isReadBusy); // 关闭显示
	LCDWriteCommand(0x01,isReadBusy); // 显示清屏
	LCDWriteCommand(0x06,isReadBusy); // 显示光标移动设置
	LCDWriteCommand(0x0F,isReadBusy); // 显示开及光标设置
}



//按指定位置显示一个字符
void putOneCharToLCD1602(uchar line, uchar position, uchar ucData)
{

	line &= DATA_LineMax;
	position &= DATA_PositionMax;
	if (line == DATA_LineTow) position |= 0x40; 			//当要显示第二行时地址码+0x40;
	position |= 0x80; 									//设置两行显示格式 D7 = 1;
	LCDWriteCommand(position, isReadBusy); 			//发送命令 设置字符地址
	LCDWriteData(ucData); 							 	//写入字符的数据	
}

//按指定位置显示一串字符
void putLineCharsToLCD1602(uchar line, uchar position, uchar count, uchar code *ucData)
{
	uchar i;
	for(i = 0; i < count; i++)							//连续显示单个字符
	{
		putOneCharToLCD1602(line, position + i, ucData[i]);	
	}
}


//按指定位置连续显示三个字符(三位数字)
void	putThreeCharToLCD1602(uchar line, uchar position, uint uiNumber)
{
	uiNumber %= 1000;
	putOneCharToLCD1602(line, position, TabNumASCII[uiNumber / 100]);
	putOneCharToLCD1602(line, ++position, TabNumASCII[uiNumber % 100 / 10]);
	putOneCharToLCD1602(line, ++position, TabNumASCII[uiNumber % 100 % 10]);	
	
}
	


// 按键检测子程序,有键按下返回键端口数据,无键返回0
uchar GetKey(void)
{	
    uchar KeyTemp = (IO_KEY | DATA_KEY_ORL);		//获取按键端口数据	
	
	if( KeyTemp != DATA_KEY_Null )				// 如果不为空
	{
		uchar CountTemp = 0;
		do
		{
			delay125us();
			if(KeyTemp != (IO_KEY | DATA_KEY_ORL)) return 0;	//在延时期间检测键,如果不稳定保持则退出	
			
		} while(++CountTemp > Data_Key20msCountMax); 				// 延时20ms去抖动 
		
		while((IO_KEY | DATA_KEY_ORL) != DATA_KEY_Null); 	//等键释放		
		
		return KeyTemp;	// 有键按下返回键端口数据
	}
	
	return 0;	// 无有效键返回0
}



//加一
uchar  INC_Number(uchar Number, uchar Min, uchar Max)
{
	if(Number >= Max) return Min; else return (++ Number);
		
}

//减一
uchar  DEC_Number(uchar Number, uchar Min, uchar Max)
{
	if(Number <= Min) return Max; else return (-- Number);
		
}

// 检测到有按键后 这里执行按键任务			
void execute_key_task(uchar ucKeyValue)	
{
	uchar state = 0;						//定义调整数据的状态变量
	uchar keyValue = 0;					//定义键值的临时变量
	
	if(ucKeyValue != DATA_KEY_Set) return;	//不是设置键退出
	
	//是设置键继续-----------------------------------------------------
	
	putLineCharsToLCD1602(lineTow, 8, 8, "C:000cm ");	//清零显示当前距离CURRENT		
	putThreeCharToLCD1602(lineOne, 8 + 2, uiD);		//光标调整到调整总距离(检测探头到水库底部的距离“D:000cm”)	
	
	while(1)
	{
		keyValue = GetKey();	
		if(keyValue == 0) continue;
		
		switch(keyValue)
		{
			case DATA_KEY_Set:
			{
				// 如果按的是设置键,顺序设置总距离D——高水位H——低水位L——退出
				switch(state)
				{
					case 0:			// 如果是设置总距离状态,改变为设置高水位状态,并显示高水位,实现移动光标到高水位后面
					{
						state = 1;						
						putThreeCharToLCD1602(lineOne, 0 + 2, uiH);						
					}
					break;
					case 1:
					{
						uchar tempMax = uiD - DATA_uiD_Min;
						if(tempMax < 2 + 2) tempMax = 2 + 2;											
						if(uiH > tempMax) 
						{
							uiH = tempMax;
							putThreeCharToLCD1602(lineOne, 0 + 2, uiH);
						}
						else if(uiH < 2 + 2)
						{
							uiH = 2 + 2;	
							putThreeCharToLCD1602(lineOne, 0 + 2, uiH);
						}							
						state = 2;
						putThreeCharToLCD1602(lineTow, 0 + 2, uiL);
					}
					break;
					case 2:
					{
						if(uiL > uiH - 2) 
						{
							uiL = uiH - 2;
							putThreeCharToLCD1602(lineTow, 0 + 2, uiL);
						}
						return;	
						
					}
					break;
				}
				
			}
			break;
			// 如果按的是增加键,改变相应数据并显示
			case DATA_KEY_INC:
			{
				switch(state)
				{
					case 0:
					{
						uiD = INC_Number(uiD, DATA_uiD_Min, DATA_uiD_Max);
						putThreeCharToLCD1602(lineOne, 8 + 2, uiD);							
					}
					break;
					case 1:
					{
						uchar tempMax = uiD - DATA_uiD_Min;
						if(tempMax < 2 + 2) tempMax = 2 + 2;
						uiH = INC_Number(uiH, 2, tempMax);						
						putThreeCharToLCD1602(lineOne, 0 + 2, uiH);	
					}
					break;
					case 2:
					{
						uiL = INC_Number(uiL, 0, uiH - 2);	
						putThreeCharToLCD1602(lineTow, 0 + 2, uiL);	
					}
					break;
				}
				
			}
			break;
			// 如果按的是减少键,改变相应数据并显示
			case DATA_KEY_DEC:
			{
				switch(state)
				{
					case 0:
					{
						uiD = DEC_Number(uiD, DATA_uiD_Min, DATA_uiD_Max);					
						putThreeCharToLCD1602(lineOne, 8 + 2, uiD);
					}
					break;
					case 1:
					{
						uchar tempMax = uiD - DATA_uiD_Min;
						if(tempMax < 2 + 2) tempMax = 2 + 2;
						uiH = DEC_Number(uiH, 2, tempMax);						
						putThreeCharToLCD1602(lineOne, 0 + 2, uiH);	

					}
					break;
					case 2:
					{
						uiL = DEC_Number(uiL, 0, uiH - 2);	
						putThreeCharToLCD1602(lineTow, 0 + 2, uiL);	

					}
					break;
				}
				
			}
			break;
			
		}
		
	}
	
}



// 蜂鸣器	
void	buzzerCall(void)
{
	uchar	i;
	
		for(i = 0; i < 90; i++)
		{
			io_Buzzer = 0;
			delay100us();
			io_Buzzer = 1;
			delay100us();
			delay100us();		
		}
		delay100us();	
		delay100us();	
}

//计算水位
bool CalculatedWaterLevel(void)
{
	uchar 	i = 8 + 2;					//当前水位的数字在LCD屏显示的起点位置
	uint  	uiTime;						//声波传播时间
	ulong 	ulDis;						//实时测量到距离	
	
	uiTime = TH0 << 8 | TL0;	
	ulDis = (uiTime * 3.40) / 200;     	//计算当前测量的距离,单位cm
	
	TH0 = 0;
	TL0 = 0;	
	
	if((ulDis > uiD) || (g_flag == isYes )) 	// ulDis > uiD 超出测量范围;g_flag == isYes超时;
	{	 
		g_flag = isNo;		
		TR0 = 0;
		putLineCharsToLCD1602(lineTow, i, 3, "Err");	// 显示Err 		
		
		//阀门动作:		
		// if(g_flagSwitch == isYes)
		// {		
			// io_Control_Inlet = isio_Control_Inlet_OFF;		
			// io_Control_Outlet = isio_Control_Outlet_ON;
			// g_flagSwitch = isNo;
		// }		
		
		//指示灯:
		ioLed_Red = ! ioLed_Red;							// 三个灯同时快速闪亮
		ioLed_Green = ! ioLed_Green;
		ioLed_Yellow = ! ioLed_Yellow;
		
		// 蜂鸣器叫:	
		if(buzzerCallFlag == isCall)
		{
			buzzerCall();					// 蜂鸣器叫	
		}
		
		return isNo;									// 返回错误信息
	}
	else
	{
		ulDis = uiD - ulDis;					// 当前水位C = 总距离 - 当前检测到的距离
		
		if(ulDis > uiH)						// 如果水位超高
		{
			
			//阀门动作:
			io_Control_Inlet = isio_Control_Inlet_OFF;
			io_Control_Outlet = isio_Control_Outlet_ON;	
			g_flagSwitch = isNo;
			
			//指示灯:
			ioLed_Red = ! ioLed_Red;			// 红灯闪
			ioLed_Green = isLedOFF;		
			ioLed_Yellow = isLedOFF;				
			// 蜂鸣器叫:
			if(ulDis - uiH > (uiD - uiH) / DATA_alarmCoefficient) //当“当前水位”超出最高水位“ ((“总高度减高水位)除以2的值”)时报警
			{
				buzzerCall();					// 蜂鸣器叫
			}
			
		}
		else if(ulDis < uiL)					// 如果水位超低
		{
			//阀门动作:	
			if(g_flagSwitch == isYes)
			{		
				io_Control_Outlet = isio_Control_Outlet_OFF;	
				io_Control_Inlet = isio_Control_Inlet_ON;	
				g_flagSwitch = isNo;
			}	
			
			//指示灯:
			ioLed_Red = isLedOFF;
			ioLed_Green = isLedOFF;
			ioLed_Yellow = ! ioLed_Yellow;	//黄灯闪
			// 蜂鸣器叫:						
			if( uiL - ulDis > uiL / DATA_alarmCoefficient)//uiL / 2 当“当前水位”低于“低水位” “低水位除以2的值”时报警
			{
				buzzerCall();					// 蜂鸣器叫
			}
			
			
		}
		else								// 水位在正常范围 
		{	
			ioLed_Red = isLedOFF;
			ioLed_Green = ! ioLed_Green;
			ioLed_Yellow = isLedOFF;	
			
		}
		putThreeCharToLCD1602(lineTow, i, ulDis);
		return isYes;	
		
	}
	return isYes;
}

/*********************************************************/
void main(void)
{
	initIO();			//初始化IO端口	
	delay500ms(); 		//启动延时,给器件进入正常工作状态留够时间	
	initLCD1602(); 	//LCD初始化	
	putLineCharsToLCD1602(lineOne, 8, 8, "D:000cm ");	//显示distance (总)距离(检测探头到水库底部的距离)D
	putThreeCharToLCD1602(lineOne, 8 + 2, uiD);		//显示三位数值
	putLineCharsToLCD1602(lineOne, 0, 8, "H:000cm ");	//显示设定的最高报警水位H
	putThreeCharToLCD1602(lineOne, 0 + 2, uiH);		//显示三位数值
	putLineCharsToLCD1602(lineTow, 0, 8, "L:000cm ");	//显示设定的最低报警水位L
	putThreeCharToLCD1602(lineTow, 0 + 2, uiL);		//显示三位数值
	putLineCharsToLCD1602(lineTow, 8, 8, "C:000cm ");	//显示当前CURRENT水位C
	
	initTimer0();		//初始化定时器0
	initTimer1();
	
	//阀门动作:初始先排水
	io_Control_Inlet = isio_Control_Inlet_OFF;	
	io_Control_Outlet = isio_Control_Outlet_ON;
	g_flagSwitch = isNo;	
	
	while(1)
	{
		io_US_TX = 1;			        //启动超声波模块信号
		delay10us();
		io_US_TX = 0;
		
		while(io_US_RX == 0);			//等待计时开始
		TR0 = 1;			    			//开启定时器0,计时开始	
		IT1 = 1; 						//设置外中断INT1输入信号模式(1:Falling only仅下降沿有效 0:Low level低电平有效)
		EX1 = 1;                        //使能外中断INT1		
		
		while(EX1 == 1 && g_flag == isNo)//等待中断或超时退出	
		{
			uchar ucKeyValue = GetKey();					//在等待中检测按键
			if(ucKeyValue) execute_key_task(ucKeyValue);	//如果有键按下则执行按键任务	
		}
		
		if(CalculatedWaterLevel() == isNo) continue;		//计算水位,如果超出范围返回isNo并重新循环		
		
		TR0 = 0;		//暂时关闭定时器0
		//清零定时器和计数变量以及标志
		TL0 = 0;		
		TH0 = 0;		
		g_flag = isNo;
		ucCount = 0;
		g_flag05s = isNo;
		
		TR0 = 1;		//打开定时器0					
		while(g_flag05s == isNo)		//延时0.52秒,以防止此期间返回的超声波产生错误信息,并使显示变化放慢,保证视觉效果
		{			
			uchar ucKeyValue = GetKey();
			if(ucKeyValue) 
			{
				if(ucKeyValue == DATA_KEY_DEC)
				{
					g_flagBeepTimer = isNo;//用减小按键取消报警标志
				}

				execute_key_task(ucKeyValue);	//如果有键按下则执行按键任务	
			}				

		}	
		TR0 = 0;		//暂时关闭定时器0
		//清零定时器和复位标志
		TL0 = 0;	
		TH0 = 0;	
		g_flag = isNo;		
		
		//-----------------------------------
		//水箱清洗提示:
		
		if(g_flagBeepTimer == isYes)
		{
			buzzerCall();		
			//用减小按键取消报警标志
			
		}
		//-----------------------------------
		
	}
}

 具体资料:

https://market.m.taobao.com/app/idleFish-F2e/widle-taobao-rax/page-detail?wh_weex=true&wx_navbar_transparent=true&id=614517002002&ut_sk=1.WUpxx7gpwUoDAMmnnrBIzAno_12431167_1585198256687.Copy.detail.614517002002.1828622527&forceFlush=1

发布了23 篇原创文章 · 获赞 7 · 访问量 334

猜你喜欢

转载自blog.csdn.net/weixin_41017942/article/details/105115590
今日推荐