DMX Shield for Arduinoをリバースエンジニアリング

2023年10月16日技術開発Arduino,DMX512

概要

ArduinoでDMXレコーダを作成しましたが、リセットシークエンスの設定で相性問題が発生してしまいました。
そこで既製品のやり方を勉強した上で設計に反映したいと考え、DMX Shield for Arduinoを購入したのでリバースエンジニアリングしてみます。
既製品をそのまま使ってもいいのですが、UNOはUARTが1chしかなく書き込みのたびにジャンパピンを付け替えるのは不便で、更にメモリが少なくてOLED等の他のモジュールが使いづらいので最終的にはRP2040等でやりたいと考えています。

参考サイト

実行環境

MCU:Arduino UNO
IDE:Arduino IDE 1.8.19

購入した基板

DMX Shield for Arduino v1.r2

まずは使ってみる

DMX512-Aについて

DMX512の信号の規格は下記です。
物理層はRS-485と同じ。
※菅公房様のHPより引用

・UARTの設定

項目設定値
速度250kbps
パリティチェックなし
スタートビット1bit
ストップビット2bit

・信号波形

#名称長さ備考
Break88uS(22bit)~1SLowレベル(シリアルコントローラによってはブレーク機能あり、なければ手動)
Mark After Break (MAB)8uS(2bit)~1S Highレベル(1990年の改訂前は4uS以上)Highレベル(1990年の改訂前は4uS以上)
Start Bit1bitデータの始まりを示す、Lowレベル
Start Code8bitデータ照明機器は「0」
Ch # Data8bitデータ
調光レベル「0~255」 × 最大512(最小24)
Stop Bit2bitデータの終わりを示す、Highレベル
Mark Time Between Slots
(Frame Between Time)
0S~1SHighレベル(Stop Bitから次のStart Bitまでの間)
Mark Before Break (MBB)0S~1S
Highレベル(フレーム間)

特に重要なのが、通常のUARTと規格が異なる①②。
相性問題もここで発生していると思われるので、この2つをどのように実現しているかを確認していきます。

プログラミング

サンプルコードの「DMX Master」のコメントを削除したものを使用しました。
まずCH1~CH25に50%を設定し、CH1を0.1秒ごとに少しずつ明るくしていくプログラムです。

#include <Conceptinetics.h>

#define DMX_MASTER_CHANNELS   100
#define RXEN_PIN                2

DMX_Master dmx_master(DMX_MASTER_CHANNELS, RXEN_PIN);

void setup() {
  dmx_master.enable ();
  dmx_master.setChannelRange(1, 25, 127);
}

void loop() {
  static int dimmer_val;
  dmx_master.setChannelValue(1, dimmer_val++);
  delay (100);
}

動作確認

OUT側端子のDATA+、GND間の電圧をロジックアナライザで確認しました。
※詳細な起動方法は参考サイトを参照。

①Breakが180us、②MABが45usとなっていた。規格の下限に対してある程度余裕を持った値で動作しているようです。

リバースエンジニアリングしてみる

回路図

シールドの現物をテスターで当たっていくことで回路図を起こしてみたところ、よくあるMAX485の周辺回路でした。
BreakとMABはソフトウェアのみで制御していると思われます。
※パスコンの容量は重要ではないと判断したので確認していません。

DMX_Libraryのソースを確認

Arduino用Libraryの中身を確認してみます。
インスタンス生成のソースは下記。

DMX_Master::DMX_Master ( uint16_t maxChannel, int readEnablePin )
: m_frameBuffer ( maxChannel + DMX_STARTCODE_SIZE ), 
  m_autoBreak ( 1 )
{
    //m_frameBuffer[0]に0x00をセット
    setStartCode ( DMX_START_CODE );
    
    //DEをOUTPUTに設定
    __re_pin = readEnablePin;
    pinMode ( __re_pin, OUTPUT );
    
    //レジスタUCSR0Cに0x0Eをセット(ストップビット/2bit、データ長/8bit)
    //レジスタUCSR0Bに0x00をセット
    ::SetISRMode ( isr::Disabled );
}

次にenable()のソースを確認。

void DMX_Master::enable  ( void )
{
    __dmx_master = this;  

    if ( m_autoBreak )
        ::SetISRMode ( isr::DMXTransmit );
    else
        ::SetISRMode ( isr::DMXTransmitManual );
}

SetISRMode(isr::DMXTransmit)の中身を確認。

case isr::DMXTransmit:
    //データレジスタの初期化
    DMX_UDR         = 0x0;
    //DEをHIGHにセット
    readEnable      = HIGH;
    //送信ステータスをBreakに設定
    __isr_txState   = isr::DmxBreak; 
    //送信許可 & 送信完了割込み許可
    DMX_UCSRB       = (1<<DMX_TXEN) | (1<<DMX_TXCIE);
    break;

送信割込み許可をすることにより割込みが発生するので、割込み処理のソースを確認。
※今回の確認で関係のない部分は省略しています。

ISR (USART_TX)
{
	static uint16_t			current_slot;

	switch ( __isr_txState ){
    	case isr::DmxBreak:
            //ボーレートを49950bpsに設定
    		DMX_UCSRA = 0x0;
            DMX_UBRRH = (unsigned char)(((F_CPU + DMX_BREAK_RATE * 8L) / (DMX_BREAK_RATE * 16L) - 1)>>8);
            DMX_UBRRL = (unsigned char) ((F_CPU + DMX_BREAK_RATE * 8L) / (DMX_BREAK_RATE * 16L) - 1);

            //データレジスタに0x00をセット
            DMX_UDR   = 0x0;
            
            if ( __isr_txState ==  isr::DmxBreak )
                __isr_txState = isr::DmxStartByte;
            
            break;
    
    	case isr::DmxStartByte:
            //ボーレートを250,000bpsに設定
    		DMX_UCSRA = 0x0;
            DMX_UBRRH = (unsigned char)(((F_CPU + DMX_BAUD_RATE * 8L) / (DMX_BAUD_RATE * 16L) - 1)>>8);
    		DMX_UBRRL = (unsigned char) ((F_CPU + DMX_BAUD_RATE * 8L) / (DMX_BAUD_RATE * 16L) - 1);	

            //データレジスタにスタートコード(0x00)をセット		
            current_slot = 0;	
            DMX_UDR = __dmx_master->getBuffer()[ current_slot++ ];
    		__isr_txState = isr::DmxTransmitData;
    		break;
    	
    
    	case isr::DmxTransmitData:
            #ifdef DMX_IBG
                _delay_us (DMX_IBG);
            #endif
            
            //バッファに入っているデータを取得してデータレジスタにセット
    		DMX_UDR = __dmx_master->getBuffer().getSlotValue( current_slot++ );
    			
    		//512chまで送信が終わったらBreakに戻る
    		if ( current_slot >= DMX_MAX_FRAMESIZE )
            {
    		    if ( __dmx_master->autoBreakEnabled () )
                    __isr_txState = isr::DmxBreak;
                else
                    SetISRMode ( isr::DMXTransmitManual );
    	    }
    		break;
    }
}

BreakとMABはボーレートを一時的に49950bpsに変更して0x00を送信することで実現していました。(MABはストップビット)
念のため計算すると、

$$ \frac{1}{49950[bps]}\approx20.02[us] $$ $$ Break:22.02[us] \times 8[bit] = 176.16[us] $$ $$ MAB:22.02[us] \times 2[bit] = 44.04[us] $$

との結果で測定結果に近い値となったので、この理解で間違っていなさそうです。

まとめ

既製品の回路とライブラリで実績のある制御方法を勉強することが出来たので、次回は自作回路で試してみたいと思います。