DMX調光器のチラつき対策 - 安定化ホールド機能の実装
はじめに
DMX512を使った調光制御では、ノイズやドリフトによる微小な値の変化が照明のチラつきを引き起こすことがあります。
一般的なデッドバンド処理だけでは対応できないケースがあり、本記事では**「一定時間安定していた後の微小変化のみを無視する」**という新しいアプローチを紹介します。
この記事で解説すること
- 既存のデッドバンドの限界
- 安定化ホールド機能の概念
- STM32での実装方法
- パラメータチューニングのコツ
問題:デッドバンドだけでは不十分なケース
既存のデッドバンド処理
// 従来のデッドバンド処理
uint8_t apply_dead_band(uint8_t current, uint8_t new_value) {
int diff = abs((int)new_value - (int)current);
if(diff <= DEAD_BAND) {
return current; // 変化を無視
}
return new_value;
}
問題点:
デッドバンドは「常に」微小変化を無視します。これにより:
| シナリオ | 期待動作 | デッドバンドの動作 |
|---|---|---|
| フェード中の±2変化 | 反映したい | ❌ 無視される |
| 安定後の±2変化(ノイズ) | 無視したい | ✅ 無視される |
| フェード中の±5変化 | 反映したい | ✅ 反映される |
| 安定後の±5変化 | 無視したい | ❌ 反映される |
フェード中は細かい変化も反映したいのに、安定後はノイズを無視したい。
デッドバンドだけではこの使い分けができません。
現象例
時間 → 0s 1s 2s 3s 4s 5s 6s 7s
│ │ │ │ │ │ │ │
DMX値 100 100 100 100 103 100 103 100
↑ ↑ ↑ ↑
│ └─────┴─────┴── ノイズ?意図的?
└── 長時間安定していた
【問題】
・デッドバンド=2 → ±3の変化は全て反映 → チラつく
・デッドバンド=5 → フェード中の細かい変化も無視 → 滑らかさ低下
解決策:安定化ホールド機能
コンセプト
「一定時間(X秒)変化がなかった後は、閾値(Y)以上の変化がないと無視する」
┌─────────────────────────────────────────────────────────────────────────────┐
│ 安定化ホールド機能の動作原理 │
└─────────────────────────────────────────────────────────────────────────────┘
【状態遷移図】
┌─────────────┐ ┌─────────────┐
│ 通常状態 │ ────X秒間変化なし────────→ │ 安定状態 │
│ (Normal) │ │ (Stable) │
└─────────────┘ └─────────────┘
↑ │
│ │
│ ┌────────────────────────────┐ │
└────────│ Y以上の変化を検知 │←─────┘
└────────────────────────────┘
│
┌──────────┴──────────┐
│ Y未満の変化は無視 │
└─────────────────────┘
タイムライン例
【設定】X = 2秒(安定判定時間)、Y = 5(安定化閾値)
時間 → 0s 1s 2s 3s 4s 5s 6s 7s 8s
│ │ │ │ │ │ │ │ │
DMX入力 100 100 100 100 102 100 108 110 110
状態 [通常][通常][安定][安定][安定][安定][通常][通常][安定]
↑ ↑ ↑ ↑ ↑
│ │ │ │ └── 2秒経過で再び安定状態
│ │ │ └── 差=2 → Y未満だが前回がY以上変化後なので反映
│ │ └── 差=8 → Y(5)以上なので反映、安定解除 ★
│ └── 差=2 → 安定状態中、Y(5)未満なので無視 ★
└── 2秒間変化なし → 安定状態に移行
【出力】
時間 0s 1s 2s 3s 4s 5s 6s 7s 8s
出力 100 100 100 100 100 100 108 110 110
↑ ↑
│ └── 大きな変化は反映
└── ノイズは無視!
処理フロー
DMX受信値
│
▼
┌─────────────────────────────────────────┐
│ ① 通常デッドバンド適用 │ ← 常時適用(既存機能)
│ 差がDEAD_BAND以下なら無視 │
│ 例: DEAD_BAND = 2 │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ ② 安定化ホールド適用(新機能) │
│ │
│ if (安定状態) { │
│ if (差 < STABLE_THRESHOLD) { │
│ 変化を無視; │
│ } else { │
│ 安定状態を解除; │
│ 変化を適用; │
│ } │
│ } else { │
│ if (STABLE_TIME_MS 経過) { │
│ 安定状態に移行; │
│ } │
│ 変化を適用; │
│ } │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ ③ 以降の処理(フェード、カーブ適用など) │
└─────────────────────────────────────────┘
│
▼
PWM出力
実装
config_compat.h パラメータ定義
//=============================================================================
// 安定化ホールド設定(Stability Hold)
//=============================================================================
// 入力がX秒以上安定していた後、Y未満の変化を無視する機能
// ノイズやドリフトによる微小変化を防ぎ、安定した出力を維持
//
// 【通常のデッドバンドとの違い】
// ・デッドバンド : 常に微小変化を無視(フェード中も)
// ・安定化ホールド : 安定状態からの微小変化のみ無視
//=============================================================================
//-----------------------------------------------------------------------------
// 安定化時間(X): この時間変化がなければ「安定状態」と判定
//-----------------------------------------------------------------------------
// 0 = 安定化ホールド無効(デッドバンドのみ使用)
// 500 = 0.5秒安定で安定状態突入
// 1000 = 1秒安定で安定状態突入(推奨)
// 2000 = 2秒安定で安定状態突入
// 5000 = 5秒安定で安定状態突入(厳格モード)
//-----------------------------------------------------------------------------
#define STABLE_TIME_MS 1000 // ★ 安定化時間(ミリ秒)
//-----------------------------------------------------------------------------
// 安定化閾値(Y): 安定状態からこの値以上の変化で解除
//-----------------------------------------------------------------------------
// 安定状態中、この値未満の変化は無視される
// 3 = DMX差が3未満の変化を無視
// 5 = DMX差が5未満の変化を無視(推奨)
// 10 = DMX差が10未満の変化を無視(ノイズ多い環境)
// ※ 通常のDEAD_BANDより大きい値を設定することを推奨
//-----------------------------------------------------------------------------
#define STABLE_THRESHOLD 5 // ★ 安定化閾値(0-20)
//-----------------------------------------------------------------------------
// 安定化モード: 動作モード選択
//-----------------------------------------------------------------------------
// 0 = 全チャンネル共通(1chでも変化あれば全ch解除)
// 1 = チャンネル個別(各chで独立して安定化判定)★推奨
//-----------------------------------------------------------------------------
#define STABLE_MODE 1 // ★ 安定化モード(0-1)
main.c 変数定義
//==============================================================================
// 安定化ホールド用変数(6チャンネル分)
//==============================================================================
#if STABLE_TIME_MS > 0
// 安定タイマー(メインループ回数をカウント)
static uint32_t stable_timer[6] = {0, 0, 0, 0, 0, 0};
// 安定状態フラグ(1 = 安定状態)
static uint8_t is_stable[6] = {0, 0, 0, 0, 0, 0};
// 前回の確定値(安定判定用)
static uint8_t stable_value[6] = {0, 0, 0, 0, 0, 0};
// メインループの実行周期(μs)- 実測または推定値
#define MAIN_LOOP_PERIOD_US 100 // 100μs = 10kHz
// 安定時間をループ回数に変換
#define STABLE_COUNT_LIMIT ((STABLE_TIME_MS * 1000) / MAIN_LOOP_PERIOD_US)
#endif
安定化ホールド処理関数
//==============================================================================
// apply_stability_hold() - 安定化ホールド適用
//
// 引数:
// ch : チャンネル番号(0-5)
// current : 現在の確定値
// new_value : 新しい入力値
//
// 戻り値:
// 適用後の値(無視する場合は current を返す)
//==============================================================================
uint8_t apply_stability_hold(uint8_t ch, uint8_t current, uint8_t new_value) {
#if STABLE_TIME_MS == 0
// 安定化ホールド無効時はそのまま返す
return new_value;
#else
// 変化量を計算(絶対値)
int diff = (int)new_value - (int)current;
if(diff < 0) diff = -diff;
// === 変化がある場合 ===
if(diff > 0) {
// 安定状態中の場合
if(is_stable[ch]) {
// 閾値未満の変化 → 無視
if(diff < STABLE_THRESHOLD) {
// タイマーは維持(安定状態継続)
return current;
}
// 閾値以上の変化 → 安定状態解除
is_stable[ch] = 0;
stable_timer[ch] = 0;
} else {
// 通常状態 → タイマーリセット
stable_timer[ch] = 0;
}
// 新しい値を確定値として記録
stable_value[ch] = new_value;
return new_value;
}
// === 変化がない場合 ===
// 安定タイマーを進める
if(!is_stable[ch]) {
stable_timer[ch]++;
// 安定時間経過 → 安定状態に移行
if(stable_timer[ch] >= STABLE_COUNT_LIMIT) {
is_stable[ch] = 1;
stable_timer[ch] = STABLE_COUNT_LIMIT; // オーバーフロー防止
}
}
return new_value;
#endif
}
全チャンネル共通モード対応版
#if STABLE_MODE == 0
//==============================================================================
// check_any_channel_changed() - いずれかのチャンネルが変化したか確認
// 全チャンネル共通モードで使用
//==============================================================================
uint8_t check_any_channel_changed(uint8_t dmx_new[], uint8_t dmx_current[]) {
for(int ch = 0; ch < 6; ch++) {
int diff = abs((int)dmx_new[ch] - (int)dmx_current[ch]);
if(diff >= STABLE_THRESHOLD) {
return 1; // 大きな変化あり
}
}
return 0; // 変化なし or 微小変化のみ
}
//==============================================================================
// reset_all_stability() - 全チャンネルの安定状態をリセット
//==============================================================================
void reset_all_stability(void) {
#if STABLE_TIME_MS > 0
for(int ch = 0; ch < 6; ch++) {
stable_timer[ch] = 0;
is_stable[ch] = 0;
}
#endif
}
#endif
メインループへの統合
// DMX受信処理(メインループ内)
void process_dmx_input(void) {
static uint8_t dmx_filtered[6] = {0}; // フィルタ後の値
for(int ch = 0; ch < 6; ch++) {
uint8_t new_dmx = DMX_Rx[DMX_START_ADDRESS + ch - 1];
// ① 通常デッドバンド適用
new_dmx = apply_dead_band(dmx_filtered[ch], new_dmx);
// ② 安定化ホールド適用 ★新機能
new_dmx = apply_stability_hold(ch, dmx_filtered[ch], new_dmx);
// ③ 下限カット適用
new_dmx = apply_low_cut(new_dmx);
// フィルタ後の値を更新
dmx_filtered[ch] = new_dmx;
// ターゲット値を設定(フェード処理へ)
target[ch] = new_dmx;
}
}
リセット処理(モード切替時など)
//==============================================================================
// reset_stability() - 安定化状態を強制リセット
// マニュアルモード切替時やDMXアドレス変更時に呼び出し
//==============================================================================
void reset_stability(void) {
#if STABLE_TIME_MS > 0
for(int ch = 0; ch < 6; ch++) {
stable_timer[ch] = 0;
is_stable[ch] = 0;
stable_value[ch] = 0;
}
#endif
}
// 使用例
void switch_to_manual_mode(void) {
reset_stability(); // 安定状態をクリア
// ... マニュアルモード処理
}
LCD表示への追加
安定状態を視覚的に確認できるようにします。
表示例
┌────────────────────────┐
│ DMX:001 Curve:7 │
│ Ch1:128● Ch2:255 │ ← ● は安定状態を示す
│ Ch3: 64 Ch4: 0● │
│ Fade:1 [STABLE] │ ← 全ch安定時
└────────────────────────┘
表示用関数
//==============================================================================
// is_channel_stable() - チャンネルの安定状態を取得(LCD表示用)
//==============================================================================
uint8_t is_channel_stable(uint8_t ch) {
#if STABLE_TIME_MS > 0
if(ch < 6) {
return is_stable[ch];
}
#endif
return 0;
}
//==============================================================================
// is_all_stable() - 全チャンネルが安定状態か確認
//==============================================================================
uint8_t is_all_stable(void) {
#if STABLE_TIME_MS > 0
for(int ch = 0; ch < 6; ch++) {
if(!is_stable[ch]) return 0;
}
return 1;
#else
return 0;
#endif
}
// LCD表示処理内で使用
void update_lcd_status(void) {
// チャンネル値の後に安定インジケータを表示
for(int ch = 0; ch < 6; ch++) {
LCD_printf("Ch%d:%3d%c ", ch+1, current[ch],
is_channel_stable(ch) ? '*' : ' ');
}
// 全ch安定時のステータス表示
if(is_all_stable()) {
LCD_printf("[STABLE]");
}
}
デッドバンドとの比較表
| 項目 | デッドバンド | 安定化ホールド |
|---|---|---|
| 判定タイミング | 常時 | 安定状態時のみ |
| フェード中の微小変化 | 無視 | 反映 |
| 安定後の微小変化 | 無視 | 無視 |
| 閾値の推奨値 | 2-3 | 5-10 |
| CPU負荷 | 低 | やや高(タイマー処理) |
| メモリ使用 | 少 | 多(ch数×3変数) |
| 設定パラメータ | 1つ | 3つ |
併用時の動作
入力値変化
│
▼
┌─────────────────────┐
│ デッドバンド判定 │ ← 差 ≤ DEAD_BAND(2) なら無視
│ (DEAD_BAND = 2) │
└─────────────────────┘
│ 差 > 2
▼
┌─────────────────────┐
│ 安定化ホールド判定 │ ← 安定状態 かつ 差 < STABLE_THRESHOLD(5) なら無視
│ (STABLE_THRESHOLD=5)│
└─────────────────────┘
│ 安定でない or 差 ≥ 5
▼
変化を適用
パラメータチューニング
推奨設定パターン
| 環境 | STABLE_TIME_MS | STABLE_THRESHOLD | DEAD_BAND | 説明 |
|---|---|---|---|---|
| ノイズ多い環境 | 2000 | 10 | 3 | 2秒安定後、差10未満を無視 |
| 標準設定 | 1000 | 5 | 2 | 1秒安定後、差5未満を無視 |
| 高速応答重視 | 500 | 3 | 2 | 0.5秒で安定判定 |
| 無効 | 0 | - | 2 | デッドバンドのみ |
設定の考え方
┌─────────────────────────────────────────────────────────────┐
│ パラメータ設定ガイド │
└─────────────────────────────────────────────────────────────┘
【STABLE_TIME_MS(安定判定時間)】
短い (500ms) ←────────────────────────────→ 長い (5000ms)
・素早く安定状態に ・確実に安定してから判定
・誤判定の可能性あり ・反応がやや遅い
・照明フェードが速い場合 ・照明フェードがゆっくりの場合
【STABLE_THRESHOLD(安定化閾値)】
小さい (3) ←────────────────────────────→ 大きい (15)
・細かいノイズも反映 ・大きな変化のみ反映
・滑らかな変化を維持 ・ノイズに強い
・低ノイズ環境向け ・高ノイズ環境向け
【DEAD_BAND(通常デッドバンド)】
※ STABLE_THRESHOLD より小さく設定すること
・DEAD_BAND = 2, STABLE_THRESHOLD = 5 が標準
・DEAD_BAND ≥ STABLE_THRESHOLD だと安定化ホールドが無意味に
トラブルシューティング
| 症状 | 原因 | 対策 |
|---|---|---|
| 安定状態にならない | STABLE_TIME_MSが短すぎる | 2000以上に増やす |
| ノイズが通過する | STABLE_THRESHOLDが小さい | 8-10に増やす |
| フェードがカクつく | DEAD_BANDが大きすぎる | 1-2に減らす |
| 反応が遅い | STABLE_TIME_MSが長すぎる | 500-1000に減らす |
| 意図的な微調整が無視 | STABLE_THRESHOLDが大きすぎる | 3-5に減らす |
応用:ヒステリシス付き安定化
さらに高度な実装として、上昇時と下降時で異なる閾値を設定できます。
//-----------------------------------------------------------------------------
// ヒステリシス付き安定化閾値
//-----------------------------------------------------------------------------
#define STABLE_THRESHOLD_UP 5 // 上昇時の閾値
#define STABLE_THRESHOLD_DOWN 3 // 下降時の閾値(暗くなる方向は敏感に)
uint8_t apply_stability_hold_hysteresis(uint8_t ch, uint8_t current, uint8_t new_value) {
#if STABLE_TIME_MS == 0
return new_value;
#else
int diff = (int)new_value - (int)current;
int abs_diff = (diff < 0) ? -diff : diff;
if(abs_diff > 0 && is_stable[ch]) {
// 方向に応じた閾値を選択
int threshold = (diff > 0) ? STABLE_THRESHOLD_UP : STABLE_THRESHOLD_DOWN;
if(abs_diff < threshold) {
return current; // 無視
}
is_stable[ch] = 0;
stable_timer[ch] = 0;
}
// ... 以下同様
#endif
}
まとめ
安定化ホールドのメリット
- フェード中の滑らかさを維持 - デッドバンドと違い、動作中は細かい変化も反映
- 安定時のノイズ耐性向上 - 長時間安定後は大きな閾値で判定
- チャンネル独立制御 - 各チャンネルが独自に安定状態を管理
- 既存処理との共存 - デッドバンドと併用可能
実装のポイント
-
STABLE_TIME_MS = 0で無効化できる(コンパイル時に最適化) - メインループの周期を把握してタイマー計算
- LCD表示で安定状態を可視化すると調整しやすい
推奨する最初の設定
#define DEAD_BAND 2 // 通常デッドバンド
#define STABLE_TIME_MS 1000 // 1秒で安定判定
#define STABLE_THRESHOLD 5 // 安定時は差5未満を無視
#define STABLE_MODE 1 // チャンネル個別モード
この設定から始めて、実際の環境に合わせて調整してください。
【重要】消灯保証対策
問題: DMX=0でも消灯しない
安定化ホールドを実装した場合、以下のシナリオで消灯できない問題が発生する可能性があります:
1. DMX=100で1秒安定 → 安定状態に入る
2. DMX=0に変更 → 差が100(閾値以上)なので変化するはず...
3. しかし、何らかの理由で消灯しない!
原因分析
安定化ホールドの閾値判定(diff < STABLE_THRESHOLD)は正しく動作するはずですが、以下の要因で問題が発生:
- デッドバンドとの組み合わせ問題: デッドバンドが先に適用され、値が変わる
-
前回値の更新タイミング:
mekeD_prevに保存される値が期待と異なる - 徐々に値が下がる場合: 各ステップが閾値未満だと連続して無視される
解決策: DMX=0 最優先ルール
DMX=0は無条件で確実に消灯を保証(フェードで滑らかに):
uint8_t apply_stability_hold(uint8_t ch, uint8_t current, uint8_t new_value) {
// ★★★ 最優先: DMX=0 は常に消灯保証 ★★★
// 安定化ホールドで無視されず、確実にフェードで消灯
if(new_value == 0) {
stable_timer[ch] = 0;
is_stable[ch] = 0;
// slow_fade_active は維持(スローフェードで滑らかに消灯)
return 0;
}
// ★ DMX値が閾値以下(1-5)も常に追従
if(new_value <= STABLE_THRESHOLD || current <= STABLE_THRESHOLD) {
stable_timer[ch] = 0;
is_stable[ch] = 0;
return new_value;
}
// ... 以下、通常の安定化ホールド処理
}
なぜ別々に判定するのか
// NG: まとめて判定すると意図が不明確
if(new_value <= STABLE_THRESHOLD) { ... }
// OK: 意図を明確に分離
if(new_value == 0) { return 0; } // 消灯保証(最優先)
if(new_value <= STABLE_THRESHOLD) { ... } // 低域追従
消灯は照明制御において最も重要な安全機能です。どんな状況でもDMX=0で確実に消灯することを保証します。
スローフェードで滑らかに消灯
消灯時も slow_fade_active を維持します。これにより:
- DMX=0でも滑らかにフェードアウト
- カット(急に消える)を防止
- 照明演出として自然な消灯を実現
※ 緊急消灯が必要な場合は別途ブラックアウト機能を実装
チャンネル個別パラメータ設定
概要
6ch調光器では、チャンネルごとに異なる用途があることが多いです。例えば:
- CH1-4: 白熱球(低域のチラつきが気になる)
- CH5-6: LED(高速応答が必要)
このような場合、チャンネルごとに異なるパラメータを設定したいケースがあります。
設定方法
config_compat.h で配列形式でチャンネル別パラメータを定義します:
//=============================================================================
// チャンネル個別パラメータ設定
//=============================================================================
// 各配列: [CH1, CH2, CH3, CH4, CH5, CH6]
//=============================================================================
// チャンネル個別設定を有効化(0=従来マクロ使用, 1=配列使用)
#define USE_CHANNEL_PARAMS 1
#if USE_CHANNEL_PARAMS
//-----------------------------------------------------------------------------
// DIPスイッチ有効/無効(チャンネル別)
//-----------------------------------------------------------------------------
// 1 = DIPスイッチの設定を使用
// 0 = 下記配列の固定値を使用
// [CH1, CH2, CH3, CH4, CH5, CH6]
#define USE_DIP_PARAM_CH { 1, 1, 1, 1, 1, 1} // 1=DIP使用, 0=配列使用
//-----------------------------------------------------------------------------
// チャンネル別パラメータ配列(DIPスイッチ対応)
//-----------------------------------------------------------------------------
// [CH1, CH2, CH3, CH4, CH5, CH6]
#define LOW_CUT_DMX_CH { 10, 10, 10, 10, 0, 0} // カット値 0-60
#define DIM_TABLE_CH { 7, 7, 7, 7, 7, 7} // 調光カーブ 0-7
#define FADE_SPEED_CH { 1, 1, 1, 1, 1, 1} // フェード速度 0-3
//-----------------------------------------------------------------------------
// チャンネル別パラメータ配列(DIPスイッチ非対応 - 常に配列値を使用)
//-----------------------------------------------------------------------------
// [CH1, CH2, CH3, CH4, CH5, CH6]
#define DEAD_BAND_CH { 0, 0, 0, 0, 0, 0} // デッドバンド(0-10)
#define STABLE_THRESHOLD_CH { 5, 5, 5, 5, 5, 5} // 安定化閾値(0-20)
#define STABLE_SLOW_RATIO_CH{ 5, 5, 5, 5, 5, 5} // スローフェード倍率(2-5)
#define PWM_OFFSET_CH { 0, 0, 0, 0, 0, 0} // PWMオフセット(0-500)
#define PWM_GAIN_CH {256, 256, 256, 256, 256, 256} // ゲイン(256=等倍)
#define PWM_MIN_CUT_CH { 0, 0, 0, 0, 0, 0} // PWM下限閾値(0-100)
#define PWM_MAX_LIMIT_CH {4000,4000,4000,4000,4000,4000} // 上限リミット(0-4000)
#endif
DIPスイッチと配列の関係
DIPスイッチとチャンネル配列の対応表です:
| DIPスイッチ | グローバル変数 | チャンネル配列 | DIP値の変換 |
|---|---|---|---|
| S1-S4 | Cut_d |
LOW_CUT_DMX_CH |
(0-15)×4 → 0-60 |
| S5-S7 | Sw2d |
DIM_TABLE_CH |
そのまま 0-7 |
| S8-S9 | Sw3d |
FADE_SPEED_CH |
そのまま 0-3 |
重要:
-
USE_DIP_PARAM_CH=1のチャンネルはDIPスイッチ値(変換後)を使用 -
USE_DIP_PARAM_CH=0のチャンネルは配列値(直接)を使用
使用例: 白熱球とLEDの混在
白熱球(CH1-4)とLED(CH5-6)で異なる設定を行う例:
// [CH1, CH2, CH3, CH4, CH5, CH6]
// │←── 白熱球 ──→│←─ LED ─→│
#define USE_DIP_PARAM_CH { 0, 0, 0, 0, 1, 1} // 白熱球=固定, LED=DIP
#define LOW_CUT_DMX_CH { 10, 10, 10, 10, 0, 0} // 白熱球=ノイズカット多め
#define DIM_TABLE_CH { 1, 1, 1, 1, 7, 7} // 白熱球=暗部滑らか, LED=急カーブ
#define FADE_SPEED_CH { 2, 2, 2, 2, 1, 1} // 白熱球=普通, LED=速い
#define STABLE_THRESHOLD_CH { 5, 5, 5, 5, 3, 3} // 白熱球=ノイズ耐性高め
#define STABLE_SLOW_RATIO_CH{ 5, 5, 5, 5, 3, 3} // 白熱球=ゆっくり追従
実装(main.c側)
// 配列実体化
#if USE_CHANNEL_PARAMS
static const uint8_t UseDipParamCh[] = USE_DIP_PARAM_CH;
static const uint8_t DeadBandCh[] = DEAD_BAND_CH;
static const uint8_t StableThresholdCh[]= STABLE_THRESHOLD_CH;
static const uint8_t FadeSpeedCh[] = FADE_SPEED_CH;
// ... 他の配列
// アクセスマクロ(chは1-6)- DIP有効なchはDIP値、無効なchは配列値
#define USE_DIP_PARAM(ch) UseDipParamCh[(ch)-1]
#define GET_DEAD_BAND(ch) DeadBandCh[(ch)-1]
#define GET_STABLE_THRESHOLD(ch) StableThresholdCh[(ch)-1]
#define GET_FADE_SPEED(ch) (USE_DIP_PARAM(ch) ? Sw3d : FadeSpeedCh[(ch)-1])
#define GET_LOW_CUT_DMX(ch) (USE_DIP_PARAM(ch) ? Cut_d : LowCutDmxCh[(ch)-1])
#define GET_DIM_TABLE(ch) (USE_DIP_PARAM(ch) ? Sw2d : DimTableCh[(ch)-1])
#endif
パラメータ一覧
| パラメータ | DIP対応 | 範囲 | 説明 |
|---|---|---|---|
LOW_CUT_DMX_CH |
✅ S1-S4 | 0-60 | DMXカット値(この値以下を消灯) |
DIM_TABLE_CH |
✅ S5-S7 | 0-7 | 調光カーブ選択 |
FADE_SPEED_CH |
✅ S8-S9 | 0-3 | フェード速度(0=即座, 3=遅い) |
DEAD_BAND_CH |
❌ | 0-10 | デッドバンド(多いとカクカクする) |
STABLE_THRESHOLD_CH |
❌ | 0-20 | 安定化閾値(1秒後の変化を無視) |
STABLE_SLOW_RATIO_CH |
❌ | 2-5 | スローフェード倍率 |
PWM_OFFSET_CH |
❌ | 0-500 | PWMオフセット(下駄) |
PWM_GAIN_CH |
❌ | 200-300 | ゲイン(256=等倍) |
PWM_MIN_CUT_CH |
❌ | 0-100 | PWM下限閾値 |
PWM_MAX_LIMIT_CH |
❌ | 0-4000 | 上限リミット |
開発履歴
2026-02-03: 実機検証とバグ修正
バグ修正(3件)
| バグ | 原因 | 修正内容 |
|---|---|---|
| 安定タイマーが発火しない | STABLE_COUNT_LIMIT計算誤り(10,000回→500秒かかる) | MekeD()呼び出し周期ベースに修正(50ms間隔、20回=1秒) |
| 安定カウントが進まない | diff>0で即return、timer++が実行されない | 小変化でもtimer++を実行するよう修正 |
| 安定化ホールドがバイパスされる | 閾値をLOW_CUT判定に誤用 | LOW_CUT_DMX以下のみバイパス |
最適値検証結果
| パラメータ | テスト値 | 結果 |
|---|---|---|
| STABLE_THRESHOLD=2 | ❌ | 完全ではない、たまに変化する |
| STABLE_THRESHOLD=3 | △ | 2より良い |
| STABLE_THRESHOLD=4 | △ | 現状大丈夫だが、フェーダノイズで変化あり |
| STABLE_THRESHOLD=5 | ○ | 推奨 - フェーダのノリとの妥協点 |
| STABLE_TIME_MS=1000 | △ | 短すぎ - ゆっくりフェードすると安定状態に入りフェーダが効かなくなる |
| STABLE_TIME_MS=2000 | ○ | 推奨 - ゆっくりフェードでも安定状態に入りにくい |
| STABLE_ENTRY_THRESHOLD=3 | ❌ | DMXノイズで安定カウントがリセットされる |
| STABLE_ENTRY_THRESHOLD=10 | ○ | 推奨 - ノイズ耐性向上 |
推奨設定値
#define STABLE_TIME_MS 2000 // 2秒で安定状態突入
#define STABLE_ENTRY_THRESHOLD 10 // ±10以内は変化なしとみなす
#define STABLE_THRESHOLD 5 // ±5以上で安定解除
残課題:フェーダとアナログノイズの問題
完璧ではない理由
安定化ホールド機能はソフトウェアでの対策であり、根本的なハードウェアノイズを完全に解決することはできません。
フェーダ(可変抵抗)の宿命
【アナログ入力の限界】
フェーダ ─→ ADC ─→ DMX値(0-255)
↑
│ 物理的な接触抵抗の揺らぎ
│ 電源ノイズの影響
│ 温度変化によるドリフト
↓
1/256(約0.4%)のバタつきは避けられない
問題: 通常の±1〜2のバタつきはSTABLE_ENTRY_THRESHOLDで吸収できますが、稀に閾値を超える変化が発生します。これはアナログ回路の特性上、完全には防げません。
確認されたノイズ源
| ノイズ源 | 現象 | 対策 |
|---|---|---|
| 12V電源 | フェーダ値が揺れる | ノイズフィルター追加 |
| PC(USB接続中) | フェーダにノイズが乗る | USBアイソレータ使用 |
| 電源アース状況 | 環境により変動 | アース強化 |
ハードウェア対策案
ソフトウェア対策の限界を超えるには、ハードウェア側の改善が必要です。
| 対策 | 効果 | 難易度 |
|---|---|---|
| フェーダ-PWM間にアイソレータ/セパレータ | ○ 少し安定 | 中 |
| フェーダの電源を別電源に分離 | ◎ 安定 | 中 |
| フェーダ出力にコンデンサ追加 | ○ 高周波ノイズ除去 | 易 |
| シールドケーブル使用 | ○ 外来ノイズ遮断 | 易 |
| USBアイソレータ使用 | ◎ PC接続時必須 | 易 |
ソフト×ハードの両立
【最終的な安定化アプローチ】
┌─────────────────────────────────────────────────────────────┐
│ ハードウェア対策 │
│ ・電源分離、フィルタリング │
│ ・アイソレータ/セパレータ │
│ → 大きなノイズを減らす │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ ソフトウェア対策 │
│ ・デッドバンド(常時微小変化を無視) │
│ ・安定化ホールド(安定後の変化を無視) │
│ → 残ったノイズを吸収 │
└─────────────────────────────────────────────────────────────┘
↓
安定した出力
結論: フェーダの滑らかさ(レスポンス)と安定化(ノイズ除去)はトレードオフの関係にあります。STABLE_THRESHOLD=5は現時点での妥協点であり、より完璧な安定性を求める場合はハードウェア対策が必須となります。
2026-02-02: チャンネル個別パラメータ対応
- 機能: 各チャンネルで異なるパラメータを設定可能
-
パラメータ:
USE_DIP_PARAM_CH,*_CH配列 - 効果: 白熱球とLED混在環境で最適な設定が可能
- 追加: フェード速度もチャンネル個別に対応
2026-02-02: 消灯保証対策
- 問題: DMX=0でも消灯しないケースが発生
- 原因: 安定状態中に徐々に値が下がる場合の処理不足
- 対策: DMX=0を最優先で判定、無条件で消灯
- 追加: スローフェードフラグも同時に解除
2026-02-02: スローフェード機能追加
- 機能: 安定解除時にゆっくり追従
-
パラメータ:
STABLE_SLOW_FADE,STABLE_SLOW_RATIO - 効果: 突然の値変化でも滑らかに追従
2026-02-02: 初版実装
- 安定化ホールド基本機能
- チャンネル個別モード対応
- LCD表示対応
参考
関連ファイル:
- config_compat.h - パラメータ設定
- main.c - 実装コード
- 12bit調光カーブ解説