如何制作一个基于单片机的自动浇花系统

电子说

1.3w人已加入

描述

生活中,人工浇灌花木要耗费大量时间,而且土壤湿度不好控制,有时候由于主人长时间外出,家里的花木会因无人浇水而枯死。为了解决上述问题,本文利用单片机,设计了自动和手动浇花系统(如图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 tmrms=0;

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

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 相关推荐

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分