我的ESP32實做書籍:我出書了 ESP32 物聯網專題
博客來網址:https://www.books.com.tw/products/0010901195
前言
溫濕度感測是物聯網實驗重要的一環,收集大量的資料可以了解溫濕度數值對環境的影響,例如偵測倉庫的溫濕度,可以避免商品受潮而故障或腐敗,監控教室的溫濕度也可以避免太熱影響到學生的學習效率,而本章將會以DHT11感測器來感測環境溫濕度,並利用1602LCD顯示數值。
溫濕度感測(DHT11)
1.感測器種類介紹
在Arduino中有許多感測溫度的感測器,例如DHT系列(11或22)、DS18B20、LM35等,其中LM35及DS18B20為三極體外型,DHT系列則分成藍色DHT11及白色DHT22,其規格比較表如下,LM35與DS18B20在感測上效果差不多,可以精確到小數點以下兩位,市售DS18B20常包覆不鏽鋼,可以用於偵測一些極端環境,例如測量水族箱溫度,或者化學溶液中。本書則是以DHT11為範例,可同時測量溫度及濕度變化。
感測器型號 | 感測內容 | 外觀 | 感測範圍 | 誤差 | 通訊方式 |
---|---|---|---|---|---|
LM35 | 溫度 | 三極體 | -55 ~ 150℃ | ±0.25℃ | 類比 |
DS18B02 | 溫度 | 三極體 | -55 ~125℃ | ±0.5℃ | 數位 |
DHT11 | 溫度、濕度 | 藍色網狀 | 溫度: 0 ~ 50℃ 濕度: 20 ~ 95% | ±2℃ ±5% | 數位 |
DHT12 | 溫度、濕度 | 白色網狀 | 溫度:-40~ -80℃ 濕度: 0 ~ 100% | ±0.5℃ ±2% | 數位 |
DHT11的外觀如下圖,由左到右依序為訊號(s)、VCC(+)及GND(-)及三個腳位,其中VCC因為位置不夠因此沒標明,特別要說明的是DHT11與其他感測器比較起來較為脆弱,若VCC、GND接反了很快就會燒毀,因此在接線上必須特別注意,以免發生危險。
DHT系列規格上雖然標明為數位傳輸,但是非digitalRead這麼簡單,而是一連串的訊號交換及解譯才能獲得數值,為了減少這個複雜的過程,我們可以安裝simpleDHT.h程式庫,只要呼叫一行指令就可以直接取得感測器數值。
2.安裝simpleDHT程式庫
安裝simpleDHT程式庫後
接下來我們將DHT11與ESP32連接起來,其腳位圖如下,除了GND及VCC之外,將DHT11的訊號(S)連接到ESP32的GPIO4,不過若要將DHT11接在GPIO12或GPIO2時,則須等程式上傳完畢後,再將DHT11接上,否則可能會遇到Time out的錯誤訊息。
DHT11請勿插在GPIO2或12腳位,將會導致Time out 錯誤
完成接線後,我們開啟上方功能表/檔案/範例/simpleDHT,找到程式庫提供的範例程式DHT11Default,開啟後,僅須修改第7行的腳位,從預設的2改為4即可執行。
#include <SimpleDHT.h>
int pinDHT11 = 4;
SimpleDHT11 dht11(pinDHT11);
void setup() {
Serial.begin(115200);
}
void loop() {
// start working... Serial.println("=================================");
Serial.println("Sample DHT11...");
// read without samples.
byte temperature = 0;
byte humidity = 0;
int err = SimpleDHTErrSuccess;
if ((err = dht11.read(&temperature, &humidity, NULL)) != SimpleDHTErrSuccess) {
Serial.print("Read DHT11 failed, err="); Serial.println(err);delay(1000);
return;
}
Serial.print("Sample OK: ");
Serial.print((int)temperature); Serial.print(" *C, ");
Serial.print((int)humidity); Serial.println(" H");
// DHT11 sampling rate is 1HZ.
delay(1500);
}
上述的範例中,關鍵語法是
dht11.read(&temperature, &humidity, NULL))
這句的意思是開始讀取dht11的溫濕度數值,並將結果放置在temperature及humidity變數的位址,也就是說,當執行完dht11.read指令後,就可以得到環境的溫濕度數值了,但因為接線錯誤或者感測器故障而讀取失敗的話,則會顯示錯誤並且返回(return)。
而範例程式在顯示溫濕度時,前方加上(int)則是為了型別轉換,例如(int)temperature,因為dht11.read的資料格式是byte,讀取出來後則用(int)轉成整數。
程式在顯示溫濕度時,後續我們在使用上也建議都加上(int)以免出錯
1602LCD顯示器
1.LCD顯示器種類介紹
上一節當我們想知道溫濕度資料時,可以透過序列監控視窗查看,可是如果手邊沒有電腦可以開啟序列監控,那要如何知道DHT11所感測的資料呢?
其實Arduino系列有相當多種的顯示裝置可以利用,例如TFT、OLED,甚至是電子紙(ePaper)都相當流行,各有不同的適用環境,不過對初學者來說最簡單的應該是點陣式LCD(Liquid Crystal Display)了,現在大部份都製作為I2C界面的1602 LCD模組(LCM:Liquid Crystal Display Module),也就是2行每行16個字的LCD顯示模組,為了介紹方便,以下我們通稱為1602LCD,除了1602之外,2004規格也很常見(4行x 20字),讀者可以依需求自行購買。
讀者在購買1602LCD時,務必要選擇具有I2C界面的LCD,所謂的I2C是Inter-Integrated Circuit的簡稱,也可寫作IIC(讀法為I-squared-C)是一種相當普遍的低速傳輸模式,由於可以利用定址方式連接最多112個節點,大量用於單晶片、手機、主機板之內,I2C界面僅須SDA及SCL兩個腳位,Arduino系列都有支援,若購入沒有I2C界面LCD,則需要用到6~8個腳位,安裝控制都會相當複雜,相形之下I2C安裝較為簡單,且直接呼叫函式庫即可顯示內容相當方便。
2.ESP32連接LCD與設定
接下來我們將ESP32與1602LCD連線,除了VCC及GND之外,ESP32預設的I2C界面為SDA是GPIO21、SCL為GPIO22,因此將LCD的SDA->GPIO21,SCL->GPIO22即可。不過使用1602LCD仍有許多常見問題,這裡大致列出如下:
- 未使用5V:LCD須使用5V電源驅動,否則背光可能無法點亮。
- 背光接口沒接:當接上5V點源,LCD仍無法點亮,這可能是背光接口沒接,通常背光接口會有一個Jump將腳位短路,若JUMP遺失可使用母母杜邦線對接即可。
- 位址設定錯誤:i2C裝置都會有一個位址,宣告錯誤的位址是不會有任何反應的,所以必須先以做一次i2c scan掃描,取得正確位址。
- 對比沒調整:若上述都完成,依然無法顯示文字,那應該是對比沒調整,請利用小螺絲起子旋轉對比旋鈕,調整一個讀者覺得清楚的對比。
我們先來說3的I2C位址掃描是什麼?上一段提及I2C可以透過定址連接最多112個裝置,那麼不同裝置必然會有不同的位址,當我們要執行程式時就要先指定這個命令是要給哪一個裝置執行,不然命令會被錯誤的裝置接收,就導致運作不正常,因此我們要在使用前先利用以下的程式掃描裝置的位址是多少。
#include <Wire.h>
void setup()
{
Serial.begin (115200);
Wire.begin (21, 22); // sda= GPIO_21 /scl= GPIO_22
}
void Scanner ()
{
Serial.println ();
Serial.println ("I2C scanner. Scanning ...");
byte count = 0;
Wire.begin();
for (byte i = 8; i < 120; i++)
{
Wire.beginTransmission (i); // Begin I2C transmission Address (i)
if (Wire.endTransmission () == 0) //0=success(ACK response)
{
Serial.print ("Found address: ");
Serial.print (i, DEC);
Serial.print (" (0x");
Serial.print (i, HEX); // PCF8574 7 bit address
Serial.println (")");
count++;
}
}
Serial.print ("Found ");
Serial.print (count, DEC); // numbers of devices
Serial.println (" device(s).");
}
void loop()
{
Scanner ();
delay (5000);
}
程式上傳後,若接線正確沒問題的話,就會出現下方的內容,其中
I2C device found at address 0x27
代表在ESP32上有找到一組I2C裝置位址在0x27,除了0x27之外,1602LCD也會有0x30或其他數值,這個數值很重要,請先把數值寫下來,等等就會用到。
3.安裝LCD程式庫
接下來我們需要安裝LCD的程式庫才能在上面顯示文字,利用上方功能表/草稿碼/匯入程式庫/管理程式庫
打開程式庫管理員之後,在上方空白處輸入關鍵字:「LiquidCrystal I2C」,並找到作者為Frank de Brabander的程式庫後(大約在中間的位置),點選右側的安裝即可。
完成安裝後,我們可以開啟下面的HelloWorld程式,裡面的關鍵語法包括:
1.指定LCD位址與規格:
LiquidCrystal_I2C lcd(0x27,16,2);
其中0x27代表我們LCD的I2C位址,此處讀者應填入上方i2c scan時所獲得的位址,而16代表我們的LCD是一行16個字,2代表是2排字。
2.LCD初始化:
lcd.init();
代表lcd初始化,LCD使用前必須要執行一次初始化。
3.開啟背光:
lcd.backlight();
代表開啟LCD背光,若缺少這句LCD無背光顯示。
4.移動游標
lcd.setCursor(3,0);
代表將游標移至第0行,第3個字(下圖○A的位置),顯示文字前要先利用這個指令指定從何處開始顯示文字。
5.印出文字
lcd.print("Hello, world!");
則是在步驟4指定的游標處印出文字,這裡要注意若印出的文字超過螢幕寬度,並不會自動換行,會直接被隱藏,因此顯示時讀者須自行計算游標與文字數量。
6.清除內容
lcd.clear();//清除所有內容
清除LCD內的內容,等候顯示更新後的資料。
4.LCD完整程式碼
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27,16,2);//設定LCD位址與大小
void setup()
{
lcd.init(); //初始化LCD
lcd.backlight(); //開啟LCD背光
}
void loop(){
lcd.setCursor(3,0);//設定游標
lcd.print("Hello, world!");//印出文字
lcd.setCursor(2,1);//設定游標
lcd.print("ESP32 LCD Test!"); //印出文字
delay(1000);
lcd.clear();//清除所有內容
delay(1000);
}
若您的LCD並沒有出現Hello World字樣,那請您依序本節一開始時所說明的LCD使用注意4個事項進行檢查,排除問題。
當顯示沒問題的話,就可以將兩隻範例程式合在一起,也就是把DHT11溫濕度的DHT11default程式碼及LCD的HelloWorld結合在一起,以本例而言,我們將較為簡單的LCD HelloWorld程式的setup()、loop()、以及最上方宣告區都搬到DHT11default中,整合後的程式如下。
#include <SimpleDHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
int pinDHT11 = 14;
SimpleDHT11 dht11(pinDHT11);
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
}
void loop() {
Serial.println("=================================");
Serial.println("Sample DHT11...");
byte temperature = 0;
byte humidity = 0;
int err = SimpleDHTErrSuccess;
if ((err = dht11.read(&temperature, &humidity, NULL)) != SimpleDHTErrSuccess) {
Serial.print("Read DHT11 failed, err="); Serial.println(err); delay(1000);
return;
}
Serial.print("Sample OK: ");
Serial.print((int)temperature); Serial.print(" *C, ");
Serial.print((int)humidity); Serial.println(" H");
lcd.clear();
lcd.setCursor(0, 0); //先設定游標
lcd.print("Temperature: ");//顯示溫度
lcd.print((int)temperature);
lcd.print("c");
lcd.setCursor(0, 1); //顯示溼度
lcd.print("Humidity: ");
lcd.print((int)humidity);
lcd.print("%");
delay(1500);
}