土壤濕度檢測仿真原理圖如下(proteus仿真工程文件可到本帖附件中下載)
1.png (28.68 KB, 下載次數: 61)
下載附件
2021-3-21 23:19 上傳
2.png (39.85 KB, 下載次數: 38)
下載附件
2021-3-21 23:19 上傳
Altium Designer畫的原理圖和PCB圖如下:(51hei附件中可下載工程文件,目前還不完整)
3.png (21.74 KB, 下載次數: 38)
下載附件
2021-3-21 23:19 上傳
單片機源程序如下,代碼注釋很全面:
- #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)
2021-3-21 23:19 上傳
點擊文件名下載附件
下載積分: 黑幣 -5
PCB:目前還不完整:
原理圖與PCB.7z
(702.42 KB, 下載次數: 107)
2021-3-21 23:19 上傳
點擊文件名下載附件
下載積分: 黑幣 -5
仿真文件不公開,以免有人抄,有要學習的按我的圖自己畫吧
|