如何制作一个基于单片机的自动浇花系统,自动浇花 2024-02-10 06:49:14 0 0 描述 生活中,人工浇灌花木要耗费大量时间,而且土壤湿度不好控制,有时候由于主人长时间外出,家里的花木会因无人浇水而枯死。为了解决上述问题,本文利用单片机,设计了自动和手动浇花系统(如图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)0; 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+‘0’);//个位 } elseif(Num《100) { LcdWriteDat(‘’); //百位 LcdWriteDat(Num/10+‘0’); //十位 LcdWriteDat(Num+‘0’); //个位 } else { LcdWriteDat(Num/100+‘0’); //百位 LcdWriteDat(Num/10+‘0’); //十位 LcdWriteDat(Num+‘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 收藏(0)