arrow-right hamburger logo-mark social-facebook social-github social-twitter
2017.08.02

フォトダイオードでリモコンの信号を検出してみよう

うこ

作ってみた

こんにちは、ものづくり担当のうこ(@harmoniko)です。

今回は、フォトダイオードというパーツを使ってリモコンの信号を読み取る方法を紹介します!

フォトダイオードとは?

半導体素子のひとつで、照射される光の強さによって流れる電流の強さが変化します。太陽光パネルと仕組みはほとんど同じで、光によって電流を発生しますが、非常に小さいため電源としては使用されません。

一口にフォトダイオードと言っても様々な種類があります。(参考:RSコンポーネンツ。多種の取り扱いがあります。) 光を検出するセンサとしてはほかにCdSなどがありますが、応答の速さ・感度・信号強度特性・対応する波長などに優位性があり、リモコンの受光器などとして産業用途に幅広く利用されています。

今回はそんなリモコンの受光器を作り、信号の中身をみてみましょう。

リモコンの仕組み

リモコンは、赤外線LEDを高速で点滅させて家電などに信号を送っています。信号の中身は、国内ではNECが開発した方式が主流で、次いで家電製品協会の方式、ソニーなどの独自方式があります。今回は、最も普及しているNEC方式を用いたリモコン信号検出システムを作ってみます。

このフォーマットを採用している主なメーカーとして、NEC・東芝・日立などがあります。これらのメーカーのリモコンなどをお持ちでしたら、ぜひ作ってみてください。

【参考】

作ってみよう

準備するもの

作り方

フォトダイオードに黒画用紙を巻く

フォトダイオードには、細長く切った黒画用紙を巻き寿司のように巻きつけてテープで固定します。こうすることでフォトダイオードを常に暗い状態にし、光の誤検出を防ぎます。

配線

まずはじめに、ブレッドボード上に抵抗器とフォトダイオードを、上の写真のように配置します。フォトダイオードは向きに注意してください。

次に、4つのジャンパワイヤを使って、ブレッドボードとArduino Dueを接続します。

接続が終わったら、このような感じになっています。

最後に、USBのマイクロBケーブルでArduino Dueとパソコンを接続して完了です。USBは、Arduino Due側は、黒いコネクタに近い方のUSBポート「Programming Port」に接続してください。

Arduino Dueボード対応化

Arduino IDEでプログラムを作成し書き込んでいきます。Arduino IDEのインストールが済んでいない方は、公式サイトからダウンロードして進めてください。

Arduino IDEのメニューから、 Tools -> Boards:“…” -> Board Manager… を選択すると、小さい画面が表示されます。検索窓に「due」と打ち込むと、ひとつだけヒットするパッケージがありますので、最新版をインストールしてください。

次に、 Tools -> Boards”…” で、”Arduino Due (Programming Port)“を選択しておきます。

ソースコードを書き込む

以下のソースコードをコピーし、スケッチにコピーしてください。

const int thresholdH = 30;     // フォトダイオードの閾値(Off->On)
const int thresholdL = 20;     // フォトダイオードの閾値(On->Off)
const int input = A0;          // フォトダイオードの入力ピン
int signalCnt = -1;            // 信号カウンタ
int level = 0;                 // フォトダイオードのanalogRead値
unsigned long offSpan;         // 信号L値持続時間(計測用)
unsigned long offTime;         // 信号Off時間(計測用)
unsigned long startTime;       // 時間計算用
unsigned long signals = 0;     // 信号保存(カスタム16bit+データ8bit+反転データ8bit)
bool now = false;              // フォトダイオード状態遷移フラグ1
bool prev = false;             // フォトダイオード状態遷移フラグ2
bool detected = false;         // リーダコード検出フラグ




// フォトダイオードからアナログ値を読んで論理値に変換
bool photoRead() {
  level = analogRead(input);
  if (level > thresholdH) return true;
  if (level < thresholdL) return false;
}


// リーダコード認識
bool leaderDetect() {
  // 積分
  unsigned long integral0 = 0;
  unsigned long integral1 = 0;
  startTime = micros();
  // 9msループ(0.1msおまけ)
  while (micros() < (startTime+9100)) {
    if (photoRead()) integral1++;
    else integral0++;
  }
  /*
    副搬送波38kHz変調のデューティ比は1:2である
    また信号で0を表す場合はPPM方式によりHとLがそれぞれ0.56ms(1:1)であるため
    リーダコード以外の部分でフォトダイオードで得られるOnとOffの時間比は
    Onが1に対してOffが6である
    リーダコード部分は常にHであるのでOnが1に対してOffが3である
    よってOnとOffの差がおおむね4倍以下であればリーダコードを判定することができる
  */
  if (integral1 > ((integral0)/4)) return true;
  else return false;
}


void setup() {
  // シリアルポートを開く
  Serial.begin(115200);
  // アナログ入力の細かさを12bit(4096段階)に設定
  analogReadResolution(12);
}


void loop() {
  // ダイオードの状態確認ループ
  while(now == prev) {
    now = photoRead();
    // 一度検出できたらループを極力回さずにディレイさせたい
    // 副搬送波のL値を誤検出しないようにするため
    if (now) {
      if (!detected) {
        // Off->On遷移かつリーダコード未検出
        if (leaderDetect()) {
          Serial.println("------- LEADER -------");
          detected = true;
          signalCnt = -1;
        }
      }
    }
  }

  // 以下は状態変化した瞬間だけ実行される
  //
  if (detected) {
    if (now) {
      // Off->On遷移かつリーダコード検出済
      offSpan = micros() - offTime;
      // 最初のリーダコードの直後のL値
      if (signalCnt < 0) {
        // リピートの検出(3ms未満)
        if (offSpan < 3000) {
          Serial.println("------- REPEAT -------");
          Serial.println("");
          signals = 0;
          signalCnt = -1;
          detected = false;
        } else {
          signalCnt++;
        }
      } else {
        // off時間が1000msより多いか少ないか
        if (offSpan < 1000) {
          // 1判定
          signals += 1<<(31-signalCnt);
        } else {
          // 0判定
        }
        signalCnt++;
      }
      // 570msも待てば必ずLになり次のコード先頭までHにならないので誤検出しない
      delayMicroseconds(570);
    } else {
      // On->Off遷移かつリーダコード検出済
      offTime = micros();
    }
  }
  // 反転
  prev = now;

  // 最後まで読めたら表示する
  if (signalCnt >= 31) {
    // カスタムコード16bit
    int custom = (int)(signals>>16);
    Serial.print("Custom code: "); Serial.println(custom,HEX);
    Serial.print("    "); Serial.println(custom,BIN);
    // データコード8bit
    byte data1 = (byte)((signals>>8)&B11111111);
    Serial.println("Data code:");
    Serial.print("    "); Serial.print(data1,BIN); Serial.print(" = "); Serial.println(data1,HEX);
    // データコード(反転)8bit
    byte data2 = (byte)(signals&B11111111);
    Serial.print("    "); Serial.print(data2,BIN); Serial.print(" = "); Serial.println(data2,HEX);
    // データ検証
    byte vf = data1^data2;
    Serial.println("Data verify: ");
    Serial.print("    "); Serial.print(data1,HEX); Serial.print(" xor "); Serial.print(data2,HEX); Serial.print(" = "); Serial.println(vf,HEX);
    if (vf == 0xFF) Serial.println("     [ VERIFIED ]");
    else Serial.println("     [ ERROR ]");
    // おわり
    Serial.println("------- ------ -------");
    Serial.println("");
    signals = 0;
    signalCnt = -1;
    detected = false;
  }
}

試してみよう

アップロードが完了したら、Tools -> Serial Monitor からシリアルモニタを表示させ、通信速度を「115200bps」に変更します。その状態のまま、リモコンをフォトダイオードに近づけてボタンを押すと……

いくつか値が表示されましたね! 点線で囲まれたブロックが信号の1セットで、中身には「Custom code」「Data code」「Data verify」が入っています。 「Custom code」は製品固有のコードで、同じリモコンだと基本的に固定の値です。

色々ボタンを押していると、「Data code」が変化するようすがわかります。「Data verify」は、信号に含まれている2つのデータを照合して、信号が壊れていないかを確認するためのものです。 自宅の扇風機のリモコンで試してみたところ、確かにボタンで値が変わっていることがわかりました。

応用編

Arduino IDEには「シリアルプロッタ」という、グラフを描画できる機能が備わっています。それを利用して、リモコンの信号波形を描画してみるとこんな感じになりました。規則的な波形をところどころに確認することができます。

また、今回使用したマイコンボード「Arduino Due」は、USB接続の外付けマウス・外付けキーボードとして簡単に動作させることができるライブラリが備わっています。例えば、今回のプログラムにこの機能を取り入れて少し書き換えるだけで、普段使っているTVリモコンでパソコンの動画プレイヤーを制御、なんてこともすぐにできちゃいます。

おわりに

いかがでしたでしょうか? フォトダイオードはいろんな種類がありますが、Arduinoのようなマイコンボードを使えば意外と簡単に光センサを作ることができます。この機会にぜひ作ってみては。