2013年4月9日 星期二

Arduino 紅外線遙控器


紅外光為可見光譜之外的一種不可見的延伸光譜,光譜位置位於可見光紅色光外側,波長介於770nm至1mm間,依波長可區分為近紅外線(0.78μm~3μm)、中紅外線(3μm~50μm)、遠紅外線(50μm~1,000μm)[1],紅外線技術在生活中已廣泛應用,在日常生活中常可發現其身影,舉凡家庭電視機遙控器、廁所的感應沖水設施、防盜的紅外線攝影機甚或軍事用的紅外線夜視鏡等,皆為紅外線的應用實例。相較於藍芽或RF等無線通訊技術,紅外線雖有方向性及光無法穿透障礙物等物理限制特性,但因其具有低成本,設計簡單的優點,對於機器人產品成本降低(cost down)具有相當程度的幫助,在機器人領域,典型的例子為i-Robot開發的吸塵器機器人Roomba[2],Roomba以紅外線作為其無線傳輸介面,其目的亦是為了降低成本而採用的。本文將著重於紅外線應用於無線遙控所使用的技術原理以及特性的介紹。
紅外線遙控器所應用的波長屬於近紅外線範圍,市面上常見的紅外線發射器為850nm、875nm、940nm這幾種波長[3],外形與普通發光二極體相同,原理也與普通發光二極體相似,差別只在於紅外線發射器產生的是紅外光而非可見光,圖1綠色部分為普通發光二極體,藍黑色部分為紅外線發射器。

圖1 紅外線發射器與普通LED
發射端的電路相當簡易,依據使用者選定的發射器功率設計適合的驅動方式,圖2為低功耗的驅動方式(電流20 mA以下),圖3為高功耗的驅動方式(電流100 mA以上),改變圖2、圖3內的電阻值R1、R2,即能調整通過發射端的電流值,即能增加或減少傳輸距離。

圖2 低功耗驅動方式
圖3 高功耗驅動方式
至於接收端,則多為將接收、放大、濾波和比較器輸出等功能整合在一起的單一模組,圖4為市面常見的接收模組實體,圖5為接收模組內部架構[4],圖6為發射端與接收端之整合應用電路圖[5]。

圖4 紅外線接收模組

圖5 接收模組內部架構

圖6 發射端與接收端之整合應用電路圖
由於環境中某些光源設備亦會產生紅外光,為避免環境光源所造成干擾,接收端皆有帶通濾波器(B.P.F),一般市面的紅外線接收器的中心頻率常見的有36KHz、38KHz、40KHz、56.8KHz這幾個類型,以38KHz為例,若接收端接收到38KHz的High/Low載波訊號(IR Carrier)時,輸出Low,若接收到的訊號不是上下震盪訊號,則輸出維持在High,在38KHz可達最遠距離,若震盪訊號頻率偏離38KHz,則傳輸距離遞減,圖7為載波發送與接收結果示意圖。

圖7 載波發送與接收結果示意圖
載波可用很多方式產生,可用RC硬體電路,或以MCU產生皆可,圖8為利用74HC000的NAND邏輯閘以及週邊RC充放電電路所完成的載波信號產生電路,當輸入訊號為Low時,輸出端P為High,當輸入訊號為High時,輸出端P為High/Low載波訊號,載波頻率可藉由調整VR而改變。

圖8 載波產生硬體電路
然而,一般設計皆希望傳輸可達較遠的距離,要達到這個指標,發射的載波頻率(38kHz)要求十分穩定,以硬體RC電路產生的載波訊號,若電壓逐漸下降時,使產生的載波頻率逐漸偏離中心頻率,易造成通訊距離不穩,若應用於以電池為電源的產品上,將有不良影響,因此大多數以晶片外接震盪器或由晶片內頻產生的訊號作為載波輸出,此類型的震盪源不易受到電壓下降的影響,因而在各時間點,P點輸出的載波頻率皆能維持在中心頻率,使傳輸距離維持在最佳狀態。圖9為以晶片產生載波訊號的示意圖。

圖9 以晶片產生載波訊號
目前紅外線遙控器發射電路的功能組成,其中的編碼方式按遙控器用途可以很簡單、也可以很複雜。例如用於電視機和組合音響的遙控器,其控制功能或可多達50種以上,此時的編碼器會採用專用的紅外線編碼協定進行編撰,然而對控制功能相對較少的遙控器而言,其編碼器也會相對簡單,圖10為市售紅外線遙控器[6]。

圖10 各種紅外線遙控器
雖說紅外線編碼解碼並無一定的規範,不過市面一些大廠所設計的製作的紅外線遙控器皆有其自己的規範,筆者在此舉Philip公司所制定的Philips RC-6 Protocol[7](以下簡稱RC-6)供讀者參考,讀者或可參考大廠所設計的protocol來設計自己的編解碼方式,RC-6的制定是Philip公司為了眾多紅外線設備而設計的Protocol,其內容如表1所示。

表1 Philips RC-6 Protocol內容
Header
Control 
 Information
 End
LS 
SB
mb2~mb0
TR  
A7~A0
C7~C0 
 Signal free

RC-6建立在36KHz下,各個單位時間t為16個載波組成,亦即t = 1/36k * 16 = 444μs,表1中Control為裝置的Address,Information為發射端的按鍵命令,Protocol內容中特別位元說明如圖11所示,其餘相關說明,若讀者有興趣可參考附件連結網址[7]。

圖11 RC-6特別位元說明
參考資料

紅外線是目前最常見的一種無線通訊,普遍使用在家電以及玩具產品,如電視、音響、錄放影機、冷氣機、DVD、MP3 Player、遙控車等。紅外線遙控之所以被大量採使用,主要是因為紅外線裝置體積小、成本低、耗電少及硬體設計容易。下圖是紅外線發射器(Transmitter 或稱 IR LED)和接收器(Receiver)常見外觀,一般來說,紅外線遙控系統由發射器和接收器這兩部份組成。
紅外線是不可見光,其實生活中充滿了紅外線,只是我們看不到。紅外線主要來自太陽,不過很多物體也會發射紅外線,例如燈泡、蠟燭、中央空調等,甚至人體也會散發紅外線。人體所發出的紅外線的量是可以偵測的,耳溫槍就是利用這個道理測量人的體溫。有這麼多紅外線光源,當然會對遙控造成干擾,所以得做一些預防措施確保通訊正確。
避免干擾的解藥是 Modulation。我們講話速度若適當,不徐不急,聽得舒服,聽者自然不漏接。相同的道理,利用 Modulation 讓 IR LED 以特定的頻率閃爍,Receiver 端也調整到同樣的頻率,便可以忽略干擾。在上圖中,可以看到調變訊號 (Modulated signal)在驅動 IR LED 發射訊號,而偵測到的訊號則從右手邊的 receiver 跑出來。 在 Serial 通訊中常會提到 mark 和 space 狀態。space 是紅外線的預設訊號,Transmitter 處於 off 狀態,這時 IR LED 不會發射光亮;而在 mark 狀態 IR LED 會以特定的頻率送出 on/off 脈衝(Pulse)。消費電子一般使用 30kHz 到 60kHz 的頻率。
兩點注意事項:
  1. 因為 Receiver 會把訊號反向,所以從 Receiver 這端來看,space 意味著 high level 訊號輸出,而 mark 則是 low level 訊號輸出。
要特別注意 mark 跟 space 並非我們要傳輸的 1 與 0 數位訊號。mark 跟 space 以及 1 與 0 之間的關係是由所用的 protocol 決定的。目前 IR Protocol 有相當多種,如 NEC, Phlips RC5, RC6, RC-MM, Toshiba, Sharp, JVC, Sony SIRC 等。






// * IRRemote 接收紅外線

#include <IRremote.h>            
const int irReceiverPin = 2;             // 紅外線接收器訊號接在 pin 2
IRrecv irrecv(irReceiverPin);            // 定義 IRrecv 物件來接收紅外線訊號
decode_results Deresults;                  // 解碼結果將放在 decode_results 結構的 Deresult 變數裏
void setup()
{
  Serial.begin(9600);                   // 設定Serial port, 9600 bps
  irrecv.enableIRIn();                   // Enable紅外線解碼
}

void loop()
{
  if (irrecv.decode(&Deresults)) {         // 收到一組正確紅外線訊號
    // 印到 Serial port
    Serial.print("irCode: ");          
    Serial.print(Deresults.value, HEX);    //  編碼值
    Serial.print(",  bits: ");         
    Serial.println(Deresults.bits);         // 編碼位元數
    irrecv.resume();                     // 繼續收下一組紅外線訊號      
  }
}




//*****************************************
/// receiving IR codes 
//*****************************************
#include <IRremote.h>

int RECV_PIN = 2;

IRrecv irrecv(RECV_PIN);

decode_results results;

void setup()
{
  Serial.begin(9600);
  irrecv.enableIRIn(); // Start the receiver
}

void loop() {
  if (irrecv.decode(&results)) {
    Serial.println(results.value, HEX);
    irrecv.resume(); // Receive the next value
  }
}




/*
 * IRremote: IRrecvDump - dump details of IR codes with IRrecv
 * An IR detector/demodulator must be connected to the input RECV_PIN.
 * Version 0.1 July, 2009
 * Copyright 2009 Ken Shirriff
 * http://arcfn.com
 */

#include <IRremote.h>

int RECV_PIN = 2;

IRrecv irrecv(RECV_PIN);

decode_results results;

void setup()
{
  Serial.begin(9600);
  irrecv.enableIRIn(); // Start the receiver
}

// Dumps out the decode_results structure.
// Call this after IRrecv::decode()
// void * to work around compiler issue
//void dump(void *v) {
//  decode_results *results = (decode_results *)v
void dump(decode_results *results) {
  int count = results->rawlen;
  if (results->decode_type == UNKNOWN) {
    Serial.println("Could not decode message");
  } 
  else {
    if (results->decode_type == NEC) {
      Serial.print("Decoded NEC: ");
    } 
    else if (results->decode_type == SONY) {
      Serial.print("Decoded SONY: ");
    } 
    else if (results->decode_type == RC5) {
      Serial.print("Decoded RC5: ");
    } 
    else if (results->decode_type == RC6) {
      Serial.print("Decoded RC6: ");
    }
    Serial.print(results->value, HEX);
    Serial.print(" (");
    Serial.print(results->bits, DEC);
    Serial.println(" bits)");
  }
  Serial.print("Raw (");
  Serial.print(count, DEC);
  Serial.print("): ");

  for (int i = 0; i < count; i++) {
    if ((i % 2) == 1) {
      Serial.print(results->rawbuf[i]*USECPERTICK, DEC);
    } 
    else {
      Serial.print(-(int)results->rawbuf[i]*USECPERTICK, DEC);
    }
    Serial.print(" ");
  }
  Serial.println("");
}


void loop() {
  if (irrecv.decode(&results)) {
    Serial.println(results.value, HEX);
    dump(&results);
    irrecv.resume(); // Receive the next value
  }
}





//************************************************
//Relay Toggle for IR Received 
//************************************************
/*
 * IRremote: IRrecvDemo - demonstrates receiving IR codes with IRrecv
 * An IR detector/demodulator must be connected to the input RECV_PIN.
 * Version 0.1 July, 2009
 * Copyright 2009 Ken Shirriff
 * http://arcfn.com
 */

#include <IRremote.h>

int RECV_PIN = 2;
int RELAY_PIN = 4;

IRrecv irrecv(RECV_PIN);
decode_results results;

// Dumps out the decode_results structure.
// Call this after IRrecv::decode()
// void * to work around compiler issue
//void dump(void *v) {
//  decode_results *results = (decode_results *)v
void dump(decode_results *results) {
  int count = results->rawlen;
  if (results->decode_type == UNKNOWN) {
    Serial.println("Could not decode message");
  } 
  else {
    if (results->decode_type == NEC) {
      Serial.print("Decoded NEC: ");
    } 
    else if (results->decode_type == SONY) {
      Serial.print("Decoded SONY: ");
    } 
    else if (results->decode_type == RC5) {
      Serial.print("Decoded RC5: ");
    } 
    else if (results->decode_type == RC6) {
      Serial.print("Decoded RC6: ");
    }
    Serial.print(results->value, HEX);
    Serial.print(" (");
    Serial.print(results->bits, DEC);
    Serial.println(" bits)");
  }
  Serial.print("Raw (");
  Serial.print(count, DEC);
  Serial.print("): ");

  for (int i = 0; i < count; i++) {
    if ((i % 2) == 1) {
      Serial.print(results->rawbuf[i]*USECPERTICK, DEC);
    } 
    else {
      Serial.print(-(int)results->rawbuf[i]*USECPERTICK, DEC);
    }
    Serial.print(" ");
  }
  Serial.println("");
}

void setup()
{
  pinMode(RELAY_PIN, OUTPUT);
  pinMode(13, OUTPUT);
    Serial.begin(9600);
  irrecv.enableIRIn(); // Start the receiver
}

int on = 0;
unsigned long last = millis();

void loop() {
  if (irrecv.decode(&results)) {
    // If it's been at least 1/4 second since the last
    // IR received, toggle the relay
    if (millis() - last > 250) {
      on = !on;
      digitalWrite(RELAY_PIN, on ? HIGH : LOW);
      digitalWrite(13, on ? HIGH : LOW);
      dump(&results);
    }
    last = millis();      
    irrecv.resume(); // Receive the next value
  }
}


//************************************************
//IR Controlled 6 LEDs
//
************************************************
/*
 * IRremote: IRrecvDemo - demonstrates receiving IR codes with IRrecv
 * An IR detector/demodulator must be connected to the input RECV_PIN.
 * Version 0.1 July, 2009
 * Copyright 2009 Ken Shirriff
 * http://arcfn.com
 */

#include <IRremote.h>

int RECV_PIN = 11;

int LED1 = 2;
int LED2 = 3;
int LED3 = 4;
int LED4 = 5;
int LED5 = 6;
int LED6 = 7;
long on1  = 0x00FFA25D;
long off1 = 0x00FFE01F;
long on2 = 0x00FF629D;
long off2 = 0x00FFA857;
long on3 = 0x00FFE21D;
long off3 = 0x00FF906F;
long on4 = 0x00FF22DD;
long off4 = 0x00FF6897;
long on5 = 0x00FF02FD;
long off5 = 0x00FF9867;
long on6 = 0x00FFC23D;
long off6 = 0x00FFB047;

IRrecv irrecv(RECV_PIN);
decode_results results;

// Dumps out the decode_results structure.
// Call this after IRrecv::decode()
// void * to work around compiler issue
//void dump(void *v) {
//  decode_results *results = (decode_results *)v
void dump(decode_results *results) {
  int count = results->rawlen;
  if (results->decode_type == UNKNOWN) 
    {
     Serial.println("Could not decode message");
    } 
  else 
   {
    if (results->decode_type == NEC) 
      {
       Serial.print("Decoded NEC: ");
      } 
    else if (results->decode_type == SONY) 
      {
       Serial.print("Decoded SONY: ");
      } 
    else if (results->decode_type == RC5) 
      {
       Serial.print("Decoded RC5: ");
      } 
    else if (results->decode_type == RC6) 
      {
       Serial.print("Decoded RC6: ");
      }
     Serial.print(results->value, HEX);
     Serial.print(" (");
     Serial.print(results->bits, DEC);
     Serial.println(" bits)");
   }
     Serial.print("Raw (");
     Serial.print(count, DEC);
     Serial.print("): ");

  for (int i = 0; i < count; i++) 
     {
      if ((i % 2) == 1) {
      Serial.print(results->rawbuf[i]*USECPERTICK, DEC);
     } 
    else  
     {
      Serial.print(-(int)results->rawbuf[i]*USECPERTICK, DEC);
     }
    Serial.print(" ");
     }
      Serial.println("");
     }

void setup()
 {
  pinMode(RECV_PIN, INPUT);   
  pinMode(LED1, OUTPUT);
  pinMode(LED2, OUTPUT);
  pinMode(LED3, OUTPUT);
  pinMode(LED4, OUTPUT);
  pinMode(LED5, OUTPUT);
  pinMode(LED6, OUTPUT);  
  pinMode(13, OUTPUT);
  Serial.begin(9600);
  
  irrecv.enableIRIn(); // Start the receiver
 }

int on = 0;
unsigned long last = millis();

void loop() 
{
  if (irrecv.decode(&results)) 
   {
    // If it's been at least 1/4 second since the last
    // IR received, toggle the relay
    if (millis() - last > 250) 
      {
       on = !on;
//       digitalWrite(8, on ? HIGH : LOW);
       digitalWrite(13, on ? HIGH : LOW);
       dump(&results);
      }
    if (results.value == on1 )
       digitalWrite(LED1, HIGH);
    if (results.value == off1 )
       digitalWrite(LED1, LOW); 
    if (results.value == on2 )
       digitalWrite(LED2, HIGH);
    if (results.value == off2 )
       digitalWrite(LED2, LOW); 
    if (results.value == on3 )
       digitalWrite(LED3, HIGH);
    if (results.value == off3 )
       digitalWrite(LED3, LOW);
    if (results.value == on4 )
       digitalWrite(LED4, HIGH);
    if (results.value == off4 )
       digitalWrite(LED4, LOW); 
    if (results.value == on5 )
       digitalWrite(LED5, HIGH);
    if (results.value == off5 )
       digitalWrite(LED5, LOW); 
    if (results.value == on6 )
       digitalWrite(LED6, HIGH);
    if (results.value == off6 )
       digitalWrite(LED6, LOW);        
    last = millis();      
    irrecv.resume(); // Receive the next value
  }
}


沒有留言:

張貼留言

WOKWI LED + MQTT Node-Red SQLite

WOKWI LED + MQTT Node-Red SQLite const char *mqtt_broker = "broker.mqtt-dashboard.com" ; const char *topic1 = "alex9ufo/e...