ATtiny85で作るシリアル接続 汎用リモコン送信モジュール

皆さん、楽しい赤外線ライフを満喫していますか? 電子工作で赤外線リモコンを作るとき、特に Raspberry Pi や MicroPython では数十μ秒のタイミング制御がうまくいかなかったり、言語やライブラリのお作法に悩まされたり、色々な問題に阻まれることがあります。かといって何でも C や Arduino で書くのは面倒だし。やっぱりイマドキの高級言語で書きたいですよね? ということで、言語やライブラリを問わずPCや各種マイコンに幅広く対応する送信モジュールを開発しました。

まずは今回作ったリモコン送信モジュール。ATTiny85を使い Software Serial で受け取ったコマンドを赤外線で送出します。

![IMG_5692.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157092/f27f072f-7532-51e0-f299-8abe2152dd3d.jpeg)


# 動機
ESP32 + MicroPython で赤外線リモコンを作ろうと思ったら On/Off のタイミングを思ったように制御できなくて悲しい気持ちになったから (結局このときは ESP32 + Arduino で開発した)。よろしい、ならば抽象化だ。

# やりたいこと
- 言語やフレームワーク・ランタイムを問わない赤外線送信の抽象化
- ハードウエアを問わず接続できるモジュール化
- NEC フォーマットとその派生フォーマットへの対応

# ハードウエア編
## 用意したもの
- 本体
 - ATtiny85
 - 赤外線LED x2 ……[Linkman 503IRC2V-2AD](https://www.marutsu.co.jp/pc/i/838650/) ([データシート](https://www.marutsu.co.jp/contents/shop/marutsu/datasheet/503IRC2V-2AD.pdf))
 - 抵抗……トランジスタのベース側に3.3kΩ、赤外線LEDの電流制限抵抗に10Ω
 - 増幅用トランジスタ……2SC1815、今回は相当品のKSC1815YTA
 - その他、ユニバーサル基板やリード線、ブレッドボードなど
- 開発環境
 - Arduino (ATtiny85の書き込み・リモコンのコード解析に使用)
 - 赤外線リモコンの受光素子と周辺部品……ローム RPM6938 ([データシート](https://docid81hrs3j1.cloudfront.net/medialibrary/2017/09/RPM6938_Rev.001.pdf))
- 接続する機器
 - [スイッチサイエンス FT231XS USB-シリアル変換ボード](http://ssci.to/2499)
 - ESP-WROOM-32 [スイッチサイエンス ESPr® Developer 32](http://ssci.to/3210)
 - [Raspberry Pi](https://raspberrypi.org/)
 - [BBC micro:bit](https://microbit.org/)

## 仕様と回路図

- 電源: 3.3V
- 通信速度: 9600bps
- ストップビット: 2
- パリティ: なし

![circuit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157092/5e74fa43-50ef-0e61-0714-96c686acce1e.png)

回路図のATtiny85から出ているRXDは接続先のTXDに、TXDはRXDに接続します。

## リモコンのコード解析
[過去の拙著](https://qiita.com/imaikazuki/items/d313bafb4be34f071c50#%E8%B5%A4%E5%A4%96%E7%B7%9A%E3%81%AE%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%82%92%E8%AA%BF%E3%81%B9%E3%82%8B)をそのまま使いました。今回もrawData配列はそのまま使えます。

# ソフトウエア編
## Arduinoのスケッチ
東芝のシーリングライト LEDH96040-LC と、そのリモコン FRC-194T(W) は、一見NECフォーマットにも見えるのですが機能によって信号が32bit長だったり64bit長だったりします。またHi/Loの持続時間も通常のNECフォーマットより100〜1000μ秒ほど長くなっています。NECフォーマット専用のプログラムでは対応できないため「Hi/Loの持続時間」をコンマ区切りで渡すことにしました。終端は"."(ピリオド)とし、これを受け取ったら送信します。

今回はNECフォーマットの派生のようですが、搬送波38kHz・1/3デューティであれば他のフォーマットにも対応できるでしょう。

[ATtiny85で赤外線リモコン用の波形を作る話 | 東京お気楽カメラ](http://okiraku-camera.tokyo/blog/?p=7480)を参考に以下のスケッチを作成し、ATtiny85に書き込みます。

```c
#include <avr/wdt.h>
#include <SoftwareSerial.h>
#define COMMAND_BUFFER_SIZE 150
#define OUTPUT_PIN _BV(1)

/**
 * ATTiny85 @ 8MHz を使い、Software Serial で受信したタイミングに
 * 沿って赤外線LEDを制御する。
 *
 */

SoftwareSerial serial(3, 4); // RX, TX
unsigned int commandBuffer[COMMAND_BUFFER_SIZE];
char c;
int index = 0;
int signalSize = 0;
unsigned int t;
boolean end = false;

void setup() {
  serial.begin(9600);

  // IR LED
  // http://okiraku-camera.tokyo/blog/?p=7480 から引用
  noInterrupts();
  DDRB = OUTPUT_PIN; // inputは0
  PORTB = 0; // 他は0
  ACSR |= 0x80; // アナログコンパレータ禁止
  ADCSRA &= 0x7f; // disable ADC
  wdt_disable();
  TIMSK = 0;  // disable Timer0/1 overflow interrupt.
  interrupts();
}

void mark() {
  // 38kHz, 1/3 duty
  // http://okiraku-camera.tokyo/blog/?p=7480 から引用、8MHz
  // に対応するよう改変
  OCR0A = OCR1C = 25;  // TOP value :  38kHz --> 26.3us.
  OCR0B = OCR1B = 6;  //  H period  : 6.4us.
  TCCR0A = B00100011; // COM0A: 00, COM0B: 10, WGM01-00: 11
  TCCR0B = B00001000; // FOC:00, WGM02:1, CS02-01: 000 (stop clock)
  GTCCR = B01100000; // OC1B cleared on compare match. Set when TCNT1 = $00.
  TCCR1 = B10000100; // CTC=1. clear TCNT1 on OCR1C==TCNT1. CS13-10 = 0100B (/8)
  TCNT1 = 0;
  TCCR0B |= B010;  // enable clock (/8).
  TCNT0 = OCR0A ;

}

void space() {
  TCCR0A = 0;
  TCCR0B = 0;
  TCCR1 = 0;
  GTCCR = 0;
}

void loop() {
  if(serial.available()) {
    c = serial.read();
    switch (c) {
      case '0': t = t * 10; break;
      case '1': t = t * 10 + 1; break;
      case '2': t = t * 10 + 2; break;
      case '3': t = t * 10 + 3; break;
      case '4': t = t * 10 + 4; break;
      case '5': t = t * 10 + 5; break;
      case '6': t = t * 10 + 6; break;
      case '7': t = t * 10 + 7; break;
      case '8': t = t * 10 + 8; break;
      case '9': t = t * 10 + 9; break;
      case ',':
        commandBuffer[index] = t;
        index++;
        t = 0;
        break;
      case '.':
        commandBuffer[index] = t;
        signalSize = index+2;
        t = 0;
        end = true;
        break;
    }
    if(COMMAND_BUFFER_SIZE <= index) {
      index = 0; // バッファがあふれたらいったん全て捨てる
    }
  }
  if(end) {
    for(index = 0; index < signalSize; index++) {
      if(0 == index % 2) {
        mark();
      } else {
        space();
      }
      delayMicroseconds(commandBuffer[index]);
    }
    space();
    end = false;
    index = 0;
    signalSize = 0;
  }
}
```

## ATtiny85へのスケッチの書き込み
[High-Low Tech – Programming an ATtiny w/ Arduino 1.6 (or 1.0)](http://highlowtech.org/?p=1695) を参考にスケッチを書き込みます。注意点は以下の通り。

- ArduinoからATmega328を外さない
- Programmerは"Arduino as ISP"を使う
- 内部クロックを使用し、周波数は8MHz
- まずブートローダを書き込め。話はそれからだ
- "ISP" といいつつ "In-System Programming"がうまくいかない。面倒でもATtiny85をプログラマに挿したり基板に戻したりする

# 使用例
東芝のシーリングライト用リモコン FRC-194T(W) の「全灯」ボタン チャネル2のコードを色々な方法で送ってみます。

## USB/Serial変換アダプタでMacにつなぎ、echoコマンドで送信

```bash
echo '10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.' | sudo tee /dev/cu.usbserial-SOMEDEVICE
```

## ESP32のMicroPythonで送信
UART0はREPLおよびWebREPLで使っているので、別のピンを使うことにします。[ESP-32とMicroPythonでCO2センサを読んでみた+α - Qiita](https://qiita.com/hogepiyo334/items/220001f621d6a8f4fabb) を参考に TXD, RXD はそれぞれ4番・5番としました。

![IMG_5695.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157092/6928458a-03fa-efe1-dff2-deb0724577ce.jpeg)


```python
import machine
uart = machine.UART(2, baudrate=9600, rx=5, tx=4)
uart.write("10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.")
```
このコードで無事にシーリングライトを制御できました。ひとまず目的は達成です。

## micro:bitから送信

本体のAボタンを押すとシーリングライトが点灯する仕組みにしました。

![IMG_5694.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157092/d87c5618-eb3d-a832-57da-0bdc4d81e343.jpeg)

![via microbit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157092/5e8c20c7-e680-e75d-ac43-d4984c8b9183.png)

ちなみに JavaScript だとこんな感じになるらしいです。

```javascript

input.onButtonPressed(Button.A, function () {
    serial.writeString("10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.")
})
serial.redirect(
SerialPin.P0,
SerialPin.P1,
BaudRate.BaudRate9600
)
basic.forever(function () {
	
})
```

余談ですが micro:bit は超絶簡単でいいですね。初めて使いましたが一気に好きになりました。

## Raspberry Piのシェルから送信
標準の TXD/RXD (14, 15) を使います。
![IMG_5696.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157092/6b12e21e-bdbb-dc51-cea5-a6f433a8c5bd.jpeg)

事前にUARTを有効にしておく必要があります。

```bash
sudo raspi-config nonint do_serial 0  # 0で有効化
sudo shutdown -r now  # 再起動
```

コマンド自体はMacのときと同様です。

```bash
echo '10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.' > /dev/serial0
```

## Raspberry PiのPythonで送信

pyserialモジュールを導入。

```bash
pip install pyserial
```

続いて本体のコード。

```python
import serial
s = serial.Serial('/dev/serial0', 9600)
s.write(b"10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.")
s.close()
```

## Arduinoから送信

[IRremote](https://github.com/z3t0/Arduino-IRremote)という強力なライブラリがあります。ただボードやコントローラによっては対応していない場合もあり、そういった場合に有効かと思います。

![IMG_5697.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/157092/ffa9647a-d139-5d57-0d0e-a0de055f054b.jpeg)

起動直後に「全灯」のコマンドを送信してみます。

```c
void setup() {
  char signal[] = "10200,5050, 650,1900, 650,1900, 650,1900, 650,600, 650,600, 700,1900, 650,1850, 700,1850, 700,600, 650,600, 650,1900, 650,1900, 650,600, 700,550, 700,600, 700,600, 650,1850, 700,1900, 650,1850, 700,600, 650,1900, 650,600, 650,650, 650,600, 650,650, 650,600, 650,600, 700,1850, 650,650, 650,1900, 650,1850, 650,1900, 650.";
  Serial.begin(9600);
  Serial.print(signal);
}

void loop() {
}

```

# まとめとか今後とか
- シェルから `echo` で家電を制御できるって楽しい
- Windows環境でも`COPY`コマンドでCOMnに書き込めば同様のことができるかも。BATで家電を制御できると楽しいですね
- シリアルの1対1接続と違ってI2Cだと1対nで接続できるので、そのうち I2C slave版も作るかも。ただシリアルの利便性も捨て難い……

# 謝辞
この工作は[調光君](https://qiita.com/imaikazuki/items/d313bafb4be34f071c50)2号の開発の過程で生まれました。すぐに何かの役に立つ品物でもないのに、家事を疎かにし実験や開発に没頭していても「仕事で帰りが遅いしストレス発散は大事だよね」と大目に見てくれる妻に感謝します。