(Arduino)ESP32モジュールを用いた赤外線リモコン受信プログラム

#プログラム動作内容
今回は、赤外線リモコン受信モジュール(IRM3638)を使用して様々な赤外線リモコンデータ(テレビ、扇風機、エアコン等)を受信したものをArduinoのシリアルモニタにメーカーや受信データを表示させるプログラムを作成する。

#そもそも赤外線リモコンとは
皆さんの家庭にもよく使用されているテレビや扇風機やエアコンなどが、赤外線のON/OFFの回数や間隔でデータを送信しているリモコンが該当します。
主に38kHz帯の信号を用いており、1つのデータが「リーダー信号、データ信号、トレーラー(ストップ)信号」の３つに構成されて以下のような役割に分かれている。
・リーダー信号:リモコンからのデータが送信されることを知らせる信号
・データ信号:機器判別コードと機器制御コードからなる機器を制御するための信号
・トレーラー(ストップ)信号:信号の送信が終了したことを知らせる信号
<img width="421" alt="スクリーンショット 2020-03-17 23.01.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/7102a1ea-28d5-fb04-756c-2b2e5fa4fb2e.png">
・参考記事:http://www.256byte.com/remocon.htm

#赤外線リモコン受信モジュール(IRM3638)
[IRM3638](https://media.digikey.com/pdf/Data%20Sheets/Everlight%20PDFs/IRM-36xx_Series.pdf)は赤外線の受信に応じて出力電圧を変化させる赤外線受信モジュールである。
このモジュールを選んだ理由は特になく、電子工作部品を買いに行った際にこれしかなかったのでこちらを使用している。
こちらのモジュールはVin,Vout,GNDの3端子でVinをそのままESP32のピンに接続するだけで使用することができる。
しかし、実際に試した際に何度か受信データにばらつきがあり適切なデータを受信することができなかった。
こちらの原因としては、入力電圧(Vin)のノイズを拾ってしまい、そちらが出力電圧に影響している可能性が考えられた。
そこで、データシートにも記載があるRCローパスフィルタを構築することでノイズ影響を受けることなく、適切な赤外線受信データを取得することができた。
※詳細な回路説明は[過去記事](https://qiita.com/pontaman007/items/f47ead88beacf50e9912)にて記載
<img width="298" alt="スクリーンショット 2020-03-17 22.40.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/92a460b6-8c51-e26e-f80c-5202af3ff378.png">

#回路図
[過去の記事](https://qiita.com/pontaman007/items/f47ead88beacf50e9912)で回路図について作成しているので詳細はそちらを確認ください。
ピン接続は以下を参考にしてください。

| 名称 | 使用部品 | データのやり取り | ESP32への接続ピン |
|:---:|:-------:|:-----------:|:---------------:|
| 赤外線受信モジュール | IRM-3638 | 赤外線データ受信 | OUT:17 |

<img width="1278" alt="スクリーンショット 2020-03-17 23.05.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/d8a8f472-0a17-b2cc-f975-e2671da01a71.png">

#プログラム作成前に
今回は赤外線受信プログラムを作成する際に様々なライブラリを使用する。
そのため、以下手順にてライブラリをインストールする必要があるため作成前に実施ください。
①「Arduino IDE」を開き、上部の「ツール>ライブラリを管理」をクリックする。
<img width="856" alt="スクリーンショット 2020-03-20 13.51.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/12a79a2d-ea98-5e73-019f-a19483ee92c4.png">
②「ライブラリマネージャ」が開かれるので、左上の検索バーに「IRremoteESP」と入力すると「IRremoteESP8266」が表示されるので、インストールボタンを押す。　※インストールしたライブラリは「/Users/[ユーザ名]/Documents/Arduino/libraries」に保存される
<img width="801" alt="スクリーンショット 2020-03-20 13.52.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/f5615a92-4462-719d-624c-a27e5566057c.png">
③インストールが完了しておけば、上部の「ファイル>スケッチ例」にIRremoteESP8266が追加される。
<img width="809" alt="スクリーンショット 2020-03-20 14.01.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/1ef51975-1977-7145-28c6-dd199d519772.png">

#作成プログラムについて
今回のプログラムは、「IRremoteESP8266」ライブラリ内のスケッチ例「IRrecvDumpV2」を参考にして作成した。
実際に動作した状態を以下に示す。
<b>・テレビリモコン(AQUOS_SHARP)の電源ボタン操作時</b>　※なぜかPANASONICと認識される
<img width="843" alt="スクリーンショット 2020-03-17 23.39.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/173f5e7c-0357-203d-608f-86506fa9348a.png">
<img width="904" alt="スクリーンショット 2020-03-17 23.30.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/c1eb9275-1035-75f2-813e-c0305de117ce.png">
<b>・扇風機リモコン(HITACHI)の電源ボタン操作時</b>
<img width="984" alt="スクリーンショット 2020-03-17 23.39.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/7f7df995-01ab-1f7e-ec13-b8d210a32dc6.png">
<img width="905" alt="スクリーンショット 2020-03-17 23.30.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/f3eee2e9-04e5-8706-2037-0a2d42d99901.png">
<b>・エアコンリモコン(DAIKIN)の電源ボタン操作時</b>
<img width="795" alt="スクリーンショット 2020-03-17 23.39.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/146a0464-bf87-2491-cca7-af74a2a335c3.png">
<img width="907" alt="スクリーンショット 2020-03-17 23.31.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/595774/6b52fc36-d07c-300b-ee7f-0d9e873bbf96.png">

以下は今回使用したプログラムなのでコピーして使用してみて下さい。

```C:02_IR_COMMUNICATION_TEST1
/*  作成日；2020/3/4
    ファイル名；02_IR_COMMUNICATION_TEST1
    プログラム内容；赤外線リモコンからの情報をシリアルモニターへ出力する
    　　　　　　　　出力内容；メーカーコード、受信コード、総ビット数、オンオフ時間
    使用部品；ESP32、IRM-3638
    ESP32へのピン接続；(17:IRM_Vout)
 */

//各ライブラリ
#include <Arduino.h>
#include <IRrecv.h>
#include <IRremoteESP8266.h>
#include <IRac.h>
#include <IRtext.h>
#include <IRutils.h>

const uint16_t kRecvPin = 17; //赤外線受信ピン
const uint16_t kCaptureBufferSize = 1024; //受信データ格納バッファ数(赤外線ON/OFFの時間を格納)

String maker_code = ""; //メーカーコード挿入変数
String recv_code = ""; //受信コード挿入変数
uint16_t recv_bits; //受信コード総ビット数挿入変数
uint16_t recv_onoff_bits; //ONOFF回数挿入変数
uint16_t recv_state_bits; //A/C受信コード挿入変数
uint32_t recv_address; //受信アドレス挿入変数
uint32_t recv_command; //受信コマンド挿入変数
uint64_t recv_data; //受信データ挿入変数

//kTimeout = 赤外線データの終了を判断するmsの値(kTimeoutミリ秒間受信がなければ終了と判断する)
#if DECODE_AC //エアコンの赤外線リモコンの場合
const uint8_t kTimeout = 50;  //50ms
#else   //その他の赤外線リモコン機器の場合
const uint8_t kTimeout = 15;  //15ms
#endif

const uint16_t kMinUnknownSize = 12;  //不明な赤外線受信をカットするための変数

IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true);  //IRrecv(赤外線受信ピン、受信データ格納バッファサイズ、データ終了判断時間、値保存機能(true推奨))
decode_results results;  //受信結果を保存するクラスのインスタンスを生成


/*  初期設定  */
void setup() {
  
  Serial.begin(115200); //シリアル通信レート
  
#if DECODE_HASH
  irrecv.setUnknownThreshold(kMinUnknownSize);  //不明な赤外線受信データは除外する
#endif

  irrecv.enableIRIn();  //赤外線受信開始
  
}
/*  初期設定  */


/*  メイン本文  */
void loop() {

  if (irrecv.decode(&results)) {  //赤外線受信があれば

    maker_code = typeToString(results.decode_type,false); //メーカーコード取得
    recv_code = resultToHexidecimal(&results);  //赤外線受信データを16進数で取得
    recv_bits = results.bits; //受信データの総Bit数を取得
    recv_onoff_bits = getCorrectedRawLength(&results);  //受信データのON/OFF切り替え回数を取得

    Serial.println("Maker : " + maker_code); //メーカーコード出力
    Serial.println("Code : " + recv_code);  //赤外線受信データを16進数で表示
    Serial.println("Bits : " + String(recv_bits)); //受信データの総Bit数を表示

    Serial.print("ON/OFF Data : rawData[" + String(recv_onoff_bits) + "] = {");  //受信データのON/OFF切り替え回数を表示
    for(int i=1; i < recv_onoff_bits + 1; i++){ //受信データのON/OFF切り替え時間を表示
      Serial.print(String(results.rawbuf[i] * kRawTick) + ", ");
    }
    Serial.println("}");

    if (hasACState(results.decode_type)) {  //メーカーコードがACだった場合

      recv_state_bits = recv_bits / 8;
      Serial.print("State Data : state[" + String(recv_state_bits) + "] = {");  //赤外線受信データの分割ビット数を表示
      for(int i = 0; i < recv_state_bits; i++) {  //赤外線受信データの分割したものを16進数で表示
        Serial.print("0x");
        if (results.state[i] < 0x10) {
          Serial.print("0");
        }
        Serial.print(uint64ToString(results.state[i], 16));
        if (i < recv_state_bits - 1) {
          Serial.print(kCommaSpaceStr);
        }
      }
      Serial.println("}");
      
    }else{  //メーカーコードがAC以外だった場合
      
      if (results.address > 0 || results.command > 0) { //アドレスとコマンドを16進数で表示

        recv_address = results.address; //アドレスを取得
        recv_command = results.command; //コマンドを取得
        
        Serial.println("Address : 0x" + uint64ToString(recv_address, 16));
        Serial.println("Command : 0x" + uint64ToString(recv_command, 16));

      }

      recv_data = results.value;  //データを取得

      //データを16進数で表示
      Serial.print("Data : 0x");
      serialPrintUint64(recv_data, HEX);
      Serial.println("");
      
    }
    
    Serial.println();

  }

}
/*  メイン本文  */
```

以下から各ブロックの説明をしていきます。

①ライブラリ定義、グローバル変数設定等

```C:02_IR_COMMUNICATION_TEST1(抜粋)
//各ライブラリ
#include <Arduino.h>
#include <IRrecv.h>
#include <IRremoteESP8266.h>
#include <IRac.h>
#include <IRtext.h>
#include <IRutils.h>

const uint16_t kRecvPin = 17; //赤外線受信ピン
const uint16_t kCaptureBufferSize = 1024; //受信データ格納バッファ数(赤外線ON/OFFの時間を格納)

String maker_code = ""; //メーカーコード挿入変数
String recv_code = ""; //受信コード挿入変数
uint16_t recv_bits; //受信コード総ビット数挿入変数
uint16_t recv_onoff_bits; //ONOFF回数挿入変数
uint16_t recv_state_bits; //A/C受信コード挿入変数
uint32_t recv_address; //受信アドレス挿入変数
uint32_t recv_command; //受信コマンド挿入変数
uint64_t recv_data; //受信データ挿入変数

//kTimeout = 赤外線データの終了を判断するmsの値(kTimeoutミリ秒間受信がなければ終了と判断する)
#if DECODE_AC //エアコンの赤外線リモコンの場合
const uint8_t kTimeout = 50;  //50ms
#else   //その他の赤外線リモコン機器の場合
const uint8_t kTimeout = 15;  //15ms
#endif

const uint16_t kMinUnknownSize = 12;  //不明な赤外線受信をカットするための変数

IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true);  //IRrecv(赤外線受信ピン、受信データ格納バッファサイズ、データ終了判断時間、値保存機能(true推奨))
decode_results results;  //受信結果を保存するクラスのインスタンスを生成
```
「const uint16_t kRecvPin = 17」：赤外線受信モジュール(IRM3638)の接続ピンを17に設定
「const uint16_t kCaptureBufferSize = 1024」：受信データ格納バッファ数(赤外線ON/OFFの時間を格納)
※バッファ数を指定しないとデータを配列に格納した際にメモリのオーバーフローでESP32が再起動したため指定した

各赤外線データ受信変数
「String maker_code = ""」：メーカーコードを格納する変数
「String recv_code = ""」：赤外線受信データコードを格納する変数
「uint16_t recv_bits」：赤外線受信データコードのデータ総ビット数を格納する変数
「uint16_t recv_onoff_bits」：赤外線受信データのON/OFFの時間[μs]を格納する変数
「uint16_t recv_state_bits」：A/C関係の赤外線受信データ(1バイト毎)のデータ総ビット数を格納する変数
「uint32_t recv_address」：A/C関係以外の赤外線受信データの機器選別アドレスを格納する変数
「uint32_t recv_command」：A/C関係以外の赤外線受信データの機器操作コマンドを格納する変数
「uint64_t recv_data」：A/C関係以外の赤外線受信データコードを格納する変数(recv_codeと同じ値)

赤外線データ受信条件設定変数
「const uint8_t kTimeout = 50or15」：赤外線データの終了を判断する時間[ms]の値(A/C:50ms,A/C以外:15ms)
「const uint16_t kMinUnknownSize = 12」：不明な赤外線受信をカットするための変数(誤受信防止)

「IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true)」：IRrecv(赤外線受信ピン、受信データ格納バッファサイズ、データ終了判断時間、値保存機能(true推奨)
「decode_results results」：受信結果を保存するクラスのインスタンスを生成

②初期設定

```C:02_IR_COMMUNICATION_TEST1(抜粋)
/*  初期設定  */
void setup() {
  
  Serial.begin(115200); //シリアル通信レート
  
#if DECODE_HASH
  irrecv.setUnknownThreshold(kMinUnknownSize);  //不明な赤外線受信データは除外する
#endif

  irrecv.enableIRIn();  //赤外線受信開始
  
}
/*  初期設定  */
```

③メイン(ループ)

```C:02_IR_COMMUNICATION_TEST1(抜粋)
/*  初期設定  */
/*  メイン本文  */
void loop() {

  if (irrecv.decode(&results)) {  //赤外線受信があれば

    maker_code = typeToString(results.decode_type,false); //メーカーコード取得
    recv_code = resultToHexidecimal(&results);  //赤外線受信データを16進数で取得
    recv_bits = results.bits; //受信データの総Bit数を取得
    recv_onoff_bits = getCorrectedRawLength(&results);  //受信データのON/OFF切り替え回数を取得

    Serial.println("Maker : " + maker_code); //メーカーコード出力
    Serial.println("Code : " + recv_code);  //赤外線受信データを16進数で表示
    Serial.println("Bits : " + String(recv_bits)); //受信データの総Bit数を表示

    Serial.print("ON/OFF Data : rawData[" + String(recv_onoff_bits) + "] = {");  //受信データのON/OFF切り替え回数を表示
    for(int i=1; i < recv_onoff_bits + 1; i++){ //受信データのON/OFF切り替え時間を表示
      Serial.print(String(results.rawbuf[i] * kRawTick) + ", ");
    }
    Serial.println("}");

    if (hasACState(results.decode_type)) {  //メーカーコードがACだった場合

      recv_state_bits = recv_bits / 8;
      Serial.print("State Data : state[" + String(recv_state_bits) + "] = {");  //赤外線受信データの分割ビット数を表示
      for(int i = 0; i < recv_state_bits; i++) {  //赤外線受信データの分割したものを16進数で表示
        Serial.print("0x");
        if (results.state[i] < 0x10) {
          Serial.print("0");
        }
        Serial.print(uint64ToString(results.state[i], 16));
        if (i < recv_state_bits - 1) {
          Serial.print(kCommaSpaceStr);
        }
      }
      Serial.println("}");
      
    }else{  //メーカーコードがAC以外だった場合
      
      if (results.address > 0 || results.command > 0) { //アドレスとコマンドを16進数で表示

        recv_address = results.address; //アドレスを取得
        recv_command = results.command; //コマンドを取得
        
        Serial.println("Address : 0x" + uint64ToString(recv_address, 16));
        Serial.println("Command : 0x" + uint64ToString(recv_command, 16));

      }

      recv_data = results.value;  //データを取得

      //データを16進数で表示
      Serial.print("Data : 0x");
      serialPrintUint64(recv_data, HEX);
      Serial.println("");
      
    }
    
    Serial.println();

  }

}
/*  メイン本文  */
/*  初期設定  */
```

「if (irrecv.decode(&results))」：赤外線データを受信すれば

各赤外線受信データを変数へ格納
「maker_code = typeToString(results.decode_type,false);」：メーカーコードをString型に変換して格納
「recv_code = resultToHexidecimal(&results)」：受信データコードを16進数表記にして格納
「recv_bits = results.bits」：受信データコードの総Bit数を格納
「recv_onoff_bits = getCorrectedRawLength(&results)」：受信データのON/OFF切り替え回数を格納

各赤外線受信データをシリアルモニタへ表示
「Serial.println("Maker : " + maker_code)」：メーカーコードをシリアルモニタへ出力
「Serial.println("Code : " + recv_code)」：赤外線受信データをシリアルモニタへ出力（16進数で表示）
「Serial.println("Bits : " + String(recv_bits))」：受信データの総Bit数をシリアルモニタへ出力
「Serial.print("ON/OFF Data : rawData[" + String(recv_onoff_bits) + "] = {")」：受信データのON/OFF切り替え回数を表示
「Serial.print(String(results.rawbuf[i] * kRawTick) + ", ")」：受信データのON/OFFの時間を1要素ずつ表示(kRawTick:時間計算に使用する固定値(2))
「Serial.println("}")」：「}」を終端に表示

メーカーコードがA/Cだった場合
「if (hasACState(results.decode_type))」：メーカーコードがA/Cだった場合
「recv_state_bits = recv_bits / 8」：受信データの総ビット数を8で割って1バイトの総ビット数に変換
「Serial.print("State Data : state[" + String(recv_state_bits) + "] = {")」：赤外線受信データの分割ビット数を表示
「if (results.state[i] < 0x10)」：1バイトデータのうち上位4ビットの数字が0ならば
「Serial.print(uint64ToString(results.state[i], 16))」：データを16進数表記でString型に変換してシリアルモニタに出力
「Serial.print(kCommaSpaceStr)」：カンマ[,]とスペース[ ]をシリアルモニタに出力(１データ毎見やすくするため)

メーカーコードがA/C以外だった場合
「if (results.address > 0 || results.command > 0)」：アドレスもしくはコマンドがある赤外線送信機の場合
「recv_address = results.address」：アドレスを格納
「recv_command = results.command」：コマンドを格納
「Serial.println("Address : 0x" + uint64ToString(recv_address, 16))」：アドレスを16進数表記でString型に変換してシリアルモニタへ出力
「Serial.println("Command : 0x" + uint64ToString(recv_command, 16))」：コマンドを16進数表記でString型に変換してシリアルモニタへ出力
「recv_data = results.value」：データコードを格納
「serialPrintUint64(recv_data, HEX)」：コマンドを16進数表記でシリアルモニタへ出力

#次回記事予定
受信した赤外線データを一時保存し、保存した赤外線データを送信するプログラムの作成

#参考文献
・http://www.256byte.com/remocon.htm