Follower

2017年5月12日 星期五

【Arduino】紅外線 & 4x4 鍵盤控制各種元件 - Basic of IoT


  原本是要準備下下禮拜的課程,內容是關於溫濕度感測器〝DHT11〞,因為下禮拜要去北部喝喜酒,當週想提早北上,所以課程就提早準備,後來自己玩一玩就變成一個體積龐大的怪獸( ? ),因為花很多時間在做這玩意,捨不得拆掉,所以寫成部落格記錄一下,如果以後想擴增功能還可以隨時組回去做延伸。



  這篇用到很多元件,其中有:

  1. 伺服馬達( Servo ) ─ SG90
  2. 繼電器( Relay ) ─ SRD-05VDC-SL-C
  3. 溫濕度感測器 ─ DHT11
  4. 紅外線接收器( Infrared )
  5. 4 x 4薄膜矩陣鍵盤(4x4 Matrix Keypad)
  6. 蘑菇頭搖桿 ( Joystick、PS4的類比搖桿 )
  7. 液晶顯示器 ─ 1602 I2C
  正文開始前還有一點要聲明,本篇不著重介紹各個元件,但施作遇到的bottleneck(瓶頸)會做說明和解釋。

【Basic of IoT】


系統線路圖(以實體圖為依據)
實體圖

如果各位有發現的話,實體圖多了遙控器,這個遙控器的功能跟鍵盤一樣,按下上面的數字會在液晶顯示器上顯示你使用某一元件(裝置)的狀態

這個系統主要連接了三個元件(裝置)和五種顯示狀態:
  1. 溫濕度感測器
  2. 繼電器
  3. 伺服馬達
  • 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(調查)一些資料來讀,基本上都解決了,問題分別有硬體問題和軟體問題,在此條列式出問題點:

﹝硬體問題﹞

  1. Arduino主版有時候會燒錄失敗,重新插拔即可解決〈燒錄失敗原因這個網站有列很多,可參考:http://yehnan.blogspot.tw/2014/10/arduinostk500getsync-not-in-sync.html〉。
  2. 液晶顯示器有時候會不正常顯示,重新插拔Arduino(重新供電)或重新燒錄也可以解決。
  3. 如果你已經使用序列通訊(Digital上非Pin1和Pin0之通訊埠),Arduino主板上的Digital Pin1(TX)和Pin0(RX)不可以亂用,我唯一有用到這兩個角位的時候是在施作藍芽模組。
  4. 紅外線接收器在接收訊號會被伺服馬達影響,如果開出Serial Monitor來看(要寫相關Code),會發現伺服馬達在運轉的時候,紅外線接收到的Decode狀態是"-1"(解碼失敗),多按幾次就可以搞定了。
  5. 沒有伺服馬達,紅外線接收非常準。

﹝軟體問題﹞

  1. 鍵盤掃描判斷之訊號是使用其解碼前的代碼,而非字元!〈上次上課有很多同學因此卡住很久〉,鍵盤上的代碼都是由ASCII編碼而成,網路上有其表,這裡就不放了。〈我自己是用16進制作判讀,上次上課有同學轉成10進制也是聰明的做法〉。
  2. 紅外線按下的判斷可以寫成一個副程式去處理,當然首先要在void loop()裡先判讀是否接收到訊號。
  3. 紅外線訊號的判斷也是用代碼,要寫出可以知道按鈕代碼的code(程式碼),當然代碼在網路上也找的到,但我還是encourage(鼓勵)用code寫出來會比較有意思。
  4. 伺服馬達的操作不可以單純寫成一個IF去開啟其功能,因為伺服馬達的工作是持續性的,如果寫成IF啟動而不再作任何改寫,那伺服馬達只會短暫做一次工作而已,你要一直不停的按"4"才會看的出來問題所在。
  5. 延續上面,我曾經在主程式的IF判斷式裡面多加了while(1)〈無窮迴圈〉去讓伺服馬達不斷工作,但是失敗收場,後來改寫成副程式就好了!改寫成副程式就好了!!改寫成副程式就好了!!!我也不知道為什麼,有機會再來debug。
  6. 每次在按下按鍵,液晶顯示氣都會顯示各種裝置的狀態,這代表每次都要把畫面清空,不要忘了使用「lcd.clear();」這條指令。
  7. 伺服馬達的lcd.clear();不可以在無窮迴圈裡,否則你會看到很驚人的結果...!
  8. 無窮迴圈記得要用條件和判斷式去break掉。
  9. 因為液晶顯示器畫面在顯示後會自動殘留(記憶),所以可以用「空格」lcd.print(" ")來處理你的畫面〈比如說伺服馬達旋轉角度的數字〉。

【可改進之地方】

這邊也分成硬體和軟體兩部分去做說明,可能受限於自己技術頗爛的關係,我需要比較足夠的硬體支持才能去撰寫軟體,沒辦法從軟體去根除硬體不夠用的狀況:

﹝硬體改善﹞

  1. 薄膜鍵盤因其構造可以再簡化接線。詳細可參考:https://swf.com.tw/?p=921
  2. 薄膜鍵盤佔用太多port,簡化後可以再掛上更多裝置。

﹝軟體改善﹞

  1. 因為硬體已使用到極限,我在施作伺服馬達因為開關需要多一個port去做判讀(簡單裝一個LED上去做為判斷媒介即可)紅外線因為接收會造成伺服馬達工作有問題的情況〈上面〝軟體問題〞的第四點第五點已被我更改回只工作一次的情況,如果語句言不及義,可參考Code會比較清楚我在說什麼,副程式已經被我修掉〉,不過port已經被我完全使用完,這可能要等待硬體改善完成後才有機會釋出比較多的port讓我去處理這個問題。
  2. 溫濕度感測因為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 ;
  }
}

【實際影片】




沒有留言:

張貼留言