SPRESENSEアプリケーションをリモコンで動かす方法

Arduino でお馴染みの IR 赤外線リモコンを SPRESENSE ボードにポーティングしてみました。単純に動かすだけも良いのですが、SPRESENSE がもつ Arduino マルチコア環境を使って、IR 受信機をメインコアではなくサブコアで動かしてみます。

# 用意するもの

- [SPRESENSEメインボード](https://www.switch-science.com/catalog/3900/)
- [SPRESENSE 拡張ボード](https://www.switch-science.com/catalog/3901/) 又は [SPRESENSE LTE拡張ボード](https://www.switch-science.com/catalog/5999/)
- 赤外線リモコン、赤外線 LED、赤外線受光部

　<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/113db96a-ba90-a3d2-df99-466af5ceb566.png" width="45%">

# 接続

結線はこんな感じにしました。IR 受光部には 5V を供給して受信信号は 11 番ピンで受けています。IR 送信部はハードウェア PWM に対応しているピン(3, 5, 6, 9) を選択する必要があるので、ここでは 3 番ピンを使っています。抵抗は省略して書いています。拡張基板のI/Oジャンパは3.3Vに設定しました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/ca52029a-d5f4-c202-be1d-a5e521c8e401.png" width="45%">

ちなみに受光部基板の3ピンは文字の表記も無く少々分かりづらいのですが以下の配置になっています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/5ed47413-a106-ee59-67e5-a82d5d4a823a.png" width="20%">

# 赤外線リモコンの通信フォーマット

詳しい説明はここに譲ります。
http://elm-chan.org/docs/ir_format.html

簡単に説明すると、
周波数38kHz/40kHz程度のPWM信号(duty比1/3)を間欠的に動作させることによって 0, 1 信号を送ります。
間欠動作させる間隔は最小で 350-600 μsec 程度で、NEC、家製協、SONYなどフォーマットによって決められています。
その他、データ長や長押ししたときのRepeat信号などもフォーマットによって規定されています。
大抵のフォーマットは後述するライブラリがサポートしてくれています。

# ライブラリ

Arduino-IRremote ライブラリをベースに Spresense 用に移植しました。
https://github.com/baggio63446333/Spresense-IRremote

プログラムの動作としては、

- **送信部：** フォーマットにしたがってPWM信号を出して止めてを繰り返します。PWM信号の出力/停止期間は数百μsecのビジーループで調整しています。
- **受信部：**50usecの周期タイマーを動かして信号のHIGH/LOWを読み出してサンプリングしています。

Arduino の libraries フォルダ以下にインストールしてください。

```console
$ cd ~/Arduino/libraries
$ git clone https://github.com/baggio63446333/Spresense-IRremote
```

オリジナルの examples に入っているサンプルは一通り動作します。それ以外に SPRESENSE 固有のサンプルを追加しています。

## リモコン送受信の同時動作サンプル

SPRESENSE の Arduino マルチコア環境を利用して、IR 送信機、IR 受信機をそれぞれ専用のコアに割り当てて動かします。送信機も受信機もそれなりに高速な I/O 処理をしなければならないのですが、一つのボードで両方を同時に動作させています。IR を使って CPU 間通信を実現しているといえなくもない。

Arduinoマルチコア環境については、[公式サイト](https://developer.sony.com/develop/spresense/docs/arduino_tutorials_ja.html#_tutorial_multicore) を参考にしてください。

Spresense-IRremote/examples/SpresenseIRcommunicationTest 

|Core|スケッチ|用途|
|----|----|----|
|MainCore|Main.ino|SubCore1,2 起動|
|SubCore1|IRrecvDumpV2.ino| IR 受信動作|
|SubCore2|IRsendDemo.ino| IR 送信動作|

動作コアの選択を変えながら各スケッチをビルドして書き込みます。Main.inoスケッチを開いて`MainCore`を選択してビルドして書き込み、IRrecvDumpV2.inoスケッチを開いて`SubCore 1`を選択してビルドして書き込み、IRsendDemo.inoスケッチを開いて`SubCore 2`を選択してビルドして書き込みます。

![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/2bb17ff3-ba11-182b-6ece-f9b8f92c0c73.png)

SubCore2 から赤外線を送信して、SubCore1 で受信してシリアルモニタに表示しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/58ac38c0-c13d-2470-f3a5-13d3b89531bd.png)
(Timing の時間をみると規定よりも少し短かめですが意図した値が通信できているのでこれでヨシとします)

## リモコン対応オーディオプレーヤーサンプル

前述した通り IR 制御にはそれなりに高速な I/O 処理が必要なので、特にオーディオのようなリアルタイム処理が必要とされるアプリケーションと組み合わせて動作させることは難しいのですが、リモコン受信部を専用のサブコアに割り当てて動かすことで、簡単にリモコン制御に対応することができます。

Spresense-IRremote/examples/SpresenseIRaudioPlayer 
オーディオプレーヤースケッチは公式の [Audio チュートリアル～プレイリスト再生](https://developer.sony.com/develop/spresense/docs/arduino_tutorials_ja.html#_%E3%83%97%E3%83%AC%E3%82%A4%E3%83%AA%E3%82%B9%E3%83%88%E5%86%8D%E7%94%9F) をベースにしました。

|Core|スケッチ|用途|
|----|----|----|
|MainCore|player_playlist_remocon.ino|オーディオプレーヤー|
|SubCore1|IRrecvSub.ino| IR 受信動作|

動作コアの選択を変えながら各スケッチをビルドして書き込みます。player_playlist_remocon.inoスケッチを開いて`MainCore`を選択してビルドして書き込み、IRrecvSub.inoスケッチを開いて`SubCore 1`を選択してビルドして書き込みます。

![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/18c60d37-cb96-a2a0-4c1f-30391c75d7d1.png)

SubCoreのスケッチは単純にレシーバーの値をMainCoreへ送信するだけです。

```c++:IRrecvSub.ino
#include <IRremote.h>
#include <MP.h>
#include "common.h"

int RECV_PIN = 11;
IRrecv irrecv(RECV_PIN);
decode_results results;

void setup() {
  MP.begin();
  irrecv.enableIRIn();
}

void loop() {
  if (irrecv.decode(&results)) {
    Serial.println(results.value, HEX);
    MP.Send(MSGID_IR, results.value); // Send IR value to MainCore
    irrecv.resume();
  }
  delay(100);
}
```

MainCoreスケッチでは、setup()の中でSubCoreを起動してポーリングモードでの受信設定を行います。

```c++:player_playlist_remocon.ino
  /* Launch subcore */
  MP.begin(SUBCORE_IR);
  /* receive with non-blocking */
  MP.RecvTimeout(MP_RECV_POLLING);
```
loop()の中でSubCoreから送られてきた IR 値によってプレーヤーの処理を変更しています。

```c++:player_playlist_remocon.ino
  ret = MP.Recv(&msgid, &irvalue, SUBCORE_IR);
  if (ret == MSGID_IR) {
    switch (irvalue) {
    case 0x00ff38c7: action = (s_state == Stopped) ? MENU_PLAY : MENU_STOP; break;
    case 0x00ff18e7: action = MENU_VOLUP; break;
    case 0x00ff4ab5: action = MENU_VOLDOWN; break;
    case 0x00ff5aa5: action = MENU_NEXT; break;
    case 0x00ff10ef: action = MENU_BACK; break;
    case 0x00ffb04f: action = MENU_REPEAT; break;
    case 0x00ff6897: action = MENU_RANDOM; break;
    default: break;
    }
  }
```

リモコンで選曲しているログです。
![audio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432852/02ca4ea7-b9f1-21f8-d537-36821fd29562.png)

# まとめ

既に作成済みのメインアプリケーションに対して、最小限の変更で簡単にリモコン機能を追加することができます。誰かの参考になればうれしいです～ written by [baggio](https://twitter.com/baggio63446333)