[Arduino互換機] 専用ライブラリを使わずにDMX信号を出力する

# TL;DR
- ArduinoをDMX出力装置として使う際に、ライブラリを使わずに出力パケットを作りました
    - AVRアーキテクチャでないArduino互換機を使ったら、既存のライブラリが動かなかったため
- Serialを駆使してDMX仕様に従ったパケットを作る
    - パケットの先頭部分にひと工夫必要（理解すればコピペで動く）
    - 長いBreak区間は通信レートを変更して作る

# コード
任意のシリアル出力ピンから、`LTC485`などのトランシーバICのデータ線に入力します。

```c++
void setup() {

  Serial1.begin(250000, SERIAL_8N2);

}

void loop() {

  // 直前のデータが送信され切るまで待つ
  Serial1.flush();
  
  // Breakセクションを送信するため一旦通信レートを遅くする
  Serial1.begin(90000, SERIAL_8N2);
  
  // Breakを送信
  Serial1.write(0);

  // 再度データが送信され切るまで待つ
  Serial1.flush();

  // 元の通信レートに戻す
  Serial1.begin(250000, SERIAL_8N2);
  
  // Start Codeの送信
  Serial1.write(0);

  // 各チャンネルデータの送信（ch数だけ繰り返す）
  for(int i = 0; i < 512; i++) {
    Serial1.write(values[i]);
  }

}
```

# 解説
## 事の顛末
なぜこんなことをやるハメになったのかという事ですが、普段DMX送信に常用していたライブラリ[DmxSimple](https://github.com/PaulStoffregen/DmxSimple)がAVRアーキテクチャを前提にしている（例えば[ESP8266](https://tech-blog.cerevo.com/archives/859/)などで使えない[^1]）ことが発覚し、仕方がないのでライブラリ内容を自前で実装することにした、というのが発端です。

```
fatal error: avr/io.h: No such file or directory
```

色々検索した結果[robertoostenveld/arduino](https://github.com/robertoostenveld/arduino/tree/master/esp8266_artnet_dmx512)にあったサンプルコードがわかりやすかったので、これを参考に実装しました。本稿はほとんどこのコードの解説です。

これまでライブラリに任せきりでものを作っていたので信号の中身については全く知らずジタバタしましたが、なんとか扱うことができました。順を追って解説します。

## 1. トランシーバICへの入力
DMXシールドなど専用基板を使わずにDMXを送信する場合、[LTC485](http://akizukidenshi.com/catalog/g/gI-02792/)などのトランシーバICを使うと思います[^2]。
ライブラリを通して使う場合、ICへの入力信号がどんなものなのか意識しないままブラックボックスにしてしまいがちですが、上のコードからもなんとなく読み取れるように、実はシリアルのDMXの信号をそのまま受け取っているだけです。
DMXケーブルはデータ線が2本あって、1つの信号を互いに逆位相になるようにして伝送していますが、このICは入力ピンからの信号を2本のデータ線に分配しているだけ、ということのようです。

## 2. DMXパケットの構成
### 仕様を調べる
DMXは基本的に単なるシリアル信号なので、ArduinoのSerialを用いて生成することができます。ただし、パケットの境界を示す先頭部分に他と異なる挙動が挟まっているため、この部分を生成するのにひと工夫が必要です。

![DMXパケットの構造](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101312/a32cf62f-05de-3b6b-2141-4db969757267.png)

DMXのパケットは上の図のようになっています。最初に長いLOW（**1: Break**）と長いHIGH（**2: MAB = Mark after Break**）があり、そのあとに各チャンネルのデータを格納する部分（**6: Frame**）が繰り返し並びます。

![データフレーム内の構造](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101312/e0a236f0-8ec4-5652-68dd-236c7acb474c.png)

Frameの中には、**3: Start**（LOW）と**5: Stop**（HIGH）のビットに挟まれて、各チャンネルのデータ値（**4: Data**）があります。この部分はシリアル通信としては一般的な構造です[^3]。

図中にもあるように、各セクションの長さ（時間）は仕様で規定されています。データ部分は250kbps = 4μs/bitの通信であるため比較的簡単に生成できますが、先頭の**1: Break**と**2: MAB**はイレギュラーな指定になります。

|No.|Name|ビット長|Time|
|--:|:-:|:--|:--|
|1|Break|   |88μs〜1s[^4]|
|2|MAB|   |8μs〜1s[^4]|
|3|Start|1|4μs x 1|
|4|Data|8|4μs x 8|
|5|End|2|4μs x 2|

## 実装する
### ストップビット数の設定

データフレーム部分は先述の通り、250kbpsの普通のシリアル信号なので、Arduino標準のSerialを使用して生成が可能です。が、実はArduinoのシリアルのフレームはデフォルトでストップビット（**5: Stop**）が1ビットとなっているので、通信レートとともに設定を行います。

`Serial.begin()`は第1引数に通信レートを、第2引数に通信モードを指定可能で、1フレームのビット数・パリティビットの有無・ストップビットの長さを変更できます（[リファレンス](https://www.arduino.cc/reference/jp/language/functions/communication/serial/begin/)）。今回は8bit・パリティなし・2bitなので、以下のようになります。

```c++
Serial1.begin(250000, SERIAL_8N2);
```

これで、例えば`Serial1.write(255)`などとするだけで、TXピンからフレーム（**6: Frame**）が送出できるようになりました。

### パケット先頭部の生成

パケット先頭にある**1: Break**と**2: MAB**はひと工夫必要です。`0`を連続してシリアルから書き出して生成できそうな気が一瞬しますが、Stop bitが存在するため、書き出した回数だけHIGHの区間が生じてしまいます。

今回のコードでは、**そもそも通信レートを遅くしてしまう**ことで対処しています。250kbpsでは1bitにかかる時間は4μsでしたが、コード中の90kbpsまで落とせば11μs。この状態で`0`を送出すると、

- LOWの区間がStart bit (1bit) + Data bit (8bit) で計9bit = 99μs
- 直後にHIGHの区間がStop bit (2bit)で22μs

となり、それぞれちょうど**1: Break**（88μs〜1s）と**2: MAB**（8μs〜1s）の要件範囲に入ります！

この、「一旦通信レートを落として0を出力する」という処理をチャンネルのデータ列を送る前に挟むことで、**1: Break**と**2: MAB**を出力することができる仕掛けです。

あとはこれを繰り返せば、DMXデータを継続して送信することができます。

# やってみて感想

現場でやっつけで動かそうとしたら既存ライブラリが使えないと判明し、時間の都合上そこから30分くらいでこれを調べて実装するハメになって泣きそうになりました。

専用ライブラリもあるし楽勝だ～と思って使っていたDMXなので、自分で実装しなければいけないと思うとかなりキツいイメージでしたが、実際にはそこまで複雑なわけでもなく、ブラックボックスだった仕組みの理解にもつながったので大変勉強になりました。他の方にも参考になれば幸いです。

## ちなみに

作ったのはこれです（見てくれた人のツイートを勝手に借用🙇）。何でも動かせるDMX楽しい！

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/%E5%B9%B3%E7%A0%82%E3%82%A2%E3%83%BC%E3%83%88%E3%83%A0%E3%83%BC%E3%83%B4%E3%83%A1%E3%83%B3%E3%83%88?src=hash&amp;ref_src=twsrc%5Etfw">#平砂アートムーヴメント</a> <br>カリヨン………神聖……………<br>見れてよかった…… <a href="https://t.co/mrqWIsbWW8">pic.twitter.com/mrqWIsbWW8</a></p>&mdash; かーぎ (@kaaagi_tkb) <a href="https://twitter.com/kaaagi_tkb/status/1134798784733503488?ref_src=twsrc%5Etfw">2019年6月1日</a></blockquote>


[^1]: ライブラリのIssueでの作者コメント: https://github.com/PaulStoffregen/DmxSimple/issues/12#issuecomment-396667827
[^2]: これの存在を知らなかった方は[こちら](https://qiita.com/Ina3/items/56a89c3cf8958bb6a0f8)など。自分も初めて扱った時はこの記事を参考にさせていただきました。
[^3]: シリアル通信一般の電気的仕様については[このページ](http://www.japansensor.co.jp/faq/940/index.html)などが詳しい（知りませんでした）
[^4]: 関係ないですが許容範囲広すぎでは。どうしてこうなった。