
簡介這款基于51單片機的孵化環境溫濕度監控系統設計壓縮包面向單片機初學者、電子設計課程項目開發者提供一套從硬件原理到軟件調試的完整參考方案可服務于課程設計、畢業設計或小型環境監控項目開發。資源共17個文件壓縮包僅100KB涵蓋Keil工程文件.uvproj/.uvopt、C語言源碼.c、編譯生成的HEX與LIST/M51中間文件以及原理圖截圖和必看說明可滿足仿真、燒錄與二次修改需求也配套了工程備份文件供對照恢復。已有436人學習/下載。內容包含傳感器數據采集、閾值判斷與加熱器/加濕器控制邏輯并對LCD1602顯示、串行通信等要點給出可運行代碼HEX文件方便直接燒錄驗證代碼注釋配合原理圖可快速理解初始化、采集、控制、顯示主流程適合結合DHT11/DHT22實際調試。建議讀者先瀏覽必看說明和原理圖再按照模塊閱讀C代碼能夠較快掌握51單片機在環境監控類項目中的典型用法。1. 孵化環境溫濕度監控51單片機課程設計的完整閉環禽蛋孵化對溫濕度極其敏感溫度長期低于37℃會導致胚胎發育遲緩超過39℃則可能直接致死濕度低于45%時蛋內水分蒸發過快影響氣室正常形成。這套基于51單片機的孵化環境溫濕度監控系統正是圍繞這兩個核心參數做的閉環控制方案DHT11采集溫度和濕度STC89C52或AT89S52讀取數據后送到LCD1602顯示并通過繼電器控制加熱器和加濕器。對于電子信息工程和嵌入式方向的課設來說這個題目幾乎把所有基礎外設都串起來了。如果你手里正好有這個源碼包下面從DHT11時序、LCD驅動、繼電器控制到Proteus仿真和燒錄排錯一條線講完。2. DHT11時序解析與51單片機單總線讀取實現2.1 DHT11的測量原理與40位數據幀DHT11使用單總線協議一條數據線負責命令和數據的雙向傳輸。傳感器上電后單片機必須先發送開始信號將數據線拉低至少18ms然后釋放。DHT11會回復80us低電平和80us高電平隨后連續輸出40位數據。這40位按順序是濕度整數、濕度小數、溫度整數、溫度小數、校驗和每項8位。DHT11的小數部分恒為0所以很多程序直接忽略小數DHT22則能讀出0.1℃分辨率的真實小數但成本更高。單總線每一位的編解碼依賴高電平持續時間。數據線先被拉低一段固定時間隨后釋放如果高電平持續26~28us表示邏輯0持續70us左右表示邏輯1。51單片機沒有硬件單總線控制器必須用sbit模擬這個時序因此代碼質量取決于延時函數的精度。DHT11和DHT22的差異主要在精度和成本DHT11精度±2℃、±5%RH采樣周期1s價格只有幾塊錢課設和常規孵化監控完全夠用。DHT11硬件接線很簡單但數據腳必須接上拉電阻否則空閑狀態無法穩定保持高電平。下表是標準接法。DHT11引腳51單片機說明VCC5V供電工作范圍3.3V~5.5VDATAP2.0單總線數據10kΩ上拉到VCCGNDGND共地連接我一般建議數據引腳固定接P2.0因為后續LCD1602和繼電器會占用大量P0、P1、P2口P2.0離繼電器輸出遠不容易受吸合時的電磁干擾。在Proteus仿真時上拉電阻可以直接用RESPACK-8排阻其中一個引腳接VCC。2.2 開始信號、讀取字節與校驗下面是一份在Keil C51中可直接編譯的DHT11讀取代碼。它只用到寄存器和sbit位定義不依賴特殊擴展庫。#include reg51.h #include intrins.h sbit DHT11 P2^0; void delay_us(unsigned int t) { while (t--) { _nop_(); } } void delay_ms(unsigned int t) { unsigned int i, j; for (i 0; i t; i) for (j 0; j 123; j); } void DHT11_Start(void) { DHT11 1; delay_us(2); DHT11 0; delay_ms(20); // 拉低20ms滿足開始信號至少18ms要求 DHT11 1; // 釋放總線等待DHT11響應 delay_us(30); } unsigned char DHT11_ReadByte(void) { unsigned char i, dat 0; for (i 0; i 8; i) { while (DHT11 0); // 等待低電平結束即數據位起始 delay_us(40); // 延時40us后采樣 if (DHT11 1) dat | (0x80 i); // 高電平時間長判為1 while (DHT11 1); // 等待高電平結束進入下一位 } return dat; }這段代碼的核心是delay_us(40)。邏輯1的高電平寬度約70us邏輯0約28us所以在高電平開始后40us處采樣只有邏輯1還維持在高這樣就區分了兩種電平。dat | (0x80 i)把當前位放進字節的對應位置。等待DHT11 1結束再進入下一位保證每位時序對齊。完整的讀取函數需要先調用DHT11_Start()然后檢測傳感器的響應電平。如果傳感器沒有拉低響應說明接線或開始信號有問題需要返回錯誤標志。unsigned char DHT11_ReadData(unsigned char *dat) { unsigned char i, buf[5] {0, 0, 0, 0, 0}; DHT11_Start(); if (DHT11 1) return 0; // 未檢測到DHT11拉低響應 while (DHT11 0); // 等待80us低電平結束 while (DHT11 1); // 等待80us高電平結束 for (i 0; i 5; i) buf[i] DHT11_ReadByte(); if ((buf[0] buf[1] buf[2] buf[3]) buf[4]) { dat[0] buf[0]; dat[1] buf[1]; dat[2] buf[2]; dat[3] buf[3]; dat[4] buf[4]; return 1; // 校驗通過 } return 0; // 校驗失敗 }調用后dat[0]是濕度整數dat[1]濕度小數dat[2]溫度整數dat[3]溫度小數dat[4]校驗和。DHT11的小數位沒有實際參考價值所以后面顯示和控制邏輯可以只取dat[0]和dat[2]。校驗失敗時不要直接丟棄數據常見做法是連續讀取三次取出現次數最多的那一組能明顯降低偶發誤碼的影響。2.3 延時函數在不同晶振下的誤差處理Keil C51的_nop_()在12MHz晶振下約等于1us但整個delay_us循環還有函數調用和while判斷的額外開銷所以實際延時比名義值偏長。如果單片機換成了11.0592MHz晶振用同一份代碼40us的實際等待可能會變成45us以上。這不會導致DHT11完全失效但會增加邊界位誤判概率尤其是邏輯0容易被人為拉長到40us以上。更穩妥的做法是使用定時器延時但課設里大多數代碼都用普通循環。我判斷代碼是否正常的經驗是連續讀取兩次溫度差不超過1℃、濕度差不超過1%就說明時序基本正確。如果數值跳變劇烈優先把delay_us(40)改成兩個_nop_()或加一個空語句而不是直接懷疑傳感器損壞。另外DHT11兩次采樣間隔不能小于1s內部傳感器完成一次測量需要一定時間主循環里連續讀會讀到舊值這個放到第4章結合主循環結構再展開。3. LCD1602顯示與繼電器控制從原理圖到Keil C51工程3.1 原理圖接線與P0口上拉問題資源包里的“原理圖截圖.png”是標準課設接法。LCD1602的控制線RS、RW、E接在P2口高位數據線D0-D7接P0口。繼電器控制端接在P1口DHT11數據腳接P2.0整體布局很清晰。有一個細節必須單獨提P0口內部沒有上拉電阻是開漏輸出。直接接LCD1602數據端時輸出高電平能力很弱顯示屏容易出現“沒有字符、亂碼、一個亮行”等表象。原理圖上通常給P0口加10kΩ排阻到VCC。如果你的板子沒有排阻至少要把P0口所有引腳在初始化時置1再外接上拉電阻提供高電平。下面是一份和原理圖對應的引腳分配表方便抄板和改程序時核對。模塊信號51引腳說明DHT11DATAP2.010kΩ上拉LCD1602RSP2.50指令 1數據LCD1602RWP2.60寫 1讀LCD1602EP2.7高電平使能下降沿鎖存LCD1602D0-D7P0.0-P0.7并聯10kΩ排阻上拉繼電器IN1P1.0控制加熱器繼電器IN2P1.1控制加濕器繼電器模塊如果是低電平觸發控制邏輯要反過來。實物用PNP三極管驅動時基極低電平導通繼電器線圈通電NPN三極管則相反。看原理圖時重點看三極管型號不要讓程序和硬件電平反著來。3.2 LCD1602驅動8線并行讀寫LCD1602大多用8線并行方式因為51的P0口正好8位。驅動時序不復雜E拉高期間RS指定命令還是數據RW固定為寫0數據線準備好后E的下降沿把數據鎖存進LCD控制器。初始化要先等上電穩定再發功能設置、顯示開關、清屏。下面是一份精簡的8線驅動代碼接口與上一節表格一致。#include reg51.h #define LCD_DB P0 sbit LCD_RS P2^5; sbit LCD_RW P2^6; sbit LCD_E P2^7; void LcdDelay(unsigned char t) { unsigned char i; while (t--) for (i 0; i 120; i); } void LcdWriteCmd(unsigned char cmd) { LCD_E 0; LCD_RS 0; // 命令模式 LCD_RW 0; // 寫模式 LCD_DB cmd; LcdDelay(2); LCD_E 1; LcdDelay(2); LCD_E 0; // 下降沿鎖存 } void LcdWriteData(unsigned char dat) { LCD_E 0; LCD_RS 1; // 數據模式 LCD_RW 0; LCD_DB dat; LcdDelay(2); LCD_E 1; LcdDelay(2); LCD_E 0; } void LcdInit(void) { LcdDelay(20); // 上電等待 LcdWriteCmd(0x38); // 8位模式雙行5x8點陣 LcdWriteCmd(0x0C); // 開顯示不顯示光標 LcdWriteCmd(0x06); // 輸入模式寫一個字符后地址加1 LcdWriteCmd(0x01); // 清屏 LcdDelay(5); }這些指令里0x38是LCD1602的核心配置漏掉它屏幕可能只能顯示一排方塊。0x01清屏需要時間所以后面加了延時。每次寫之前把E拉低是為了避免初始化過程中GPIO電平抖動導致誤寫入。顯示字符串時先設置DDRAM地址再逐字符寫入數據。首行地址從0x00開始第二行從0x40開始因為DDRAM基地址是0x80所以第二行首地址是0x800x400xC0。void LcdShowString(unsigned char addr, unsigned char *str) { LcdWriteCmd(0x80 | addr); // 設置DDRAM地址0x00為第一行 while (*str) { LcdWriteData(*str); } }調用時LcdShowString(0x00, Temp:)會在左上角顯示標題LcdShowString(0x40, Hum:)會在第二行顯示。如果要顯示動態溫度值可以用sprintf把數字轉成字符串但keil C51的sprintf比較占代碼空間課設里更常見的是像第5章那樣用0x30加上數字位直接轉ASCII字符。3.3 繼電器驅動與加熱、加濕輸出繼電器部分的關鍵不是代碼而是驅動電路。51單片機IO口不能直接驅動5V繼電器線圈常規做法是使用PNP三極管作為開關基極接IO口發射極接VCC集電極接繼電器線圈線圈兩端并聯續流二極管。PNP三極管高電平截止、低電平導通所以程序里控制加熱器時通常置0有效。如果使用成品低電平觸發繼電器模塊模塊內部已經完成三極管和續流二極管照著手冊接線即可。控制代碼可以封裝為獨立函數傳入溫度和濕度輸出加熱與加濕兩個IO狀態。sbit HEATER P1^0; sbit HUMIDIFIER P1^1; void ControlOutput(unsigned char temp, unsigned char hum) { if (temp 37) HEATER 0; // 低電平導通PNP加熱器啟動 else if (temp 39) HEATER 1; // 高電平截止加熱器停止 if (hum 50) HUMIDIFIER 0; // 加濕器啟動 else if (hum 60) HUMIDIFIER 1; }這種寫法在中間區間不修改IO繼電器能保持上一狀態但如果是簡單閾值判斷溫度在37℃附近波動時仍可能頻繁通斷。更平滑的做法是加回差這個放到第4章結合仿真講。實物調試時要注意一個現象繼電器吸合瞬間LCD數值可能跳一個字這通常是繼電器線圈干擾DHT11數據線。解決方法是繼電器供電和單片機供電分開數據線用雙絞線縮短長度。3.4 Keil工程文件識別與編譯輸出源碼包里看到的1.uvproj、1.uvopt、1.c、1.hex是一套完整的Keil工程。1.uvproj是工程文件用Keil uVision5打開1.uvopt是窗口布局配置刪掉不影響編譯1.c是主程序源文件1.hex是編譯生成的燒錄文件。1.OBJ、1.LST、1.M51都是編譯中間產物不是人看的源碼。打開工程后按F7編譯出現“0 Error(s)”就說明編譯通過。如果只拿到源碼沒有工程文件也可以新建一個工程把1.c添加進去然后設置Atmel AT89C52或STC89C52RC芯片型號。編譯前務必在Options for Target的Output選項卡勾選Create HEX File否則不會生成新的hex。4. 閾值控制與Proteus仿真聯調4.1 孵化溫濕度閾值設置與回差邏輯孵化過程中溫度和濕度不能只做單點控制。常見禽蛋孵化參考條件是溫度設定37.8℃允許波動范圍37℃~39℃濕度設定55%允許波動范圍50%~60%。如果代碼采用“低于下限開啟、高于上限關閉”的雙閾值中間區間保持現狀繼電器就不會在臨界點瘋狂翻轉。這就是回差控制的思路也叫滯回控制。下面代碼把閾值寫在宏定義里調整方便也方便答辯時演示參數變化。#define TEMP_LOW 37 #define TEMP_HIGH 39 #define HUM_LOW 50 #define HUM_HIGH 60 void SmartControl(unsigned char temp, unsigned char hum) { if (temp TEMP_LOW) { HEATER 0; // 低電平導通繼電器 } else if (temp TEMP_HIGH) { HEATER 1; } if (hum HUM_LOW) { HUMIDIFIER 0; } else if (hum HUM_HIGH) { HUMIDIFIER 1; } }有人會問溫度在37℃到39℃之間時這段代碼沒有對HEATER賦值繼電器會怎樣答案是保持原狀態因為HEATER對應的是IO鎖存器函數返回后引腳電平不變。所以“低于下限開、高于上限關、中間保持”已經天然形成回差。如果采用普通單閾值比如低于37℃開、高于37℃關溫度在臨界抖動時繼電器就會快速通斷觸點和加熱器壽命都會受影響。如果你想讓控制更精細可以用溫度小數位參與判斷。DHT11的小數位恒為0實際需要DHT22或者放大10倍的虛擬溫度值。課設答辯時能說清這個局限反而比強行顯示小數位更專業。4.2 Proteus仿真元件查找與hex加載資源包沒有附帶Proteus工程文件但按原理圖可以很快搭出仿真。Proteus 8以上版本在元件庫搜索AT89C51、LM016L、DHT11、RELAY即可。注意DHT11在Proteus里的模型名稱可能因版本不同有差異有些版本叫DHT11有些版本用AM2301模型代替。仿真元件Proteus關鍵詞用途單片機AT89C51與STC89C52引腳兼容溫濕度傳感器DHT11讀取溫濕度顯示屏LM016L對應LCD1602繼電器RELAY模擬加熱/加濕開關排阻RESPACK-8P0口上拉在Proteus中雙擊AT89C51在Program File一欄選擇Keil生成的1.hex然后運行仿真。正常情況LCD會顯示當前溫濕度當溫度低于37℃時加熱燈亮高于39℃時加熱燈滅。仿真環境里DHT11模型默認輸出固定數值所以多數人驗證的是顯示和控制邏輯而不是真實讀取協議。想驗證DHT11讀取代碼本身可以用示波器觀察數據腳波形或者用信號發生器模擬單總線數據。我一般會先在Proteus里把閾值調低一點用虛擬溫度變量模擬比如讓溫度從35℃緩慢升到41℃觀察繼電器吸合和釋放的邊界是否在37℃和39℃附近。這樣能快速確認回差邏輯是否生效再燒到實物上。4.3 主循環中的采樣節拍與狀態刷新DHT11兩次采樣之間必須間隔至少1s否則傳感器內部測量未完成讀出的數據不會更新。如果主循環寫成下面這樣讀取頻率會遠高于1s實際溫度會卡在固定值或者偶爾讀到舊數據。// 錯誤示例主循環里連續讀 while (1) { DHT11_ReadData(dat); LcdShowString(0x00, T:); // ... }正確做法是用定時器產生時間片。定時器0中斷每10ms進入一次累積到100次就是1s主循環只在標志位置位時做一次完整采集、顯示和控制。下面是基礎框架。volatile unsigned int tick_10ms 0; bit flag_1s 0; void Timer0_ISR(void) interrupt 1 { TH0 0xDC; // 10ms重裝載12MHz晶振 TL0 0x00; tick_10ms; if (tick_10ms 100) { tick_10ms 0; flag_1s 1; // 1秒定時到 } } void main(void) { unsigned char dat[5] {0, 0, 0, 0, 0}; LcdInit(); TMOD 0x01; TH0 0xDC; TL0 0x00; ET0 1; EA 1; TR0 1; while (1) { if (flag_1s) { flag_1s 0; if (DHT11_ReadData(dat)) { SmartControl(dat[2], dat[0]); // 更新LCD顯示 } } } }定時器初值0xDC00是12MHz晶振下10ms溢出的標準值。如果晶振是11.0592MHzTH0和TL0要重算否則1s標志會提前或延后導致DHT11讀取頻率超出限制。主循環中while(1)不阻塞1s之內還能做按鍵掃描、蜂鳴器報警等其他任務這是嵌入式系統最基礎的“時間片輪轉”結構也是這個課設從“能跑”到“結構合理”的分水嶺。5. 燒錄hex與現場排錯解決傳感器誤讀和控制混亂5.1 STC單片機的串口下載流程資源包里的1.hex就是最終燒錄文件。以STC89C52RC為例使用USB轉TTL模塊連接單片機P3.0和P3.1注意交叉USB轉TTL的TXD接單片機P3.0RXD接P3.1GND共地。打開STC-ISP軟件選擇STC89C52RC加載1.hex波特率可以先選2400點擊下載后再給單片機板上電。如果提示握手失敗先檢查串口號是否被占用再確認USB轉TTL模塊是否插緊。5.2 三種現場故障的排查順序現象優先排查點常見原因LCD有背光無字符對比度電位器、P0上拉對比度電壓異常或P0口沒有上拉電阻溫濕度顯示0或255DHT11供電、上拉電阻、Data線Data線接錯或延時函數與晶振不匹配繼電器不動作三極管基極電阻、模塊電平觸發邏輯與三極管類型不匹配DHT11誤讀是最難定位的。如果溫度一直顯示0先測量Data引腳電壓空閑狀態應該在3.3V以上。如果只有0.5V說明上拉電阻焊接有問題如果始終為高說明DHT11沒有響應可能是數據引腳定義和原理圖不一致。用LED閃爍判斷程序是否卡在while(DHT110)是在沒有示波器時最直接的辦法。在讀取函數里加入超時計數也能防止傳感器損壞后程序死鎖。5.3 串口輸出調試把現場數據送出來當LCD顯示和控制邏輯同時出問題時我一般會屏蔽LCD代碼用串口把DHT11原始讀到的數據發到上位機先確認傳感器本身是否工作。這樣能把問題范圍縮小一半。void Uart_Init(void) { SCON 0x50; // 模式18位UART允許接收 TMOD | 0x20; // 定時器1作為波特率發生器 TH1 0xFD; // 9600bps 11.0592MHz TL1 0xFD; TR1 1; } void Uart_SendChar(unsigned char c) { SBUF c; while (TI 0); TI 0; } void Uart_SendString(unsigned char *s) { while (*s) { Uart_SendChar(*s); } }調用時把讀到的數據轉換成可見ASCII字符發送串口助手連續收到“T:37 H:54”這樣的數據說明傳感器和單片機通信正常問題在顯示或控制環節如果收不到或全是FF則問題在傳感器或單總線時序。這個串口調試技巧同樣適用于其他51單片機課設比反復看LCD定位快得多。本文還有配套的精品資源點擊獲取