目次 [ Contents ]
IR Remoteを使って、も少し具体的に活用出来るような「IRコード受信」スケッチを書いてみました。
任意のIR信号を記憶・判定し、それによって指定アクションを行います。また、取得したIRコードはEEPROMへ保存されるので、再度電源を入れたときにも覚えている、といった構成です。今回は物理的なスイッチの代わりにシリアルモニタを利用しているので、必要なものは、家電リモコンとIR受信機、(あればLED)だけです。
最終的なスケッチは最後にありますが、自分が普段スケッチを構築してく過程で書いているので、そこら辺を楽しんで読んで頂ければ幸いです。
回路図
- D11 pin : IR Reciever
- D13 pin : normal LED
リモコンコードを取得する
前回の記事に追記しましたが、”decode_type”の返り値でメーカーが分かるので、とりあえず、下記のような文字配列を用意しておきます。
#define MK_TXT 8 // characters number const char id_name[20][MK_TXT] { "UNKNOWN", "UNUSED", "RC5", "RC6", "NEC", "SONY", "PANA", "JVC", "SAMSNG", "WYHNTER", "AIWA", "LG", "SANYO", "M-BISI", "DISH", "SHARP", "DENON", "PRONTO", "LEGO", "---" };
で、前回の学習を踏まえた上での基本スケッチはこんな感じになります。
#include <IRremote.h> #define LED 13 #define RECV_PIN 11 IRrecv irrecv(RECV_PIN); decode_results results; int ir_id; long ir_code; #define MK_TXT 8 // characters number const char id_name[20][MK_TXT] { "UNKNOWN", "UNUSED", "RC5", "RC6", "NEC", "SONY", "PANA", "JVC", "SAMSNG", "WYHNTER", "AIWA", "LG", "SANYO", "M-BISI", "DISH", "SHARP", "DENON", "PRONTO", "LEGO", "---" }; void setup() { Serial.begin(38400); Serial.println("*ir code capture*"); pinMode(LED, OUTPUT); irrecv.enableIRIn(); // Start the receiver } void loop() { if (irrecv.decode(&results)) { digitalWrite(LED, HIGH); Serial.print("Captured! "); ir_id = results.decode_type + 1; ir_code = results.value; PRINT_CODE(); delay(50); // wait to avoid duplication receive irrecv.resume(); // restart receiving for "IR remote" } digitalWrite(LED, LOW); } // display captured code to serial monitor void PRINT_CODE() { Serial.print('['); Serial.print(ir_id - 1, DEC); Serial.print(':'); Serial.print(id_name[min(ir_id, 19)]); Serial.print("] 0x"); Serial.print(ir_code, HEX); Serial.println(); }
メーカー番号を“ir_id”変数、IRコードは“ir_code”変数としています。赤外線コードが受信されたら、その情報をこれら変数に投げ込みシリアルモニタに表示するという流れになってます。
“ir_id”は取得時に+1しています。これは、最小の返り値‘UNKNOWN’が「-1」から始まっているため、decode_typeは正負型の変数に入れておく必要があるんですが、後のEEPROMへの書き込みを考慮すると、マイナスの数値では面倒なので、こうしてます。
行動分岐
上記のままだと、ただただ赤外線コードを取得するだけです。なので、通常では「受信モード」で、シリアルモニタから任意の文字が入力されると「キャプチャモード」に切り替わるように分岐させます。
void loop() { if (Serial.available()) { char cmd = Serial.read(); if (cmd == 'g') CAPTURE_IR(); } else if (irrecv.decode(&results)) { digitalWrite(LED, HIGH); int temp_id = results.decode_type + 1; long temp_cmd = results.value; if (ir_id == temp_id && ir_code == temp_cmd) { Serial.print("Recieved command! "); PRINT_CODE(); } delay(50); // wait to avoid duplication receive irrecv.resume(); // restart receiving for "IR remote" digitalWrite(LED, LOW); } } boolean CAPTURE_IR() {}
シリアルモニタから‘g’を送ると“CAPTURE_IR”を実行。それ以外の時にIR受信した場合、それが取得済みの”IRメーカー・IRコード”に合致すると、任意のアクションをします。
更に、これを関数化。
void loop() { if (Serial.available()) { char cmd = Serial.read(); if (cmd == 'g') CAPTURE_IR(); } else if (RECV_CMD()) { digitalWrite(LED, HIGH); Serial.print("Recieved command! "); PRINT_CODE(); digitalWrite(LED, LOW); } } boolean RECV_CMD() { if (irrecv.decode(&results)) { digitalWrite(LED, HIGH); int temp_id = results.decode_type + 1; long temp_cmd = results.value; delay(50); // wait to avoid duplication receive irrecv.resume(); // restart receiving for "IR remote" digitalWrite(LED, LOW); if (ir_id == temp_id && ir_code == temp_cmd) return true; } return false; } boolean CAPTURE_IR() {}
RECV_CMD()関数に受信コードの真偽をさせることで、分岐がスッキリします。
キャプチャーモードを作る
上記のままだと、数値を取得しないので、反応はしません(LED点滅はする)。“CAPTURE_IR()”関数にそこら辺のタスクを書きます。で、中身はこんな感じに。
void CAPTURE_IR() { Serial.println("Now Capturing"); unsigned long start_time = millis(); while ((start_time + 5000) > millis()) { if (irrecv.decode(&results)) { digitalWrite(LED, HIGH); ir_id = results.decode_type + 1; ir_code = results.value; Serial.print("Captured! "); PRINT_CODE(); digitalWrite(LED, LOW); irrecv.resume(); } } }
5秒間の待ち受け時間を設け、その間に受信したIRメーカーとIRコードを取得します。
ただし、この形だと、受け取った順に取得コードが更新されていってしまいます。
市販リモコンには、複数回発信したり、長く押すと「リピートコード」を出すタイプもあるそうで、この場合、そっちが最終取得にされてしまうためかと思います(更に、メーカー名も上手く識別出来ない場合も…)。
一度に複数取得
これをなるべく回避するためには、取得直後に長いdelayを挟むのが単純ですが、もう一歩進めて、2つ拾うことにします。
#define DUB_SIZE 2 // この部分は7行目~へ上書き int ir_id [DUB_SIZE]; // この部分は7行目~へ上書き long ir_code [DUB_SIZE]; // この部分は7行目~へ上書き void CAPTURE_IR() { Serial.print("Now Capturing"); unsigned long start_time = millis(); byte keepin = 0, stamp = 1; while ((start_time + 5000) > millis()) { if (irrecv.decode(&results)) { ir_id [keepin] = results.decode_type + 1; ir_code [keepin] = results.value; keepin++; irrecv.resume(); // restart receiving for "IR remote" } // interval task if (millis() >= (1000 * stamp + start_time)) { Serial.print('.'); stamp++; } if (keepin >= DUB_SIZE) break; } if (keepin) { Serial.println("Captured!"); // LED reaction for (byte dub = 0 ; dub < DUB_SIZE ; dub++) { digitalWrite(LED, !(dub % 2)); PRINT_CODE(dub); delay(100); } //IR_SAVE(10); } else { Serial.println("No IR code!"); } irrecv.resume(); // Receive the next value } // display captured code to serial monitor void PRINT_CODE(int bank) { Serial.print('['); Serial.print(ir_id[bank] - 1, DEC); Serial.print(':'); Serial.print(id_name[min(ir_id[bank], 19)]); Serial.print("] 0x"); Serial.print(ir_code[bank], HEX); Serial.println(); }
“ir_id”と“ir_code”を配列に変更しました。
で、キャプチャ中、“keepin”変数で取得回数を覚えておき、DUB_SIZEで定義した回数を越えたらWhileループを離脱。その後、取得の有無判定によってLED点滅とシリアルプリントで、取得コードをまとめて示します。
また、シリアルプリントするPRINT_CODE()関数も、配列別に表示するよう変更を入れました。
それに伴い他の関数も修正。
void loop() { if (Serial.available()) { char cmd = Serial.read(); if (cmd == 'g') CAPTURE_IR(); } else if (RECV_CMD()) { Serial.println("Recieved command!"); for (byte dub = 0 ; dub < DUB_SIZE ; dub++) PRINT_CODE(dub); } } boolean RECV_CMD() { boolean ret = false; if (irrecv.decode(&results)) { digitalWrite(LED, HIGH); int temp_id = results.decode_type + 1; long temp_cmd = results.value; for (byte dub = 0 ; dub < DUB_SIZE ; dub++) { if (ir_id [dub] == temp_id && ir_code [dub] == temp_cmd) ret = true; } delay(100); // wait to avoid duplication receive irrecv.resume(); // restart receiving for "IR remote" digitalWrite(LED, LOW); } return ret; }
有効コードの選択
2個のコマンドコードを拾うことで、より反応が良くなりますが、逆に意図しないコードにも反応してしまう場合があります。なので、片方だけに反応出来るよう設定を組み込みます。
byte pickup_num; void loop() { if (Serial.available()) { char cmd = Serial.read(); if (cmd == 'g') CAPTURE_IR(); // get remote code if (cmd >= '0' && cmd <= '9') // set active code in array { pickup_num = cmd - '0'; if (pickup_num > DUB_SIZE) pickup_num = 0; // limit the value PRINT_ACTIVE(); //IR_SAVE(10); } } else if (RECV_CMD()) { // --- reaction after command receive --- Serial.println("Recieved command!"); for (byte dub = 0 ; dub < DUB_SIZE ; dub++) PRINT_CODE(dub); // LED action digitalWrite(LED, HIGH); delay(100); digitalWrite(LED, LOW); } } void PRINT_ACTIVE() { Serial.print("Active array:"); if (pickup_num == 0 ) Serial.print("ALL"); else Serial.print(pickup_num); Serial.println(); }
シリアルモニタで0~9の数字を受けた場合、それを数値として“pickup_num”変数に投げ込みます。その番号の配列だけで判別させる事にします。この場合、「1~2」ならそれぞれの変数配列番号、「0」は「全て有効」という形にしました。
byte current_num; boolean RECV_CMD() { boolean ret = false; if (irrecv.decode(&results)) { digitalWrite(LED, HIGH); int temp_id = results.decode_type + 1; long temp_cmd = results.value; byte i = (pickup_num == 0) ? 0 : min(pickup_num - 1, DUB_SIZE); byte times = (pickup_num == 0) ? DUB_SIZE : i + 1; for (byte dub = i ; dub < times ; dub++) { if (ir_id [dub] == temp_id && ir_code [dub] == temp_cmd) { ret = true; current_num = dub; } } delay(100); // wait to avoid duplication receive irrecv.resume(); // restart receiving for "IR remote" digitalWrite(LED, LOW); } return ret; }
受信判定の関数内で有効かどうかを見るんですが、for文が「0」の場合はDUB_SIZE分(全部)、「1~2」なら1回ずつになるようにしています。
ちなみに、“current_num”変数はシリアルプリントで目印を着けるために設けました。
// display captured code to serial monitor void PRINT_CODE(int bank) { char mark = (bank == current_num || pickup_num == 0) ? '*' : ' '; Serial.print(mark); Serial.print(bank + 1); Serial.print(". ["); Serial.print(ir_id[bank] - 1, DEC); Serial.print(':'); Serial.print(id_name[min(ir_id[bank], 19)]); Serial.print("] 0x"); Serial.print(ir_code[bank], HEX); Serial.println(); }
EEPROMで記憶・読み込み
キャプチャしたIRコードをEEPROMへread・writeすることで、電源が落ちても保持できるようにします。
EEPROMへ保存
指定メモリーアドレスを引数とする“IR_SAVE”関数を作ります。保存するのは、有効配列用番号の“pickup_num”に1バイト。IRメーカーの1バイト分とIRコードの4バイト分で5バイト。それがDUB_SIZEで定義した2個ということで、計11バイトとなります。
無駄に上書きしないよう、EEPROM.write()ではなく、EEPROM.update()を使いました。
#include <EEPROM.h> // これは2行目に挿入 void IR_SAVE(int adres) { int bank = adres; Serial.println("saving to EEPROM..."); // save "pickup_num" to EEPROM EEPROM.update(bank, pickup_num); PRINT_ACTIVE(); PRINT_DATA(bank, pickup_num); Serial.println(); for (byte dub = 0 ; dub < DUB_SIZE ; dub++) { // save "ir_id" value to EEPROM PRINT_CODE(dub); bank++; byte id_temp = ir_id[dub] & 0xFF; EEPROM.update(bank, id_temp); PRINT_DATA(bank, id_temp); // save "command code" to EEPROM for (byte i = 0 ; i < 4 ; i++) { bank++; byte code_temp = (ir_code[dub] >> (8 * i)) & 0xFF; // bit-shift & mask EEPROM.update(bank, code_temp); PRINT_DATA(bank, code_temp); } } } void PRINT_DATA(int adres, byte data) { Serial.print("[#"); Serial.print(adres); Serial.print("] 0x"); if (data < 16) Serial.print('0'); Serial.print(data, HEX); Serial.println(); }
EEPROMのアドレス指定用に“bank”変数を作り、1バイト保存するごとに++1で順当に増分させています。最初の指定アドレスには“pickup_num”。
次にIRメーカー用1バイトを書き込み、その後、4バイトlong型の“ir_code”数値を、ビットシフト&ビットマスクして、1バイト分ずつ順当アドレスに投げ込んでいます。あと、書き込むデータを“PRINT_DATA”、“PRINT_ACTIVE”関数に投げることで、一応確認出来るようにしてます。更に、5バイト分は配列にした2つ分行うので、“dub”で括って反復しています。
ちなみに、EEPROM.update()内の引数は、直接数式のかたちだと上手く対応してくれないので、せつな的な変数(temp~)を作って計算させてから指定しています。
EEPROMから読み込み
読み込みは上記の工程を行います。
void IR_LOAD(int adres) { int bank = adres; Serial.println("loading from EEPROM..."); // from "pickup_num" from EEPROM pickup_num = EEPROM.read(bank); // update upper limit if (pickup_num > DUB_SIZE) { if (pickup_num > DUB_SIZE) pickup_num = 0; // limit the value EEPROM.update(10, pickup_num); } PRINT_DATA(bank, pickup_num); Serial.println(); for (byte dub = 0 ; dub < DUB_SIZE ; dub++) { // loar "ir_id" value from EEPROM bank++; ir_id[dub] = EEPROM.read(bank); PRINT_DATA(bank, ir_id[dub]); ir_code[dub] = 0; // load "command code" from EEPROM for (byte i = 0 ; i < 4 ; i++) { bank++; unsigned long code_temp = EEPROM.read(bank); ir_code[dub] += code_temp << (i * 8); PRINT_DATA(bank, code_temp); } Serial.println(); } Serial.println("loaded command data."); for (byte dub = 0 ; dub < DUB_SIZE ; dub++) PRINT_CODE(dub); PRINT_ACTIVE(); Serial.println(); }
ロードした“pickup_num”が規定のDUB_SIZEを越えてしまった場合、「0」にして再保存するようにしました。また、SAVE同様、EEPROMから読み込んだ値をそのまま足すと変になるので、一旦、変数‘temp’に投げ込んでから、それぞれの変数へ加算していってます。
これらの関数を使うために、再度修正。
void setup() { Serial.begin(38400); Serial.println("*ir code capture*"); pinMode(LED, OUTPUT); IR_LOAD(10); // read IR data from EEPROM irrecv.enableIRIn(); // Start the receiver } void loop() { if (Serial.available()) { char cmd = Serial.read(); if (cmd == 'g') CAPTURE_IR(); // get remote code if (cmd >= '0' && cmd <= '9') // set active code in array { pickup_num = cmd - '0'; if (pickup_num > DUB_SIZE) pickup_num = 0; // limit the value PRINT_ACTIVE(); IR_SAVE(10); } } else if (RECV_CMD()) { // --- reaction after command receive --- Serial.println("Recieved command!"); for (byte dub = 0 ; dub < DUB_SIZE ; dub++) PRINT_CODE(dub); // LED action digitalWrite(LED, HIGH); delay(100); digitalWrite(LED, LOW); } } void CAPTURE_IR() { Serial.print("Now Capturing"); unsigned long start_time = millis(); byte keepin = 0, stamp = 1; while ((start_time + 5000) > millis()) { if (irrecv.decode(&results)) { ir_id [keepin] = results.decode_type + 1; ir_code [keepin] = results.value; keepin++; irrecv.resume(); // restart receiving for "IR remote" } // interval task if (millis() >= (1000 * stamp + start_time)) { Serial.print('.'); stamp++; } if (keepin >= DUB_SIZE) break; // escape from 'capture' loop } if (keepin) { Serial.println("Captured!"); // LED reaction for (byte dub = 0 ; dub < DUB_SIZE ; dub++) { digitalWrite(LED, !(dub % 2)); PRINT_CODE(dub); delay(100); } IR_SAVE(10); } else { Serial.println("No IR code!"); } irrecv.resume(); // Receive the next value }
まとめ:“IRコードキャプチャ” スケッチ
というわけで、諸々まとめた完成スケッチです。
シリアルモニタから‘g’を入力すると、5秒間IRコードの「キャプチャモード」になるので、任意のリモコンのボタンを押して取得させる。取得するとEEPROMに保存。電源再投入時に保存したデータを読み込み。
取得は2個セットで行われるので、有効にしたい方をシリアルモニタで指定できる。「1~2」で具体的な指定。「0」だと全て有効に。
- WAIT_TIME — 受信後等の待ち時間。大きいほど反復を拾いにくくなるが、反応は鈍くなる。
- EEPROM_ADRES — EEPROMの保存・読み出し先頭アドレス。
- DUB_SIZE — キャプチャしておくデータのセット数。リピートコード等不要なら「1」でもOKかも。
// 2018.7.14 // // // // ir code capture sketch // // by jumbler // // version1.0 // #include <IRremote.h> #include <EEPROM.h> #define LED 13 #define RECV_PIN 11 IRrecv irrecv(RECV_PIN); decode_results results; #define WAIT_TIME 100 #define EEPROM_ADRES 10 #define CODE_SIZE 4 #define DUB_SIZE 2 int ir_id [DUB_SIZE]; long ir_code [DUB_SIZE]; byte pickup_num, current_num; #define MK_TXT 8 // characters number const char id_name[20][MK_TXT] { "UNKNOWN", "UNUSED", "RC5", "RC6", "NEC", "SONY", "PANA", "JVC", "SAMSNG", "WYHNTER", "AIWA", "LG", "SANYO", "M-BISI", "DISH", "SHARP", "DENON", "PRONTO", "LEGO", "---" }; void setup() { Serial.begin(38400); Serial.println("*ir code capture*"); pinMode(LED, OUTPUT); IR_LOAD(EEPROM_ADRES); // read IR data from EEPROM irrecv.enableIRIn(); // Start the receiver } void loop() { if (Serial.available()) { char cmd = Serial.read(); if (cmd == 'g') CAPTURE_IR(); // get remote code if (cmd >= '0' && cmd <= '9') // set active code in array { pickup_num = cmd - '0'; if (pickup_num > DUB_SIZE) pickup_num = 0; // limit the value PRINT_ACTIVE(); IR_SAVE(EEPROM_ADRES); } } else if (RECV_CMD()) { // --- reaction after command receive --- Serial.println("Recieved command!"); for (byte dub = 0 ; dub < DUB_SIZE ; dub++) PRINT_CODE(dub); // LED action digitalWrite(LED, HIGH); delay(WAIT_TIME); digitalWrite(LED, LOW); } } void CAPTURE_IR() { Serial.print("Now Capturing"); unsigned long start_time = millis(); byte keepin = 0, stamp = 1; while ((start_time + 5000) > millis()) { if (irrecv.decode(&results)) { ir_id [keepin] = results.decode_type + 1; ir_code [keepin] = results.value; keepin++; irrecv.resume(); // restart receiving for "IR remote" } // interval task if (millis() >= (1000 * stamp + start_time)) { Serial.print('.'); stamp++; } if (keepin >= DUB_SIZE) break; // escape from 'capture' loop } if (keepin) { Serial.println("Captured!"); // LED reaction for (byte dub = 0 ; dub < DUB_SIZE ; dub++) { digitalWrite(LED, !(dub % 2)); PRINT_CODE(dub); delay(WAIT_TIME); } IR_SAVE(EEPROM_ADRES); } else { Serial.println("No IR code!"); } irrecv.resume(); // Receive the next value } boolean RECV_CMD() { boolean ret = false; if (irrecv.decode(&results)) { digitalWrite(LED, HIGH); int temp_id = results.decode_type + 1; long temp_cmd = results.value; byte i = (pickup_num == 0) ? 0 : min(pickup_num - 1, DUB_SIZE); byte times = (pickup_num == 0) ? DUB_SIZE : i + 1; for (byte dub = i ; dub < times ; dub++) { if (ir_id [dub] == temp_id && ir_code [dub] == temp_cmd) { ret = true; current_num = dub; } } delay(WAIT_TIME); // wait to avoid duplication receive irrecv.resume(); // restart receiving for "IR remote" digitalWrite(LED, LOW); } return ret; } void IR_SAVE(int adres) { int bank = adres; Serial.println("saving to EEPROM..."); // save "pickup_num" to EEPROM EEPROM.update(bank, pickup_num); PRINT_ACTIVE(); PRINT_DATA(bank, pickup_num); Serial.println(); for (byte dub = 0 ; dub < DUB_SIZE ; dub++) { // save "ir_id" value to EEPROM PRINT_CODE(dub); bank++; byte id_temp = ir_id[dub] & 0xFF; EEPROM.update(bank, id_temp); PRINT_DATA(bank, id_temp); // save "command code" to EEPROM for (byte i = 0 ; i < CODE_SIZE ; i++) { bank++; byte code_temp = (ir_code[dub] >> (8 * i)) & 0xFF; // bit-shift & mask EEPROM.update(bank, code_temp); PRINT_DATA(bank, code_temp); } } } void IR_LOAD(int adres) { int bank = adres; Serial.println("loading from EEPROM..."); // from "pickup_num" from EEPROM pickup_num = EEPROM.read(bank); // update upper limit if (pickup_num > DUB_SIZE) { if (pickup_num > DUB_SIZE) pickup_num = 0; // limit the value EEPROM.update(EEPROM_ADRES, pickup_num); } PRINT_DATA(bank, pickup_num); Serial.println(); for (byte dub = 0 ; dub < DUB_SIZE ; dub++) { // loar "ir_id" value from EEPROM bank++; ir_id[dub] = EEPROM.read(bank); PRINT_DATA(bank, ir_id[dub]); ir_code[dub] = 0; // load "command code" from EEPROM for (byte i = 0 ; i < CODE_SIZE ; i++) { bank++; unsigned long code_temp = EEPROM.read(bank); ir_code[dub] += code_temp << (i * 8); PRINT_DATA(bank, code_temp); } Serial.println(); } Serial.println("loaded command data."); for (byte dub = 0 ; dub < DUB_SIZE ; dub++) PRINT_CODE(dub); PRINT_ACTIVE(); Serial.println(); } // display data to serial monitor void PRINT_CODE(int bank) { char mark = (bank == current_num || pickup_num == 0) ? '*' : ' '; Serial.print(mark); Serial.print(bank + 1); Serial.print(". ["); Serial.print(ir_id[bank] - 1, DEC); Serial.print(':'); Serial.print(id_name[min(ir_id[bank], 19)]); Serial.print("] 0x"); Serial.print(ir_code[bank], HEX); Serial.println(); } void PRINT_DATA(int adres, byte data) { Serial.print("[#"); Serial.print(adres); Serial.print("] 0x"); if (data < 16) Serial.print('0'); Serial.print(data, HEX); Serial.println(); } void PRINT_ACTIVE() { Serial.print("Active array:"); if (pickup_num == 0 ) Serial.print("ALL"); else Serial.print(pickup_num); Serial.println(); }
if(RECV_CMD()){}内を変更すれば、簡単にやりたいことへのカスタマイズ出来るかと思います。ただ、今更ですが、こういった場合にこそ構造体みたいなものを使うべきでしたね、と…。
参考リンク