Spresenseで赤外線リモコンをデコードする

大昔に購入した赤外線受光モジュールが見つかったので、今回はこれで遊んでみたいと思います。

# 必要な材料

今回使用した材料は以下の4つになります。

## 1. Spresense Mainボード
ソニーが製造しているSpresenseのMainボードです。低消費電力でありながら6つのCortex-M4Fコアを持つプロセッサーを持つ高性能なシングルボードコンピュータ。
単体でGNSS/みちびきなどの位置測位が行える高機能なボードです。
今回はもちろんこのボードがメインの処理を行います。
<img width="50%" alt="spresense_main_board_top.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2374160/7af1321d-e542-0b3f-5b61-15662b5b350b.jpeg">

購入サイトはこちら [スイッチサイエンス-SPRESENSEメインボード[CXD5602PWBMAIN1]](https://www.switch-science.com/products/3900)

## 2. Spresense LTE 拡張ボード

Spresense Mainボードのインタフェースを拡張するためのLTE拡張ボードです。
今回は5VのGPIOが使えればよいので、通常の拡張ボードでもいいのですが、あえてLTEボードを使ってみます。

購入サイトはこちら [スイッチサイエンス-SPRESENSE LTE拡張ボード [CXD5602PWBLM1JUL]](https://www.switch-science.com/products/5999)

![SpresenseLTE.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2374160/0b82eb72-931e-eff1-bcea-4ed031f6c99b.jpeg)

## 3. 赤外線受光モジュール

様々なメーカのものが売っていますが、今回は手元にあった"PL-IRM-0101-3"という赤外線リモコン受信モジュールを使いました。

![ir-sensor.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2374160/3a246eb3-4d8a-5576-d6b4-59ec1f0b793d.jpeg)

購入サイトはこちら [赤外線リモコン受信モジュール　ＰＬ－ＩＲＭ０１０１（３７．９ｋＨｚ）シールド付](https://akizukidenshi.com/catalog/g/gI-00622/)

## 4. その他電子部品

今回は赤外線を受光してSpresense LTEボードのGPIOに接続すれば十分ですが、受信していることが目で分かるように赤色LEDをつけます。
また、赤外線受光モジュールは赤外線を受光した際、出力ピンに信号が出力されるものの、微弱な電流であるため赤色LEDが明るく光りません。
なので、赤色LEDを光らせるためにトランジスタを使って回路を組みます。

参考サイト [No.4 リモコン受光モジュールを使った回路](https://inagidenshi.amebaownd.com/posts/3788331/)

#### 抵抗器

**R1**: 43Ω
**R2**: 43Ω
**R3**: 5.6kΩ

![R.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2374160/ac1ec1eb-c72d-1b98-6aa6-40f26abcc35f.jpeg)

※手元にあった抵抗を使ったので適当です。より正確な抵抗はLEDなどの特性に応じた抵抗を選んでください。

#### トランジスタ

**Q1**: PNP型 2SA1015 × 1

![2SA1015.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2374160/95b6cbeb-7424-7c57-9cc0-ef8d8881f6af.jpeg)

#### 可視光LED(赤)

**D1**: 赤色LED
![LED.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2374160/252615e7-c1dd-d9a2-7abd-900a7d0c7ae8.jpeg)

# Arduino IDEの環境準備

## Spresense開発環境

ソニー公式の [Spresense Arduino スタートガイド](https://developer.sony.com/develop/spresense/docs/arduino_set_up_ja.html) を一通り実行してセットアップしました。
今回は **バージョン2.4.0** をインストールしました。

<img width="70%" alt="Arduino_Spresense.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2374160/7500f3c0-9048-4a11-6215-1e2eaf692c9d.png">

# 赤外線受光モジュールについて

今回使用する赤外線受光モジュールは、一般的な赤外線リモコンと同様、38kHzのキャリア変調がされた赤外線を受光できるようにモジュール化されています。
なので、キャリア変調に対応するための特別な回路は必要ありません。

# 赤外線受光器回路の作成

[No.4 リモコン受光モジュールを使った回路](https://inagidenshi.amebaownd.com/posts/3788331/)　を参考に、Spresense LTE拡張ボードに接続できる回路を組みます。
Spresense LTE拡張ボードのGPIOは、内部回路の特性によりInputに割り当てていても電圧が若干かかっている状態になっているので、プルダウン抵抗(R2)を導入しています。

![IR.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2374160/260bcbd3-6741-3f28-d683-11199d26f0df.png)

## ブレッドボードを使って回路を作成

前述の回路をブレッドボードで組んでみました。

![DSC_2539_mini.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2374160/4b8a0b9d-403c-1586-2340-1e2ec4190ff4.jpeg)


若干複雑になってしまったので、ユニバーサル基盤を使って小型化。
これでSpresense LTE拡張ボードにさすだけでよくなりました。

![DSC_2603_mini.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2374160/54157d92-1673-61b0-d161-52c78906429a.jpeg)


# 赤外線を受けて割り込みを観測してみる

## 割り込みの観測

まずは、LEDの店頭と同期してGPIO割り込みが発生するかを確認します。
今回はD07ピンを入力ピンとして使っているので、このピンで割り込みを受けるプログラムを作成。

```c++

// 入力ピン(D07)
const int IR_PIN = 7;

void isr_handler() {
  int status = digitalRead(IR_PIN);
  Serial.print("State: ");
  Serial.print(status);
  Serial.println();
}

void setup() {
  // デバッグポートのボーレートを設定
  Serial.begin(115200);
  
  // 赤外線回路から入力した信号ピンの入力設定。
  pinMode(IR_PIN, INPUT_PULLDOWN);

  // 割り込みハンドラの設定。GPIOのHigh/Lowが変更した場合ハンドラが呼ばれる。
  attachInterrupt(IR_PIN, isr_handler, CHANGE);
}

void loop() {
  // Loopでは今回何もしない。
  delay(100);
}

```

このスケッチを実行してみます。
以下のログの通り、確かにリモコンの信号を受けて割り込みが発生していそうです。
ちなみに、赤外線リモコンが発光しているとき赤外線受光モジュールは出力を落とし(State: 0)、発光していないときは出力しています(State: 1)。

```
01:49:08.907 -> State: 1
01:49:08.907 -> State: 0
01:49:08.907 -> State: 1
01:49:08.907 -> State: 0
01:49:08.907 -> State: 1
01:49:08.907 -> State: 0
01:49:08.907 -> State: 1
01:49:08.907 -> State: 0
01:49:08.907 -> State: 1
01:49:08.907 -> State: 0
01:49:08.907 -> State: 1
```

## 割り込みを使って時間を計測

次は、どのような信号が送り込まれているかを観測するため、各割り込み区間の時間測定を行うプログラムを作成します。
Serial出力を出すために多少の時間がかかってしまうので、敢えてボーレートを2000000上げています。

```c++
// 入力ピン
const int IR_PIN = 7;

struct timespec start_time;
struct timespec end_time;

// ★時間間隔を計算
int get_microtime(struct timespec st_t, struct timespec ed_t) {
  long nanotime;

  // nanotimeの差分を計算(今回は1秒未満の時間測定なので秒は無視)
  nanotime = (ed_t.tv_nsec - st_t.tv_nsec);

  // 秒が桁上がりした場合逆転してしまうので補正
  if (nanotime < 0) {
    nanotime += 1000000000;
  }

  return (int) nanotime / 1000;
}

void isr_handler() {
  int status;
  int microtime;
  
  // ★割り込みが入った時刻を取得
  clock_gettime(CLOCK_REALTIME, &end_time);

  // 割り込み信号の状態を確認
  status = digitalRead(IR_PIN);

  // ★時間計測
  microtime = get_microtime(start_time, end_time);
  Serial.print("State: ");
  Serial.print(status);
  Serial.print(", Time: ");
  Serial.print(microtime);
  Serial.println("us");
  start_time = end_time;
}

void setup() {
  // デバッグポートのボーレートを設定(ログ出力の時間で時間測定がズレてしまうので高速に設定。)
  Serial.begin(2000000);
  
  // 赤外線回路から入力した信号ピンの入力設定。
  pinMode(IR_PIN, INPUT_PULLDOWN);

  // 割り込みハンドラの設定。GPIOのHigh/Lowが変更した場合ハンドラが呼ばれる。
  attachInterrupt(IR_PIN, isr_handler, CHANGE);

  // ダミーの開始時間を取得
  clock_gettime(CLOCK_REALTIME, &start_time);
}

void loop() {
  // Loopでは今回何もしない。
  delay(100);
}

```

上記のプログラムを動かしてみた時のログが以下の通り。
一番最初の時間は `setup()` で入れたダミーの `start_time` と割り込みの間の時刻なので無視します。
それ以降は、2400usec, 600usec, 1200usecと600usecの定数倍付近の時刻が並んでいることがわかります。

```
02:14:44.096 -> State: 1, Time: 733968us
02:14:44.096 -> State: 0, Time: 2349us
02:14:44.096 -> State: 1, Time: 610us
02:14:44.096 -> State: 0, Time: 610us
02:14:44.096 -> State: 1, Time: 610us
02:14:44.096 -> State: 0, Time: 579us
02:14:44.096 -> State: 1, Time: 610us
02:14:44.096 -> State: 0, Time: 579us
02:14:44.096 -> State: 1, Time: 610us
02:14:44.096 -> State: 0, Time: 579us
02:14:44.096 -> State: 1, Time: 610us
02:14:44.096 -> State: 0, Time: 610us
02:14:44.096 -> State: 1, Time: 610us
02:14:44.096 -> State: 0, Time: 579us
02:14:44.096 -> State: 1, Time: 610us
02:14:44.096 -> State: 0, Time: 579us
02:14:44.096 -> State: 1, Time: 610us
02:14:44.096 -> State: 0, Time: 579us
02:14:44.096 -> State: 1, Time: 610us
02:14:44.096 -> State: 0, Time: 1190us
02:14:44.096 -> State: 1, Time: 610us
02:14:44.096 -> State: 0, Time: 579us
02:14:44.143 -> State: 1, Time: 610us
```

# ソニーのリモコンをデコードしてみる

次は、ソニーのリモコンを使って送信データをデコードしてみたいと思います。
先ほどのログで、600usec単位の時間が見つかったと思いますが、実はこの600usecという時間は、リモコンのデコード単位時間となっています。
ソニーのリモコンは、600usecを基準に赤外線の発光をコントロールしてデータを送信しています。
詳細は以下の参考サイトの"SONYフォーマット"を参照ください。
[赤外線リモコンの通信フォーマット](http://elm-chan.org/docs/ir_format.html)

Learderと呼ばれる 4T (T=600usec, 4T=2400usec)の発光区間が開始の合図、
その後にくる1T(600usec), 2T(1200sec)の発光区間がそれぞれ1bit 0と1の合図です。

なので、4Tの時にデコードされた値を表示し、1T/2Tの発光区間でビット演算をするようなプログラムを作成します。

```c++

// 入力ピン
const int IR_PIN = 7;

struct timespec start_time;
struct timespec end_time;

// デコードされた値を保持
volatile uint32_t ir_code = 0x0000;

// ★デコード本体
void do_decoding(int status, int microsec) {
  // 誤差を±100usecとしてどのパターンで割り込みが来たかを判定
  if (status == 0 && 2300 < microsec && microsec < 2500) {
    // Status 0(発光)期間が4Tの場合 -> 新規コード開始の合図

    // 前回のデコード結果をここで表示する。
    if (ir_code != 0x0000) {
      Serial.print("CODE: ");
      Serial.print(ir_code, HEX);
      Serial.println();
    }
    
    // 次のデコードに備え値を初期化
    ir_code = 0x0000;
  } else if (status == 1 && 500 < microsec && microsec < 700) {
    // Status 1(消灯)期間が1Tの場合 -> コード送信中消灯時間はこの時間のみ。特にここでやることはない。
  } else if (status == 0 && 500 < microsec && microsec < 700) {
    // Status 0(発光)期間が1Tの場合 -> 1bit の 0 が送られた合図

    // ir_codeを1bit左にシフトして今回のbit = 0を加える
    ir_code = (ir_code << 1) + 0;
  } else if (status == 0 && 1100 < microsec && microsec < 1300) {
    // Status 0(発光)期間が2Tの場合 -> 1bit の 1 が送られた合図

    // ir_codeを1bit左にシフトして今回のbit = 1を加える
    ir_code = (ir_code << 1) + 1;
  }
}

int get_microtime(struct timespec st_t, struct timespec ed_t) {
  long nanotime;

  // nanotimeの差分を計算(今回は1秒未満の時間測定なので秒は無視)
  nanotime = (ed_t.tv_nsec - st_t.tv_nsec);

  // 秒が桁上がりした場合逆転してしまうので補正
  if (nanotime < 0) {
    nanotime += 1000000000;
  }

  return (int) nanotime / 1000;
}

void isr_handler() {
  int status;
  int microtime;
  
  // 割り込みが入った時刻を取得
  clock_gettime(CLOCK_REALTIME, &end_time);

  // 割り込み信号の状態を確認
  status = digitalRead(IR_PIN);

  // 時間計測
  microtime = get_microtime(start_time, end_time);

  // ★測定時間をデコードにかける
  do_decoding(status, microtime);
  
  start_time = end_time;
}

void setup() {
  // デバッグポートのボーレートを設定(ログ出力の時間で時間測定がズレてしまうので高速に設定。)
  Serial.begin(2000000);
  
  // 赤外線回路から入力した信号ピンの入力設定。
  pinMode(IR_PIN, INPUT_PULLDOWN);

  // 割り込みハンドラの設定。GPIOのHigh/Lowが変更した場合ハンドラが呼ばれる。
  attachInterrupt(IR_PIN, isr_handler, CHANGE);

  // ダミーの開始時間を取得
  clock_gettime(CLOCK_REALTIME, &start_time);
}

void loop() {
  // Loopでは今回何もしない。
  delay(100);
}
```

このスケッチを実行してみます。

![5fps.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2374160/650a34ca-21a1-e637-527d-a88bcfb0c2a9.gif)

```
12:44:10.115 -> CODE: 10   <- ★TVリモコンの1を押した時
12:44:10.162 -> CODE: 10
12:44:10.210 -> CODE: 10
12:44:10.256 -> CODE: 10
12:44:11.145 -> CODE: 10
12:44:11.193 -> CODE: 810  <- ★TVリモコンの2を押した時
12:44:11.240 -> CODE: 810
12:44:11.286 -> CODE: 810
12:44:12.361 -> CODE: 810
12:44:12.361 -> CODE: 410  <- ★TVリモコンの3を押した時
12:44:12.408 -> CODE: 410
12:44:12.455 -> CODE: 410
12:44:12.924 -> CODE: 410
12:44:12.972 -> CODE: C10  <- ★TVリモコンの4を押した時
12:44:13.019 -> CODE: C10
12:44:13.067 -> CODE: C10
12:44:13.492 -> CODE: C10
12:44:13.539 -> CODE: 210  <- ★TVリモコンの5を押した時
12:44:13.587 -> CODE: 210
12:44:13.634 -> CODE: 210
12:44:13.962 -> CODE: 210
12:44:14.009 -> CODE: A10  <- ★TVリモコンの6を押した時
12:44:14.056 -> CODE: A10
```

確かに押したボタンによって送っているデータが変わっていることに気が付きます。
これで押したボタンが何なのかを判定できそうですね。

# 最後に

今回は、赤外線受光モジュールを使ってSpresenseでリモコンの送信データをデコードしてみました。
赤外線の発光、消灯を割り込みで受けて時間を測定すれば簡単にデコードできることがわかりました。

来週は、デコードした結果を使って簡単な楽器を作成してみたいと思います！