1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

M5Stack Core2 + ISOLATED RS485 Unitで作るDMX512モニター

1
Last updated at Posted at 2025-12-10

M5Stack Core2 + ISOLATED RS485 Unitで作るDMX512モニター

はじめに

舞台照明やイベント現場で使われるDMX512信号。デバッグやトラブルシューティング時に「今、何チャンネルに何の値が来ているか?」をサッと確認できるモニターがあると便利です。

本記事では、M5Stack Core2ISOLATED RS485 Unitを使って、コンパクトなDMX512モニターを作成する方法を紹介します。

M5Stack Core2

🤖 本プロジェクトは Claude Opus 4.5 との協働で開発しました。 AIペアプログラミングにより、約2時間で動作するDMXモニターを完成させることができました。開発過程については後述します。

特徴

  • 📺 60チャンネル同時表示(10列×6行)
  • 📊 バーグラフ表示で視覚的に値を確認
  • 🔢 10進/16進表示切り替え
  • 📄 ページ切り替えでCH1-512まで全チャンネル確認可能
  • 電気的絶縁でDMXラインを保護
  • 🔋 バッテリー駆動で現場でコードレス運用可能
  • 📐 コンパクト設計(54×54×24mm + ユニット)

必要なもの

部品 型番 参考価格 購入先
M5Stack Core2 - ¥8,976 スイッチサイエンス / M5Stack公式
ISOLATED RS485 Unit U094 ¥1,826 スイッチサイエンス / M5Stack公式
Grove ケーブル - 付属 Core2に付属
DMXケーブル XLR 3pin/5pin - お手持ちのもの

💡 購入のヒント

  • スイッチサイエンスは国内発送で早い(1-2日)
  • M5Stack公式は海外発送だが品揃えが豊富
  • Amazonでも購入可能だが、正規代理店からの購入を推奨

ISOLATED RS485 Unit について

ISOLATED RS485 Unit

M5StackのISOLATED RS485 Unitは、絶縁型RS485トランシーバ「CA IS3082W」を搭載しています。

主な特徴:

  • 電気的絶縁(2500Vrms)で外部機器との分離
  • 自動方向切り替え(ENピン不要)
  • 最大10Mbpsの高速通信対応
  • 3.3V/5V両対応

💡 自動方向切り替えにより、ENピンの制御が不要でソフトウェアがシンプルになります。

配線

Port A 接続

M5Stack Core2のPort A(赤いGroveコネクタ)にISOLATED RS485 Unitを接続します。

┌─────────────────────────────────────────┐
│  M5Stack Core2          ISOLATED RS485  │
│                                         │
│  Port A (GPIO32/33)                     │
│  ┌───────────┐        ┌───────────┐     │
│  │ GND ──────┼────────┤ GND       │     │
│  │ 5V  ──────┼────────┤ 5V        │     │
│  │ G32 ──────┼────────┤ RXD       │     │
│  │ G33 ──────┼────────┤ TXD       │     │
│  └───────────┘        └───────────┘     │
│                              │          │
│                        RS485側          │
│                        ┌─────────┐      │
│                        │ A → DMX+│      │
│                        │ B → DMX-│      │
│                        │ G → GND │      │
│                        └─────────┘      │
└─────────────────────────────────────────┘

ピン対応表

Core2 Port A GPIO ISOLATED RS485 方向
白線 GPIO33 TXD ← (入力)
黄線 GPIO32 RXD → (出力)
赤線 5V 5V 電源
黒線 GND GND GND

⚠️ 重要: ESP32から見ると、RS485ユニットのTXDがESP32のRX、RS485ユニットのRXDがESP32のTXになります。

DMX側接続

ISOLATED RS485 DMX (XLR)
A Pin 3 (Data+)
B Pin 2 (Data-)
G Pin 1 (GND)

開発環境

PlatformIO 設定

platformio.ini:

[env:m5stack-core2-iso485]
platform = espressif32
board = m5stack-core2
framework = arduino
board_build.f_flash = 80000000L
board_build.f_cpu = 240000000L
monitor_speed = 115200
upload_speed = 921600
monitor_filters = time, esp32_exception_decoder
build_flags = 
    -DCORE_DEBUG_LEVEL=3
build_type = release
lib_deps = 
    M5Stack/M5Unified
    M5Stack/M5GFX
    https://github.com/someweisguy/esp_dmx.git

使用ライブラリ

ライブラリ バージョン 用途
M5Unified 0.2.x M5Stack共通API
M5GFX 0.2.x グラフィック描画
esp_dmx 4.1.0 DMX512受信

ソースコード

全体構成

/**
 * DMX Monitor for M5Stack Core2 + ISOLATED RS485 Unit
 * 
 * 接続:
 *   ISOLATED RS485 Unit (Port A):
 *     - TXD: GPIO33 (ESP32のRXとして使用)
 *     - RXD: GPIO32 (ESP32のTXとして使用)
 *     - 5V:  5V
 *     - GND: GND
 *   RS485側:
 *     - A → DMX+ (Data+)
 *     - B → DMX- (Data-)
 *     - GND → DMX GND
 */

#include <M5Unified.hpp>
#include <M5GFX.h>
#include <esp_dmx.h>

// ========================================
// DMX設定(Core2 + ISOLATED RS485 Unit - Port A)
// ========================================
#define DMX_RX_PIN      33    // GPIO33 (Port A) ← ユニットのTXDに接続
#define DMX_TX_PIN      32    // GPIO32 (Port A) → ユニットのRXDに接続
#define DMX_EN_PIN      -1    // ENピンなし(IS3082Wは自動方向切り替え)

const dmx_port_t DMX_PORT = DMX_NUM_1;  // UART1を使用

DMX初期化

esp_dmx v4.x では新しいAPI構造を使用します:

void initDmx() {
    Serial.println("Initializing DMX...");
    
    // DMX設定(esp_dmx v4 API)
    dmx_config_t config = {
        .interrupt_flags = ESP_INTR_FLAG_IRAM,
        .root_device_parameter_count = 0,
        .sub_device_parameter_count = 0,
        .model_id = 0,
        .product_category = 0,
        .software_version_id = 0x00010000,
        .software_version_label = "1.0.0",
        .queue_size_max = 0
    };
    
    // DMXドライバインストール
    if (!dmx_driver_install(DMX_PORT, &config, nullptr, 0)) {
        Serial.println("DMX driver install failed!");
        return;
    }
    
    // GPIOピン設定
    if (!dmx_set_pin(DMX_PORT, DMX_TX_PIN, DMX_RX_PIN, DMX_EN_PIN)) {
        Serial.println("DMX set pin failed!");
        return;
    }
    
    Serial.printf("DMX initialized: RX=GPIO%d, TX=GPIO%d\n", DMX_RX_PIN, DMX_TX_PIN);
}

DMX受信処理

static uint8_t dmx_data[DMX_PACKET_SIZE];  // 513バイト
static bool dmx_signal_detected = false;

void processDmxReceive() {
    dmx_packet_t packet;
    
    // DMXパケット受信(ノンブロッキング)
    if (dmx_receive(DMX_PORT, &packet, DMX_TIMEOUT_TICK)) {
        if (!packet.err) {
            // データ読み取り
            dmx_read(DMX_PORT, dmx_data, DMX_PACKET_SIZE);
            dmx_signal_detected = true;
            last_dmx_packet = millis();
            dmx_data_length = packet.size;
        }
    }
    
    // タイムアウト検出
    if (dmx_signal_detected && (millis() - last_dmx_packet > 3000)) {
        dmx_signal_detected = false;
        Serial.println("DMX signal lost!");
    }
}

タッチ操作

Core2のタッチスクリーンで操作します:

void handleButtons() {
    M5.update();
    
    auto touch = M5.Touch.getDetail();
    if (touch.wasPressed()) {
        int tx = touch.x;
        int ty = touch.y;
        
        // 下部1/3: ページ切り替え
        if (ty > 160) {
            if (tx < 107) {
                // 左: 前の60ch
                display_start_ch -= 60;
                if (display_start_ch < 1) display_start_ch = 1;
            } else if (tx > 213) {
                // 右: 次の60ch
                display_start_ch += 60;
                if (display_start_ch > 453) display_start_ch = 453;
            } else {
                // 中央: 表示モード切り替え
                dmx_display_mode = (dmx_display_mode + 1) % 3;
            }
        }
    }
}

操作方法

タッチ操作

画面下部を3分割してタッチ操作に対応:

エリア 操作
左(◀) 前の60チャンネルへ
中央 表示モード切り替え
右(▶) 次の60チャンネルへ

表示モード

モード 表示内容
0 10進数のみ(0-255)
1 バーグラフのみ
2 16進数 + 10進数

ステータス表示

画面下部にDMX受信状態を表示:

DMX OK  25fps  Len:128 (128-512)
項目 説明
DMX OK / NO SIGNAL 受信状態
fps 秒間受信パケット数
Len 最新パケット長
(min-max) 受信したパケット長の範囲

注意点・制限事項

Port A の本来の用途

Port A は通常 I2C Grove ポートとして使用されます。ISOLATED RS485 Unitを接続すると、他のI2C Groveデバイスは接続できなくなります。

ただし、Core2の内蔵I2C(タッチコントローラ、電源管理IC)は別バスのため影響ありません。

GPIO32/33 の特性

  • どちらも ADC1 チャンネル
  • WiFi使用時はADC1が使用できない制限がありますが、今回はADCとして使用しないため問題なし
  • ブートモード選択には関与しない(strapping pinではない)

トラブルシューティング

DMXが受信できない

  1. 配線確認: TXDとRXDが逆になっていないか
  2. A/B確認: DMX+とDMX-が逆になっていないか
  3. GND接続: DMX機器とのGND接続を確認
  4. 終端抵抗: 長距離の場合は120Ω終端抵抗を検討

「NO SIGNAL」表示のまま

  • DMX送信機が正常に動作しているか確認
  • 別のDMXテスターで信号を確認

完成品

タッチ操作でページ切り替え・表示モード変更ができる、コンパクトなDMX512モニターの完成です!

スクリーンショット 2025-12-10 090329.png

M5Stack Core2 + ISOLATED RS485 Unit でDMX信号を受信している様子。CH181-240を表示中、緑のバーグラフで各チャンネルの値が一目でわかる。

バッテリー駆動について

M5Stack Core2は390mAhのリチウムポリマーバッテリーを内蔵しており、USB電源なしで動作します。

項目 仕様
バッテリー容量 390mAh (3.7V)
消費電流(実測) 約120-150mA
連続稼働時間 約2.5〜3時間
充電時間 約2時間(USB Type-C)
サイズ 54 × 54 × 16mm(Core2本体)

💡 現場での運用ヒント

  • 画面輝度を下げると稼働時間が延びる(M5.Display.setBrightness(64)
  • モバイルバッテリー接続で長時間運用可能
  • 充電しながらの使用もOK

市販のDMXテスター(数万円)と比較して、約10,000円でバッテリー駆動のコンパクトモニターが作れるのは大きなメリットです。

参考リンク

関連商品

本記事で紹介したISOLATED RS485 Unit以外にも、M5StackにはDMX関連の製品があります。用途に応じて選択してください。

項目 内容
製品ページ DMX Base - M5Stack
特徴 SK9822 LEDコントローラー内蔵、XLR端子付き
用途 DMX制御のLEDストリップ用途

DMX Unit(絶縁なし・コンパクト)

項目 内容
製品ページ Unit-DMX - M5Stack
特徴 XLR 3ピン端子付き、コンパクト
注意点 非絶縁タイプ
用途 手軽にDMX送受信したい場合

RS485 Module(M5Stack用モジュール)

項目 内容
製品ページ RS485 Module - M5Stack Shop
特徴 本体にスタック可能なモジュール形式
用途 一体型で使いたい場合

RS485 Unit(スイッチサイエンス)

項目 内容
購入先 スイッチサイエンス
特徴 国内発送で入手しやすい
注意点 非絶縁タイプ(ISOLATED版ではない)

選び方のポイント

製品 絶縁 XLR端子 LED制御 おすすめ用途
ISOLATED RS485 Unit DMXモニター(本記事)
DMX Unit 簡易DMXテスター
DMX Base LED照明制御
RS485 Module スタック型構成

💡 推奨: 現場で使うならISOLATED RS485 Unit(絶縁タイプ)を推奨。DMXラインのグラウンドループ問題を回避でき、接続機器を保護します。


AI協働開発の過程

本プロジェクトは、GitHub Copilot(Claude Opus 4.5) とのペアプログラミングで開発しました。従来なら1日以上かかりそうな作業が、AIとの協働により約2時間で完成しました。

開発タイムライン

時間 作業内容
0:00 「Core2とISOLATED RS485でDMXモニター作りたい」と依頼
0:10 esp_dmx v4 API の仕様確認、基本構造の設計
0:30 初版コード生成、ビルド成功
0:45 動作テスト → DMX受信できず
1:00 Port Aピン設定の修正(GPIO32/33)
1:15 TX/RXピン入れ替えの試行
1:30 動作確認成功! 🎉
1:45 表示モード切り替え機能追加
2:00 ドキュメント整備、Qiita記事作成

AIとの対話の様子

: 「ISOLATED RS485 UnitをPort Aに繋いでDMX受信したい」

Claude: 「Port AはGPIO32/33です。ISOLATED RS485 UnitのIS3082Wは自動方向切り替えなのでENピンは-1で設定します」

→ ビルドは通るが受信できない...

: 「動きません」

Claude: 「TX/RXが逆かもしれません。ESP32から見ると、ユニットのTXDがESP32のRX、ユニットのRXDがESP32のTXになります。入れ替えてみましょう」

成功!

AI協働開発で良かった点

  1. esp_dmx v4 の新API をすぐに把握してくれた
  2. ピン配置の問題 を論理的に切り分けて解決
  3. コード生成が速い - 約500行のコードを数分で生成
  4. ドキュメント作成 も同時に依頼できる

人間が担当した部分

  • ハードウェアの接続・配線
  • 実機での動作確認
  • 「動かない」→「動いた」の判断
  • 最終的な品質チェック

💡 AIは万能ではない: ピン配置の問題など、ハードウェアに関わる部分は実機での確認が必要です。AIは仮説を立てて修正案を提示してくれますが、最終確認は人間の仕事です。

開発環境

  • VS Code + GitHub Copilot Chat
  • AIモデル: Claude Opus 4.5 (Preview)
  • OS: Windows 11

ライセンス

MIT License

完全なソースコード

src_dmx_monitor_core2_iso.cpp(クリックで展開)
/**
 * DMX Monitor for M5Stack Core2 + ISOLATED RS485 Unit
 * 
 * ISOLATED RS485ユニット(CA IS3082W絶縁トランシーバ)をPort Aに接続して
 * DMX512信号を受信し、Core2のディスプレイにチャンネル値を表示
 * 
 * 接続:
 *   ISOLATED RS485 Unit (Port A):
 *     - TXD: GPIO33
 *     - RXD: GPIO32
 *     - 5V:  5V
 *     - GND: GND
 *   RS485側:
 *     - A → DMX+ (Data+)
 *     - B → DMX- (Data-)
 *     - GND → DMX GND
 */

#include <M5Unified.hpp>
#include <M5GFX.h>
#include <esp_dmx.h>
#include <esp_intr_alloc.h>

// ========================================
// DMX設定(Core2 + ISOLATED RS485 Unit - Port A)
// ========================================
#define DMX_RX_PIN      33    // GPIO33 (Port A) ← ユニットのTXDに接続
#define DMX_TX_PIN      32    // GPIO32 (Port A) → ユニットのRXDに接続
#define DMX_EN_PIN      -1    // ENピンなし(IS3082Wは自動方向切り替え)

const dmx_port_t DMX_PORT = DMX_NUM_1;  // UART1を使用
#define DMX_CH_COUNT    512   // 表示対象チャンネル数
#define DMX_TIMEOUT_MS  3000  // DMX信号タイムアウト(ms)
#define DMX_TIMEOUT_TICK pdMS_TO_TICKS(100)  // 受信タイムアウト

// DMXデータ
static uint8_t dmx_data[DMX_PACKET_SIZE];
static bool dmx_signal_detected = false;
static unsigned long last_dmx_packet = 0;
static int dmx_packet_count = 0;
static int dmx_packet_per_sec = 0;
static unsigned long last_count_reset = 0;
static int dmx_data_length = 0;
static int dmx_data_length_min = 9999;
static int dmx_data_length_max = 0;

// 表示設定
static int display_start_ch = 1;
static const int DISPLAY_COLS = 10;
static const int DISPLAY_ROWS = 6;
static const int DISPLAY_CH_COUNT = DISPLAY_COLS * DISPLAY_ROWS;

// 前回値(差分更新用)
static uint8_t prev_dmx_values[DMX_PACKET_SIZE];
static bool screen_initialized = false;
static int prev_packet_per_sec = -1;
static int prev_data_length = -1;
static int prev_len_min = -1;
static int prev_len_max = -1;
static int prev_start_ch = -1;
static int dmx_display_mode = 0;  // 0=10進のみ, 1=数値なし, 2=16進+10進

// スプライト(高速描画用)
static M5Canvas cellSprite(&M5.Display);
static bool sprite_initialized = false;

// ========================================
// 小型数字フォント(4x6ピクセル)
// ========================================
static const uint8_t smallDigits[16][6] = {
    {0b0110, 0b1001, 0b1001, 0b1001, 0b1001, 0b0110}, // 0
    {0b0010, 0b0110, 0b0010, 0b0010, 0b0010, 0b0111}, // 1
    {0b0110, 0b1001, 0b0010, 0b0100, 0b1000, 0b1111}, // 2
    {0b0110, 0b1001, 0b0010, 0b0001, 0b1001, 0b0110}, // 3
    {0b0001, 0b0011, 0b0101, 0b1001, 0b1111, 0b0001}, // 4
    {0b1111, 0b1000, 0b1110, 0b0001, 0b1001, 0b0110}, // 5
    {0b0110, 0b1000, 0b1110, 0b1001, 0b1001, 0b0110}, // 6
    {0b1111, 0b0001, 0b0010, 0b0010, 0b0100, 0b0100}, // 7
    {0b0110, 0b1001, 0b0110, 0b1001, 0b1001, 0b0110}, // 8
    {0b0110, 0b1001, 0b1001, 0b0111, 0b0001, 0b0110}, // 9
    {0b0110, 0b1001, 0b1111, 0b1001, 0b1001, 0b1001}, // A
    {0b1110, 0b1001, 0b1110, 0b1001, 0b1001, 0b1110}, // B
    {0b0110, 0b1001, 0b1000, 0b1000, 0b1001, 0b0110}, // C
    {0b1110, 0b1001, 0b1001, 0b1001, 0b1001, 0b1110}, // D
    {0b1111, 0b1000, 0b1110, 0b1000, 0b1000, 0b1111}, // E
    {0b1111, 0b1000, 0b1110, 0b1000, 0b1000, 0b1000}, // F
};

// スプライトに小さい数字を描画
void drawSmallDigitSprite(M5Canvas* spr, int x, int y, int digit, uint16_t color, int scale) {
    if (digit < 0 || digit > 15) return;
    for (int row = 0; row < 6; row++) {
        for (int col = 0; col < 4; col++) {
            if (smallDigits[digit][row] & (0b1000 >> col)) {
                if (scale == 1) {
                    spr->drawPixel(x + col, y + row, color);
                } else {
                    spr->fillRect(x + col * scale, y + row * scale, scale, scale, color);
                }
            }
        }
    }
}

// LCDに小さい数字を描画
void drawSmallDigit(int x, int y, int digit, uint16_t color, int scale) {
    if (digit < 0 || digit > 15) return;
    for (int row = 0; row < 6; row++) {
        for (int col = 0; col < 4; col++) {
            if (smallDigits[digit][row] & (0b1000 >> col)) {
                if (scale == 1) {
                    M5.Display.drawPixel(x + col, y + row, color);
                } else {
                    M5.Display.fillRect(x + col * scale, y + row * scale, scale, scale, color);
                }
            }
        }
    }
}

// 2桁数字を描画(LCD)
void drawSmallNumber2(int x, int y, int num, uint16_t color, int scale) {
    char buf[3];
    snprintf(buf, sizeof(buf), "%2d", num);
    int char_w = 5 * scale;
    for (int i = 0; i < 2; i++) {
        char c = buf[i];
        if (c == ' ') continue;
        int digit = c - '0';
        drawSmallDigit(x + i * char_w, y, digit, color, scale);
    }
}

// ========================================
// DMX初期化
// ========================================
void initDmx() {
    Serial.println("Initializing DMX...");
    
    // DMX設定(esp_dmx v4 API)
    dmx_config_t config = {
        .interrupt_flags = ESP_INTR_FLAG_IRAM,
        .root_device_parameter_count = 0,
        .sub_device_parameter_count = 0,
        .model_id = 0,
        .product_category = 0,
        .software_version_id = 0x00010000,
        .software_version_label = "1.0.0",
        .queue_size_max = 0
    };
    
    // DMXドライバインストール
    if (!dmx_driver_install(DMX_PORT, &config, nullptr, 0)) {
        Serial.println("DMX driver install failed!");
        return;
    }
    
    // GPIOピン設定
    if (!dmx_set_pin(DMX_PORT, DMX_TX_PIN, DMX_RX_PIN, DMX_EN_PIN)) {
        Serial.println("DMX set pin failed!");
        return;
    }
    
    // ENピン設定(-1の場合はスキップ)
    if (DMX_EN_PIN >= 0) {
        pinMode(DMX_EN_PIN, OUTPUT);
        digitalWrite(DMX_EN_PIN, LOW);
    }
    
    Serial.printf("DMX initialized: RX=GPIO%d, TX=GPIO%d\n", DMX_RX_PIN, DMX_TX_PIN);
}

// ========================================
// 初期画面描画
// ========================================
void drawInitScreen() {
    M5.Display.fillScreen(TFT_BLACK);
    
    // タイトル
    M5.Display.setTextDatum(textdatum_t::top_left);
    M5.Display.setFont(&fonts::Font0);
    M5.Display.setTextColor(TFT_CYAN);
    char title[32];
    snprintf(title, sizeof(title), "DMX Monitor %d-%d", display_start_ch, display_start_ch + DISPLAY_CH_COUNT - 1);
    M5.Display.drawString(title, 2, 2);
    
    // グリッド設定
    const int grid_x = 0;
    const int grid_y = 12;
    const int cell_w = 32;
    const int cell_h = 36;
    const int bar_w = 2;
    const int bar_max_h = 26;
    
    // チャンネル番号とバー背景を描画
    for (int row = 0; row < DISPLAY_ROWS; row++) {
        for (int col = 0; col < DISPLAY_COLS; col++) {
            int ch = display_start_ch + row * DISPLAY_COLS + col;
            int x = grid_x + col * cell_w;
            int y = grid_y + row * cell_h;
            
            // チャンネル番号
            drawSmallNumber2(x + cell_w - bar_w - 11, y + 1, ch, TFT_DARKGREY, 1);
            
            // バー背景
            int bar_x = x + cell_w - bar_w;
            int bar_y = y - 1;
            M5.Display.fillRect(bar_x, bar_y, bar_w, bar_max_h, M5.Display.color565(30, 30, 30));
        }
    }
    
    // スプライト初期化
    if (!sprite_initialized) {
        cellSprite.setColorDepth(16);
        cellSprite.createSprite(32, 36);
        sprite_initialized = true;
    }
    
    screen_initialized = true;
    prev_start_ch = display_start_ch;
    memset(prev_dmx_values, 0xFF, sizeof(prev_dmx_values));
}

// ========================================
// DMXモニター画面更新
// ========================================
void updateDmxMonitor() {
    if (!screen_initialized) {
        drawInitScreen();
    }
    
    if (display_start_ch != prev_start_ch) {
        drawInitScreen();
    }
    
    const int grid_x = 0;
    const int grid_y = 12;
    const int cell_w = 32;
    const int cell_h = 36;
    const int bar_w = 2;
    const int bar_max_h = 26;
    
    // 各チャンネルを更新
    for (int row = 0; row < DISPLAY_ROWS; row++) {
        for (int col = 0; col < DISPLAY_COLS; col++) {
            int ch = display_start_ch + row * DISPLAY_COLS + col;
            if (ch < 1 || ch > 512) continue;
            
            uint8_t val = dmx_data[ch];
            if (val == prev_dmx_values[ch]) continue;
            
            prev_dmx_values[ch] = val;
            
            int x = grid_x + col * cell_w;
            int y = grid_y + row * cell_h;
            
            // セル描画(スプライト使用)
            cellSprite.fillSprite(TFT_BLACK);
            
            // チャンネル番号
            char chStr[4];
            snprintf(chStr, sizeof(chStr), "%d", ch);
            int chLen = strlen(chStr);
            int chX = cell_w - bar_w - chLen * 5 - 1;
            for (int i = 0; i < chLen; i++) {
                int digit = chStr[i] - '0';
                drawSmallDigitSprite(&cellSprite, chX + i * 5, 1, digit, TFT_DARKGREY, 1);
            }
            
            // 値の色(レベルに応じて)
            uint16_t valColor;
            if (val == 0) {
                valColor = TFT_DARKGREY;
            } else if (val < 128) {
                valColor = cellSprite.color565(0, val * 2, 0);
            } else {
                valColor = cellSprite.color565((val - 128) * 2, 255, 0);
            }
            
            // 値表示
            if (dmx_display_mode == 0) {
                // 10進のみ
                char valStr[4];
                snprintf(valStr, sizeof(valStr), "%3d", val);
                int vx = 2;
                int vy = 10;
                for (int i = 0; i < 3; i++) {
                    if (valStr[i] == ' ') continue;
                    int digit = valStr[i] - '0';
                    drawSmallDigitSprite(&cellSprite, vx + i * 8, vy, digit, valColor, 2);
                }
            } else if (dmx_display_mode == 2) {
                // 16進 + 10進
                char hexStr[3];
                snprintf(hexStr, sizeof(hexStr), "%02X", val);
                for (int i = 0; i < 2; i++) {
                    int digit = (hexStr[i] >= 'A') ? (hexStr[i] - 'A' + 10) : (hexStr[i] - '0');
                    drawSmallDigitSprite(&cellSprite, 2 + i * 5, 8, digit, valColor, 1);
                }
                char decStr[4];
                snprintf(decStr, sizeof(decStr), "%3d", val);
                for (int i = 0; i < 3; i++) {
                    if (decStr[i] == ' ') continue;
                    int digit = decStr[i] - '0';
                    drawSmallDigitSprite(&cellSprite, 2 + i * 8, 16, digit, valColor, 2);
                }
            }
            
            // バー
            int bar_x = cell_w - bar_w;
            int bar_h = (val * bar_max_h) / 255;
            cellSprite.fillRect(bar_x, 0, bar_w, bar_max_h, cellSprite.color565(30, 30, 30));
            if (bar_h > 0) {
                cellSprite.fillRect(bar_x, bar_max_h - bar_h, bar_w, bar_h, valColor);
            }
            
            cellSprite.pushSprite(x, y);
        }
    }
    
    // ステータス更新
    bool update_status = false;
    if (dmx_packet_per_sec != prev_packet_per_sec) {
        prev_packet_per_sec = dmx_packet_per_sec;
        update_status = true;
    }
    if (dmx_data_length != prev_data_length) {
        prev_data_length = dmx_data_length;
        update_status = true;
    }
    
    if (update_status) {
        M5.Display.fillRect(0, 232, 280, 8, TFT_BLACK);
        M5.Display.setTextDatum(textdatum_t::bottom_left);
        M5.Display.setFont(&fonts::Font0);
        
        char status[64];
        if (dmx_signal_detected) {
            M5.Display.setTextColor(TFT_GREEN);
            snprintf(status, sizeof(status), "DMX OK %2dfps Len:%d (%d-%d)",
                     dmx_packet_per_sec, dmx_data_length,
                     dmx_data_length_min, dmx_data_length_max);
        } else {
            M5.Display.setTextColor(TFT_RED);
            snprintf(status, sizeof(status), "NO SIGNAL");
        }
        M5.Display.drawString(status, 2, 240);
    }
}

// ========================================
// DMX受信処理
// ========================================
void processDmxReceive() {
    dmx_packet_t packet;
    
    if (dmx_receive(DMX_PORT, &packet, DMX_TIMEOUT_TICK)) {
        if (!packet.err) {
            dmx_read(DMX_PORT, dmx_data, DMX_PACKET_SIZE);
            dmx_signal_detected = true;
            last_dmx_packet = millis();
            dmx_data_length = packet.size;
            
            if (dmx_data_length < dmx_data_length_min) dmx_data_length_min = dmx_data_length;
            if (dmx_data_length > dmx_data_length_max) dmx_data_length_max = dmx_data_length;
            
            dmx_packet_count++;
        }
    }
    
    unsigned long now = millis();
    if (now - last_count_reset >= 1000) {
        dmx_packet_per_sec = dmx_packet_count;
        dmx_packet_count = 0;
        last_count_reset = now;
    }
    
    if (dmx_signal_detected && (now - last_dmx_packet > DMX_TIMEOUT_MS)) {
        dmx_signal_detected = false;
        dmx_packet_per_sec = 0;
        dmx_data_length_min = 9999;
        dmx_data_length_max = 0;
        Serial.println("DMX signal lost!");
    }
}

// ========================================
// ボタン処理
// ========================================
void handleButtons() {
    M5.update();
    
    auto touch = M5.Touch.getDetail();
    if (touch.wasPressed()) {
        int tx = touch.x;
        int ty = touch.y;
        
        if (ty > 160) {
            if (tx < 107) {
                display_start_ch -= DISPLAY_CH_COUNT;
                if (display_start_ch < 1) display_start_ch = 1;
            } else if (tx > 213) {
                display_start_ch += DISPLAY_CH_COUNT;
                if (display_start_ch > 512 - DISPLAY_CH_COUNT + 1) {
                    display_start_ch = 512 - DISPLAY_CH_COUNT + 1;
                }
            } else {
                dmx_display_mode = (dmx_display_mode + 1) % 3;
                memset(prev_dmx_values, 0xFF, sizeof(prev_dmx_values));
            }
        }
    }
}

// ========================================
// セットアップ
// ========================================
void setup() {
    auto cfg = M5.config();
    cfg.serial_baudrate = 115200;
    M5.begin(cfg);
    
    Serial.println("\n=== DMX Monitor for Core2 + ISOLATED RS485 ===");
    Serial.printf("Port A: RX=GPIO%d, TX=GPIO%d\n", DMX_RX_PIN, DMX_TX_PIN);
    
    M5.Display.setRotation(1);
    M5.Display.setBrightness(128);
    M5.Display.fillScreen(TFT_BLACK);
    
    initDmx();
    drawInitScreen();
    
    Serial.println("Setup complete!");
}

// ========================================
// メインループ
// ========================================
void loop() {
    processDmxReceive();
    handleButtons();
    
    static unsigned long last_display_update = 0;
    unsigned long now = millis();
    if (now - last_display_update >= 33) {
        last_display_update = now;
        updateDmxMonitor();
    }
}


失敗でした 出力電圧が低いです

image.png

M5Stack用DMXユニット 絶縁型RS485トランシーバー搭載 (CA-IS3092W)
https://shop.m5stack.com/products/dmx-unit-with-isolated-rs485-transceiver?srsltid=AfmBOorcKxpOg2WGzX8A_MSvBjRsXf2YXEMJJiRddoHmBtsqVSoEWCqw

image.png

M5StackのISOLATED RS485 Unitは、
絶縁型RS485トランシーバ「CA IS3082W」を搭載しています。

CA-IS3082W と CA-IS3092W の違いを徹底検証

DMX 出力波形・終端抵抗・M5Stack 接続の実態まとめ

概要

DMX 通信で CA‑IS3082W は安定動作するのに、
CA‑IS3092W(M5Stack DMX Unit 搭載)は出力電圧が低く、120Ω 終端を入れないと動かない
という現象について、実測と構造から理由を整理した技術メモです。

同じ M5Stack、同じプログラム、同じ A ポートを使っているのに挙動が大きく異なる理由を
「IC の構造」「電源方式」「終端抵抗」「波形品質」から解説します。



CA‑IS3082W と CA‑IS3092W の構造的な違い

CA‑IS3082W

  • 外部電源で RS‑485 ドライバを 5V 近くで強く駆動できる
  • 入力負荷が軽く、M5Stack 側の TX/RX に優しい
  • 差動電圧が 2〜3V クラスと大きい
  • 終端抵抗の ON/OFF にあまり依存しない
    → 波形マージンが厚く、DMX 機器が安定しやすい

CA‑IS3092W(M5Stack DMX Unit 搭載)

  • 内蔵 DC‑DC により 2次側電源が 3.3V 系で低め
  • 終端抵抗・バイアス・保護回路が強めで 負荷が重い
  • 差動電圧は 1〜1.5V 程度と控えめ
  • 終端 OFF だと反射が大きく、DMX が不安定
    120Ω 終端必須の設計

DMX 出力波形の違い

CA‑IS3082W

  • A–B 差動が 2〜3V と大きい
  • 終端の有無にあまり左右されない
  • 波形が太く、現場マージンが厚い

CA‑IS3092W

  • 差動が 半分くらい(1〜1.5V)
  • 終端 OFF だと反射・リンギングが増えて UART 判定が不安定
  • 終端 ON で波形が締まり、DMX が安定する

なぜ CA‑IS3092W は 120Ω 終端が必須なのか

終端 OFF のとき

  • 反射が大きく、波形が「揺れる」
  • ピーク電圧は高く見えても、
    実効的な差動電圧はむしろ小さくなる
  • DMX 受信機がビット判定に失敗しやすい

終端 ON のとき

  • 反射が吸収され、波形が素直になる
  • 差動電圧は下がるが、
    しきい値を安定して超える“使える波形”になる

3092W は「低めの振幅+終端前提」で設計されている


M5Stack A ポート使用は適正か?

結論:

プログラムや UART 設定は正しく動いているが、
A ポート+3092W の組み合わせは電気的マージンが薄い。

理由:

  • A ポートは I2C 前提のことが多く、保護回路や負荷が UART 用と異なる
  • 3082W のような「軽負荷・強ドライブ」なら問題が出ない
  • 3092W のような「重負荷・低電圧ドライブ」では影響が出やすい
  • 本来 DMX Unit は PORT.C(UART 専用)で使う前提

まとめ(結論)

  • 制御側(M5Stack プログラム)は適正に動作している
  • 問題の本質は CA‑IS3092W の設計(低めの差動電圧+終端必須)
  • DMX の現場マージンを重視するなら
    CA‑IS3082W 系の方が圧倒的に安心
  • CA‑IS3092W を使う場合は
    120Ω 終端 ON が必須
    PORT.C を使うのが望ましい

おまけ:どちらを選ぶべきか

項目 CA‑IS3082W CA‑IS3092W
差動電圧 大きい(2〜3V) 小さい(1〜1.5V)
終端依存性 低い 高い(120Ω必須)
安定性 高い 条件次第
M5Stack との相性 Aポートでも動きやすい PORT.C 推奨
現場向けDMX

DMX は「波形の形」が命なので、
IC の性格差がそのまま安定性に出ます。
3082W の“余裕設計”はやはり強いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?