赤外線リモコンコード解析(NECフォーマット)
UNO

概要

赤外線センサを利用して、NECフォーマットの赤外線リモコンのコードを表示するプログラムを作成します。

PARA LIGHT ELECTRONICS社のPL-IRM2161-C438という赤外線センサを使います。

この実験ではArduino Unoを利用しています。ESP-WROOM-32での実験はこちら

ハードウェア

以下のものを利用しました。

  • Arduino Uno
  • 赤外線センサ(PARA LIGHT ELECTRONICS社のPL-IRM2161-C438)
  • (PC)
  • (USBケーブル)
  • (ブレッドボード)
  • (ジャンプワイヤ)

データフォーマット

赤外線リモコンでよく使われるデータフォーマットには、以下の3つがあるようです。

  • NECフォーマット
  • 家電協フォーマット
  • SONYフォーマット

私の家にあるDVDレコーダはは東芝製で、このレコーダは、NECフォーマットを採用しているようです。ここでは、NECフォーマットのリモコンコードを読み取っていきます。

概要

NECフォーマットの概要は以下の通りです。詳細は、 FAQ 1007798 : 赤外線リモコンの信号はどうなっているのですか? を参照してください。

リーダコード

リーダコードは、9msのon、その後、4.5msのoffです。

カスタムコード・データコード

カスタムコードとデータコードは、1と0との組み合わせです。

1は、0.56ミリ秒のonの後、1.69ミリ秒のoff(全体では、2.25ミリ秒)、0は、0.56ミリ秒のonの後、0.565ミリ秒のoff)全体では1.125ミリ秒)であらわされます。また、カスタムコードは、16ビット、データコードは8ビットです。それぞれのバイトを、下位ビットから(第0ビットから=LSBファーストで)送信していきます。

データコード(反転)は、データコードのONとOFFを反転させたデータです。これにより、エラーチェックが行えるようになっています。

ストップビット

ストップビットについては、参照したページには明確な定義は見つけられませんでしたが、0.56ミリ秒のonの後、offになるのではと思います。

リピートコード

リピートコードは、リモコンのキーを押し続けた時に出るコードで、リピートコードの後に、すぐに、ストップビットが来ます。

リピートコードは、9msのon、その後、2.25msのoffです。

余談ですが、テレビのリモコンボタンを一度だけ押したところ、そのボタンが押し続けられるような現象が発生しました。音量を一段階上げたつもりがどんどん上がり続け、チャンネルを一つ上げるとどんどんチャンネルが上がり続ける…リモコンが壊れたのかと思い、リモコンの電池を抜いても事態は変わらず、部屋を見渡してみると、別のリモコンの上に本が乗っていました。こちらのリモコンがリピートコードを出し続けて、別のリモコンで出したコードを繰り返し実行していたようです(リピートコードにはカスタムコードがありません)。

まとめ

今までのことをまとめると、上記の数値と少し異なりますが、9/16ミリ秒(=0.5625ミリ秒)を1単位とすると、おおよそ以下のように表せます。

リーダコード
16単位のon、8単位のoff
データの1
1単位のon、3単位のoff
データの0
1単位のon、1単位のoff

1回のリモコンデータの送信では、リーダコード1回、データ32ビット、ストップビット1回が送信されるので、onが34回、offが34回となります。

デバイスの接続

Arduinoと赤外線センサは以下のように接続しました。

赤外線センサのピン Arduinoのピン番号
VCC 5V
GND GND
Vout 2

プログラム

赤外線センサでリモコンコードを読み取る

データフォーマットで示した情報を、赤外線センサ(PL-IRM2161-C438)を利用して、Arduinoで読み取ります。

このセンサは、38kHzのキャリア周波数で変調された赤外線信号を受信します。データがONのときにLOW、OFFのときにHIGHを出力します。

今回は、リーダコードから始まる68個のパルスの開始時刻(ONからOFF、OFFからONに切り替わる時刻)を配列に記録し、配列の情報を基に、コードを解析する方式をとりました。

具体的には、以下のような情報を記録します。

配列 説明 備考
time[0] リーダーコードの開始時刻
time[1] ON→OFFになった時刻 time[2]-time[1]が4.5msだったらリーダーコード、2.25msだったらリピートコード
time[2] カスタムコード(1バイト目)の開始時刻 0ビット目on開始時刻
time[3] 0ビット目off開始時刻
time[4] 1ビット目on開始時刻 → time[4]-time[3] or time[4]-time[2]が、0ビット目が0か1かを決める。
time[18] カスタムコード(2バイト目)の開始時刻
time[34] データコードの開始時刻 0ビット目on開始時刻
time[35] 0ビット目off開始時刻
time[36] 1ビット目on開始時刻 → time[36]-time[35] or [36]-time[34]が、0ビット目が0か1かを決める。
time[50] データコード(反転)の開始時刻 0ビット目on開始時刻
time[51] 0ビット目off開始時刻
time[52] 1ビット目on開始時刻 → time[52]-time[51] or time[52]-time[50]が、0ビット目が0か1かを決める。
time[66] ストップビットの開始時刻
time[67] ストップビットの終了時刻 onがoffになった時刻。

上記のデータを取得するため、ビジーループを用いました。通常はあまり良い方法ではありませんが、他の処理もないので今回は良しとしました。

  • 赤外線センサの出力がLOW(=ON)になるまで待つ(=HIGHの間は待つ)。
  • 以降は、赤外線センサの出力がonになった時とoffになった時の時刻を、配列(timeという名前にしました)に記録していく。
  • 通常は全部で68回読み取りを繰り返す。ただし、カスタムコードを読み取った後(4回目の記録を行ったとき)に、リーダーコードのoffの時間を調べて、3000ミリ秒以下だったら、読み取りは終了する。

フローにすると以下の通りです。微妙に上記説明とは異なりますが…

受信したデータを出力する

読み取ったコードを人間が読める形に変換して出力します。

  • リーダーコードは、onになっていた時間と、offになっていた時間をそれぞれ、ミリ秒単位で表示します。
  • カスタムコードは2バイトを1バイトずつにわけて表示、データコードとデータコード(反転)は、それぞれ1バイトを表示します。
  • 1バイトを表示する際は、ビット列と16進表記の2種類を表示します。ビット列については受信した順序で表示し、16進表記は、データの値として表示します。
i
下位ビットから受信しているため、表示したビット列は左が下位ビット、右が上位ビットとなること注意してください。

スケッチ

スケッチは以下の通りです。マジックナンバーが多いです。

 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
/*
 * NECフォーマットの赤外線リモコン出力を読取表示する
 */
const int irPin = 2;  // 赤外線センサの出力を接続するデジタルピン番号

void setup() {
  pinMode(irPin, INPUT);  // irPinを入力モードに設定する
  Serial.begin(115200);   // シリアル通信速度を115200bpsに設定する
}

void loop() {
  const int numRead = 68;       // 1回の処理で赤外線センサの出力を受信する回数
  unsigned long time[numRead];  //赤外線センサの出力の受信時刻を格納する変数
  char buffer[32];              // 結果表示用の文字列を格納するバッファ
  unsigned long startTime;      // タイムアウトを調べるための変数
  int repeatCode = 0;           // リピートコードを表すフラグ
  int mode = LOW;               // 赤外センサからの期待する入力

  for (int i = 0; i < numRead; i++) {
    startTime = millis();                 // 開始時刻を取得する
    while (digitalRead(irPin) != mode) {  // 期待する入力になるまで待機する
      if ((millis() - startTime) > 30) {  // 30ms以上期待する入力にならない場合は中止して再実行する
        return;
      }
    }

    time[i] = micros();  // 期待する入力に切り替わった時刻をマイクロ秒単位で記録する
    if (i == 1) {
      unsigned long t = time[1] - time[0];
      if ((t < 8000) || (t > 10000)) {  // 最初のONが8000~10000マイクロ秒ではない場合中止して再実行する
        return;
      }
    } else if ((i == 2) && ((time[2] - time[1])) < 2500) {  // リピートコードの場合はループ終了
      repeatCode = 1;
      break;
    }

    if (mode == HIGH) {  // 期待する入力を反転させる
      mode = LOW;
    } else {
      mode = HIGH;
    }
  }

  // データの出力
  Serial.print("--------- BEGIN ---------\n");

  sprintf(buffer, "Leader Code:\n HIGH %ldms\n", (time[1] - time[0]));
  Serial.print(buffer);
  sprintf(buffer, " LOW  %4ldms\n\n", (time[2] - time[1]));
  Serial.print(buffer);

  if (repeatCode) {
    Serial.println("Repeat Code:");
  } else {
    Serial.println("Custom Code:");
    printData(time, 2);
    printData(time, 18);

    Serial.println("");

    Serial.println("Data:");
    printData(time, 34);
    printData(time, 50);
  }
  Serial.print("---------  END  ---------\n\n");
}

// time[]のstartTimeバイト目から8個分のデータを表示する。
// time[]はグローバル変数。
void printData(unsigned long time[], int startTime) {
  int code = 0;
  char buffer[32];

  for (int i = 0; i < 8; i++) {
    if ((time[2 * i + startTime + 2] - time[2 * i + startTime]) > 1500) {
      code |= 1 << i;
      sprintf(&buffer[2 * i], " 1");
    } else {
      sprintf(&buffer[2 * i], " 0");
    }
  }

  sprintf(&buffer[16], ": %02x", code);
  Serial.println(buffer);
}

動作している様子

以下に動作している様子を示します。リモコンの1、2を順に押した時の様子です。

カスタムコードが、45 bc、データコードがそれぞれ、01、02であることがわかります。また、データコードのビットを反転したものがデータコード(反転)に入っている様子もわかります。

カスタムコードとデータの2進数の0と1は、受信順に表示しているので、LSBから表示していることに注意してください。

2の後には、リピートコードが入っています。どうも、手元のリモコンはリピートコードが送信されるまでの時間が短いようです。

その他

赤外線リモコンを操作する実験のページはこちらを参照してください。

バージョン

Hardware:Arduino Uno
Software:Arduino 1.8.19

最終更新日

January 2, 2022

inserted by FC2 system