生活中,人工澆灌花木要耗費大量時間,而且土壤濕度不好控制,有時候由于主人長時間外出,家里的花木會因無人澆水而枯死。為了解決上述問題,本文利用單片機,設計了自動和手動澆花系統(如圖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
-
單片機
+關注
關注
6022文章
44375瀏覽量
628254
發布評論請先 登錄
相關推薦
評論