2012年11月24日土曜日

Arduinoで赤外線リモコンを受信、解析してみる(2)


こちらもどうぞ
Arduinoで赤外線リモコンの学習、送信(EEPROMに保存)2014/05/08

こちらの投稿(Arduinoで赤外線リモコンを受信、解析してみる)で赤外線リモコンを受信して、シリアルに出力するスケッチを書きましたが、
・カスタムコード+データコードが32ビット固定
・赤外線LEDのOFF時間でデータの送信
のNECフォーマットのリモコンをターゲットにしていました。

・家電製品協会フォーマットはデータコードが可変長
・ソニーフォーマットは赤外線LED ONの時間の長さの違いでデータを送信
・赤外線リモコンヘリコプターなど未知のフォーマットの赤外線信号の解析にも使えるように、赤外線LEDのON-OFF時間を記録
にも使えるようにしましたが、長いコードは処理できません。



前回のスケッチは、1ビットごとにシリアルで送信していましたが、今回は配列にデータを格納し、受信が終わってからまとめてシリアル出力します。


long型の配列変数にLED ON-OFFの時間を記録しますが、データ1つに4バイト消費します。
下記スケッチで、NECフォーマットの単発信号なら72個(288バイト)、ソニーDVDプレーヤーの電源信号単発で126個(504バイト)消費しました。
エアコンのリモコン信号は長いらしいのでもっと消費するか、記録しきれなくなるかも知れません。

配列の要素数は400個で確保しています。450個では正常に起動しませんでした。
2KBのSRAMだから仕方ないです。
変数の宣言時、SRAMでなくFlashメモリを使うように指定するPROGMEMを使えば、もっと長いデータを記憶できるようです。

フォーマットの判別に、こちらを参考にしました。
参考:ELM - 赤外線リモコンの通信フォーマット概要
http://elm-chan.org/docs/ir_format.html

必ずフォーマットを守っているわけではないようです。
手持ちのSHARP AQUOSの選局↑を押しっぱなしにしても、同じ信号を繰り返し送信で、リピート信号は出ていませんでした。
電源ボタン1度押しでは、同じコードを2度送信しています。

フォーマットを調べる内にLSBファーストというキーワードが出てきました。
データを送りたい時、最下位ビットから送信していくとういことです。

実際に赤外線を受信した結果です。
■東芝バズーカ(TV)の電源ボタン:NECフォーマット
・連番は配列の添字
・その右隣 正の数値が赤外線LED ON(受信モジュール出力はLOW)の時間μs
・負の数値が赤外線LED OFF(受信モジュール出力はHIGH)の時間μs
・判別したフォーマット
・受信した順番に0,1表示
・データはLSBファーストで送られてくるので、元に戻して16進数に変換したもの
以下同じ
 
上記画像の例では、カスタムコード40BF、データコード12、データコード反転EDになります。
40BF12EDを送りたい時は1バイトごとに区切り、
40→01000000 を下位(LSBファースト)から 0-0-0-0-0-0-1-0 と送信
BF→10111111 を下位(LSBファースト)から 1-1-1-1-1-1-0-1 と送信
12→00010010を下位(LSBファースト)から 0-1-0-0-1-0-0-0 と送信
ED→11101101を下位(LSBファースト)から 1-0-1-1-0-1-1-1 と送信



出力されたものをLibreOfficeのCalcにコピペしてグラフに
LED ONの時間は上に伸びて、LED OFFの時間は下に伸びます。
最初の2本がリーダー部
LED OFFの時間で0,1を送るので下側のグラフの長さが変化してます。

■シャープAQUOS(TV)の電源ボタン:家電製品協会フォーマット
一度押しただけで同じコードを2度送信します。
こちらもLED OFFの時間で0,1を送っているのが判ります。
同じコードが2回。真ん中の一番長いのはコードとコードの間隔で、切れてますが70msです。



■SONY DVDプレーヤーの電源ボタン:SONYフォーマット
一度押しても3度送信されます。
SONYフォーマットにリピートコードはなく、同じコードを繰り返し送信します。
3回同じコードの送信。
LED ON の時間で0,1を送っています。
2本下に伸びているのは切れていますが13ms。

■4ch赤外線リモコンヘリコプターF103の赤外線(購入記事
メインローターの回転数を少し上げただけで一切操作していない時の一部。
下に伸びている長いのは70~100msのコード間の間隔。
上に2本伸びてるのがリーダー部でしょう。
それ以外は、LED ONが長短2種類、LED OFFが長短2種類あります。
コードの頭から次のコードの頭まで122msと出ました。
詳しく解析できれば、何かのコントローラーとして使えそうですね。

無駄が多くて、改変もしにくそう。
読みにくいスケッチなんで読まなくていいです。
ミスがあったらすみません。

スケッチ:

// 赤外線リモコンを受信し、シリアルに出力する by cranberry
// http://cranberrytree.blogspot.jp/
//                           2012/11/24

int pin = 2;         //受信モジュール出力の信号入力ピン
int num = 0;
unsigned long st;    //計測開始時間μs
unsigned long dur;   //時間μs
int num_max = 400;   //配列要素数
long time[400];      //num_maxと同じに
boolean mode = HIGH;      //赤外線モジュール出力がHIGHの待機から始まる
long timeout = 120000;    //無受信時のタイムアウトμs

void setup() {
  Serial.begin(9600);
  pinMode(pin, INPUT);
}

void loop() {
  st = micros();                      //計測開始の時間を記憶
  while(digitalRead(pin) == mode){    //LOW,HIGHが変化するまでループで待機
    if(micros() - st > timeout){      //無受信時のタイムアウトでループを抜ける
      break;
    }
  }
  dur = micros() - st;                //時間を計算
  if(mode == LOW){
    time[num] = dur;
  }
  else{
    time[num] = dur * -1;             //LEDオフの時間(受信センサー出力はHIGH)はマイナス符号をつける
  }
  if(dur > timeout){                  //タイムアウトした時
    if(num > 0){                      //配列に格納されていれば、シリアル出力
      ser_pri();
    }
    num = -1;                         //タイムアウトかつデータ無しなので変数リセット
    mode = LOW;
  }

  num += 1;
  mode =! mode;
  if(num == num_max){                //データが多すぎた時の処理
    Serial.println("Too Many Data");
    delay(2000);
    ser_pri();
    num = 0;
    mode = HIGH;
  }
}

void ser_pri(){                        //受信データのシリアル出力
  int data = 0;
  Serial.println("");

  for(int i = 0; i < num; i++){        //配列のデータをそのまま出力
    Serial.print(i);
    Serial.print("\t");
    Serial.println(time[i]);
  }

//判別 NECフォーマット、家電製品協会(AEHA)フォーマット、ソニーフォーマット、不明
//NEC:T=562 leader 16T+8T repeat 16T+4T
//家電製品協会:T=425 leader 8T+4T repeat 8T+8T
//ソニー:T=600 leader 4T
  Serial.println("");
  byte data_f = 0;
  if(time[1] >= 8430 && time[1] < 9554 && time[2]*-1 >= 3934 && time[2]*-1 < 5058){   //リーダー部のON-OFF時間+-1Tの範囲で判別
    Serial.println("NEC Format");
    data_f = 1;
  }
  if(time[1] >= 2975 && time[1] < 3825 && time[2]*-1 >= 1275 && time[2]*-1 < 2125){
    Serial.println("AEHA Format");
    data_f = 2;
  }
  if(time[1] >= 1800 && time[1] < 3000 && time[2]*-1 < 600){
    Serial.println("SONY Format");
    data_f = 3;
  }
  if(data_f == 0){
    Serial.println("UNKNOWN Format");
  }
  if(data_f == 1){                        //NECフォーマットの時
    byte dig = 0;
    for(int j = 4; j < num; j=j+2){
      if(time[j]* -1  < 1000){
        Serial.print("0");
      }
      if(time[j]* -1 >=1000 && time[j]* -1 <1800 ){
        Serial.print("1");
        bitSet(data,dig);
      }
      if(time[j]* -1 >=1800 && time[j]* -1 <2500){            //リピートコード
        if((time[j-4]* -1 >=1800) && (time[j-4]* -1 <2500)){  //4つ前のデータもリピートコードかどうか
          Serial.print(" .");
        }
        else{
          Serial.print("REPEAT CODE");
        }
        dig = 0;
      }
      dig += 1;
      if(dig == 8){                  //8ビットごとに区切り
        Serial.print("\t");
        Serial.print(data,HEX);
        Serial.println("");
        dig = 0;
        data = 0;
      }
    }
  }

  if(data_f == 2){                        //家電製品協会フォーマットの時
    byte dig = 0;
    for(int j = 4; j < num; j=j+2){
      if(time[j]* -1  < 1000){
        Serial.print("0");
      }
      if(time[j]* -1 >=1000 && time[j]* -1 <1800 ){
        Serial.print("1");
        bitSet(data,dig);
      }
      if(time[j]* -1 >=2975 && time[j]* -1 <3825){            //リピートコード
        if((time[j-4]* -1 >=2975) && (time[j-4]* -1 <3825)){  //4つ前のデータもリピートコードかどうか
          Serial.print(" .");
        }
        else{
          Serial.print("REPEAT CODE");
        }
        dig = 0;
      }
      if(time[j]* -1 >=2000){            //同信号繰り返し
        Serial.println("REPEAT");
        j += 2;
        dig = -1;
      }
      dig += 1;
      if(dig == 8){                  //8ビットごとに区切り
        Serial.print("\t");
        Serial.print(data,HEX);
        Serial.println("");
        dig = 0;
        data = 0;
      }
    }
  }

  if(data_f == 3){                        //ソニーフォーマットの時
    byte dig = 0;
    boolean sony_func = 0;
    for(int j = 3; j < num; j=j+2){
      if(time[j]  < 1000){
        Serial.print("0");
      }
      if(time[j] >=1000 && time[j] <1800 ){
        Serial.print("1");
        bitSet(data,dig);
      }
      if(time[j] >=2000){            //同信号繰り返し
        Serial.print("\t");
        Serial.print(data,HEX);
        Serial.println("");
        Serial.println("REPEAT");
        dig = -1;
        data = 0;
        sony_func = 0;
      }
      dig += 1;
      if(dig == 7 && sony_func == 0){                  //最初の7ビットが機能コード、以下5~13ビットは機器識別コード
        sony_func = 1;
        Serial.print("\t");
        Serial.print(data,HEX);
        Serial.println("");
        dig = 0;
        data = 0;
      }
    }
    Serial.print("\t");
    Serial.print(data,HEX);
    data = 0;
  }

  if(data_f == 0){                        //フォーマット不明の時
  }

  Serial.println("");
}






環境:arduinoUNO、arduino1.0.2、win7



1 件のコメント:

  1. エアコンのリモコンのコード受信に使わせていただきました。
    ありがとうございました。

    返信削除