Arduinoで遊ぶページ
Arduinoで遊んだ結果を残すページです
赤外線リモコンコード解析(NECフォーマット)
UNO

概要

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

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

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

作るもの

赤外線リモコンが出しているコードを表示するプログラムを作成します。

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

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

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

データフォーマット

NECフォーマットの概要は以下の通りです。詳細は、ルネサス エレクトロニクスのページのFAQの119ページからを参照してください。

リーダコードは、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ファーストで)送信していきます。

ストップビットについては、参照したページには明確な定義は見つけられませんでしたが、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回となります。

プログラム

今回作成するプログラムは以下の通りです。

  • リーダコードの、onの時間とoffの時間を表示する。
    • リピートコードだった場合は、ここで終了。
  • カスタムコードのon/offをそれぞれ、0と1で表示するとともに、16進数でも表示します。
  • データコード・データコード(反転)についても、カスタムコード同様、on/offをそれぞれ、0と1で表示するともに、16進数でも表示します。
  • 表示先はシリアルコンソールとします。

用意するもの

以下のものを用意します。

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

利用機器

今回利用した赤外線センサ(PARA LIGHT ELECTRONICS社のPL-IRM2161-C438)は、赤外線リモコンで利用される38kHzのキャリアで変調した赤外線を受信し、以下を出力します。

  • onのとき: LOW
  • offのとき: HIGH

ノイズが大きい場所で利用する際は抵抗・コンデンサを接続してノイズの影響を抑えたほうがいいと思いますが、今回は特別なことは行わず、赤外線センサだけを接続しました。

基本的な考え方

状況把握

1回のループで、リモコンのコード1回分(34回のonと34回のoff)を配列に記録します。ただし、リピートコードだった場合は、2回のonと2回のoffだけを記録します。

処理決定

読み取ったコードを解析し、リーダコードは、onとoffのそれぞれの時間、それ以外は、コードに変換し、1バイトずつ2進数と16進数とで、シリアルコンソールに表示します。

機器操作

現在の気温、最高気温、最低気温を液晶ディスプレイに表示します。

設計

ハードウェアの設計

赤外線センサをArduinoに接続します。

赤外線センサの出力は、デジタルの2番ピンに接続しました。他のピンに接続する場合は、プログラムを変更してください。

プログラムの設計

赤外線コードの読み取り

1回のloop()関数の呼び出しで、以下を行います。

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

時刻は、micros()を使って、マイクロ秒単位で記録していきます。

上記のセンサの出力がonになったときや、offになったときというのは、デジタルピンの入力を読み以前の状態と同じ状態のときは入力を再度読むというビジーウエイトを使いました。マルチタスクの場合はビジーウエイトは勧められませんが、今回のような場合は他に動かしているプログラムもないので問題はないでしょう。pulseIn()を使うほうが簡単かもしれません。

ループが終わった後は、配列には以下のようなデータが入っています。

配列 説明 備考
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になった時刻。

本来であれば、リーダコードの正当性をもう少し真面目に確認したほうがいいですが、実験目的なので今回はそこまでは考慮しないこととしました。

コードの出力

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

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

スケッチ

スケッチは以下の通りです。

  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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
const int pin = 2;          // 赤外線センサの出力を接続するデジタルピン。
const int count = 68;       // NECフォーマットの1データ分のonとoffの合計数。
unsigned long time[count];  // データ受信時刻を格納する配列。

void setup() {
  pinMode(pin, INPUT);   // pin を入力モードに設定。
  Serial.begin(115200);  // シリアル出力を115200bpsに設定。
}

void loop() {
  int repeat = 0;       // リピートコード判定フラグ。
  int mode;             // 読み取るデータを決める。
  char str[64];         // 表示用バッファ。
  unsigned long start;  // forループ開始時刻取得用。

  mode = HIGH;                          // 最初はHIGH。読み取る必要があるのは、LOW。赤外線センサは、反転した値を出力する。
  for (int i = 0; i < count; i++) {     // 68回データを読み取る。
    start = millis();                   // このループの開始時刻を取得。
    while (digitalRead(pin) == mode) {  // 状態が変わるまで待つ。
      if ((millis() - start) > 30) {    // このループを開始してから30msたった場合は、異常とみなし、中断する。
        return;
      }
    }
    time[i] = micros();  // 時刻を記録する。

    if (mode == HIGH) {  // onとoffが交互に来るので、待つべき状態を変える。
      mode = LOW;
    } else {
      mode = HIGH;
    }

    // リーダコードのoffが2.25msだと次にストップビットが来て終了。
    // 3000マイクロ秒以下かどうかで判定。
    if ((i == 3) && ((time[2] - time[1])) < 3000) {
      repeat = 1;
      break;
    }
  }

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

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

  if (repeat) {
    Serial.println("Repeat");
  } else {
    Serial.println("Custom Code:");
    print_data(2);
    print_data(18);

    Serial.println("");

    Serial.println("Data:");
    print_data(34);
    print_data(50);

    Serial.print("---------  END  ---------\n\n");
  }
}

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

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

  sprintf(&str[17], ", %02x", code);
  Serial.println(str);
}const int pin = 2;          // 赤外線センサの出力を接続するデジタルピン。
const int count = 68;       // NECフォーマットの1データ分のonとoffの合計数。
unsigned long time[count];  // データ受信時刻を格納する配列。

void setup() {
  pinMode(pin, INPUT);   // pin を入力モードに設定。
  Serial.begin(115200);  // シリアル出力を115200bpsに設定。
}

void loop() {
  int repeat = 0;       // リピートコード判定フラグ。
  int mode;             // 読み取るデータを決める。
  char str[64];         // 表示用バッファ。
  unsigned long start;  // forループ開始時刻取得用。

  mode = HIGH;                          // 最初はHIGH。読み取る必要があるのは、LOW。赤外線センサは、反転した値を出力する。
  for (int i = 0; i < count; i++) {     // 68回データを読み取る。
    start = millis();                   // このループの開始時刻を取得。
    while (digitalRead(pin) == mode) {  // 状態が変わるまで待つ。
      if ((millis() - start) > 30) {    // このループを開始してから30msたった場合は、異常とみなし、中断する。
        return;
      }
    }
    time[i] = micros();  // 時刻を記録する。

    if (mode == HIGH) {  // onとoffが交互に来るので、待つべき状態を変える。
      mode = LOW;
    } else {
      mode = HIGH;
    }

    // リーダコードのoffが2.25msだと次にストップビットが来て終了。
    // 3000マイクロ秒以下かどうかで判定。
    if ((i == 3) && ((time[2] - time[1])) < 3000) {
      repeat = 1;
      break;
    }
  }

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

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

  if (repeat) {
    Serial.println("Repeat");
  } else {
    Serial.println("Custom Code:");
    print_data(2);
    print_data(18);

    Serial.println("");

    Serial.println("Data:");
    print_data(34);
    print_data(50);

    Serial.print("---------  END  ---------\n\n");
  }
}

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

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

  sprintf(&str[17], ", %02x", code);
  Serial.println(str);
}

組立

赤外線センサに5Vの電源を接続します。また、データ出力端子をArduinoの2番ピンに接続します。他のピンに接続するときは、スケッチ中の pin に代入している数字を変更してください。

動作している様子

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

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

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

その他

データが1のときもデータが0のときも、リモコン信号のonの長さは同じなので、データが0か1かを知るだけなら、実は、デジタルピンがHIGHからLOWになる部分だけを読めば十分です。これだけであれば、割り込みを使って簡単に実現できますが、今回は、リーダ部分のonとoffの長さを表示したかったので、すべての信号を記録しました。

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

バージョン

Hardware:Arduino Uno
Software:Arduino 1.8.12

最終更新日

January 23, 2021

inserted by FC2 system