生活中,人工浇灌花木要耗费大量时间,而且土壤湿度不好控制,有时候由于主人长时间外出,家里的花木会因无人浇水而枯死。为了解决上述问题,本文利用单片机,设计了自动和手动浇花系统(如图1所示)。
图1 自动浇花系统
一、功能描述
系统有自动和手动两种工作模式,两种工作模式由手自动切换按键切换。系统开机进行自检,如果系统有故障则报警,若系统工作正常则进入自动状态,初始设定值为25%。当土壤湿度小于设定值时水泵工作浇水,当高于设定值加上偏移量(偏移量可根据实际确定,本文设为2)时系统停止加水。在自动工作模式下,如果由于缺水、加水不能停止或是测量信号不正常,则系统报警,水泵停止加水。在手动工作模式下,可按加水键和停止键实现手动加水和停止。背光按键控制液晶背光的开关。电路成品如图2所示。
图2 自动浇花系统电路板
二、硬件系统设计
(一)硬件系统构成
系统选择的各种元器件都是目前市面上常见的,系统核心控制器件采用应用广泛的STC公司的STC89C52RC单片机。除了单片机外,还包括土壤湿度传感器、AD转化器、LCD1602液晶显示器、按键、指示灯、蜂鸣器、继电器及水泵等。
(二)电路工作原理
电路原理图见图3,包括电源电路、晶振和复位电路、检测电路、按键和显示电路、报警电路、输出控制电路。
1.电源电路
系统所需5V电源由外部直接提供,可由常见的各种手机充电器提供,留有USB电源端子和圆形端子跟外部电源连接,使用时连接其中之一,另外一个可以向外提供5V电源,方便系统电源连接。
2.晶振和复位电路
电容器C6、C7和晶振Y1构成振荡电路,给单片机提供所需的时钟。C8、R13和按键RST1构成单片机复位电路,实现上电复位和手动复位。
3.检测电路
检测电路由土壤湿度传感器和AD转换器组成,选择价格便宜且使用方便的土壤湿度传感器模块和AD转换器PCF8591模块(见图4)。该模块有4路输入、1路输出,可满足系统扩展需要。此电路把土壤湿度转换成模拟量,经AD转换后转换成单片机可以识别的对应数字量。
图4 土壤湿度传感器和AD转换模块
4.按键和显示电路
按键和显示电路由四个独立按键、LED指示灯和LCD1602构成,用于人机操作和系统各种工作状态的信息显示。
5.报警电路
报警电路由蜂鸣器和LED指示灯构成,实现声光报警。声音报警15次后停止,LED指示灯一直闪烁报警。
6.输出控制电路
单片机引脚输出信号通过驱动电路驱动继电器,控制继电器的通断,用以驱动执行元件(文中为5V直流水泵)的通断,LED指示灯显示输出状态。
三、软件系统设计
(一)系统流程图(见图5)
(二)系统软件设计
系统采用项目化多文件,模块化编程,便于程序编写和程序移植。由main.c,I2C.c,KEYBOARD.C,Lcd1602.c文件和对应头文件以及配置文件config.h组成。Lcd1602.c主要负责液晶的底层显示驱动。I2C.c主要负责AD转化器的底层功能驱动。KEYBOARD.C责按键处理,main.c负责系统初始化,自检,土壤湿度检测,中值滤波和信号线性化处理,手动自动程序运行。中断程序负责,系统运行时的故障处理和报警,程序较短,也放在main.c中。整个项目,软件按流程图编写,结构清晰,流程也比较清楚。为方便读者学习和参考,把所有文件和程序都列了出来,并做了详细的注释。
整个项目的软件按流程图编写即可,程序代码如下(注意报纸因为版面原因,无法全部刊登,这里可以看到完整的代码)。
参考程序
/***************************************
config.h
**************************************/
#ifndef _CONFIG_H
#define _CONFIG_H
/*通用头文件*/
#include《reg52.h》
#include《intrins.h》
#define ON 0
#define OFF 1
#define OFFSET 2 //上下限偏差
/*数据类型定义*/
typedef unsigned char uchar ;//8位无符号数
typedef unsigned int uint ;//8位无符号数
/*IO引脚分配定义*/
sbit AK_KEY=P1^0;//背光控制
sbit UP_KEY=P1^1; //增加_启动
sbit DN_KEY=P1^2;//减小_停止
sbit MA_KEY=P1^3;//手动_自动
sbit ALARM_LED=P1^4;//故障指示
sbit PUMP_LED=P1^5;//抽水指示
sbit MANUAL_LED=P1^6;//手动指示
sbit AUTO_LED=P1^7;//自动指示
sbit PUMP=P2^2;//水泵控制
sbit BUZZER=P2^3;//蜂鸣器报警
#define LCD1602_DB P0 //1602液晶数据口
sbit LCD1602_RS=P2^7; //1602液晶数据_指令选择
sbit LCD1602_RW=P2^6; //1602液晶读写选择
sbit LCD1602_E=P2^5; //1602液晶使能信号
sbit LCD1602_AK=P2^4; //1602液晶背光信号
sbit I2C_SCL=P2^1;//AD转换器
sbit I2C_SDA=P2^0;
#endif
/***************************************
MAIN.H
***************************************/
#ifndef _MAIN_H
#define _MAIN_H
enum eStaSystem //系统运行状态枚举
{
E_AUTO,E_MANUAL,E_ALARM
}; //设置,自动,手动,故障报警
#ifndef _MAIN_C
extern enum eStaSystem staSystem;
#endif
void delayms(unsigned int t);//延时函数
void SysInit();//系统初始化
void SelfCheck();//系统自检
void AutoWork();//系统自动运行
void ShowLcd1602();//测量、状态显示
void Humidity();//土壤湿度检测
unsigned char GetADCValue(unsigned char chn); //AD转换
void ConfigTimer0(unsigned int ms); //T0 配置函数
#endif
/***************************************
main.c
***************************************/
#define _MAIN_C
#include“config.h”
#include “main.h”
#include “Keyboard.h”
#include “I2C.h”
#include “Lcd1602.h”
#define N 3 //AD采样次数
#define OFFSET 2//上限偏移量
enum eStaSystem staSystem=E_AUTO;//初始为自动状态
unsigned char T0RH=0; //定时器T0载值
unsigned char T0RL=0;
unsigned char TestVal;//测量值
unsigned char cnt; //蜂鸣器报警15次
//----------------------------------------------------------------------------------
void main ()
{
SysInit();
SelfCheck();
while(1)
{
KeyAction();
AutoWork();
}
}
/****************************************
函数功能:系统初始化
入口参数:无
*****************************************/
void SysInit()
{
AK_KEY=OFF;//按键IO口初始化
UP_KEY=OFF;//
DN_KEY=OFF;
MA_KEY=OFF;
ALARM_LED=OFF;//指示灯熄灭
PUMP_LED=OFF;
MANUAL_LED=OFF;
AUTO_LED=OFF;
BUZZER=OFF;//关蜂鸣器
PUMP=OFF;//关水泵
LcdInit(); // 初始化液晶
LcdShowStr(0,0, “-Auto watering- ”); // 显示系统自检
LcdShowStr(1,1, “system testing”); //
ConfigTimer0(10); // 配置T0 定时10ms
EA=0;// 关中断
}
/****************************************
函数功能:系统自动运行,故障报警
入口参数:无
*****************************************/
void AutoWork()
{
if(staSystem==E_AUTO)
{
Humidity();
AUTO_LED=ON;
MANUAL_LED=OFF;
if(TestVal》96||TestVal《5)
{
staSystem=E_ALARM;
cnt=30;
EA=1;
}//故障
elseif(TestVal《SetVal)
{
PUMP=ON;
PUMP_LED=ON;
}
elseif(TestVal》(SetVal+OFFSET))
{
PUMP=OFF;PUMP_LED=OFF;
}
}
ShowLcd1602();//更新显示
}
/****************************************
函数功能:系统自检,故障报警
入口参数:无
*****************************************/
void SelfCheck()
{
unsignedchar i,k,n;
unsignedchar temp;
for(i=0;i《3;i++)
{
ALARM_LED=ON;
PUMP_LED=ON;//抽水指示
MANUAL_LED=ON;//手动指示
AUTO_LED=ON;//自动指示
delayms(500);
ALARM_LED=OFF;
PUMP_LED=OFF;//抽水指示
MANUAL_LED=OFF;//手动指示
AUTO_LED=OFF;//自动指示
delayms(500);
}
BUZZER=ON;
delayms(500);
BUZZER=OFF;
LcdClearScreen();
LcdShowStr(0,0,“Test Results:”);
for(i=0;i《4;i++)//读取3次测量结果,过高故障报警
{
temp=GetADCValue(3);//直接读取0通道
if(temp》240||temp《10)k++;
}
if(k》2)//自检故障
{
EA=0;//关中断
while(1)
{
ALARM_LED=ON;
LcdShowStr(1,10,“ERROR!”);
delayms(550);
LcdShowStr(1,10,“ ”);
ALARM_LED=OFF;
delayms(250);
if(n++《3)BUZZER=!BUZZER;
elseBUZZER=OFF;
}
}
LcdClearScreen();
}
/****************************************
函数功能:测量、状态显示
入口参数:无
*****************************************/
void ShowLcd1602()
{
LcdShowStr(0,0,“PV:”);
ShowNum(0,3,TestVal);//显示实测值
LcdShowStr(0,6,“%RH”);
LcdShowStr(0,10,“State:”);
LcdShowStr(1,0,“SV:”);
ShowNum(1,3,SetVal);//显示设定值
LcdShowStr(1,6,“%RH”);
if(staSystem==E_AUTO)LcdShowStr(1,10,“ Auto ”);
elseif(staSystem==E_MANUAL) LcdShowStr(1,10,“Manual”);
}
/**********************************************
函数功能:读取当前的ADC 转换值
入口参数:chn,AD通道号0-3
**********************************************/
unsigned char GetADCValue(unsigned charchn)
{
ucharval;
I2CStart();
if(!I2CWrite(0x48《《1)) // 寻址PCF8591 PCF8591,如未应答,则停止操作并返回,00
{
I2CStop();
return0;
}
I2CWrite(0x40|chn);// 写入控制字节,选择转换通道
I2CStart();
I2CWrite((0x48《《1)|0x01);// 寻址PCF8591 PCF8591,指定后续为读操作
I2CReadACK();// 先空读一个字节,提供采样转换时间
val= I2CReadNAK(); // 读取刚刚转换完的值
I2CStop();
returnval;
}
/****************************************
函数功能:按升序排列数组元素
入口参数:数组及数组长度
*****************************************/
void SortArray(unsigned char a[],unsignedchar a_len)
{
unsignedchar i,temp;
for(i=1;i《a_len;i++)
{
if(a[i-1]》a[i])
{
temp=a[i-1];
a[i-1]=a[i];
a[i]=temp;
i=0;
}
}
}
/****************************************
函数功能:中值滤波,线性转换后,获得土
壤湿度0-100%
入口参数:无
*****************************************/
void Humidity()
{
unsigned char i, tmp;
unsigned char adBuf[N];
for(i=0;i《N;i++)adBuf[i]=GetADCValue(1);//读取湿度
SortArray(adBuf,sizeof(adBuf));
tmp=adBuf[N/2];
tmp=GetADCValue(3);
TestVal=100-100.*tmp/255;
}
/****************************************
函数功能:配置定时器0,定时时间
壤湿度0-100
入口参数:ms,定时时间(毫秒)
****************************************/
void ConfigTimer0(unsigned int ms)
{
unsigned long tmp;
tmp = 11059200 /12; // 定时器计数频率
tmp = (tmp * ms) /1000; // 计算所需的计数值
tmp = 65536 - tmp; // 计算定时器重载值
tmp = tmp + 12; // 修正中断响应延时造成的误差
T0RH = (unsigned char)(tmp 》》 8); // 定时器重载值拆分为高低字节
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; // 清零T0 的控制位
TMOD |= 0x01; // 配置T0 为模式11
TH0 = T0RH; // 加载T0 重载值
TL0 = T0RL;
ET0 = 1; // 使能T0 中断
TR0 = 1; // 启动T0
}
/****************************************
函数功能:延时函数
入口参数:t,延时约t毫秒
****************************************/
//---------延时----------------
void delayms(uint t)
{
uchari;
while(t--)
for(i=0;i《123;i++);
}
/****************************************
函数功能:T0 中断服务函数
入口参数:无
****************************************/
void InterruptTimer0() interrupt 1
{
static unsigned char tmr=0;
TH0 = T0RH; // 定时器重新加载重载值
TL0 = T0RL;
tmrms++;
tmr=(tmr+1)%100;
if(staSystem==E_ALARM) //故障液晶报警显示
{
if(tmr《50)LcdShowStr(1,11, “ALARM ”);
elseLcdShowStr(1,11, “ ”);
if(tmrms》=50) // 定时0.5s
{
tmrms=0;
ALARM_LED=!ALARM_LED;
if(cnt》0){cnt--;BUZZER=!BUZZER;}
else{BUZZER=OFF;}
}
}
else
{
ALARM_LED=OFF;
BUZZER=OFF;
}
}
/***************************************
Lcd1602.h
***************************************/
#ifndef _LCD1602_H
#define _LCD1602_H
#ifndef _LCD1602_C
#endif
void LcdWaitReady(); //读取“忙”表示,等待液晶准备好
void LcdWriteCmd(unsigned char cmd);//给液晶发送命令cmd
void LcdWriteDat(unsigned char dat); //写入数据函数
void LcdClearScreen();//液晶清屏
void LcdInit(); //液晶初始化函数
void CursorPos(unsigned char row,unsignedchar col);//光标定位
void ShowNum(unsigned char row,unsignedchar col,unsigned char Num);//显示3位数
void LcdShowStr(unsigned char row, unsignedchar col,unsigned char code *str); //显示字符串
#endif
/***************************************
Lcd1602.c
***************************************/
#define _LCD1602_C
#include“config.h”
#include“Lcd1602.h”
/********************************************
函数功能:判断液晶模块忙碌状态。忙,等待。
入口参数:无。
********************************************/
void LcdWaitReady() //读取“忙”表示,等待液晶准备好
{
unsigned char sta;
LCD1602_DB = 0xFF;
LCD1602_RS = 0;
LCD1602_RW = 1;
do// do.。.while 循环语句
{
LCD1602_E= 1;
sta= LCD1602_DB; //读取状态字
LCD1602_E= 0; //读完了要关闭使能,防止液晶输出数据干扰总线
}while (sta & 0x80); //bit7 等于1 表示液晶正忙,重复检测直到其等于0 为止
}
/************************************************
函数功能:给液晶发送命令
入口参数:cmd
**************************************************/
void LcdWriteCmd(unsigned char cmd)
{
LcdWaitReady();
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = cmd;
LCD1602_E = 1;
LCD1602_E = 0;
}
/************************************************
函数功能:给液晶发送数据
入口参数:dat
**************************************************/
void LcdWriteDat(unsigned char dat) //写入数据函数
{
LcdWaitReady();
LCD1602_RS = 1;
LCD1602_RW = 0;
LCD1602_DB = dat;
LCD1602_E = 1;
LCD1602_E = 0;
}
/************************************************
函数功能:清屏
入口参数:无
**************************************************/
void LcdClearScreen()
{
LcdWriteCmd(0x01);
}
/************************************************
函数功能:液晶初始化
入口参数:无
**************************************************/
void LcdInit() //液晶初始化函数
{
LcdWriteCmd(0x38); //16*2 显示,5*7 点阵,8 位数据接口
LcdWriteCmd(0x0C); //显示器开,光标关闭
LcdWriteCmd(0x06); //文字不动,地址自动加1
LcdWriteCmd(0x01); //清屏
}
/************************************************
函数功能:定位光标
入口参数:row行位置,col列位置
**************************************************/
void CursorPos(unsigned char row,unsignedchar col)
{
row%=2; col%=40; //防止越界
if(row)LcdWriteCmd(0xC0+col); //第2行
elseLcdWriteCmd(0x80+col); //第1行
}
/************************************************
函数功能:显示3位数
入口参数:row行位置,col列位置,Num显示数据
**************************************************/
void ShowNum(unsigned char row,unsignedchar col,unsigned char Num)
{
row%=2; col%=40; //防止越界
LcdWriteCmd(0x80+row*0x40+col);//光标定位
if(Num《10)
{
LcdWriteDat(‘’); //百位
LcdWriteDat(‘’);//十位
LcdWriteDat(Num%10+‘0’);//个位
}
elseif(Num《100)
{
LcdWriteDat(‘’); //百位
LcdWriteDat(Num/10%10+‘0’); //十位
LcdWriteDat(Num%10+‘0’); //个位
}
else
{
LcdWriteDat(Num/100+‘0’); //百位
LcdWriteDat(Num/10%10+‘0’); //十位
LcdWriteDat(Num%10+‘0’); //个位
}
}
/************************************************
函数功能:在给定位置显示字符串
入口参数:row行位置,col列位置,str字符串
**************************************************/
void LcdShowStr(unsigned char row, unsignedchar col, unsigned char code *str) //显示字符
{
uchari=0;
row%=2;col%=40; //防止越界
LcdWriteCmd(0x80+row*0x40+col);//光标定位
for(;col《40&&str[i]!=0;i++,col++){LcdWriteDat(str[i]);}
}
/***************************************
I2C.h
***************************************/
#ifndef _I2C_H
#define _I2C_H
#ifndef _I2C_C
#endif
void I2CStart(); // 产生总线起始信号
void I2CStop(); // 产生总线停止信号
bit I2CWrite(unsigned char dat); //I2C 总线写操作,待写入字节dat dat,返回值为应答状态
bit I2CWrite(unsigned char dat); //I2C 总线写操作,待写入字节dat dat,返回值为应答状态
unsigned char I2CReadACK(); //I2C 总线读操作,并发送应答信号,返回值为读到的字节
unsigned char I2CReadNAK();
#endif
/***************************************
I2C.c
***************************************/
#define _I2C_C
#include “config.h”
#include “I2C.h”
#define I2CDelay(){_nop_();_nop_();_nop_();_nop_();}
/********************************************
函数功能:产生总线起始信号。
入口参数:无。
********************************************/
void I2CStart()
{
I2C_SDA = 1; //首先确保SDA、SCL 都是高电平
I2C_SCL = 1;
I2CDelay();
I2C_SDA = 0; //先拉低SDA
I2CDelay();
I2C_SCL = 0; //再拉低SCL
}
/********************************************
函数功能:产生总线停止信号。
入口参数:无。
********************************************/
voidI2CStop()
{
I2C_SCL = 0; //首先确保SDA、SCL 都是低电平
I2C_SDA = 0;
I2CDelay();
I2C_SCL = 1; //先拉高SCL
I2CDelay();
I2C_SDA = 1; //再拉高SDA
I2CDelay();
}
/********************************************
函数功能:I2C 总线写操作,返回值为应答状态。
入口参数:待写入字节dat。
********************************************/
bit I2CWrite(unsigned char dat)
{
bit ack; //用于暂存应答位的值
unsigned char mask; //用于探测字节内某一位值的掩码变量
for (mask=0x80; mask!=0; mask》》=1) //从高位到低位依次进行
{
if ((mask&dat) == 0) //该位的值输出到SDA 上
I2C_SDA = 0;
else
I2C_SDA = 1;
I2CDelay();
I2C_SCL = 1; //拉高SCL
I2CDelay();
I2C_SCL = 0; //再拉低SCL,完成一个位周期
}
I2C_SDA = 1; //8 位数据发送完后,主机释放SDA,以检测从机应答
I2CDelay();
I2C_SCL = 1; //拉高SCL
ack = I2C_SDA; //读取此时的SDA 值,即为从机的应答值
I2CDelay();
I2C_SCL = 0; //再拉低SCL 完成应答位,并保持住总线
return (~ack); //应答值取反以符合通常的逻辑:0=不存在或忙或写入失败,1=存在且空闲或写入成功
}
/********************************************
函数功能:I2C 总线读操作,并发送非应答信号,
返回值为读到的字节。
入口参数:无。
********************************************/
unsigned char I2CReadNAK()
{
unsigned char mask;
unsigned char dat;
I2C_SDA = 1; //首先确保主机释放SDA
for (mask=0x80; mask!=0; mask》》=1) //从高位到低位依次进行
{
I2CDelay();
I2C_SCL = 1; //拉高SCL
if(I2C_SDA == 0) //读取SDA 的值
dat &= ~mask; //为0 时,dat 中对应位清零
else
dat |= mask; //为1 时,dat 中对应位置1
I2CDelay();
I2C_SCL = 0; //再拉低SCL,以使从机发送出下一位
}
I2C_SDA = 1; //8 位数据发送完后,拉高SDA,发送非应答信号
I2CDelay();
I2C_SCL = 1; //拉高SCL
I2CDelay();
I2C_SCL = 0; //再拉低SCL 完成非应答位,并保持住总线
return dat;
}
/********************************************
函数功能:I2C 总线读操作,并发送应答信号,
返回值为读到的字节。
入口参数:无。
********************************************/
unsigned char I2CReadACK()
{
unsigned char mask;
unsigned char dat;
I2C_SDA = 1; //首先确保主机释放SDA
for (mask=0x80; mask!=0; mask》》=1) //从高位到低位依次进行
{
I2CDelay();
I2C_SCL = 1; //拉高SCL
if(I2C_SDA == 0) //读取SDA 的值
dat &= ~mask; //为0 时,dat 中对应位清零
else
dat |= mask; //为1 时,dat 中对应位置1
I2CDelay();
I2C_SCL = 0; //再拉低SCL,以使从机发送出下一位
}
I2C_SDA = 0; //8 位数据发送完后,拉低SDA,发送应答信号
I2CDelay();
I2C_SCL = 1; //拉高SCL
I2CDelay();
I2C_SCL = 0; //再拉低SCL 完成应答位,并保持住总线
return dat;
}/***************************************
Keyboard.h
***************************************/
#ifndef _KEY_BOARD_H
#define _KEY_BOARD_H
#ifndef _KEY_BOARD_C
extern unsigned char SetVal;
#endif
void KeyAction();//按键处理,手动运行
#endif
/***************************************
KEYBOARD.C
***************************************/
#define _KEY_BOARD_C
#include “config.h”
#include “keyboard.h”
#include “Lcd1602.h”
#include “main.h”
unsigned char SetVal=25;//设定初始值
/****************************************
函数功能:按键处理,手动运行
入口参数:无
*****************************************/
void KeyAction()
{
if(AK_KEY==0)//LCD1602背光控制
{
delayms(10);
if(AK_KEY==0)
{
LCD1602_AK=!LCD1602_AK;
while(!AK_KEY);
}
}
if(MA_KEY==0)
{
delayms(10);
if(MA_KEY==0)
{
if(staSystem==E_AUTO)
{
staSystem=E_MANUAL;
AUTO_LED=OFF;
MANUAL_LED=ON;
}
elseif(staSystem==E_MANUAL)
{
staSystem=E_AUTO;
AUTO_LED=ON;
MANUAL_LED=OFF;
}
while(!MA_KEY);
}
}
if(staSystem==E_MANUAL)//手动工作状态
{
if(UP_KEY==0){PUMP=ON;PUMP_LED=ON;}//手动开
elseif(DN_KEY==0){PUMP=OFF;PUMP_LED=OFF;}//手动关}
}
elseif(staSystem==E_AUTO)//自动状态
{
if(UP_KEY==0)
{
delayms(10);
if(UP_KEY==0)
{
if(SetVal《100) SetVal++; //设置+
while(!UP_KEY);
}
}
if(DN_KEY==0)
{
delayms(10);
if(DN_KEY==0)
{
if(SetVal》0) SetVal--; //设置-
while(!DN_KEY);
}
}
}
}
责任编辑人:CC
-
单片机
+关注
关注
6030文章
44505浏览量
632364
发布评论请先 登录
相关推荐
评论