首页 分享 51单片机土壤湿度检测源程序与原理图PCB文件(注释很详细)

51单片机土壤湿度检测源程序与原理图PCB文件(注释很详细)

来源:花匠小妙招 时间:2024-11-12 17:03
土壤湿度检测仿真原理图如下(proteus仿真工程文件可到本帖附件中下载)

Altium Designer画的原理图和PCB图如下:(51hei附件中可下载工程文件,目前还不完整)

单片机源程序如下,代码注释很全面:

#include<reg52.h>                                 //头文件
#include<intrins.h>
#include"eeprom52.h"
#define uchar unsigned char                 //宏定义
#define uint unsigned int
#define LCD1602_dat P0         //液晶数据口定义


sbit LCD1602_rs=P2^5;//IO 定义
sbit LCD1602_rw=P2^6;
sbit LCD1602_e=P2^7;
sbit beep=P1^3;                //蜂鸣器
sbit led_1=P1^4;        //指示灯
sbit led_2=P1^6;
sbit key_1=P3^2;        //按键
sbit key_2=P3^3;
sbit key_3=P3^4;
sbit alarm_1=P2^0;        //继电器


sbit ADC0832_CS=P1^2;
sbit ADC0832_CLK=P1^0;
sbit ADC0832_DIO=P1^1;         //adc0832引脚

uint sum;
uchar RH,RH_H=60,RH_L=20,state,ms,time_num,cs;
bit beep1,zt,s1;


/*
    ADC0832是一款8位Ad芯片,因为单片机不能直接处理模拟信号(电压),所以单片机测电压的时候基本都是先经过一个模数转换芯片,将模拟量
转化成数字量,然后处理,ADC0832测量的电压范围是0-5V,它能够将0—5V的电压转化成对应比例关系的0-255(8位是0-255)的数据,单片机直
接读取ADC0832的数据获取AD值数据,然后因为0-5V对应0-255数据,所以1V电对应的AD值就是51,就会有如下公式

  电压=AD值/51;

如果想把电压数据精确到小数点后一位就是   电压=AD值/5.1;
                                          小数点后两位就是   电压=AD值/0.51;

不要问我为什么,纯数学,小学生都会算。

*/
unsigned int  A_D()
{
        unsigned char i;
        unsigned char dat;
        ADC0832_CS=1;   //一个转换周期开始
        ADC0832_CLK=0;  //为第一个脉冲作准备
        ADC0832_CS=0;  //CS置0,片选有效
        ADC0832_DIO=1;    //DIO置1,规定的起始信号  
        ADC0832_CLK=1;   //第一个脉冲
        ADC0832_CLK=0;   //第一个脉冲的下降沿,此前DIO必须是高电平
        ADC0832_DIO=1;   //DIO置1, 通道选择信号  
        ADC0832_CLK=1;   //第二个脉冲,第2、3个脉冲下沉之前,DI必须跟别输入两位数据用于选择通道,这里选通道RH0
        ADC0832_CLK=0;   //第二个脉冲下降沿
        ADC0832_DIO=0;   //DI置0,选择通道0
        ADC0832_CLK=1;    //第三个脉冲
        ADC0832_CLK=0;    //第三个脉冲下降沿
        ADC0832_DIO=1;    //第三个脉冲下沉之后,输入端DIO失去作用,应置1
        ADC0832_CLK=1;    //第四个脉冲
        for(i=0;i<8;i++)  //高位在前
        {
                ADC0832_CLK=1;         //第四个脉冲
                ADC0832_CLK=0;
                dat<<=1;       //将下面储存的低位数据向右移
                dat|=(unsigned char)ADC0832_DIO;          //将输出数据DIO通过或运算储存在dat最低位
        }                                 
        ADC0832_CS=1;          //片选无效
        return dat;         //将读书的数据返回     
}

void delay(uint T)                                          //延时函数
{
        while(T--);
}



/*
    1602液晶,是常用的显示器件,一共是16个管脚,其中有八个管脚是数据传输管脚,有三个管脚是数据命令使能端管脚,还有两组电源管脚,
其中一组电源管脚是给整个液晶进行供电的,还有一组电源是单纯的背景光电源,还剩下的最后一个管脚是对比度调节管脚,一般接上一个3K电
阻再接地即可。
一般我们用的函数,无非就是  LCD1602_write 和 LCD1602_writebyte
LCD1602_write(x,y);   这个函数括号里面可以填写两个数据,第一个数据只能是 0  1 ,是0就说明第二个数据对液晶来说就是命令,填1就说明
第二个数据对于液晶来说就是要显示的数据。
LCD1602_writebyte();  这个函数里面直接填上要显示的字符串即可,自动进行显示


*/
void LCD1602_write(uchar order,dat)                                  //1602 一个字节  处理
{
    LCD1602_e=0;
    LCD1602_rs=order;
    LCD1602_dat=dat;
    LCD1602_rw=0;
    LCD1602_e=1;
    delay(1);
    LCD1602_e=0;                                                                                                                                                                                                     
}

void LCD1602_writebyte(uchar *prointer)                                   //1602 字符串    处理
{
    while(*prointer!='')
    {
        LCD1602_write(1,*prointer);
        prointer++;
    }
}

void LCD1602_cls()                                                                         //1602 初始化
{
        LCD1602_write(0,0x01);     //1602 清屏 指令
        delay(1500);
        LCD1602_write(0,0x38);     // 功能设置 8位、5*7点阵
        delay(1500);
        LCD1602_write(0,0x0c);     //设置 光标   不显示开关、不显示光标、字符不闪烁
        LCD1602_write(0,0x06);
        LCD1602_write(0,0xd0);
        delay(1500);
}

/*
数据显示的时候一般的处理:

    首先,无论是数码管显示还是液晶显示,进行显示的时候绝对都是一个一个进行显示的,那么,比如说一个数据123,一百二十三,
进行显示的时候,要先显示1,然后是2,然后是3,那么怎么把数据提取出来??   
提取百位    123/100=1
提取十位    123/10=12      12%10=2     “%”是取余的意思,像这个,就是12对10取余,换句话说,12除以10,然后取余数,就是2
提取个位    123%10=3       解释同上

取余的用法也有很多种,大家只要知道出现这个的时候,一般都是进行数据提取的就行


然后
如果您是数码管显示数据,将提取的数据放到段码数组里面送给IO即可,
如果是液晶显示,需要将数据转化成字符,因为液晶是字符屏,只能显示字符数据,数据0对应的字符是0x30,数据1对应的字符是0x31,
所以将提取出的数据直接加上0x30送给液晶即可,或者加上'0' 也是一样的



*/

void show()        //显示子函数
{
        if(state==0) //如果非设置状态
        {
                LCD1602_write(0,0x80);                 //第一行
                LCD1602_writebyte("Humidity:");//显示湿度
                LCD1602_write(0,0x80+9);
                if(RH>99)LCD1602_write(1,0x30+RH/100%10);        //如果数据大于99显示百位数
                else LCD1602_writebyte(" ");                                //否则显示空白
                LCD1602_write(0,0x80+10);
                if(RH>9)LCD1602_write(1,0x30+RH/10%10);
                else LCD1602_writebyte(" ");
                LCD1602_write(0,0x80+11);
                LCD1602_write(1,0x30+RH%10);
                LCD1602_write(0,0x80+12);
                LCD1602_writebyte("%   ");                   //显示%号

                LCD1602_write(0,0xC0);        //第二行,显示当前模式
                LCD1602_writebyte("State:");
                LCD1602_write(0,0xC0+6);
                if(zt==1)
                {
                        LCD1602_writebyte(" Auto     ");   //自动
                }else
                {
                        LCD1602_writebyte("Manul     ");   //手动
                }
                        
        }else
        {
                LCD1602_write(0,0x80);          //第一行显示上限
                LCD1602_writebyte("RH_H:");
                LCD1602_write(0,0x80+5);
                if(state==1&&s1==1)                        //当设置上限时,state=1,这时候会根据s1的状态去显示空白或者上限数据,而s1在定时器里是连续取反的
                {
                        LCD1602_writebyte("   ");
                }else
                {
                        if(RH_H>99)LCD1602_write(1,0x30+RH_H/100%10);
                        else LCD1602_writebyte(" ");
                        LCD1602_write(0,0x80+6);
                        if(RH_H>9)LCD1602_write(1,0x30+RH_H/10%10);
                        else LCD1602_writebyte(" ");
                        LCD1602_write(0,0x80+7);
                        LCD1602_write(1,0x30+RH_H%10);               
                }
                LCD1602_write(0,0x80+8);
                LCD1602_writebyte("%       ");

                LCD1602_write(0,0xC0);           //第二行显示下限
                LCD1602_writebyte("RH_L:");
                LCD1602_write(0,0xC0+5);
                if(state==2&&s1==1)
                {
                        LCD1602_writebyte("   ");
                }else
                {
                        if(RH_L>99)LCD1602_write(1,0x30+RH_L/100%10);
                        else LCD1602_writebyte(" ");
                        LCD1602_write(0,0xC0+6);
                        if(RH_L>9)LCD1602_write(1,0x30+RH_L/10%10);
                        else LCD1602_writebyte(" ");
                        LCD1602_write(0,0xC0+7);
                        LCD1602_write(1,0x30+RH_L%10);
                }
                LCD1602_write(0,0xC0+8);
                LCD1602_writebyte("%       ");        
        }
}



/*

无论是什么单片机,只要是用内部存储区域EEPROM基本使用都是这样

关于内部存储区,EEPROM,不同的单片机使用流程基本一致,单片机内部有很多存储单元,或者说扇区,每一个扇区下面有很多地址,
数据就是存储在这些地址下面的。存储函数的程序都是官方提供好的,这些程序,咱们只需要用三个,一个是扇区擦除函数,一个是
数据写函数,还有一个就是数据读取函数。
扇区擦除函数------使用哪个扇区,先对那个扇区进行擦,函数里填写要擦除扇区的首地址  例如  SectorErase(0x2000);就是说擦除首地址为0x2000的扇区数据  
数据存储----------扇区擦除之后,就可以使用这个扇区下的地址进行存储数据     例如   byte_write(0x2000,123);  就是说将123存储在0x2000地址下
数据读取----------直接调用即可,例如     Dat=byte_read(0x2000);就是说将0x2000地址下的数据读取出来给  Dat


另外----
//51单片机存储区域是8位的,也就是说能够存下的最大数据是 255,而我们存的数据一旦大于256就会出现一些问题
//所以,如果您的设计需要存储的数据大于256,那就把数据拆开存   /256得到高位    %256得到低位,之所以是256,是因为0-255,256个数
// 例如数据257           257/256=1        257%256=1    ,这就存进去两个1,读取的时候,将高位数据乘以256加低位数据,还原数据

*/

void key()          //按键扫描
{
        if(!key_1)        //如果按键1按下
        {
                delay(888);          //延时消抖
                if(!key_1)          //如果按键1按下
                {
                        while(!key_1) show();         //松手检测
                        state=(state+1)%3;                 //设置变量自加,state的值不同设置的数据不同
                }
        }

        if(!key_2)
        {
                delay(888);   //按键去抖
                if(!key_2)
                {
                        while(!key_2)show();
                        if(state==1)                 //state=1的时候设置上限
                        {
                                if(RH_H<100)RH_H++;
                                SectorErase(0x2000);         //保存上限值
                                byte_write(0x2000,RH_H);
                        }else if(state==2)                  //state=2的时候设置下限
                        {
                                if(RH_L<RH_H-1)RH_L++;
                                SectorErase(0x2200);         //保存上限值
                                byte_write(0x2200,RH_L);
                        }else
                        {
                                zt=!zt;                        
                        }        
                }
        }

        if(!key_3)
        {
                delay(888);
                if(!key_3)
                {               
                        while(!key_3)show();
                        if(state==1)
                        {
                                if(RH_H>RH_L+1)RH_H--;
                                SectorErase(0x2000);         //保存上限值
                                byte_write(0x2000,RH_H);
                        }else if(state==2)
                        {
                                if(RH_L>0)RH_L--;
                                SectorErase(0x2200);         //保存上限值
                                byte_write(0x2200,RH_L);
                        }else
                        {
                                if(zt==0)
                                {
                                        alarm_1=!alarm_1;
                                }
                        }
                }
        }               
}

void proc()          //报警函数
{
        if(zt==1)  //自动状态
        {
                if(RH>=RH_H)        //湿度大于上限
                {
                        alarm_1=1;          //继电器断开
                        led_1=0;          //上限指示灯点亮
                }else
                {
                        led_1=1;         //否则上限指示灯熄灭
                }

                if(RH<=RH_L)         //如果小于下限
                {
                        alarm_1=0;          //继电器闭合
                        led_2=0;           //下限指示灯点亮
                }else
                {
                        led_2=1;           //否则下限指示灯熄灭
                }

                if(RH>=RH_H||RH<=RH_L)                //如果不在上下限区间内
                {
                        beep1=1;                   //报警标志位置1
                }else
                {
                        beep1=0;                 //否则置0
                }
        }else                           //手动状态
        {
                beep1=0;                //不报警
                led_1=led_2=1;         //指示灯不亮
        }
}





void main()        //主循环
{        
        float Ad_dat=0;
        TMOD=0x01;        //配置定时器0
        TH0=0x3c;
        TL0=0xb0;        //赋50ms初值
        ET0=1;
        TR0=1;
        EA=1;                 //打开总中断
        LCD1602_cls();         //液晶初始化
        RH_H=byte_read(0x2000);
        RH_L=byte_read(0x2200);
        if((RH_H>99)||(RH_L>99)||(RH_L>=RH_H))   {RH_H=30;  RH_L=20;}
        while(1)
        {
                sum+=A_D();        //累加5次AD数据
                cs++;
                if(cs==5)
                {
                        cs=0;
                        Ad_dat=(float)(sum/5);         //取一个平均值,用于滤波
                        if(Ad_dat>250) Ad_dat=0;
                        else if(Ad_dat<=70) Ad_dat=100;
                        else Ad_dat=100-((Ad_dat-70)/1.8);         
                        RH=(uint)(Ad_dat);
                        sum=0;
                }
                show();           //调用显示函数
                key();           //调用按键扫描
                proc();           //调用报警子函数
        }
}

void UART_1() interrupt 1
{
        TH0=0x3c;
        TL0=0xb0;
        ms++;
        if(ms%5==0)          //s1每5*50ms=250ms取反一次
        {
            s1=!s1;
        }
        if(ms%10==0)         
        {
                if(beep1==1)   //如果报警标志位是1,每10*50ms=500ms蜂鸣器状态取反一次
                {
                        beep=!beep;
                }else
                {
                        beep=1;
                }        
        }
        if(ms>19)
        {
                ms=0;
        }        
}

复制代码


本人初学,仅供参考,存在错误和不足之处,请大家回帖多多指教,切勿照搬,文件下载:
代码: 程序.7z (46.64 KB, 下载次数: 119)
PCB:目前还不完整: 原理图与PCB.7z (702.42 KB, 下载次数: 107)
仿真文件不公开,以免有人抄,有要学习的按我的图自己画吧

相关知识

基于51单片机控制的自动浇水系统设计
基于51单片机土壤湿度检测及自动浇花系统
土壤湿度检测模块51单片机程序原理图与说明书(4线制)
基于单片机智能花卉灌溉浇花浇水检测系统设计
基于单片机士壤湿度检测及自动浇花系统
基于单片机智能自动浇花控制系统设计
基于单片机自动智能浇花浇水控制系统设计
基于单片机的智能自动浇花浇水控制系统设计
基于单片机智能浇花控制系统设计
基于STM32单片机远程浇花花盆GSM短信浇水补光灌溉系统

网址: 51单片机土壤湿度检测源程序与原理图PCB文件(注释很详细) https://www.huajiangbk.com/newsview514199.html

所属分类:花卉
上一篇: 土壤湿度检测自动浇花系统设计
下一篇: 著名兰花专家来我院作学术报告

推荐分享