土壤湿度检测仿真原理图如下(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!='\0')
- {
- 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)
仿真文件不公开,以免有人抄,有要学习的按我的图自己画吧
|