51单片机超声波水位控制器设计 (C源码+PCB+原理图与实物制作)
最近几天总是下雨,搞的人心情也很差,昨天看到有粉丝留言说,导师让他们组做一个关于超声波控制液体水位的项目,但是无从下手,希望我帮帮他,可以有偿。他是我的铁粉,下面我就把我前段时间帮人家设计的一个超声波水位自动控制系统 分享出来,希望可以帮到他们。
51单片机超声波水位控制器设计
设计说明:
本设计中液晶显示有4个字母,分别为
H------容器的最高水位设定值(不能高于实际高度) L------容器的最低水位设定值
D-----容器实际高度(可以设置)
C-----容器内液体的高度(在实际演示中,障碍物离探头越近,液晶C显示越大,因为障碍物好比液面,离探头近了说明水位高了)
特别提醒:如果容器实际高度D你设置为1米,那么C液体的高度最高能测到98cm,因为探头的盲区在2cm左右。如果D设为2米,那么最高能测到1.98m.
按键功能分别为:设置键 增加键 减小键 复位键
三个指示灯的分别功能为:红色----超过设定的最高水位H 黄色-----低于设定的最低水位L
绿色----最高H和最低L中间
本文采用AT89C52单片机系统实现了水塔水位的自动控制,设计出一种低成本、高实用价值的水塔水位控制器。该系统具有水位检测、水位高度LCD显示、低水位高水位报警以及自动加水等功能。
本设计过程中主要采用了传感技术、单片机技术、光报警技术以及弱电控制强电的技术。本设计传感器使用了超声波模块,并且详细阐述了超声波测距测的原理,给出了系统构成框图。此系统具有易控制、工作可靠、测量精度高的优点,可实时监控液位。并采用52单片机系统控制整个电路的信号处理以及采用光电耦合和继电器来实现弱电控制强电来实现加水系统的自动控制。它能自动完成水位检测、光报警、上水停水的全部工作循环,保证液面高度始终处于较理想的范围内,它结构简单,制造成本低,灵敏度高,节约能源显著,是用于各种高层液体储存的理想设备。
为了大家更好地理解,请如下看示意图
制作出来的实物图如下:
AD的设计图如下:
超声波水位控制器元件清单
1) 9*15万用板 1
2) AT89C51单片机 1
3) 超声波探头 0
4) 40脚IC座 1
5) 4脚排针 0
6) 杜邦线4根 0
7) 继电器*2 0
8) LCD1602液晶 1
9) 103电位器 0
10) 16脚IC座 0
11) 16脚排针 1
12) 蜂鸣器 0
13) 8550三极管*3 0
14) 1k电阻*8 0
15) 10k电阻 0
16) 10uf电容 0
17) 30pf电容*2 0
18) 12M晶振 1
19) 3mmLED(红、绿各2个,黄1个) 0
20) 轻触按键*4 1
21) 自锁开关 1
22) DC电源插口 1
23) USB电源线(电池盒)
24) 直流水泵*2(根据客户自选)
单片机程序源码如下:
/***************************************************************
名称:基于51单片机的超声波水位监测报警系统
单片机型号:AT89C51
单片机设置:时钟12T,晶体12MHZ
作者:从零开始学单片机
注:修改增加水泵控制和排水控制,即双继电器
***************************************************************/
#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
鉴于篇幅限制,只能写部分代码
最后,如果有什么意见或者建议欢迎您留言给我,让我们共同学习一起进步,
如果需要 完整代码或设计文件,请在下方留言或者私信我,看到后会第一时间回复。
谢谢!
感谢你的阅读,希望您有所收获,喜欢请点赞评论加关注!
基于单片机高低水位控制系统设计实例
硬件原理水位检测部分要分析,程序简单,检测IO高低电平,控制显示和马达运转
低水位时,显示1,继电器接通,保持抽水
高水位时,显示2,继电器开,停止抽水
相关问答
英语翻译采用8051 单片机系统 实现了 水位的 自动控制 , 设计 一种...[最佳回答]采用8051单片机系统实现了水位的自动控制,设计一种成本低、实用价值高的水塔水位控制器.\x05Usingthe8051single-chipmicrocomputersystemrea....
如何用 单片机控制 开关?就是可以控制开关“打开和闭合”的电路啊通过这些开关的打开或者闭合,可以实现许多控制功能,举个最简单的例子,水箱的进水和出水,各有一个开关,通过传感器...
全自动 水位 水塔传感器原理?随着科技的进步啊,人们研究科技的手法也成熟起来了,对于水位传感器,不管是在水箱上,还是在水塔上,还是在洗衣机上,都会用到这个技术,那么你知道什么是水位...
高温高压液位计选择?-盖德问答-化工人互助问答社区请教各位高手,关于高温高压型液位计,除了磁翻板的之外,选择磁浮子的好,还是射频的好,亦或是光电的,目前国内有没有合适一些的厂家?给推荐几个,国外...
求解C语言编写的51 单片机 用按键实现 暂停 开启-ZOL问答否则不执行该模式里面的内容1程序{};2启/停{用一个标志是否为1或为0,表示启动或暂停};3电源{进入SLEEP,再次按按键就唤醒单片机};4水位{当水位<
洗衣机的 水位 传感器 - 懂得洗衣机中的水位传感器是靠改变电感量来感知的所以非常精确,不知道你那单片机是什么东东,不过简单点的你可以用水位开关,水位开关的原理就简单了,就...
能告诉我!!有实力的水泵变频 控制柜 原理及参数,水泵变频 控制 ...产品简介CWS-VI型全自动变频增压水泵是结构紧凑、功能强大、性能范围更广的中大型立式单控式恒压型无塔次供水系统。产品简介产品简介HXK-Y系列...
...工业的冰层液位检测装置价格,冰层液位检测装置 设计 怎么样??[回答]武汉静磁栅机电制造有限公司是专业从事绝对位移传感器系列产品研发和产业化的小微高新技术企业,以自主研发和技术创新为核心,专注位移精确测量,注重...
物联网如何打造智慧城市?什么是智慧城市?借助数字技术和大数据致力于改善人民生活的城市是智慧城市。一个智慧城市是城市地区使用不同类型的电子的传感器来收集数据,然后使用从中获得...
麻烦各位老司机 麻烦回复一下!杭州专业的协同 控制 检测,协同...[回答]通俗一点儿讲,开关量控制也叫开环控制,举个例子:红绿灯指挥车辆、行人时,它不管你路上是否有车或是行人,不需要检测和判断车辆行人的状态,它按照事...