原本是要準備下下禮拜的課程,內容是關於溫濕度感測器〝DHT11〞,因為下禮拜要去北部喝喜酒,當週想提早北上,所以課程就提早準備,後來自己玩一玩就變成一個體積龐大的怪獸( ? ),因為花很多時間在做這玩意,捨不得拆掉,所以寫成部落格記錄一下,如果以後想擴增功能還可以隨時組回去做延伸。
這篇用到很多元件,其中有:
- 伺服馬達( Servo ) ─ SG90
- 繼電器( Relay ) ─ SRD-05VDC-SL-C
- 溫濕度感測器 ─ DHT11
- 紅外線接收器( Infrared )
- 4 x 4薄膜矩陣鍵盤(4x4 Matrix Keypad)
- 蘑菇頭搖桿 ( Joystick、PS4的類比搖桿 )
- 液晶顯示器 ─ 1602 I2C
實體圖 |
如果各位有發現的話,實體圖多了遙控器,這個遙控器的功能跟鍵盤一樣,按下上面的數字會在液晶顯示器上顯示你使用某一元件(裝置)的狀態
這個系統主要連接了三個元件(裝置)和五種顯示狀態:
- 溫濕度感測器
- 繼電器
- 伺服馬達
- Waiting Command. →預設 或 按下"0"
- 溫度 和 濕度 → 按下"1"
- 繼電器打開 → 按下"2"
- 繼電器關閉 → 按下"3"
- 伺服馬達旋轉角度 → 按下"4"
因為Arduino本身接腳的數量有限,連接這三個元件(裝置)已經到了硬體極限,消耗掉所有可使用的Arduino本體的腳位,大部分接腳都消耗在4x4薄膜矩陣鍵盤,不過因為其矩陣鍵盤的內部線路特性,實體接線還可以再減化,如果有機會把這個系統進行擴充再做簡化的動作。
拉近一點看,這是系統的第一種狀態─待機狀態,「Waiting Command」是我設定的文字,等待使用者下達指令。
系統待機狀態 |
原本應該是「Waiting Command..」,因為LCD顯示空間的關係,最後一個"點"沒顯示出來。 而void setup()裡面的code都是位系統裝置做一個開啟的動作。 |
第二種狀態─顯示溫度和濕度。
溫度:27°C 濕度:52% |
第三種狀態─繼電器打開,繼電器打開會有「喀」的一聲,開關燈隨即亮起。
第四種狀態─繼電器關閉,關閉也會「喀」的一聲,開關燈滅掉
第五種狀態─伺服馬達旋轉角度
初始角度大概都落在90°。 可以透過使用搖桿來操控其旋轉角度。 因為伺服馬達本身的機件構造,所以角度範圍為0 - 180°。 |
【Bottleneck】
施作過程中遇到問題不少,耗費大量時間去Try & Error,還有上網survey(調查)一些資料來讀,基本上都解決了,問題分別有硬體問題和軟體問題,在此條列式出問題點:
﹝硬體問題﹞
- Arduino主版有時候會燒錄失敗,重新插拔即可解決〈燒錄失敗原因這個網站有列很多,可參考:http://yehnan.blogspot.tw/2014/10/arduinostk500getsync-not-in-sync.html〉。
- 液晶顯示器有時候會不正常顯示,重新插拔Arduino(重新供電)或重新燒錄也可以解決。
- 如果你已經使用序列通訊(Digital上非Pin1和Pin0之通訊埠),Arduino主板上的Digital Pin1(TX)和Pin0(RX)不可以亂用,我唯一有用到這兩個角位的時候是在施作藍芽模組。
- 紅外線接收器在接收訊號會被伺服馬達影響,如果開出Serial Monitor來看(要寫相關Code),會發現伺服馬達在運轉的時候,紅外線接收到的Decode狀態是"-1"(解碼失敗),多按幾次就可以搞定了。
- 沒有伺服馬達,紅外線接收非常準。
﹝軟體問題﹞
- 鍵盤掃描判斷之訊號是使用其解碼前的代碼,而非字元!〈上次上課有很多同學因此卡住很久〉,鍵盤上的代碼都是由ASCII編碼而成,網路上有其表,這裡就不放了。〈我自己是用16進制作判讀,上次上課有同學轉成10進制也是聰明的做法〉。
- 紅外線按下的判斷可以寫成一個副程式去處理,當然首先要在void loop()裡先判讀是否接收到訊號。
- 紅外線訊號的判斷也是用代碼,要寫出可以知道按鈕代碼的code(程式碼),當然代碼在網路上也找的到,但我還是encourage(鼓勵)用code寫出來會比較有意思。
- 伺服馬達的操作不可以單純寫成一個IF去開啟其功能,因為伺服馬達的工作是持續性的,如果寫成IF啟動而不再作任何改寫,那伺服馬達只會短暫做一次工作而已,你要一直不停的按"4"才會看的出來問題所在。
- 延續上面,我曾經在主程式的IF判斷式裡面多加了while(1)〈無窮迴圈〉去讓伺服馬達不斷工作,但是失敗收場,後來改寫成副程式就好了!改寫成副程式就好了!!改寫成副程式就好了!!!我也不知道為什麼,有機會再來debug。
- 每次在按下按鍵,液晶顯示氣都會顯示各種裝置的狀態,這代表每次都要把畫面清空,不要忘了使用「lcd.clear();」這條指令。
- 伺服馬達的lcd.clear();不可以在無窮迴圈裡,否則你會看到很驚人的結果...!
- 無窮迴圈記得要用條件和判斷式去break掉。
- 因為液晶顯示器畫面在顯示後會自動殘留(記憶),所以可以用「空格」lcd.print(" ")來處理你的畫面〈比如說伺服馬達旋轉角度的數字〉。
【可改進之地方】
這邊也分成硬體和軟體兩部分去做說明,可能受限於自己技術頗爛的關係,我需要比較足夠的硬體支持才能去撰寫軟體,沒辦法從軟體去根除硬體不夠用的狀況:
﹝硬體改善﹞
- 薄膜鍵盤因其構造可以再簡化接線。詳細可參考:https://swf.com.tw/?p=921
- 薄膜鍵盤佔用太多port,簡化後可以再掛上更多裝置。
﹝軟體改善﹞
- 因為硬體已使用到極限,我在施作伺服馬達因為開關需要多一個port去做判讀(簡單裝一個LED上去做為判斷媒介即可)紅外線因為接收會造成伺服馬達工作有問題的情況〈上面〝軟體問題〞的第四點和第五點已被我更改回只工作一次的情況,如果語句言不及義,可參考Code會比較清楚我在說什麼,副程式已經被我修掉〉,不過port已經被我完全使用完,這可能要等待硬體改善完成後才有機會釋出比較多的port讓我去處理這個問題。
- 溫濕度感測因為IF判斷式的關係,也只能工作一次,我窮盡手段要讓他可以線性工作,但是遭遇鍵盤和紅外線按鈕判讀上的問題,這當然也是只能透過軟體去改善,或許還要再使用一個port去做判斷媒介,情況同上述第一點。
結論:因為個人技術不足,需要硬體支援我的軟體能力QQ
【Code】
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <dht.h>
#include <Keypad.h>
#include <IRremote.h>
#include <Servo.h>
#define KEY_ROWS 4 // 按鍵模組的列數
#define KEY_COLS 4 // 按鍵模組的行數
#define dht_dpin 2
Servo myservo1 ;
Servo myservo2 ;
int xPin = A1;
int yPin = A0;
int buttonPin = 3;
int xPosition = 0 ;
int yPosition = 0 ;
int buttonState = 0 ;
int joyval ;
dht DHT ;
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE) ; // 設定 LCD I2C 位址
char keymap[KEY_ROWS][KEY_COLS] =
{
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
} ;
byte colPins[KEY_COLS] = {9, 8, 7, 6} ; // 按鍵模組,行1~4接腳。
byte rowPins[KEY_ROWS] = {13, 12, 11, 10} ; // 按鍵模組,列1~4接腳。
Keypad myKeypad = Keypad(makeKeymap(keymap), rowPins, colPins, KEY_ROWS, KEY_COLS) ;
int RelayPin = 5 ;
int RECV_PIN = 4 ;
decode_results results ;
IRrecv irrecv(RECV_PIN) ;
void setup()
{
Serial.begin(9600) ;
pinMode(RelayPin, OUTPUT) ;
irrecv.enableIRIn() ; // 啟動紅外線接收
pinMode(xPin, INPUT) ;
pinMode(yPin, INPUT) ;
pinMode(buttonPin, INPUT) ;
myservo1.attach(A2) ;
myservo2.attach(A3) ;
lcd.begin(16, 2) ; // 初始化 LCD
lcd.backlight() ; //開啟背光
lcd.setCursor(0, 0) ;
lcd.print("Waiting Command..") ;
}
void loop()
{
char key = myKeypad.getKey() ;
if (irrecv.decode(&results)) //當紅外線收到訊號
{
translateIR() ;
Serial.print("results value is "); // 輸出解碼後的資料
Serial.print(results.value, HEX);
Serial.print(", bits is ");
Serial.print(results.bits);
Serial.print(", decode_type is ");
Serial.println(results.decode_type);
irrecv.resume() ; // 重新接收下一個訊號
}
// ↑紅外線設定完成↑
// ↓鍵盤按下之功能↓
if (key == 0x31) //按下"1"
{
lcd.clear() ;
DHT.read11(dht_dpin) ;
lcd.setCursor(0, 0) ;
lcd.print("Temp: ") ;
lcd.print(DHT.temperature) ;
lcd.print("C") ;
lcd.setCursor(0, 1) ;
lcd.print("Humi: ") ;
lcd.print(DHT.humidity) ;
lcd.print("% ") ;
}
if(key == 0x30 ) //按下"0"
{
lcd.clear() ;
lcd.setCursor(0, 0) ;
lcd.print("Waiting Command..") ;
}
if(key == 0x32 ) //按下"2"
{
lcd.clear() ;
digitalWrite(RelayPin, HIGH) ;
lcd.setCursor(0, 0) ;
lcd.print("Relay On !") ;
lcd.setCursor(0, 1) ;
lcd.print("'Off' press 3") ;
}
if(key == 0x33 ) //按下"3"
{
lcd.clear() ;
digitalWrite(RelayPin, LOW) ;
lcd.setCursor(0, 0) ;
lcd.print("Relay Off !") ;
lcd.setCursor(0, 1) ;
lcd.print("'On' press 2") ;
}
if(key == 0x34) //按下"4"
{
lcd.setCursor(0, 0) ;
lcd.print("x-axis angle:") ;
lcd.print(xPosition) ;
lcd.print(" ") ;
lcd.setCursor(0, 1) ;
lcd.print("y-axis angle:") ;
lcd.print(yPosition) ;
lcd.print(" ") ;
}
xPosition = analogRead(xPin);
xPosition = map(xPosition, 0, 1023, 0, 180) ;
myservo1.write(xPosition) ;
yPosition = analogRead(yPin);
yPosition = map(yPosition, 0, 1023, 0, 180) ;
myservo2.write(yPosition) ;
}
void translateIR() //紅外線按下之功能
{
switch(results.value)
{
case 0xFF6897:
lcd.clear() ;
DHT.read11(dht_dpin) ;
lcd.setCursor(0, 0) ;
lcd.print("Temp: ") ;
lcd.print(DHT.temperature) ;
lcd.print("C") ;
lcd.setCursor(0, 1) ;
lcd.print("Humi: ") ;
lcd.print(DHT.humidity) ;
lcd.print("% ") ;
irrecv.blink13(true) ;
break ;
case 0xFF4AB5:
lcd.clear() ;
lcd.setCursor(0, 0) ;
lcd.print("Waiting Command..") ;
irrecv.blink13(true) ;
break ;
case 0xFF9867:
lcd.clear() ;
digitalWrite(RelayPin, HIGH) ;
lcd.setCursor(0, 0) ;
lcd.print("Relay On !") ;
lcd.setCursor(0, 1) ;
lcd.print("'Off' press 3") ;
irrecv.blink13(true) ;
break ;
case 0xFFB04F:
lcd.clear() ;
digitalWrite(RelayPin, LOW) ;
lcd.setCursor(0, 0) ;
lcd.print("Relay Off !") ;
lcd.setCursor(0, 1) ;
lcd.print("'On' press 2") ;
irrecv.blink13(true) ;
break ;
case 0xFF30CF:
lcd.setCursor(0, 0) ;
lcd.print("x-axis angle:") ;
lcd.print(xPosition) ;
lcd.print(" ") ;
lcd.setCursor(0, 1) ;
lcd.print("y-axis angle:") ;
lcd.print(yPosition) ;
lcd.print(" ") ;
break ;
}
}
#include <LiquidCrystal_I2C.h>
#include <dht.h>
#include <Keypad.h>
#include <IRremote.h>
#include <Servo.h>
#define KEY_ROWS 4 // 按鍵模組的列數
#define KEY_COLS 4 // 按鍵模組的行數
#define dht_dpin 2
Servo myservo1 ;
Servo myservo2 ;
int xPin = A1;
int yPin = A0;
int buttonPin = 3;
int xPosition = 0 ;
int yPosition = 0 ;
int buttonState = 0 ;
int joyval ;
dht DHT ;
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE) ; // 設定 LCD I2C 位址
char keymap[KEY_ROWS][KEY_COLS] =
{
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
} ;
byte colPins[KEY_COLS] = {9, 8, 7, 6} ; // 按鍵模組,行1~4接腳。
byte rowPins[KEY_ROWS] = {13, 12, 11, 10} ; // 按鍵模組,列1~4接腳。
Keypad myKeypad = Keypad(makeKeymap(keymap), rowPins, colPins, KEY_ROWS, KEY_COLS) ;
int RelayPin = 5 ;
int RECV_PIN = 4 ;
decode_results results ;
IRrecv irrecv(RECV_PIN) ;
void setup()
{
Serial.begin(9600) ;
pinMode(RelayPin, OUTPUT) ;
irrecv.enableIRIn() ; // 啟動紅外線接收
pinMode(xPin, INPUT) ;
pinMode(yPin, INPUT) ;
pinMode(buttonPin, INPUT) ;
myservo1.attach(A2) ;
myservo2.attach(A3) ;
lcd.begin(16, 2) ; // 初始化 LCD
lcd.backlight() ; //開啟背光
lcd.setCursor(0, 0) ;
lcd.print("Waiting Command..") ;
}
void loop()
{
char key = myKeypad.getKey() ;
if (irrecv.decode(&results)) //當紅外線收到訊號
{
translateIR() ;
Serial.print("results value is "); // 輸出解碼後的資料
Serial.print(results.value, HEX);
Serial.print(", bits is ");
Serial.print(results.bits);
Serial.print(", decode_type is ");
Serial.println(results.decode_type);
irrecv.resume() ; // 重新接收下一個訊號
}
// ↑紅外線設定完成↑
// ↓鍵盤按下之功能↓
if (key == 0x31) //按下"1"
{
lcd.clear() ;
DHT.read11(dht_dpin) ;
lcd.setCursor(0, 0) ;
lcd.print("Temp: ") ;
lcd.print(DHT.temperature) ;
lcd.print("C") ;
lcd.setCursor(0, 1) ;
lcd.print("Humi: ") ;
lcd.print(DHT.humidity) ;
lcd.print("% ") ;
}
if(key == 0x30 ) //按下"0"
{
lcd.clear() ;
lcd.setCursor(0, 0) ;
lcd.print("Waiting Command..") ;
}
if(key == 0x32 ) //按下"2"
{
lcd.clear() ;
digitalWrite(RelayPin, HIGH) ;
lcd.setCursor(0, 0) ;
lcd.print("Relay On !") ;
lcd.setCursor(0, 1) ;
lcd.print("'Off' press 3") ;
}
if(key == 0x33 ) //按下"3"
{
lcd.clear() ;
digitalWrite(RelayPin, LOW) ;
lcd.setCursor(0, 0) ;
lcd.print("Relay Off !") ;
lcd.setCursor(0, 1) ;
lcd.print("'On' press 2") ;
}
if(key == 0x34) //按下"4"
{
lcd.setCursor(0, 0) ;
lcd.print("x-axis angle:") ;
lcd.print(xPosition) ;
lcd.print(" ") ;
lcd.setCursor(0, 1) ;
lcd.print("y-axis angle:") ;
lcd.print(yPosition) ;
lcd.print(" ") ;
}
xPosition = analogRead(xPin);
xPosition = map(xPosition, 0, 1023, 0, 180) ;
myservo1.write(xPosition) ;
yPosition = analogRead(yPin);
yPosition = map(yPosition, 0, 1023, 0, 180) ;
myservo2.write(yPosition) ;
}
void translateIR() //紅外線按下之功能
{
switch(results.value)
{
case 0xFF6897:
lcd.clear() ;
DHT.read11(dht_dpin) ;
lcd.setCursor(0, 0) ;
lcd.print("Temp: ") ;
lcd.print(DHT.temperature) ;
lcd.print("C") ;
lcd.setCursor(0, 1) ;
lcd.print("Humi: ") ;
lcd.print(DHT.humidity) ;
lcd.print("% ") ;
irrecv.blink13(true) ;
break ;
case 0xFF4AB5:
lcd.clear() ;
lcd.setCursor(0, 0) ;
lcd.print("Waiting Command..") ;
irrecv.blink13(true) ;
break ;
case 0xFF9867:
lcd.clear() ;
digitalWrite(RelayPin, HIGH) ;
lcd.setCursor(0, 0) ;
lcd.print("Relay On !") ;
lcd.setCursor(0, 1) ;
lcd.print("'Off' press 3") ;
irrecv.blink13(true) ;
break ;
case 0xFFB04F:
lcd.clear() ;
digitalWrite(RelayPin, LOW) ;
lcd.setCursor(0, 0) ;
lcd.print("Relay Off !") ;
lcd.setCursor(0, 1) ;
lcd.print("'On' press 2") ;
irrecv.blink13(true) ;
break ;
case 0xFF30CF:
lcd.setCursor(0, 0) ;
lcd.print("x-axis angle:") ;
lcd.print(xPosition) ;
lcd.print(" ") ;
lcd.setCursor(0, 1) ;
lcd.print("y-axis angle:") ;
lcd.print(yPosition) ;
lcd.print(" ") ;
break ;
}
}
沒有留言:
張貼留言