找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 5190|回复: 2
打印 上一主题 下一主题
收起左侧

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

  [复制链接]
跳转到指定楼层
楼主
土壤湿度检测仿真原理图如下(proteus仿真工程文件可到本帖附件中下载)


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


单片机源程序如下,代码注释很全面:
  1. #include<reg52.h>                                 //头文件
  2. #include<intrins.h>
  3. #include"eeprom52.h"
  4. #define uchar unsigned char                 //宏定义
  5. #define uint unsigned int
  6. #define LCD1602_dat P0         //液晶数据口定义


  7. sbit LCD1602_rs=P2^5;//IO 定义
  8. sbit LCD1602_rw=P2^6;
  9. sbit LCD1602_e=P2^7;
  10. sbit beep=P1^3;                //蜂鸣器
  11. sbit led_1=P1^4;        //指示灯
  12. sbit led_2=P1^6;
  13. sbit key_1=P3^2;        //按键
  14. sbit key_2=P3^3;
  15. sbit key_3=P3^4;
  16. sbit alarm_1=P2^0;        //继电器


  17. sbit ADC0832_CS=P1^2;
  18. sbit ADC0832_CLK=P1^0;
  19. sbit ADC0832_DIO=P1^1;         //adc0832引脚

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


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

  27.   电压=AD值/51;

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

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

  31. */
  32. unsigned int  A_D()
  33. {
  34.         unsigned char i;
  35.         unsigned char dat;
  36.         ADC0832_CS=1;   //一个转换周期开始
  37.         ADC0832_CLK=0;  //为第一个脉冲作准备
  38.         ADC0832_CS=0;  //CS置0,片选有效
  39.         ADC0832_DIO=1;    //DIO置1,规定的起始信号  
  40.         ADC0832_CLK=1;   //第一个脉冲
  41.         ADC0832_CLK=0;   //第一个脉冲的下降沿,此前DIO必须是高电平
  42.         ADC0832_DIO=1;   //DIO置1, 通道选择信号  
  43.         ADC0832_CLK=1;   //第二个脉冲,第2、3个脉冲下沉之前,DI必须跟别输入两位数据用于选择通道,这里选通道RH0
  44.         ADC0832_CLK=0;   //第二个脉冲下降沿
  45.         ADC0832_DIO=0;   //DI置0,选择通道0
  46.         ADC0832_CLK=1;    //第三个脉冲
  47.         ADC0832_CLK=0;    //第三个脉冲下降沿
  48.         ADC0832_DIO=1;    //第三个脉冲下沉之后,输入端DIO失去作用,应置1
  49.         ADC0832_CLK=1;    //第四个脉冲
  50.         for(i=0;i<8;i++)  //高位在前
  51.         {
  52.                 ADC0832_CLK=1;         //第四个脉冲
  53.                 ADC0832_CLK=0;
  54.                 dat<<=1;       //将下面储存的低位数据向右移
  55.                 dat|=(unsigned char)ADC0832_DIO;          //将输出数据DIO通过或运算储存在dat最低位
  56.         }                                 
  57.         ADC0832_CS=1;          //片选无效
  58.         return dat;         //将读书的数据返回     
  59. }

  60. void delay(uint T)                                          //延时函数
  61. {
  62.         while(T--);
  63. }



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


  72. */
  73. void LCD1602_write(uchar order,dat)                                  //1602 一个字节  处理
  74. {
  75.     LCD1602_e=0;
  76.     LCD1602_rs=order;
  77.     LCD1602_dat=dat;
  78.     LCD1602_rw=0;
  79.     LCD1602_e=1;
  80.     delay(1);
  81.     LCD1602_e=0;                                                                                                                                                                                                     
  82. }

  83. void LCD1602_writebyte(uchar *prointer)                                   //1602 字符串    处理
  84. {
  85.     while(*prointer!='\0')
  86.     {
  87.         LCD1602_write(1,*prointer);
  88.         prointer++;
  89.     }
  90. }

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

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

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

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


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



  114. */

  115. void show()        //显示子函数
  116. {
  117.         if(state==0) //如果非设置状态
  118.         {
  119.                 LCD1602_write(0,0x80);                 //第一行
  120.                 LCD1602_writebyte("Humidity:");//显示湿度
  121.                 LCD1602_write(0,0x80+9);
  122.                 if(RH>99)LCD1602_write(1,0x30+RH/100%10);        //如果数据大于99显示百位数
  123.                 else LCD1602_writebyte(" ");                                //否则显示空白
  124.                 LCD1602_write(0,0x80+10);
  125.                 if(RH>9)LCD1602_write(1,0x30+RH/10%10);
  126.                 else LCD1602_writebyte(" ");
  127.                 LCD1602_write(0,0x80+11);
  128.                 LCD1602_write(1,0x30+RH%10);
  129.                 LCD1602_write(0,0x80+12);
  130.                 LCD1602_writebyte("%   ");                   //显示%号

  131.                 LCD1602_write(0,0xC0);        //第二行,显示当前模式
  132.                 LCD1602_writebyte("State:");
  133.                 LCD1602_write(0,0xC0+6);
  134.                 if(zt==1)
  135.                 {
  136.                         LCD1602_writebyte(" Auto     ");   //自动
  137.                 }else
  138.                 {
  139.                         LCD1602_writebyte("Manul     ");   //手动
  140.                 }
  141.                         
  142.         }else
  143.         {
  144.                 LCD1602_write(0,0x80);          //第一行显示上限
  145.                 LCD1602_writebyte("RH_H:");
  146.                 LCD1602_write(0,0x80+5);
  147.                 if(state==1&&s1==1)                        //当设置上限时,state=1,这时候会根据s1的状态去显示空白或者上限数据,而s1在定时器里是连续取反的
  148.                 {
  149.                         LCD1602_writebyte("   ");
  150.                 }else
  151.                 {
  152.                         if(RH_H>99)LCD1602_write(1,0x30+RH_H/100%10);
  153.                         else LCD1602_writebyte(" ");
  154.                         LCD1602_write(0,0x80+6);
  155.                         if(RH_H>9)LCD1602_write(1,0x30+RH_H/10%10);
  156.                         else LCD1602_writebyte(" ");
  157.                         LCD1602_write(0,0x80+7);
  158.                         LCD1602_write(1,0x30+RH_H%10);               
  159.                 }
  160.                 LCD1602_write(0,0x80+8);
  161.                 LCD1602_writebyte("%       ");

  162.                 LCD1602_write(0,0xC0);           //第二行显示下限
  163.                 LCD1602_writebyte("RH_L:");
  164.                 LCD1602_write(0,0xC0+5);
  165.                 if(state==2&&s1==1)
  166.                 {
  167.                         LCD1602_writebyte("   ");
  168.                 }else
  169.                 {
  170.                         if(RH_L>99)LCD1602_write(1,0x30+RH_L/100%10);
  171.                         else LCD1602_writebyte(" ");
  172.                         LCD1602_write(0,0xC0+6);
  173.                         if(RH_L>9)LCD1602_write(1,0x30+RH_L/10%10);
  174.                         else LCD1602_writebyte(" ");
  175.                         LCD1602_write(0,0xC0+7);
  176.                         LCD1602_write(1,0x30+RH_L%10);
  177.                 }
  178.                 LCD1602_write(0,0xC0+8);
  179.                 LCD1602_writebyte("%       ");        
  180.         }
  181. }



  182. /*

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

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


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

  194. */

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

  206.         if(!key_2)
  207.         {
  208.                 delay(888);   //按键去抖
  209.                 if(!key_2)
  210.                 {
  211.                         while(!key_2)show();
  212.                         if(state==1)                 //state=1的时候设置上限
  213.                         {
  214.                                 if(RH_H<100)RH_H++;
  215.                                 SectorErase(0x2000);         //保存上限值
  216.                                 byte_write(0x2000,RH_H);
  217.                         }else if(state==2)                  //state=2的时候设置下限
  218.                         {
  219.                                 if(RH_L<RH_H-1)RH_L++;
  220.                                 SectorErase(0x2200);         //保存上限值
  221.                                 byte_write(0x2200,RH_L);
  222.                         }else
  223.                         {
  224.                                 zt=!zt;                        
  225.                         }        
  226.                 }
  227.         }

  228.         if(!key_3)
  229.         {
  230.                 delay(888);
  231.                 if(!key_3)
  232.                 {               
  233.                         while(!key_3)show();
  234.                         if(state==1)
  235.                         {
  236.                                 if(RH_H>RH_L+1)RH_H--;
  237.                                 SectorErase(0x2000);         //保存上限值
  238.                                 byte_write(0x2000,RH_H);
  239.                         }else if(state==2)
  240.                         {
  241.                                 if(RH_L>0)RH_L--;
  242.                                 SectorErase(0x2200);         //保存上限值
  243.                                 byte_write(0x2200,RH_L);
  244.                         }else
  245.                         {
  246.                                 if(zt==0)
  247.                                 {
  248.                                         alarm_1=!alarm_1;
  249.                                 }
  250.                         }
  251.                 }
  252.         }               
  253. }

  254. void proc()          //报警函数
  255. {
  256.         if(zt==1)  //自动状态
  257.         {
  258.                 if(RH>=RH_H)        //湿度大于上限
  259.                 {
  260.                         alarm_1=1;          //继电器断开
  261.                         led_1=0;          //上限指示灯点亮
  262.                 }else
  263.                 {
  264.                         led_1=1;         //否则上限指示灯熄灭
  265.                 }

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

  274.                 if(RH>=RH_H||RH<=RH_L)                //如果不在上下限区间内
  275.                 {
  276.                         beep1=1;                   //报警标志位置1
  277.                 }else
  278.                 {
  279.                         beep1=0;                 //否则置0
  280.                 }
  281.         }else                           //手动状态
  282.         {
  283.                 beep1=0;                //不报警
  284.                 led_1=led_2=1;         //指示灯不亮
  285.         }
  286. }





  287. void main()        //主循环
  288. {        
  289.         float Ad_dat=0;
  290.         TMOD=0x01;        //配置定时器0
  291.         TH0=0x3c;
  292.         TL0=0xb0;        //赋50ms初值
  293.         ET0=1;
  294.         TR0=1;
  295.         EA=1;                 //打开总中断
  296.         LCD1602_cls();         //液晶初始化
  297.         RH_H=byte_read(0x2000);
  298.         RH_L=byte_read(0x2200);
  299.         if((RH_H>99)||(RH_L>99)||(RH_L>=RH_H))   {RH_H=30;  RH_L=20;}
  300.         while(1)
  301.         {
  302.                 sum+=A_D();        //累加5次AD数据
  303.                 cs++;
  304.                 if(cs==5)
  305.                 {
  306.                         cs=0;
  307.                         Ad_dat=(float)(sum/5);         //取一个平均值,用于滤波
  308.                         if(Ad_dat>250) Ad_dat=0;
  309.                         else if(Ad_dat<=70) Ad_dat=100;
  310.                         else Ad_dat=100-((Ad_dat-70)/1.8);         
  311.                         RH=(uint)(Ad_dat);
  312.                         sum=0;
  313.                 }
  314.                 show();           //调用显示函数
  315.                 key();           //调用按键扫描
  316.                 proc();           //调用报警子函数
  317.         }
  318. }

  319. void UART_1() interrupt 1
  320. {
  321.         TH0=0x3c;
  322.         TL0=0xb0;
  323.         ms++;
  324.         if(ms%5==0)          //s1每5*50ms=250ms取反一次
  325.         {
  326.             s1=!s1;
  327.         }
  328.         if(ms%10==0)         
  329.         {
  330.                 if(beep1==1)   //如果报警标志位是1,每10*50ms=500ms蜂鸣器状态取反一次
  331.                 {
  332.                         beep=!beep;
  333.                 }else
  334.                 {
  335.                         beep=1;
  336.                 }        
  337.         }
  338.         if(ms>19)
  339.         {
  340.                 ms=0;
  341.         }        
  342. }
复制代码

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

评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏8 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:143767 发表于 2021-3-22 12:47 | 只看该作者
好资料,最近我在用慧编程和光环板做这个,你这个很有参考价值
回复

使用道具 举报

板凳
ID:1059782 发表于 2022-12-30 16:19 | 只看该作者
您好,有proteus仿真文件可以发一下吗
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表