Seeed XIAO RP2040とMAX485でDMX512制御

技術開発Arduino,DMX512,Seeed XIAO RP2040

概要

前回の記事・DMX Shield for ArduinoをリバースエンジニアリングでArduino UNOとMAX485を使ったDMX512制御について理解できたので、私の中でブームとなっているSeeed XIAO RP2040で同じことをしてみます。

Seeed XIAO RP2040は性能が良い・小さい・安いとメリットの大きいマイコンモジュールです。
2コアを生かしたマルチスレッド処理も出来るので、片方のコアはDMXを送信し続け、もう片方のコアでその他の作業をする方向で実装していきます。

実行環境

MCU: Seeed XIAO RP2040
IDE: Arduino IDE 1.8.19 windows(portable)

回路図

MAX485はAmazonで購入したモジュールを使用します。

以下が回路図です。
XIAO RP2040とMAX485モジュールを繋ぐだけなので、回路図というより配線図ですね。
RJ45は手頃なコネクタが無かったので、冒頭の写真のようにLANケーブルを切ってQIコネクタに変換して接続しています。

プログラミング

上手くいったスケッチ

結論から言うと、下記のコードで動作しました。
これをXIAO RP2040に書き込んで、LEDを接続したDMXデコーダに接続すると以下の動作になります。

チャンネルLEDの挙動
CH194.1%で点灯
CH266.7%で点灯
CH339.2%で点灯
CH411.8%で点灯

DMX信号は常に送信されており、メインルーチンとなるloop()の中でvalue[ch]を変更すると送信するデータが変更されるプログラムになっています。

今後はI2C通信やSPI通信、タイマー制御も同時にしたいので、可能な限りArduino標準の関数を使うことで他の機能を邪魔しないように作った結果、こうなりました。ここに辿り着いた経緯は後ほど説明します。

#define DE_PIN D0
#define RE_PIN D1
const char START_CODE = 0;

char value[513] = {0};

void setup() {
  /* GPIOの設定 */
  pinMode(DE_PIN, OUTPUT);
  pinMode(RE_PIN, OUTPUT);
  digitalWrite(DE_PIN, HIGH);
  digitalWrite(RE_PIN, LOW);

  /* DMX送信用のシリアルポートの設定 */
  Serial1.begin(49950, SERIAL_8N2);
}

/* メインルーチン(core0を使用) */
void loop() {
  value[1] = 240;
  value[2] = 170;
  value[3] = 100;
  value[4] = 30;
}

/* DMX送信用ルーチン(core1を使用) */
void loop1() {
  /* BreakとMABの送信 */
  uart_set_baudrate(uart0, 49950);
  Serial1.write(START_CODE);
  Serial1.flush();

  /* スタートコードとCH1~CH512の値を送信 */
  uart_set_baudrate(uart0, 250000);
  for (int i = 0; i <= 512; i++) {
    Serial1.write(value[i]);
  }
  Serial1.flush();
}

スケッチの解説

DMX Shield for ArduinoをリバースエンジニアリングではUARTのボーレート変更がカギだということがわかりました。ただ、Arduinoリファレンスでシリアル通信の関数を調べてもボーレートを通信中に変更する関数はありませんでした。

ArduinoのSerialライブラリでは、ボーレートの設定はSerial.begin()でしか行っていないようです。なのでbegin()のソースを確認しました。(これを探すのが大変だった…)

※SerialUART.cppを確認していますが、本当にこのbegin()を使っているか自信がありません。もしかしたら他ファイルの同名の関数が実行されているのかも…。詳しい方がいましたらコメントをお願いします。

void SerialUART::begin(unsigned long baud, uint16_t config) {
    if (_running) {
        end();
    }
    _overflow = false;
    _queue = new uint8_t[_fifoSize];
    _baud = baud;
    uart_init(_uart, baud);
    int bits, stop;
    uart_parity_t parity;

~~~ 以下省略 ~~~

uart_init()でボーレートを設定しているので、ソースを確認します。

uint uart_init(uart_inst_t *uart, uint baudrate) {
    invalid_params_if(UART, uart != uart0 && uart != uart1);

    if (clock_get_hz(clk_peri) == 0)
        return 0;

    uart_reset(uart);
    uart_unreset(uart);

#if PICO_UART_ENABLE_CRLF_SUPPORT
    uart_set_translate_crlf(uart, PICO_UART_DEFAULT_CRLF);
#endif

    // Any LCR writes need to take place before enabling the UART
    uint baud = uart_set_baudrate(uart, baudrate);
    uart_set_format(uart, 8, 1, UART_PARITY_NONE);

    // Enable the UART, both TX and RX
    uart_get_hw(uart)->cr = UART_UARTCR_UARTEN_BITS | UART_UARTCR_TXE_BITS | UART_UARTCR_RXE_BITS;
    // Enable FIFOs
    hw_set_bits(&uart_get_hw(uart)->lcr_h, UART_UARTLCR_H_FEN_BITS);
    // Always enable DREQ signals -- no harm in this if DMA is not listening
    uart_get_hw(uart)->dmacr = UART_UARTDMACR_TXDMAE_BITS | UART_UARTDMACR_RXDMAE_BITS;

    return baud;
}

uart_set_baudrate(uart, baudrate)がボーレートの設定用関数のようです。
念のため、ソースを確認します。

uint uart_set_baudrate(uart_inst_t *uart, uint baudrate) {
    invalid_params_if(UART, baudrate == 0);
    uint32_t baud_rate_div = (8 * clock_get_hz(clk_peri) / baudrate);
    uint32_t baud_ibrd = baud_rate_div >> 7;
    uint32_t baud_fbrd;

    if (baud_ibrd == 0) {
        baud_ibrd = 1;
        baud_fbrd = 0;
    } else if (baud_ibrd >= 65535) {
        baud_ibrd = 65535;
        baud_fbrd = 0;
    }  else {
        baud_fbrd = ((baud_rate_div & 0x7f) + 1) / 2;
    }

    // Load PL011's baud divisor registers
    uart_get_hw(uart)->ibrd = baud_ibrd;
    uart_get_hw(uart)->fbrd = baud_fbrd;

    // PL011 needs a (dummy) line control register write to latch in the
    // divisors. We don't want to actually change LCR contents here.
    hw_set_bits(&uart_get_hw(uart)->lcr_h, 0);

    // See datasheet
    return (4 * clock_get_hz(clk_peri)) / (64 * baud_ibrd + baud_fbrd);
}

引数のボーレートをレジスタのパラメータに変換して、レジスタ書き込み用関数を実行しているようです。
ということでuart_set_baudrate(uart, baudrate)を使えば、既製品のDMXシールドと同じ動作をさせられることになります。

信号波形の確認

DATA+とGND間の電圧波形をロジックアナライザで確認しました。
BreakとMABの長さがDMX Shield for Arduinoをリバースエンジニアリングで計算した時間とほぼ同じになっています。

Breakの長さ:180[us]
MABの長さ:43[us]

まとめ

Arduinoを使わずにSTM32とかPICとか普通のマイコンでやった方が簡単なのに…と何度も思いながら調べましたが、豊富なライブラリを使ってシンプルなコードを書くならArduinoなので頑張ってみました。