TL;DR
- ArduinoをDMX出力装置として使う際に、ライブラリを使わずに出力パケットを作りました
- AVRアーキテクチャでないArduino互換機を使ったら、既存のライブラリが動かなかったため
- Serialを駆使してDMX仕様に従ったパケットを作る
- パケットの先頭部分にひと工夫必要(理解すればコピペで動く)
- 長いBreak区間は通信レートを変更して作る
コード
任意のシリアル出力ピンから、LTC485
などのトランシーバICのデータ線に入力します。
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がAVRアーキテクチャを前提にしている(例えばESP8266などで使えない1)ことが発覚し、仕方がないのでライブラリ内容を自前で実装することにした、というのが発端です。
fatal error: avr/io.h: No such file or directory
色々検索した結果robertoostenveld/arduinoにあったサンプルコードがわかりやすかったので、これを参考に実装しました。本稿はほとんどこのコードの解説です。
これまでライブラリに任せきりでものを作っていたので信号の中身については全く知らずジタバタしましたが、なんとか扱うことができました。順を追って解説します。
1. トランシーバICへの入力
DMXシールドなど専用基板を使わずにDMXを送信する場合、LTC485などのトランシーバICを使うと思います2。
ライブラリを通して使う場合、ICへの入力信号がどんなものなのか意識しないままブラックボックスにしてしまいがちですが、上のコードからもなんとなく読み取れるように、実はシリアルのDMXの信号をそのまま受け取っているだけです。
DMXケーブルはデータ線が2本あって、1つの信号を互いに逆位相になるようにして伝送していますが、このICは入力ピンからの信号を2本のデータ線に分配しているだけ、ということのようです。
2. DMXパケットの構成
仕様を調べる
DMXは基本的に単なるシリアル信号なので、ArduinoのSerialを用いて生成することができます。ただし、パケットの境界を示す先頭部分に他と異なる挙動が挟まっているため、この部分を生成するのにひと工夫が必要です。
DMXのパケットは上の図のようになっています。最初に長いLOW(1: Break)と長いHIGH(2: MAB = Mark after Break)があり、そのあとに各チャンネルのデータを格納する部分(6: Frame)が繰り返し並びます。
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〜1s4 | |
2 | MAB | 8μs〜1s4 | |
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フレームのビット数・パリティビットの有無・ストップビットの長さを変更できます(リファレンス)。今回は8bit・パリティなし・2bitなので、以下のようになります。
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楽しい!
#平砂アートムーヴメント
— かーぎ (@kaaagi_tkb) 2019年6月1日
カリヨン………神聖……………
見れてよかった…… pic.twitter.com/mrqWIsbWW8