51单片机超声波水位控制器设计
说明:
本设计中液晶显示有4个字母,分别为
H------容器的最高水位设定值(不能高于实际高度)
L------容器的最低水位设定值
D-----容器实际高度(可以设置)
C-----容器内液体的高度(在实际演示中,障碍物离探头越近,液晶C显示越大,因为障碍物好比液面,离探头近了说明水位高了)
特别提醒:如果容器实际高度D你设置为1米,那么C液体的高度最高能测到98cm,因为探头的盲区在2cm左右。如果D设为2米,那么最高能测到1.98m.
按键功能分别为:设置键 增加键 减小键 复位键
三个指示灯的分别功能为:红色----超过设定的最高水位H 黄色-----低于设定的最低水位L
绿色----最高H和最低L中间
本文采用AT89C52单片机系统实现了水塔水位的自动控制,设计出一种低成本、高实用价值的水塔水位控制器。该系统具有水位检测、水位高度LCD显示、低水位高水位报警以及自动加水等功能。
本设计过程中主要采用了传感技术、单片机技术、光报警技术以及弱电控制强电的技术。本设计传感器使用了超声波模块,并且详细阐述了超声波测距测的原理,给出了系统构成框图。此系统具有易控制、工作可靠、测量精度高的优点,可实时监控液位。并采用52单片机系统控制整个电路的信号处理以及采用光电耦合和继电器来实现弱电控制强电来实现加水系统的自动控制。它能自动完成水位检测、光报警、上水停水的全部工作循环,保证液面高度始终处于较理想的范围内,它结构简单,制造成本低,灵敏度高,节约能源显著,是用于各种高层液体储存的理想设备。
单片机程序源码如下:
/***************************************************************
名称:基于51单片机的超声波水位监测报警系统
单片机型号:AT89C51
单片机设置:时钟12T,晶体12MHZ
作者:从零开始学单片机
注:修改增加水泵控制和排水控制,即双继电器
***************************************************************/
#include
#include
#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
鉴于篇幅限制,只能写部分代码
最后,如果有什么意见或者建议欢迎您留言给我,让我们共同学习一起进步,
如果需要 完整代码或设计文件,请在下方留言或者私信我,看到后会第一时间回复。
谢谢!