历史上的今天
返回首页

历史上的今天

今天是:2024年12月21日(星期六)

2021年12月21日 | 51单片机超声波水位控制器设计

2021-12-21 来源:eefocus

说明:


本设计中液晶显示有4个字母,分别为

H------容器的最高水位设定值(不能高于实际高度)

L------容器的最低水位设定值

D-----容器实际高度(可以设置)

C-----容器内液体的高度(在实际演示中,障碍物离探头越近,液晶C显示越大,因为障碍物好比液面,离探头近了说明水位高了)


特别提醒:如果容器实际高度D你设置为1米,那么C液体的高度最高能测到98cm,因为探头的盲区在2cm左右。如果D设为2米,那么最高能测到1.98m.


按键功能分别为:设置键 增加键 减小键 复位键


三个指示灯的分别功能为:红色----超过设定的最高水位H 黄色-----低于设定的最低水位L

绿色----最高H和最低L中间


本文采用AT89C52单片机系统实现了水塔水位的自动控制,设计出一种低成本、高实用价值的水塔水位控制器。该系统具有水位检测、水位高度LCD显示、低水位高水位报警以及自动加水等功能。


本设计过程中主要采用了传感技术、单片机技术、光报警技术以及弱电控制强电的技术。本设计传感器使用了超声波模块,并且详细阐述了超声波测距测的原理,给出了系统构成框图。此系统具有易控制、工作可靠、测量精度高的优点,可实时监控液位。并采用52单片机系统控制整个电路的信号处理以及采用光电耦合和继电器来实现弱电控制强电来实现加水系统的自动控制。它能自动完成水位检测、光报警、上水停水的全部工作循环,保证液面高度始终处于较理想的范围内,它结构简单,制造成本低,灵敏度高,节约能源显著,是用于各种高层液体储存的理想设备。


为了大家更好的理解,请如下看示意图

在这里插入图片描述

制作出来的实物图如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

AD的设计图如下:

在这里插入图片描述
在这里插入图片描述

超声波水位控制器元件清单


9*15万用板 1

AT89C51单片机 1

超声波探头 0

40脚IC座 1

4脚排针 0

杜邦线4根 0

继电器*2 0

LCD1602液晶 1

103电位器 0

16脚IC座 0

16脚排针 1

蜂鸣器 0

8550三极管*3 0

1k电阻*8 0

10k电阻 0

10uf电容 0

30pf电容*2 0

12M晶振 1

3mmLED(红、绿各2个,黄1个) 0

轻触按键*4 1

自锁开关 1

DC电源插口 1

USB电源线(电池盒)

直流水泵*2(根据客户自选)

单片机程序源码如下:


/***************************************************************

名称:基于51单片机的超声波水位监测报警系统

单片机型号:AT89C51

单片机设置:时钟12T,晶体12MHZ

作者:从零开始学单片机

注:修改增加水泵控制和排水控制,即双继电器

***************************************************************/


#include

#include // 包含循环移位: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);

1

2

3

}


// 延时100us

void delay100us(void) //@12MHz

{

uchar i;


    _nop_();

    i = 47;

    while (--i);

1

2

3

}


// 延时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);

1

2

3

4

5

6

}


// 延时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;

推荐阅读

史海拾趣

Gigarams Semiconductor Device Corp公司的发展小趣事
这可能是由于定位力量不够或初始力矩过大导致的。解决方案包括调整定位参数、优化启动策略或增加缓冲装置以减少振动。
Helicomm公司的发展小趣事
这可能是由于控制算法复杂或系统响应速度不足导致的。解决方案包括简化控制算法、提高系统响应速度或采用更高效的控制策略。
Bud Industries公司的发展小趣事
这可能是由于传感器精度不足、控制器算法不准确或驱动器性能不稳定等原因导致的。解决方案包括提高传感器精度、优化控制器算法和更换性能更稳定的驱动器。
DDK公司的发展小趣事

在电子行业中,产品质量是企业生存和发展的关键。DDK公司深知这一点,因此始终将产品质量放在首位。公司建立了严格的质量管理体系,从原材料采购到产品生产、检测的每一个环节都严格把控,确保每一件产品都符合高标准、严要求。这种对品质的执着追求,让DDK公司的产品在市场上赢得了良好的声誉,赢得了消费者的信任和忠诚。

Advanced Detector Corp公司的发展小趣事

随着技术的不断成熟和市场的扩大,ADC开始将业务扩展到更广泛的领域。公司不仅继续深耕探测器领域,还逐渐拓展到传感器、测量仪器等多个领域。通过不断推出多样化的产品,ADC成功满足了不同客户的需求,并在市场上取得了良好的口碑。

Elite Semiconductor Products Inc公司的发展小趣事

Elite Semiconductor Products Inc(以下简称Elite)自创立之初,就坚持以技术创新为核心竞争力。公司创始人是一位经验丰富的半导体工程师,他深知在电子行业,技术的领先意味着市场的领先。因此,Elite投入大量资源进行研发,不断推出具有领先性能的半导体产品。其中,一款高性能的芯片产品因其卓越的功耗控制和计算能力,在行业内引起了广泛关注。这款产品不仅帮助Elite打开了市场,也为公司后续的技术创新奠定了坚实的基础。

问答坊 | AI 解惑

急救!!5.0到6.0移值时,串口驱动问题

将5.0BSP移值到6.0 上时,运行到串口驱动时,就运行不下去了, DNW提示信息: WARNING: CReg2440Uart::CReg2440Uart failed to obtain processor frequency - using default value (66750000). DEBUG: Serial2 SYSINTR : -766318612 WARNING: C ...…

查看全部问答>

求 插值算法

小弟最近写一个程序,由传感器测量后由单片机进行AD转换,然后在lcd上显示出来,可是测出的东西如果不是连续的那么用什么算法能把显示的曲线图形变成连续的?比如测温度、电流、电压,是不是要用不同的插值算法?哪位能指点一下啊?谢谢。…

查看全部问答>

请教如何获取monthcalendar(日历控件)被选中的日期信息,急急急急急急急!

如题,小弟在做一个日程管理的小应用程序, 请问如何获取在日历控件上选定的日期信息, 比如选定某日, 在程序中要提取该日的日期和星期几的信息。 谢谢各位,很急,请大家帮帮忙。…

查看全部问答>

那个用单片机编写《小草》程序的人在吗

可不可以吧《小草》的单片机程序发给我啊? 234901369@qq.com…

查看全部问答>

今天晚上在家没事,给老冰箱做了个LED照明灯

    去年夏天,家里用了20多年的老冰箱(老人家买的,买了新冰箱后不够用,所以一直用着)坏了照明灯泡,用起来很不方便。当时把灯泡拿到电子市场去配,竟然找不到这种灯头的,只有作罢。     今天晚上在家没事,就 ...…

查看全部问答>

JLINK设置出问题了。怎么办

之前能仿真的。能下载程序的。  后来设置了一下               就只有不行了  一直是  AXD Uable to halt ARM core  我也不知道哪里出了问题。.         ...…

查看全部问答>

求大家帮助啊,最近学msp430单片机

#include #include void main(void) {   WDTCTL=WDT_MDLY_32;   IE1|=WDTIE;   P1DIR|=BIT6;   while(1); } #pragma vector=WDT_VECTOR __interrupt void watchdor(void) {   P1O ...…

查看全部问答>

求帮忙~~~~

经放大整形后传感器输入的电压如果是大于2V的脉冲信号,则数码管显示加1,否则没反应,我用的单片机是STC89C52,单片机工作电压5V,我的问题有两个:   一:传感器输入的脉冲信号能与I0口直接相连吗? 二:我这个需要用到单片机的哪些知识,因为 ...…

查看全部问答>

四层板PCB学习@PCB设计

我也来加入四层板的学习,在上班抽空时间总算布局完了,大概布了下,还不是很整齐,在这里得感谢@okhxyyo 的耐心帮忙和指导~ 希望能大家一起共同进步和学习…

查看全部问答>