目次 [ Contents ]
以前、赤外線を使ってカメラのリモコンシャッター操作を試してみましたが、今度はArduinoIDEに用意されている「IR Remote」というライブラリで赤外線データを扱ってみます。
自力で赤外線バイナリを発信した時とは違い、このライブラリを使うと、簡単に一般的な家電リモコンのデータを取得したり、任意の赤外線データを送れるようになります。
準備
使用パーツ
- Arduino
- 赤外線受信モジュール
- 赤外線LED&抵抗 — 抵抗値は任意
- 家庭内にある家電リモコン
- タクトスイッチ × 2
- (確認用LED&抵抗 — 抵抗値は任意)
一般的に赤外線LEDは定格電圧が低いので、3V駆動のArduinoか、3V供給ピンがあるようなタイプを使うといいかと思います。ただ、他のサイト同様、5V配線でも動きはするので、壊れても痛くない方は定格無視でも結果は見れます。また、後述のArduino間通信をしたければ2セット分用意。
ここでの家電リモコンはSonyのブルーレイプレイヤーリモコンを使っています。
今回使った受信モジュールはGP1UXC41QS・OSRB38C9AAですが、他の受信モジュールでもピン配置以外は似たようなものかと思います。
また、データシートを見たら、47Ω位の抵抗と47μFのコンデンサを奨励しているので、安全・確実に進めたい人はちゃんと使うパーツのデータシートを確認してください。
IR Remoteを使う
まず、IR Remoteの使い方を書いていきます。
受信データをモニタリング
IR Remoteを使って家電リモコンのデータを見るのは、フォーマットに沿っていけば難しくはありません。というか、サンプルスケッチを開けば、それさえも必要なく、データをシリアルモニタで確認できます。
「ファイル」メニュー「スケッチ例」から「IRremote」の「IRrecvDump」を開く。
シリアルモニタを開いて(9600kbps)、家電リモコンのボタンを押せば、そのボタンのデータが取得、表示されます。
最初の行が実際のデータです(HEX表記)。次行のDecodedにはリモコンのメーカー名が出ます。IR Remoteはデータの内容から、ある程度メーカー名を識別してくれます。
カメラシャッター送信で触れたように、赤外線送信のフォーマットは、ある程度揃っているものの、詳細がメーカー別で違います。フォーマットの違いは、こちらに詳しく書かれています。
最後の数値羅列は、データの長さ、つまりON/OFF切り替わりの時間尺です。実際にシグナルが反転した時間(マイクロ秒)になっています。
IR Remoteの関数
サンプルスケッチ「IRrecvDump」を例に、ざっくり説明していきます。詳細はGitHubに書かれています。
IRrecv ~(pin number);
受信で使用するオブジェクトを作成。名称と使用するピンを一緒に指定。サンプルスケッチでは「irrecv」がオブジェクト名、カッコ内が受信に使用するピンになっています。
18 19 20 |
int RECV_PIN = 11; IRrecv irrecv(RECV_PIN); |
~.enableIRIn();
作成したオブジェクトで、割り込みによる赤外線受信をスタート。
24 25 26 27 28 |
void setup() { Serial.begin(9600); irrecv.enableIRIn(); // Start the receiver } |
decode_results ~~;
受信情報の格納先を作成。
22 |
decode_results results; |
受信したデータは、この作成した「results」内に格納されます。中には複数の情報が入っていて、指定して任意の情報を取得できます。
名 | 説明 |
decode_type | メーカー名 |
value | 受信したデータ |
bits | 受信したデータのビット数 |
rawbuf[] | それぞれの赤外線ON/OFFの切替にかかった時間(μ秒)の配列 |
rawlen | 実際に赤外線のON/OFFがあった回数 |
例えば、
long recv_data = results.value;
とすれば、recv_dataへ受信したデータを格納することになります。
シリアルモニタへの返りを表示する「dump」関数内では、指定に「->」というアロー演算子を使っていますが、ドット演算子「.」でも同様に呼び出せるようです(ここら辺の相違は勉強不足なので説明は割愛で…)。
ちなみにdefine定義されたメーカー名文字列は、ライブラリフォルダ内のKEYWORDファイル内で確認できます。
~.decode(&~~);
受信したかどうかを確認します。サンプルスケッチ内ではif文で使われていますが、未受信 = 0、受信 = 1 という返り値になっています。シリアル通信でいうserial.availableと同じ、と考えていいと思います。
91 92 93 94 95 96 97 |
void loop() { if (irrecv.decode(&results)) { Serial.println(results.value, HEX); dump(&results); irrecv.resume(); // Receive the next value } } |
~.resume();
.decode()の返り値をリセットします。これがないと、上記の.decode()は1を返し続けてしまいます。試しにこの行をコメントアウトして、テストしてみると分かりやすいと思います。
91 92 93 94 95 96 97 |
void loop() { if (irrecv.decode(&results)) { Serial.println(results.value, HEX); dump(&results); irrecv.resume(); // Receive the next value } } |
.decode()と.resume()はセットで使うもの、と覚えておくといいかもしれません。
以上を踏まえて、簡単なサンプルスケッチを白紙から書いてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <IRremote.h> IRrecv irrecv(11); decode_results results; void setup() { Serial.begin(9600); irrecv.enableIRIn(); } void loop() { if (irrecv.decode(&results)) { if (results.decode_type == SONY) Serial.print("It's SONY! "); Serial.println(results.value, HEX); irrecv.resume(); } } |
自分が使ったのがSony製なので、そこは任意に使っているリモコンメーカーへ変更してください。
状況によっては、1回押しただけなのに、複数回表示されてしまうかもしれません。これは、リモコンが同じデータを複数回送っているから、あるいは“リピートコード”も拾ってしまうからで、.resume()前にdelay関数を置いたり、間を作るかして、重複を回避してください。
ちなみにdecode_typeの数値と定義された文字列の関係は以下のようになっているかと思われます(一部、推測があるのでご注意)。
value | メーカー名文字列 |
-1 | UNKNOWN |
0 | UNUSED(推測) |
1 | RC5 |
2 | RC6 |
3 | NEC |
4 | SONY |
5 | PANASONIC |
6 | JVC |
7 | SAMSUNG(推測) |
8 | WHYNTER |
9 | AIWA_RC_T501 |
10 | LG |
11 | SANYO(推測) |
12 | MITSUBISHI(推測) |
13 | DISH(推測) |
14 | SHARP(推測) |
15 | DENON(推測) |
16 | PRONTO(推測) |
17 | LEGO_PF(推測) |
赤外線データ送信
IR Remoteで赤外線データを送信するための準備は、オブジェクトをひとつ作るだけです。
IRsend irsend;
ただし、送信するために使う関数はメーカー別なので、個別に使用する必要があります。
irsend.sendNEC(data, bits)
irsend.sendSony(data, bits)
irsend.sendRC5(data, bits)
irsend.sendRC6(data, bits)
irsend.sendJVC(data, bits, repeat)
irsend.sendPanasonic(pre data, data)
irsend.sendRaw(data buf, length, hertz)
引数のdataは実データ、そのままbitsはビット数。
.sendRawはメーカーフォーマットに当てはまらない場合に使います。また、パナソニックとビクターはちょっと違う形式です。それぞれ、スケッチ例が用意されているので、気になる方はそちらを探ってください。
任意のデータを読み取り・送信できるサンプルスケッチを書いて見ます。.sendの行は、各自のメーカーに合わせて変更してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
#include <IRremote.h> #define SW 7 #define LED 13 IRrecv irrecv(11); IRsend irsend; decode_results results; long cache_data; byte cache_bits; #define PUSH_SHORT 100 void setup() { Serial.begin(9600); pinMode(SW, INPUT_PULLUP); pinMode(LED, OUTPUT); irrecv.enableIRIn(); } void loop() { // capture IR signal if (irrecv.decode(&results)) { // set values from IR cache_bits = results.bits; cache_data = results.value; // serial monitoring Serial.print("Memorized! "); SPRI(); // LED flicker for check for (byte i = 0 ; i < 5 ; i++) { digitalWrite(LED, i % 2); delay(100); } // ready to next IR recieve irrecv.resume(); } //----- pushed switch task ----- int gauge; while (digitalRead(SW) == 0) { digitalWrite(LED, HIGH); gauge++; } // send IR data if switch is pushed if (gauge > PUSH_SHORT) { irsend.sendSony(cache_data, cache_bits); Serial.print("SEND! "); SPRI(); irrecv.enableIRIn(); // restart IR recieve } digitalWrite(LED, LOW); } // Serial monitor display void SPRI() { Serial.print("/ BITS:"); Serial.print(cache_bits); Serial.print(" DATA:0x"); Serial.print(cache_data, HEX); Serial.println(); } |
赤外線データを受信すれば、LEDが点滅し、そのデータとビット数をcash_にキープ。スイッチを押すとその信号を発信します。一応、シリアルモニタにも状況が出る様にしました。
このサンプルで一番大事なところは.sendした後、再度.enableIRIn();を呼んでいるところです。IR Remote、関数によってはピンの状態を変更してしまうようです。send/recieve命令を一つのスケッチに共存させたい場合、これを入れないとうまく機能しないので気をつけてください。
で、取得したパターンを家電に送ると…ちゃんと動作するでしょうか?前回、言及したとおり、赤外線LEDはかなり光の幅が狭いので、受信部へ近づけ、相当真っ直ぐ入るようにしないと上手く反応してくれないと思います。自分はここでずいぶん長いことつまづきました。
IR Remoteで無線通信
実は、今回書きたかったのはこの部分です。つまり、
「IR RemoteでArduinoを簡単・安価に無線化できる」
っていう話。
TWE-LITEは動作がしっかりしていて確実ですが、送受信で2台用意すると4000円近くかかります。対してこの赤外線通信なら、数百円あれば無線化することが出来るわけです。飛距離や角度などの制限はあるものの、「そこまでしっかりしなくていいので、とりあえず手軽に無線化したい」なら、かなり有用な方法と言えます。
ということで、Arduino同士に赤外線通信をさせます。
回路図
- D03 pin : IR LED
- D06 pin : tact switch 2
- D07 pin : tact switch 1
- D11 pin : IR Reciever
- D13 pin : normal LED
上述の回路図にスイッチを1つ追加しているだけです。可能なら、これを2セット用意します。
スケッチ “IR Controller”
各々のスイッチを押している間に家電リモコンの赤外線を受信すれば、その通信データを記憶します。普通にスイッチを押せば、記憶した赤外線データを送信します。記憶した赤外線データを受信すると、指定した命令を実行します。
.sendSonyは同様に環境に合わせ書き換えてください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
#include <IRremote.h> #define SW_1 6 #define SW_2 7 #define LED 13 IRrecv irrecv(11); IRsend irsend; decode_results results; #define SW_SIZE 2 byte sw_pins [SW_SIZE] = {SW_1, SW_2}; long cache_data [SW_SIZE]; byte cache_bits [SW_SIZE]; #define PUSH_SHORT 100 void setup() { Serial.begin(9600); for (byte i = 0 ; i < SW_SIZE; i++) pinMode(sw_pins[i], INPUT_PULLUP); pinMode(LED, OUTPUT); irrecv.enableIRIn(); Serial.println("This is IR Controller!"); } void loop() { // ----- send IR data if you pushed switches ----- for (byte i = 0 ; i < SW_SIZE ; i++) { if (FUNC_SW(i) == true) { irsend.sendSony(cache_data[i], cache_bits[i]); Serial.print("SEND SW:"); Serial.print(i + 1); Serial.print("! "); SPRI(i); delay(40); irrecv.enableIRIn(); // restart IR recieve function } digitalWrite(LED, LOW); } // ----- task for each recieving ----- if (irrecv.decode(&results)) { // --- Switch 1 reaction if (cache_data[0] == results.value) { Serial.print("Recieved! SW:1 "); SPRI(0); LED_FLICKER(20, 100); // --- Switch 2 reaction } else if (cache_data[1] == results.value) { Serial.print("Recieved! SW:2 "); SPRI(1); LED_FLICKER(10, 400); // --- non assigned switch reaction } else { Serial.print("no Assigned! "); Serial.print("/ BITS:"); Serial.print(results.bits); Serial.print(" DATA:0x"); Serial.println(results.value, HEX); } irrecv.resume(); digitalWrite(LED, LOW); } } boolean FUNC_SW(byte pins) { int gauge; bool pushed = true; while (digitalRead(sw_pins[pins]) == 0) { digitalWrite(LED, HIGH); if (pushed == true) gauge++; // capture IR signal if (irrecv.decode(&results)) { // set values from IR cache_bits[pins] = results.bits; cache_data[pins] = results.value; // serial monitoring Serial.print("Memorized to SW:"); Serial.print(pins + 1); Serial.print("! "); SPRI(pins); LED_FLICKER(5, 100); // ready to next IR recieve irrecv.resume(); pushed = false; gauge = 0; } } if (gauge < PUSH_SHORT) pushed = false; digitalWrite(LED, LOW); return pushed; } // LED flicker for check void LED_FLICKER(byte times, short dur) { for (byte i = 1 ; i <= times ; i++) { digitalWrite(LED, i % 2); delay(dur); } digitalWrite(LED, LOW); } // Serial monitor display void SPRI(byte pins) { Serial.print("/ BITS:"); Serial.print(cache_bits[pins]); Serial.print(" DATA:0x"); Serial.print(cache_data[pins], HEX); Serial.println(); } |
スイッチの反応は「シリアルモニタへの返し」と「LEDの点滅違い」ですが、“Switch 1~2 reaction”内を書き換えれば、受信した時の反応をカスタマイズできます。また、Arduinoをリセットすると取得データは消えてしまいますが、cash_の配列変数を具体的なHEX数値に書き換えておけば、常用の仕組みとしても扱えます。
簡単に無線コントローラが、あるいは、単純に家電リモコンでArduinoを操作できるようになるわけで、工夫次第で面白いモノが作れるかようになるのではないか、と。余裕があったら、赤外線ラジコンでも作ってみたいですね。
参考リンク
- GitHub – Arduino IRremote
- ELM – 赤外線リモコンの通信フォーマット
- アキバ通いと旅 – ArduinoのIRリモコン用ライブラリ(IRremote)を徹底的に試してみる