在 Arduino 既有函式庫中就有紅外線庫可以讓我們簡便的擷取紅外線遙控器的訊號,並且擷錄下來重新發送出去。
實作影片連結
電風扇、電視機等等的家電,其紅外線遙控器的編碼基本上較為簡單,都是可以直接用函式庫內的函數來進行解碼。但冷氣機的編碼設計的較為複雜,就沒辦法很簡易的來進行解碼的動作。
半年前我做了一個 Esp8266(wifi) 搭配 Blynk 來進行溫濕度感測的應用,但總覺得不過癮。於是我就想到既然我都取得了溫度的資訊,不如就順便把冷氣機也搭上了,不做還好,一做才發現冷氣機的遙控編碼真不好搞,網上查詢了很久都找不到成功的方法,果斷放棄。
隨著天氣越來越熱,又不想冷氣開一整晚,定時關閉到早上又常常睡到被熱醒,望著我的 ESP8266 ,明明就可以抓到溫度,知道我都要被熱死了,卻還是很笨的不會幫我開個冷氣。這下子我才決定我一定要把這個冷氣機的紅外線編碼搞出來,完成依溫度自動開關冷氣加上手機手動遙控的功能。
紅外線編碼的原理網上查都很多,在此不贅述。接收的電路在網上查都有,我也就不貼了;發射的電路在文末。
首先我們來看看簡單版的紅外線遙控(電視機、電風扇等等),直接給出 code
/*
紅外線簡單接收
*/
#include "IRremote.h"
#define RECV_PIN 6 //定義紅外線接收器腳位
#define LED1 3
IRrecv my_irrecv(RECV_PIN); //初始化紅外線接收器腳位
decode_results results; //定義解碼結構,名稱為results
void setup()
{
Serial.begin(9600);
pinMode(LED1,OUTPUT);
my_irrecv.blink13(true);
my_irrecv.enableIRIn(); // 致能接收
Serial.println("IR remote Initial completed");
}
void loop() {
if (my_irrecv.decode(&results)) { // 接收紅外線訊號並解碼
Serial.print("results value in HEX is "); // 輸出解碼後的資料
Serial.print(results.value, HEX);
Serial.print(", results value in DEC is "); // 輸出解碼後的資料
Serial.print(results.value);
Serial.print(", bits is ");
Serial.print(results.bits);
Serial.print(", decode_type is ");
Serial.println(results.decode_type);
my_irrecv.resume(); // 準備接收下一個訊號
}
delay(100);
}
如果運氣很好 type 不是 unknow 的話,多半都可以接著使用紀錄下來的編碼來進行送出
假設收到的編碼為0x77E11050,且類型為 NEC
/*
紅外線簡單接收
*/
#include "IRremote.h"
IRsend irsend;
// pin 7 for Arduino Due, pin 9 for Arduino Mega, pin 3 for Arduino UNO
void setup(){
Serial.begin(115200);
}
void loop() {
Serial.println(0x77E11050, HEX);
irsend.sendNEC(0x77E11050, 32); // 輸出紅外線訊號
}
如果遇到 unknow 的紅外線編碼該怎麼辦?
=> 我們只需要擷取出原訊號,並且重建出一個幾乎一模一樣的訊號就可以了。
在這之前我們有必要先了解一下紅外線編碼的組成是怎麼回事,簡單來說如下圖
對於一個紅外線編碼來說,會有一個起始訊號,該訊號用來指出 "一個編碼的開始"
接著會跟著一連串的實際編碼,而這個編碼比較特殊,是藉由 特定長度ton + 特定長度 toff 所組成的一個編碼邏輯。
舉例來說如下圖的中間(自行定義為1)的地方,就是由一個較短的 HIGH 訊號配上較長的 LOW 訊號所組成。
你可以將這個樣子的訊號假設為 "1" (當然你要假設為 0 也沒有關係,因為我們待會兒就只是要複製這樣的訊號而已)
該如何擷取到由由控器發射出來的訊號呢?
以下程式碼擷錄自86duino,我做了一些參數上的修改
const int IR_rec_pin = 2; // IR 接收器輸出腳位
int IRstate = LOW; // IR 接收器輸出腳位狀態
int IRstate_last = LOW; // IR 接收器輸出腳位狀態(上一次)
unsigned long int time_last = 0; // 上一次 IRstate 變化的時間
boolean isIdle = true; // 是否在等待 IR 訊號模式Idle)
const long int durationMax = 10000; // 一段時間沒變化就進入 Idle,單位 us
const long int durationMin = 300; // 電壓狀態不變的最小持續時間,單位 us
void IR_rec_Check();
void setup( ) {
Serial.begin( 115200 );
pinMode( IR_rec_pin, INPUT ); // 設定針腳 I/O 模式
IRstate = digitalRead( IR_rec_pin ); // 取得腳位元狀態初始值
IRstate_last = IRstate;
}
void loop( ) {
IR_rec_Check();
delayMicroseconds( 5 );
}
void IR_rec_Check(){
IRstate = digitalRead( IR_rec_pin );// 讀取腳位狀態
if( IRstate != IRstate_last ){ //
long int timeNow = micros( ); // 記錄目前時間
long int dT = timeNow - time_last; // 與上一次腳位變化的時間差
if( (dT >= durationMax) && !isIdle ){ // 時間間隔大於設定的時間,且原本的狀態為接收中狀態
isIdle = true; //進入等待狀態
Serial.println( "Idling...\n" );
}
else if( (dT < durationMax) && (dT > durationMin) ){
isIdle = false; //進入接收中狀態
if(IRstate == HIGH) Serial.print(dT);
else Serial.print( 0-dT );
Serial.print(" ");
}
// 記錄此次時間
time_last = timeNow;
}
// 記錄此次狀態
IRstate_last = IRstate;
}//end IR_rec_Check
在接收紅外線遙控器的訊號時需注意,因為我們等等將要人工觀察這些訊號,為了別造成麻煩,盡量在收訊號時讓燈光暗些,能夠少一些雜訊的干擾。
並且收錄個三到四筆訊號如下圖
將這個收錄到的結果複製到 Excel 內來進行觀察,如下圖所示
如果發現有些訊號的數據組相較其它組多,則捨棄。盡量找到幾組數據組,整體長度是差不多的,如下圖
接著將數據組轉為「直方圖」
再來就是重頭戲了,我選了其中三組的直方圖來進行觀察
上圖中,我們先找出「起始訊號」、「自行定義的 1」以及「自行定義的 0」特徵
接著就可以觀察數據,如上圖所述
黑框的部分,三個數據組無法觀察出一個明確的値,所以先行跳過
接著我們發現又出現一個起始訊號,如上圖
發現左右兩邊的數據是相同的,也就是說廠商將「相同的數據」重覆發送兩次,做為編碼
第二筆數據後方的資料,三個數據組完全一致,表示第一筆數據後方的資料出現漏禎,造成混亂無法觀察,所幸我們已經知道兩邊的數據是相同的,所以就把右邊的後半段補到前面來用就好。
綜合以上,我們可以把整段數據組拆解成如上圖的結構
接著再回到 Excel 來進行訊號的重建
待重建完後,使用 Excel 內的 Abs() 指定將負號轉正,並且搭配 & 運算子來加上逗號,方便建立陣列
於此我們已經將遙控器發射出來的訊號擷取出來了,接下來要做的事情就是用自己的紅外線發射電路,重新將這個訊號發射出去,這樣子電器的紅外線接收器就會認為是遙控器發射出來的
#include "IRremote.h"
IRsend irsend;// pin 7 for Arduino Due, pin 9 for Arduino Mega, pin 3 for Arduino UNO
const unsigned int Power_ON[]={
4900,2280,//起始訊號
400,860,400,2200,400,2200,400,2200,//0111
400,860,400,860,400,2200,400,860,//0010
400,860,400,860,400,860,400,860,//0000
400,860,400,860,400,860,400,860,//0000
400,2200,400,2200,400,2200,400,2200,//1111
400,2200,400,860,400,860,400,860,//0000
400,860,400,860,400,2200,400,2200,//0111
400,860,400,860,400,860,400,8000,//000(短ton+長toff)
4900,2280,//起始訊號
400,860,400,2200,400,2200,400,2200,
400,860,400,860,400,2200,400,860,
400,860,400,860,400,860,400,860,
400,860,400,860,400,860,400,860,
400,2200,400,2200,400,2200,400,2200,
400,2200,400,860,400,860,400,860,
400,860,400,860,400,2200,400,2200,
400,860,400,860,400,860,400,860,
}; // Teco 冷氣 turn off
void setup()
{
Serial.begin(9600);
pinMode(13,OUTPUT);
}
void loop()
{
irsend.sendRaw(Power_ON, sizeof(Power_ON)/sizeof(Power_ON[0]), 38);
Serial.println("ON");
digitalWrite(13,!digitalRead(13));
}
使用紅外線函式庫裡面的 sendRaw 將我們重建的紅外線編碼陣列送出
程式碼中 sizeof 的部分可以參考這篇 《筆記》C語言 - 06_2:氣泡排序、二元搜尋、sizeof 與資料型別
由上述可知東元的冷氣紅外線訊號,一次是 64 bits 的
但不同的廠商都會自己去設計自己的紅外線訊號, 64 bits 應該是較為常見的
下面是 Apton 的冷氣遙控氣擷取出來的訊號,是 128 bits 的
我們忽略起始訊號以及分隔訊號不看,單單看裡面的數據資料(一樣定義一短一長是 1 )
圖一(開的訊號)
圖二(關的訊號)
這牌子的冷氣用上面的程式碼去收,發現按一次遙控器按鈕後會出現一串數字(ex: D4D4 4242),然後進入 Idle狀態,緊接著又馬上出現第二串數字,然後進入 Idle ,才結束。
實際把開跟關的訊號都觀察後,就可以發現這廠商為了確保紅外線訊號的正確性,故意將重覆的資料都進行重覆的發送
仔細看 圖一(開的訊號) 跟 圖二(關的訊號) 就能觀察出有趣的端倪,例如兩者後 64 bits 的十六進制相同,都是 80806E6E ,不難看出應該是廠商(或該冷氣型號)的內部編碼(代碼) 806E。
而兩者的前 64 bits 很顯然可以再細分為前半段的 32 bits 與後半段的 32bits ,且前半段等於後半段。所以我們就比較開的訊號跟關的訊號的前半段 32bits 就好
可以看出開的訊號是D4D4 5252;關的訊號是D4D4 4242,所以 D4 有可能代表的是接收到訊號,後面跟上的 52代表的是開;42代表的是關
可以看出開的訊號是D4D4 5252;關的訊號是D4D4 4242,所以 D4 有可能代表的是接收到訊號,後面跟上的 52代表的是開;42代表的是關
這種連續發射很多重複訊號的通常都是一次上百個 bit 在傳送,而前面的測試結果發現按一次按鈕後會 "分次" 接收到兩列的訊號(前 64bits 跟後 64bits),貌似有些不完美。
回頭去看看接收的程式碼,嘗試將 durationMax 改成長一點看能不能抓出兩個 64 bits 之間的分隔訊號長成什麼樣子,修改成兩萬後得到結果如下圖(開的訊號)
const unsigned int Power_ON[] = {
3500, 3500, 900, 850, 900, 850, 900, 2500, 900, 2500, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 850, 900, 850, 900, 2500, 900, 2500, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 850, 900, 2500, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 2500, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 2500, 900, 850, 3500, 3500, 900, 850, 900, 850, 900, 2500, 900, 2500, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 850, 900, 850, 900, 2500, 900, 2500, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 850, 900, 2500, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 2500, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 2500, 900, 850, 3500, 3500, 900, 14000, 3500, 3500, 900, 2500, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 2500, 900, 2500, 900, 850, 900, 2500, 900, 2500, 900, 2500, 900, 850, 900, 850, 900, 2500, 900, 2500, 900, 850, 900, 2500, 900, 2500, 900, 2500, 900, 850, 3500, 3500, 900, 2500, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 2500, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 850, 900, 2500, 900, 2500, 900, 850, 900, 2500, 900, 2500, 900, 2500, 900, 850, 900, 850, 900, 2500, 900, 2500, 900, 850, 900, 2500, 900, 2500, 900, 2500, 900, 850, 3500, 3500, 900
};
後記 :
如果發現照著步驟做,還是沒辦法成功遙控冷氣機,有可能是紅外線發射器訊號太弱。可加上電晶體來放大訊號的強度,詳細可參考本篇 《進階※電子電路篇》寫程式Arduino教學 - 01:BJT電晶體 飽合區 截止區 到底該怎用? 與下圖
後續我是把這個功能結合 BLYNK 實現「用手機遙控冷氣」的功能,詳見此篇
《進階※應用篇》寫程式Arduino教學 - 04:使用 BLYNK 監控 Arduino 並使用手機遙控冷氣家電
參考連結
EduCake 紅外線收發功能實作 - 86Duino