M5Stack Core2 + ISOLATED RS485 Unitで作るDMX512モニター
はじめに
舞台照明やイベント現場で使われるDMX512信号。デバッグやトラブルシューティング時に「今、何チャンネルに何の値が来ているか?」をサッと確認できるモニターがあると便利です。
本記事では、M5Stack Core2とISOLATED RS485 Unitを使って、コンパクトなDMX512モニターを作成する方法を紹介します。
🤖 本プロジェクトは 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 について
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が受信できない
- 配線確認: TXDとRXDが逆になっていないか
- A/B確認: DMX+とDMX-が逆になっていないか
- GND接続: DMX機器とのGND接続を確認
- 終端抵抗: 長距離の場合は120Ω終端抵抗を検討
「NO SIGNAL」表示のまま
- DMX送信機が正常に動作しているか確認
- 別のDMXテスターで信号を確認
完成品
タッチ操作でページ切り替え・表示モード変更ができる、コンパクトなDMX512モニターの完成です!
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協働開発で良かった点
- esp_dmx v4 の新API をすぐに把握してくれた
- ピン配置の問題 を論理的に切り分けて解決
- コード生成が速い - 約500行のコードを数分で生成
- ドキュメント作成 も同時に依頼できる
人間が担当した部分
- ハードウェアの接続・配線
- 実機での動作確認
- 「動かない」→「動いた」の判断
- 最終的な品質チェック
💡 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();
}
}
失敗でした 出力電圧が低いです
M5Stack用DMXユニット 絶縁型RS485トランシーバー搭載 (CA-IS3092W)
https://shop.m5stack.com/products/dmx-unit-with-isolated-rs485-transceiver?srsltid=AfmBOorcKxpOg2WGzX8A_MSvBjRsXf2YXEMJJiRddoHmBtsqVSoEWCqw
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 の“余裕設計”はやはり強いです。




