资源描述
毕 业 (设 计) 论 文
题目: 基于51单片机控制的水塔
自动供水系统
系部: 电气工程与自动化系
专业: 自动化技术
班级: 电气A0701班
姓名: 李月鹏
指导教师: 陈毅朋、张慧明
山 西 综 合 职 业 技 术 学 院
摘 要
微型计算机SCMC,简称单片机,又称单片微控制器,它不是完成某一个逻辑功能的芯片,而是把一个计算机系统集成到一个芯片上。这种计算机的最小系统只用了一片集成电路,可进行简单运算和控制。虽然单片机只有一个芯片,但无论从组成还是从功能上看,它已具备了计算机系统的属性,是一个简单的微型计算机。
单片机以其体积小、功能全、价格优等种种优势充斥着整个市场。现在,单片机的使用领域已十分广泛,如智能仪表、实时工控、导航系统、家用电器等。单片机开发出的各种产品遍布于我们日常生活中的每个角落。
为了加深对单片机智能型控制器的了解,经过综合分析,本次设计最终选取了由51单片机控制的智能型液位控制器作为研究项目,本文对单片机水塔水位控制系统进行了整体设计,完成了单片机水塔水位控制系统硬件接线图和流程图以及单片机内部控制程序设计,并完成了开发板模拟仿真过程。通过此次设计过程,自己在分析问题、解决问题方面的能力得到了很大程度的提高。
MCS-51单片机 液压传感器 AD转换 水塔水位检控
基于51单片机控制的水塔自动供水系统
山西综合职业技术学院 李月鹏
引言
水塔供水的主要问题是塔内水位应始终保持在一定范围,避免“空塔”、“溢塔”现象发生。目前,控制水塔水位方法较多,其中较为常用的是由单片机控制实现自动运行,使水塔内水位保持恒定,以保证连续正常地供水。实际供水过程中要确保水位在允许的范围内浮动,应采用水压监测来控制水位。首先通过压力传感器实时检测水压,测量水位变化,再变送成电压信号传回单片机,由单片机执行内部控制程序指令,从而控制水泵电动机,保证水位在正常范围内变化。为此,这里给出以STC公司的STC89C52RC单片机为核心器件、以压力传感器为检测原件、通过ADC0804芯片为信号转换原件的水塔水位检测控制系统仿真设计,实现水位的自动检测控制、电机故障检测和报警等功能,通过在51单片机实验开发板上实际仿真,实验结果表明:该系统具有良好的检测和控制功能,实用性很强。
1 系统设计方案比较及论证
对于液位进行控制的方式有很多,而应用较多的主要有2种,一种是通过简单的机械控制装置来实现,而另一种是由复杂的控制器来控制。两种方式的实现简介如下:
(1)简单的机械式控制方式。其常用形式有浮标式、电极式等,这种控制形式的优点是结构简单,成本低廉。存在问题是精度不高,不能进行数值显示,另外很容易引起误动作,且只能单独控制,与计算机进行通信较难实现。
(2)复杂控制器控制方式。这种控制方式是通过安装在水塔出口管道上的压力传感器来把出口水压变成标准工业电信号的模拟信号,再经过前置放大、A/D转换模块变换成数字信号传送到单片机中,而后经单片机运算和给定参量的比较,进行PID运算,得出调节参量;最后经由D/A变换给调压、变频调速装置输入给定端,控制其输出电压变化,来调节电机转速,以达到控制水箱液位的目的。
针对上述2种控制方式,以及设计需达到的性能要求,这里选取第二种控制方式,同时考虑到成本问题需要把PID控制去掉。最终形成的方案是,利用单片机为控制核心,设计一个对水塔水位能进行自动监控的工控系统。根据监控对象的特征,要求实时检测水塔的液位高度,并与开始预设定的上、下限值做比较,由单片机控制固态继电器的开断进行液位的调整,最终达到液位的预设定值范围内。检测值若高于上限设定值时,要求报警,同时断开继电器,使水泵停止上水;检测值若低于下限设定值,要求报警,同时开启继电器,控制水泵开始上水。现场在LCD1602液晶屏上实时显示测量值,从而实现对水箱液位的自动监控。 在功能上,本设计还预留了两个调参按键,通过这两个按键可以自由设定水塔内水位的上下限值,以此来实现人工可控功能。
2 系统原理框图
图1 系统原理框图
3 工作原理
基于51单片机实现液位控制的控制器是以STC89C52RC芯片为核心,由键盘、LCD1602液晶显示、ADC0804模数转换、液压传感器、开关电源、蜂鸣报警、电磁继电器等部分组成。工作过程如下:水塔液位发生变化时,引起水塔中液压传感器的输出电压值变化,即把压力变化量转化成电压信号;该信号经过运算放大电路放大后变成幅度为0~5 V标准信号,送入A/D转换器,A/D转换器把模拟信号量变成数字信号量,再由单片机进行实时数据采集,并进行处理,根据设定要求控制输出,同时由液晶屏显示液位高度。通过键盘能够自由设置水位上下限值。该系统控制器的最大特点是可直观地显示水位占水塔容积的百分量,并可任意控制水位上下限高度。
4 硬件设计
液位控制器的硬件主要包括由单片机、液压传感器(带变送器)、键盘电路、液晶显示电路、A/D转换器和输出控制电路等。
4.1 STC89C52单片机简介
单片机采用的是由STC公司生产的双列40脚STC89C52RC芯片。STC公司生产的STC89C52RC单片机,是一款性价比非常高的单片机,普通用户可完全将其当作一般的51单片机来使用,高级用户可使用其扩展功能。 STC公司的单片机内部资源比起来ATMEL公司的单片机要丰富的多,它内部有1280字节的SRAM、8-64K字节的内部程序存储器、2-8K字节的ISP引导码、除P0-P3口外还多出了P4口(PLCC封装)、片内自带8路8位AD(AD系列),片内自带EEPROM、单片机内自带看门狗、双数据指针等。目前STC公司的单片机在国内市场上的占有率与日俱增。其中在这里我们把P0口连接LCD1602液晶显示屏;P1口用于A/D转换; P2口用于控制电磁阀、蜂鸣报警和键盘输入;P3口用于读写控制和中断等。下图是STC89C52RC单片机管脚图。
图2 STC89C52RC单片机管脚图
4.2 锁存器(74HC573)简介 锁存器,顾名思义,就是把输入端的数据锁存(或送出)到输出端,如下图所示,第11角(锁存端)为高电平的时候,右边D0-D7的输入与左边Q0-Q7的输出是直通的,就是说,输入端是什么电平,输出端就是什么电平,可以把它当作不存在。当第11角为低电平的时候,左右两端就被断开了,无论输入端怎么变化,输出端都不会变化,当第11角由低电平变为高电平的一瞬间,输入端的数据立刻被传送到输出端,并且在11角保持为高电平期间,输出端数据始终的输入端数据相同,如果此时我们再次把第11角设置为低电平,那么以后当输入端无论再怎么变化,输出端都不会变化而是保持刚才第11角在下降沿(由高电平到低电平跳变)之间时输入端的值,这样就达到了锁存数据的目的,这也就是所谓的总线设计思路,一个8位的数据线加一个锁存器后就可以扩接多个元件,当选通哪个元件的片选信号,就送数据给那个元件。
图3 74HC573管脚图
4.3 ADC0804简介 ADC0804是8位全MOS中速A/D 转换器,它是逐次逼近式A/D 转换器,片内有三态数据输出锁存器,可以和单片机直接接口。单通道输入,转换时间大约为100us。ADC0804 转换时序是:当CS=0 许可进行A/D 转换。WR由低到高时,A/D开始转换。CS与WR同时有效时启动A/D转换,转换结束产生INTR 信号(低电平有效),可供查询或者中断信号。在CS和RD 的控制下可以读取数据结果。在使用时可选择中断、查询和延时等待3种方式编制A/D转换程序。本实验没有使用INTR信号,而是采用了延时等待的方式,以便把中断口留给LCD1602液晶显示屏接线使用。A/D转换电路在控制器中起主导作用,用它来将液压传感器输出的模拟电压信号转换成单片机能处理的数字量。下图是A/D转换部分原理图,在接线时先要经过运算放大器和分压电路把传感器输出的电流信号转换成电压信号,然后输入到A/D转换器。由于实际条件较为有限,在此暂时由电位器来代替模拟液压传感器传回的电压信号量。
图4 ADC0804与单片机接线图
4.4 单片机与继电器及蜂鸣器的接口电路简介
采用光电耦合器的开关量输出电路如下图所示。+5V电源为单片机电源,+24V电源为开关量输出电源,两个电源是隔离的。当单片机输出端口输出高电平时,经与非门电路,A点为低电平,经光耦器件使驱动晶体管V导通,出口继电器J得电吸合。当输出端输出低电平时,经与非门电路,A点输出高电平,经光耦器件使V截止,J释放。以此来控制水泵执行运行或停止工作。
4.5 井中缺水信号检测电路
经过市场及网上实际调查发现,目前的液压传感器价格普遍偏高,多为高规格工业用品。为实现低价位高功能,在此我们通过气压传感器改制了一个液压传感器。同样可以满足实际要求。传感器使用SY一9411L—D型变送器,它内部含有1个压力传感器和相应的放大电路。该压力传感器是美国SM公司生产的555—2型OEM压阻式压力传感器,其有全温度补偿及标定(O~70℃),传感器经过特殊加工处理,用坚固的耐高温塑料外壳封装。其引脚分布如图3所示。1脚为信号输出(一);2脚为信号输出(一);3脚为激励电压;4脚为地;5脚为信号输出(+);6脚为信号输出(+)。 在水塔底部安装1根直径为5 mm的软管,一端安装在水塔底部;另一端与传感器连接。水塔水位高度发生变化时,引起软管内气压变化,然后传感器把气压转换成电压信号,输送到A/D转换器。
图8 SY-9411L-D型变送器引脚结构图
图9 LCD1602与单片机的接线图
图10 LCD1602液晶屏的各引脚功能图
5. 软件设计
5.1 程序流程图
图13 程序流程图
5.2 程序流程图解析
1 正常工作时:
(1)假设初始时水塔中无水,经过开始、初始化程序后首先判断是否有按键键入,如果有的话执行按键调试子程序,通过按键可以设定上限位,下限位值。设定完成后再执行AD转换,接下来是判断一下液晶屏上是否有报警指示输出,如果有的话就不执行显示水塔液位这一子程序,直接判断是否首次启动AD转换;否则在液晶屏上显示通过AD转换得来的水塔水位值。下一步再判断AD转换是否是首次启动,是首次启动就直接判断测量值是否小于预设的上限位AD值,如果此时小于上限位,再判断是否小于下限位AD值,如果判得小于下限位则再判断井中是否有水,接着如果判得井中有水就执行上水工作。经过一个延时程序后再执行下一次循环。
(2)有所不同的是在执行到判得检测值大于下限位时不执行上述循环,而是再判断之前水泵的工作状态,如果之前水泵是上水状态则转到判断井中是否有水,接着再执行上水工作,否则继续循环保持水泵停机状态。
(3)当水塔中的水高出上限位,此时执行断开继电器这一指令,让水泵停机。到此,上水工作完成。继续循环检测,用户用水会使水塔水位低于上限值,这时再执行判断之前水泵是否在上水状态,如果判得之前水泵没在上水状态则继续执行循环判断,如此一来就保证了低于水位下限时水泵开始上水,并一直上水达到上限位为止。再保持水塔水位用到低于下限位时开始打水。
2 出现故障时:
(1)当井中水位传感器检测到井中缺水时会调用报警子程序,在液晶屏上显示“井中缺水”字样,并响起蜂鸣器,同时断开控制水泵的继电器。再执行一个长延时子程序,等待井中积攒下足够的水后再执行上水工作。
(2)当判断AD值始终保持不变次数达到十次时,则需要再判断控制水泵的继电器是否一直吸合,如果满足这两个条件则可判知水泵可能出现故障,这时就需要液晶显示“水泵故障”,同时蜂鸣报警,并立即断开控制水泵的继电器。
6 实验仿真结果
根据所设计系统的软件流程图,编写相应的程序在kill软件环境下实现仿真,并且还通过51单片机实验开发板进行了模拟实验。实验结果表明,该系统能够实现水位检测、电机故障检测、处理和报警等功能,具有良好的检测控制功能,可移植性和扩展性强。
7 结束语
该系统设计是基于在单片机嵌入式系统基础上而设计的,充分利用单片机强大控制功能,该检测控制系统经过软件仿真实验和单片机实验开发板模拟实验,基本实现了水位模拟检测、电机故障模拟检测、处理和报警等功能。进一步优化了系统软硬件整体功能,可实时实现水塔自动控制,因此,该系统在水塔自动控制领域有着广阔的应用前景。
附录1 PCB原理图
附录2 C程序
#include <reg52.h>
#include <intrins.h>
#define uchar unsigned char
#define uint unsigned int
sbit adrd=P3^7; //IO口定义
sbit diola=P2^5;
sbit dula=P2^6;
sbit wela=P2^7;
sbit BEEP=P2^3 ; //蜂鸣器驱动线
sbit LCD_RS = P3^5;
sbit LCD_RW = P3^6;
sbit LCD_EN = P3^4;
bit presence,flag;
uchar code cdis1[ ] = {" PLEASE CHECK "};
uchar code cdis2[ ] = {" DEEP: "};
uchar code cdis3[ ] = {"TH: TL: "};
unsigned char data deep_data[2] = {0x00,0x00};
unsigned char data deep_alarm[2] = {0x0a,0xff};
unsigned char data display[5] =
{0x00,0x00,0x00,0x00,0x00}; //深度值显示
unsigned char data display1[3] = {0x00,0x00,0x00};
unsigned char code mytab1[8] =
{0x0C,0x12,0x12,0x0C,0x00,0x00,0x00,0x00};
unsigned char code mytab2[8] =
{0x01,0x1b,0x1d,0x19,0x1d,0x1b,0x01,0x00}; //小喇叭
#define delayNOP(); {_nop_();_nop_();_nop_();_nop_();};
unsigned char deep,deep_comp,timecount,count,compare_th,compare_tl;//数据存放位置
void Disp_deep_alarm(uchar addr,uchar num);
void Natural_Menu ();
void spk(uchar addr);
void set_deep_alarm();
void deep_compare();
void beep();
/***********************************************************/
void delay1(int ms)
{
unsigned char y;
while(ms--)
{
for(y = 0; y<250; y++)
{
_nop_();
_nop_();
_nop_();
_nop_();
}
}
}
//检查LCD忙状态
/*lcd_busy为1时,忙,等待。lcd-busy为0时,闲,可写指令与数据*/
/***********************************************************/
bit lcd_busy()
{
bit result;
LCD_RS = 0;
LCD_RW = 1;
LCD_EN = 1;
delayNOP();
result = (bit)(P0&0x80);
LCD_EN = 0;
return(result);
}
/***********************************************************/
/*写指令数据到LCD */
/*RS=L,RW=L,E=高脉冲,D0-D7=指令码。
/***********************************************************/
void lcd_wcmd(uchar cmd)
{
delay1(10);
LCD_RS = 0;
LCD_RW = 0;
LCD_EN = 0;
_nop_();
_nop_();
P0 = cmd;
delayNOP();
LCD_EN = 1;
delayNOP();
LCD_EN = 0;
}
/***********************************************************/
/*写显示数据到LCD
/*RS=H,RW=L,E=高脉冲,D0-D7=数据。
/***********************************************************/
void lcd_wdat(uchar dat)
{
delay1(10);
LCD_RS = 1;
LCD_RW = 0;
LCD_EN = 0;
P0 = dat;
delayNOP();
LCD_EN = 1;
delayNOP();
LCD_EN = 0;
}
/**********************************************************/
/* LCD初始化设定
/***********************************************************/
void lcd_init()
{
delay1(15);
lcd_wcmd(0x01); //清除LCD的显示内容
lcd_wcmd(0x38); //16*2显示,5*7点阵,8位数据
delay1(5);
lcd_wcmd(0x38);
delay1(5);
lcd_wcmd(0x38);
delay1(5);
lcd_wcmd(0x0c); //显示开,关光标
delay1(5);
lcd_wcmd(0x06); //移动光标
delay1(5);
lcd_wcmd(0x01); //清除LCD的显示内容
delay1(5);
}
/***********************************************************/
/* 设定显示位置
/***********************************************************/
void lcd_pos(uchar pos)
{
lcd_wcmd(pos | 0x80); //数据指针=80+地址变量
}
/***********************************************************/
/*us级延时函数
/***********************************************************/
void Delay(unsigned int num)
{
while( --num );
}
/***********************************************************/
/* 读取深度
/*********************************************************/
Read_deep(void)
{
uchar a,sl;
TR0=0; //关中断,防止读数错误
wela=1;
P0=0; //选通ADCS
LCD_RW=0; //AD写入(随便写个什么都行,
LCD_RW=1;
P0=0xff; //关闭ADCS
Delay(10);
wela=0; //关闭有AD片选信号锁存器的锁
//存端以防止在操作数码管时使AD的片选发生变化
for(a=20;a>0;a--) ; //需要注意的是ADC0804在写和
//读之间的时间间隔要足够长否则无法读出数据
for(a=20;a>0;a--); //这里把显示部分放这里的原
wela=1; //重新打开有AD片选信号锁存器
P1=0xff; //读取P1口之前先给其写全1
P0=0; //选通ADCS
adrd=0; //AD读使能
sl=P1; //AD数据读取赋给P1口
adrd=1;
P0=0xff; //关闭ADCS
LCD_RW=0;
deep_data[0] = sl ; //深度
TR0=1; //开中断
}
/***********************************************************
/* 数据转换与深度显示
/***********************************************************/
Disp_Temperature()
{
display[4]=deep_data[0]&0x0f;
display[0]=(display[4]*10/16)+0x30;
display[4]=((deep_data[0]&0xf0)>>4)|((deep_data[1]&0x0f)<<4);
display[3]=display[4]/100+0x30;
display[1]=display[4]%100;
display[2]=display[1]/10+0x30;
display[1]=display[1]%10+0x30;
if(display[3]==0x30) //高位为0,不显示
{ display[3]=0x20;
if(display[2]==0x30) //次高位为0,不显示
{display[2]=0x20;
};
};
lcd_pos(0x07);
lcd_wdat(display[3]); //百位数显示
lcd_wdat(display[2]); //十位数显示
lcd_wdat(display[1]); //个位数显示
lcd_wdat('.');
lcd_wdat(display[0]); //小数位数显示
lcd_wdat('%'); //显示%
}
void beep()
{
unsigned char y;
for (y=0;y<100;y++)
{
Delay(70);
BEEP=!BEEP; //BEEP取反
};
BEEP=1; //关闭蜂鸣器
Delay(25000);
}
/***********************************************************/
/* ad0804 正常显示菜单
/***********************************************************/
void Natural_Menu ()
{
uchar m;
lcd_init(); //初始化LCD
lcd_pos(0); //设置显示位置为第一行的第1个
for(m=0;m<16;m++);
lcd_wdat(cdis2[m]); //显示字符
lcd_pos(0x40); //设置显示位置为第二行第1个字
for(m=0;m<16;m++);
lcd_wdat(cdis3[m]); //显示字符
Read_deep();
Disp_deep_alarm(0x43,0); //显示deeph值
Disp_deep_alarm(0x4b,1); //显示deepl值
Disp_Temperature(); //显示实时深度值
}
/*********************************************************/
// 显示报警深度
/*********************************************************/
void Disp_deep_alarm(uchar addr,uchar num)
{ //0=TH,1=TL
display1[2]=deep_alarm[num]/100+0x30;
display1[0]=deep_alarm[num]%100;
display1[1]=display1[0]/10+0x30;
display1[0]=display1[0]%10+0x30;
lcd_pos(addr);
lcd_wdat(display1[2]); //百位数显示
lcd_wdat(display1[1]); //十位数显示
lcd_wdat(display1[0]); //个位数显示
}
/**********************************************************/
// Time0中断函数
/**********************************************************/
void Time0(void) interrupt 1 using 0
{
TH0=0x4c; //50ms定时
TL0=0x00;
timecount++;
if(timecount>9)
{
timecount=0;
flag=~flag;
};
}
/*********************************************************/
/*小喇叭闪动 */
/*********************************************************/
void spk(uchar addr)
{
if(flag)
{
lcd_pos(addr);
lcd_wdat(0x01); //小喇叭显示
}
else
{
lcd_pos(addr);
lcd_wdat(0x20); //清屏
};
}
/*********************************************************/
// 深度比较函数
/*********************************************************/
void deep_compare()
{
if(deep_comp >= deep_alarm[0]) //比较 TH 值
{
compare_th=1;
}
else compare_th=0;
if(deep_comp < deep_alarm[1]) //比较 TL 值
{
compare_tl=1;
}
else compare_tl=0;
if(compare_th)
{
spk(0x46); //小喇叭闪动
beep();
count=deep_alarm[0];
}
else
{
lcd_pos(0x46);
lcd_wdat(0x20);
}
if(compare_tl)
{
spk(0x4e); //小喇叭闪动
beep();
count=deep_alarm[1];
}
else
{
lcd_pos(0x4e);
lcd_wdat(0x20);
}
}
/*********************************************************/
// 深度报警值闪动
/*********************************************************/
void Set_tempalarm_Flash(uchar addr,uchar num)
{
if(flag)
{
deep_alarm[num]=count;
Disp_deep_alarm(addr,num); //显示温度报警值
}
else
{
lcd_pos(addr);
lcd_wdat(0x20);
lcd_wdat(0x20);
lcd_wdat(0x20);
};
}
/**********************************************************/
/* 主函数
/***********************************************************/
void main()
{
uchar m;
TMOD=0x01; TH0=0x4c; TL0=0x00;
//50ms定时
EA=1; ET0=1; TR0=1;
P0=0;
P2&=0x1F;
Natural_Menu ();
//正常显示界面
Delay(50000);
while(1)
{
Read_deep();
//读取当
展开阅读全文