適当のごった煮

Pythonと境界標とQGISを中心にいろいろと

Arduinoで赤外線受信(NECフォーマット)

スポンサードリンク

4月に「ELEGOO UNO R3」を購入してから、試行錯誤を繰り返し、ようやく赤外線の受信について理解できた感じがするので忘れないように記録しておきます。

Arduinoからの赤外線データ送信は、照明用赤外線リモコンのデータ読み取りと送信をご覧ください。

赤外線の送受信環境は、オプトサプライ赤外線リモコンで送信し、受信モジュール PL-IRM2121のVoutをArduinoのデジタル7番ピンにを接続しています。

赤外線リモコンのデータシートリモコンフォーマット 参考資料を参考にしました。

実験で得た個人的な教訓

  • 受信スケッチのシリアルモニタのビットレートは、9600bpsだとデータの取りこぼしが発生するようなので、115200bpsにする
  • 経過秒数を保存する変数は、「unsigned long int」にする
  • データ部分は、8ビットごとに最下位ビットから送られてくる
  • 受光モジュールは受信した赤外線を保存して順番に出力しているわけではなく、瞬時値をVoutに出力している
  • 受光モジュールからは、送信側のHIGHとLOWが逆に出力される

リモコンフォーマット

送信機からの出力信号は、下記のようにいくつかのブロックが集まって一つのまとまりを構成している。

コード 概要
リーダーコード HIGH 9ms LOW 4.5ms
識別コード 8ビット+反転8ビット
データコード 8ビット+反転8ビット
ストップビット HIGH 0.56ms(その後、フレームスペース約30ms)

また、通常のデータとは別に、送信ボタンを押し続けることで送信されるリピートコードというものが存在する。これはデータありの場合と比べて、LOWの長さが違うリーダーコード(HIGHが9ms、LOWが2.25ms)とストップビットからなるデータ列である。

識別コードとデータコードで使われる0と1の区別は、下記のようなHIGHとLOWの時間の長さの組み合わせで表現される。

データ HIGH LOW 合計時間(ms)
0 0.56 0.56 1.125
1 0.56 1.68 2.25

識別コードとデータコードは、それぞれ8ビットのデータとその反転ビットの8ビットなので、並び順を考えなければ、常に16ビットの0と16ビットの1が送信されることになる。

よって、識別コードとデータコード部分の送信(受信)時間は、(1.125 + 2.25)*16=54msになる。リーダーコードと合わせれば68msほどだが、ストップビットの後にフレームスペースと呼ばれるLOWが続き、一つのフレーム(リーダーコードからフレームスペースまで)は108msと決められている。

受光モジュールからの出力模式図(HIGHとLOWの長さは正確ではない。受信では送信とHIGHとLOWが逆)
f:id:tekito-gottani:20180512174323j:plain

番号 概要
1 赤外線未受信状態のHIGH
2 赤外線受信開始。リーダーコードLOW 9ms
3 リーダーコードHIGH 4.5ms
4 最初のデータ(識別コード) LOWのはじめからHIGHの終わりまで計測
5 2個目のデータ(識別コード) LOWとHIGHの時間が同じなので0を表す
6 3個目のデータ(識別コード) HIGHが長いので1を表す
7 4個目のデータ。以下32個目までが識別+データコード部分
8 32個目のデータ
9 ストップビット
10 フレームスペース

受信スケッチ

受光モジュールは、何も受信していないときはHIGHを出力しているので読み飛ばす。次のLOWからHIGHになるまでの時間を計測してリーダーコードなら、さらにHIGHからLOWになるまでの時間を計測する。計測した時間については、全般的にマイナス10パーセント程度の幅を持たせる。

計測したHIGH継続時間がリピートコードでなければ識別コードとデータコードの読み込みに入る。コード部分はLOWとHIGHを一組として時間を計測し、その時間の長さで0か1を判断する。

32ビット分データが集まったらデータ表示する。8ビットが4セットで、それぞれの8ビットが最下位ビットから配列に入っていることに注意してbitSet関数でデータをセットしていく。

void setup() {
  pinMode(7, INPUT);
  Serial.begin(115200);
  Serial.println("Serial Start.");
}

void loop() {
  unsigned long int start, ltime, htime;

  while(digitalRead(7) == HIGH){;} // 未送信状態のHIGH。読み飛ばし
  start = micros();
  while(digitalRead(7) == LOW){;}
  ltime = micros() - start;

  if(ltime > 8000){
    start = micros();
    while(digitalRead(7) == HIGH){;}
    htime = micros() - start;
    if(htime > 4000){ // リーダーコードなら以降のデータを読み込み
      read_data();
    } else if(htime > 2000){
      Serial.println("リピートコード");
    } else { // 読み取りエラーの可能性
      Serial.println("先頭部分でリーダーコードでもなくリピートコードでもない");
    }
  }
}

// 識別コードとデータコード読み込み
void read_data(){
  int i, code;
  int data[40];
  unsigned long int start, len;

  i=0;
  while(1){
    start = micros();
    while(digitalRead(7) == LOW){;}
    while(digitalRead(7) == HIGH){;}
    len = micros() - start;

    if(len > 2000){ // LOWとHIGHの合計時間で0と1を判断
      data[i] = 1;
    } else {
      data[i] = 0;
    }
    i++;
    if(i >= 32){
      break;
    }
  }
  display_data(data);
}

// データ表示
void display_data(int data[40]){
  int i, j, code;

  for(i=0; i<32; i++){
    Serial.print(data[i]);
  }
  Serial.println("");
  for(i=0; i<4; i++){
    code = 0;
    for(j=0; j<8; j++){
      if(data[j+8*i] == 1){
        bitSet(code, j);
      }
    }
    Serial.print(String(code, BIN));
    Serial.print(" ");
    Serial.println(String(code, HEX));
  }
}

受信例(シリアルモニタ)

f:id:tekito-gottani:20180512174626j:plain

一つのフレームは108msなので、0.1秒以上ボタンを押しているとリピートコードが出力される模様。ちょっと押した感覚でもリピートコードが表示されて、精密さに驚く。

コード表示の2進数は8桁固定にできていないので、最上位ビットから0が続くと表示桁数が減ってしまう。

受信例は、赤外線リモコン送信機の電源ボタンを何回か押した結果で、説明書通りの出力を確認できた。その他のボタンについても説明書通りの出力値であった。

参考