Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

フェーダーのバタつきを完全攻略!ADCノイズ対策の決定版

0
Posted at

STM32でフェーダーのバタつきを完全攻略!ADCノイズ対策の決定版

はじめに

照明制御やオーディオミキサーなど、アナログフェーダー(スライドボリューム)を使う組み込みシステムで必ず直面する問題があります。

「フェーダーを触っていないのに出力値がチラチラ変わる」

いわゆる「バタつき」問題です。

本記事では、STM32マイコンで実際に運用しているDMX照明コントローラーで採用している3段階のノイズ対策を詳しく解説します。


アナログフェーダー読み取りの難しさ

アナログフェーダーを正確に読み取るのは、なかなか難しい点があります。

昔のハードウェア対策

大昔(1980〜90年代)は、ダイオード+コンデンサによる積分回路でノイズを抑えていました。

         ダイオード      コンデンサ
フェーダー ──┤>├──┬──┤├── GND
                 │
                 └──→ ADC入力

問題点: 積分回路を入れると、その分立ち上がりが遅れます
フェーダーを素早く動かしても、出力がゆっくり追従する「もっさり感」が出てしまいます。

現代のアプローチ

現在はプログラム(ソフトウェア)で対処します。

  • ハードウェア積分なし → 高速応答を維持
  • ソフトウェアフィルタ → 柔軟なパラメータ調整
  • 上昇/下降で異なる処理 → ハードウェアでは困難な処理が可能

ハードウェア側の最低限の対策

ソフトウェアで対処するとはいえ、ハードウェア側でも最低限の対策は必須です。

1. パスコン(バイパスコンデンサ)

フェーダー基板にはパスコン(0.1μF程度) を入れます。

        VCC ──┬── フェーダー ──┬── GND
              │                │
             ─┴─ 0.1μF       ─┴─ 0.1μF
              │                │
             GND              GND

⚠️ 注意: パスコンの配置や値を間違えると発振してNGになります。

  • 容量が大きすぎる → 発振の原因
  • 配線が長い → インダクタンスで発振
  • 電源ラインに近すぎる → ノイズを拾う

2. 電源の多段安定化

本システムでは以下の電源構成を採用しています:

┌─────────────────────────────────────────────────────────┐
│  12V給電 → 5V安定化 → 3.3V安定化 → A/Dサンプリング     │
│    ↓          ↓           ↓              ↓              │
│  入力電源   ロジック用   ADC基準電圧    高精度読み取り  │
└─────────────────────────────────────────────────────────┘
段階 電圧 用途 備考
入力 12V 外部給電 AC/DCアダプタ等
1段目 5V デジタル回路 7805等のリニアレギュレータ
2段目 3.3V ADC・マイコン LDO推奨(低ノイズ)

ポイント:

  • スイッチング電源のリプルは12V→5Vで大幅に減衰
  • 5V→3.3VはLDO(Low Dropout Regulator)で低ノイズ化
  • ADCの基準電圧は3.3Vラインから取得

ハード+ソフトの組み合わせ

結論として、ハードウェアだけでもソフトウェアだけでも不十分です。

┌─────────────────────────────────────────────────────────────┐
│  ハードウェア対策          ソフトウェア対策                 │
│  ├─ パスコン              ├─ ヒステリシス                  │
│  ├─ 多段電源安定化        ├─ デッドバンド                  │
│  └─ 適切な配線            └─ スムージング                  │
│         ↓                        ↓                        │
│    高周波ノイズ除去         低周波バタつき防止              │
│         └────────────┬────────────┘                       │
│                      ↓                                     │
│              安定したフェーダー動作                         │
└─────────────────────────────────────────────────────────────┘

目次

  1. なぜバタつくのか?原因の理解
  2. 対策1: ADCヒステリシス
  3. 対策2: 出力デッドバンド
  4. 対策3: スムージング処理
  5. 実装コード全体
  6. パラメータ調整ガイド
  7. 評価と改善案

なぜバタつくのか?原因の理解

ADCノイズの発生源

┌─────────────────────────────────────────────────────────┐
│  フェーダー → ADC → マイコン → DMX出力 → 照明器具      │
└─────────────────────────────────────────────────────────┘
     ↑           ↑
     │           └── 量子化ノイズ、電源ノイズ
     └── 接触抵抗変動、温度ドリフト
ノイズ源 原因 影響範囲
量子化ノイズ 12bit ADCの最下位ビットの揺れ ±1〜2 LSB
電源ノイズ スイッチング電源、モーター ±3〜5 LSB
接触抵抗 ボリュームの摺動子劣化 ±2〜4 LSB
温度ドリフト 周囲温度変化 長期的な変動

典型的な症状

フェーダーを50%位置に固定しても:

時刻    ADC生値   そのまま出力すると...
0.00s   127       照明127
0.01s   128       照明128 ← チカッ
0.02s   127       照明127 ← チカッ
0.03s   129       照明129 ← チカッ
0.04s   127       照明127

人間の目は1〜2%の明るさ変化も敏感に察知します。


対策1: ADCヒステリシス

概念

ヒステリシス(履歴効果)とは、変化量が閾値を超えない限り前回値を維持する手法です。

        AD_HYST=3 の場合
        
入力値: 127 → 128 → 129 → 127 → 130 → 131 → 128
        ↓     ↓     ↓     ↓     ↓     ↓     ↓
出力値: 127 → 127 → 127 → 127 → 130 → 131 → 128
              無視   無視   無視   更新!  更新   更新

実装コード

// dmx_config.h
#define AD_HYST 3  // ヒステリシス幅(±3で変化を無視)

// main.c
uint16_t ReadADC1_Hyst(uint8_t channel, uint16_t prev) {
    uint16_t dd = ReadADC1(channel);
    
    // 重要: 端(0と255)は即座に更新
    // → フェーダー最小・最大で確実に0%/100%を出すため
    if (dd == 0 || dd == 255) {
        return dd;
    }
    
    // 変化量の絶対値を計算
    int16_t diff = (int16_t)dd - (int16_t)prev;
    if (diff < 0) diff = -diff;
    
    // 閾値以下なら前回値を維持
    if (diff <= AD_HYST) {
        return prev;
    }
    
    return dd;
}

ポイント解説

処理 理由
dd == 0 || dd == 255 端の例外処理が重要! ヒステリシスで端に到達しない問題を回避
int16_tでキャスト uint16_t同士の引き算でアンダーフローを防ぐ
AD_HYST = 3 経験的に3〜5が最適。大きすぎると応答が鈍い

効果

Before: 127 → 128 → 129 → 127 → 130  (4回変化)
After:  127 → 127 → 127 → 127 → 130  (1回変化)

ノイズ起因の変化を約75%削減!


対策2: 出力デッドバンド

問題: LED照明の低輝度応答

多くのLED照明器具は、DMX値1〜5程度ではほとんど発光しないか、チラつく問題があります。

DMX値:    0   1   2   3   4   5   6   7   ...
実際の光: ×   ×   △   △   △   ○   ○   ○   ...
          消灯       不安定      安定発光

解決策: デッドバンド

0以外の出力には一定値を加算して、LED照明の安定動作領域へジャンプさせます。

計算値:    0   1   2   3   4   5   6   ...
出力値:    0   4   5   6   7   8   9   ...
              └──────────────────────┘
              OUT_DEADBAND=3 を加算

重要: 上昇時のみ加算

単純に「0以外なら加算」とすると、下降時に0まで到達しないバグが発生します。

❌ 間違った実装:
計算値:  10 → 5 → 3 → 1 → 0
出力値:  13 → 8 → 6 → 4 → 0  ← 急に4→0でジャンプ!

✅ 正しい実装(上昇時のみ加算):
計算値:  0 → 1 → 3 → 5 → 3 → 1 → 0
出力値:  0 → 4 → 6 → 8 → 3 → 1 → 0
            上昇時加算   下降時はそのまま

実装コード

// dmx_config.h
#define OUT_DEADBAND 3  // 上昇時に加算する値

// main.c - id()関数
void id(void) {
    uint16_t i, d1, d2, d3, d4, d5;
    d1 = Vd[9];          // マスターフェーダー値
    Od[9] = Vd[9];       // マスターはそのまま出力
    
    for (i = 1; i <= 8; i++) {
        d2 = Vd[i];              // チャンネル入力値
        d3 = d2 * d1;            // マスターを掛ける
        d4 = d3 / 255;           // 0-255にスケーリング
        
        // ★ 核心部分: 上昇/下降で処理を分ける
        if (Od[i] > d4 + OUT_DEADBAND) {
            // 下降中: デッドバンドなしで滑らかに減少
            d5 = d4;
        } else if (Od[i] >= d4 && d4 > 0) {
            // 下降途中: 引き続き下降
            d5 = d4;
        } else if (d4 == 0) {
            // 完全消灯を目指す
            d5 = 0;
        } else {
            // 上昇中: デッドバンド加算
            d5 = d4 + OUT_DEADBAND;
        }
        
        // スムージング(後述)
        if (Od[i] > d5) {
            Od[i]--;
        } else if (Od[i] < d5) {
            Od[i]++;
        }
    }
}

状態遷移図

                    ┌──────────────┐
     d4増加         │              │  d4減少
   ┌────────────────│   通常状態   │────────────────┐
   │                │  Od = d4+DB  │                │
   │                └──────────────┘                │
   │                       ↑                        │
   │                       │ d4増加                 ↓
   │                ┌──────────────┐         ┌──────────────┐
   │                │              │         │              │
   └───────────────→│   上昇中     │         │   下降中     │
                    │  Od = d4+DB  │         │  Od = d4     │
                    └──────────────┘         └──────────────┘
                                                    │
                                                    │ d4=0
                                                    ↓
                                             ┌──────────────┐
                                             │    消灯      │
                                             │  Od = 0      │
                                             └──────────────┘

対策3: スムージング処理

概念

急激な値変化を防ぐため、1回の処理で1ステップずつしか変化させない手法です。

目標値:  100 → 200(急変)
出力値:  100 → 101 → 102 → ... → 199 → 200(滑らかに変化)

実装

// 1ステップずつ変化
if (Od[i] > d5) {
    Od[i]--;        // 目標より大きければ1減らす
} else if (Od[i] < d5) {
    Od[i]++;        // 目標より小さければ1増やす
}

DMXリフレッシュレートとの関係

DMX512は通常25〜44Hzでリフレッシュします。

30Hz × 255ステップ = 約8.5秒で0→255
                     ↓
                  十分滑らかなフェード

実装コード全体

dmx_config.h

#ifndef __DMX_CONFIG_H
#define __DMX_CONFIG_H

// ADCヒステリシス値(バタつき防止)
// 大きいほど安定、小さいほど応答速い
#define AD_HYST       3

// DMX出力オフセット(照明器具のLED応答向上用)
// 上昇時のみ加算、下降時は加算しない
#define OUT_DEADBAND  3

// リフレッシュレート選択
#define DMX_30HZ      // または DMX_25HZ

// チャンネル数
#define DMX_CHANNELS  512

#endif

main.c(抜粋)

//==============================================================================
// ヒステリシス付きADC読み取り
//==============================================================================
uint16_t ReadADC1_Hyst(uint8_t channel, uint16_t prev) {
    uint16_t dd = ReadADC1(channel);
    
    // 端(0と255)は即座に更新
    if (dd == 0 || dd == 255) {
        return dd;
    }
    
    int16_t diff = (int16_t)dd - (int16_t)prev;
    if (diff < 0) diff = -diff;
    
    if (diff <= AD_HYST) {
        return prev;
    }
    return dd;
}

//==============================================================================
// 出力値計算(スムージング + デッドバンド)
//==============================================================================
void id(void) {
    uint16_t i, d1, d2, d3, d4, d5;
    d1 = Vd[9];
    Od[9] = Vd[9];
    
    for (i = 1; i <= 8; i++) {
        d2 = Vd[i];
        d3 = d2 * d1;
        d4 = d3 / 255;
        
        // 上昇/下降で処理を分ける
        if (Od[i] > d4 + OUT_DEADBAND) {
            d5 = d4;  // 下降中
        } else if (Od[i] >= d4 && d4 > 0) {
            d5 = d4;  // 下降途中
        } else if (d4 == 0) {
            d5 = 0;   // 消灯
        } else {
            d5 = d4 + OUT_DEADBAND;  // 上昇中
        }
        
        // 1ステップずつ変化
        if (Od[i] > d5) {
            Od[i]--;
        } else if (Od[i] < d5) {
            Od[i]++;
        }
    }
}

パラメータ調整ガイド

AD_HYST(ヒステリシス幅)

効果 用途
1-2 高応答・やや不安定 高精度要求
3-4 バランス良好 一般用途(推奨)
5-8 超安定・応答鈍い ノイズ環境劣悪

OUT_DEADBAND(出力オフセット)

効果 用途
0 オフセットなし 高品質LED
3-5 チラつき防止 一般LED(推奨)
8-10 安全マージン大 安価なLED

調整フロー

1. まずAD_HYST=3, OUT_DEADBAND=3で試す
    ↓
2. バタつきが残る → AD_HYSTを+1
    ↓
3. 低輝度でチラつく → OUT_DEADBANDを+1
    ↓
4. 応答が遅い → AD_HYSTを-1

評価と改善案

現在の実装の評価

項目 評価 コメント
バタつき抑制 ⭐⭐⭐⭐⭐ 実運用で問題なし
応答速度 ⭐⭐⭐⭐ スムージングで若干遅延
コード量 ⭐⭐⭐⭐⭐ シンプルで保守性良好
CPU負荷 ⭐⭐⭐⭐⭐ 整数演算のみ、軽量
0到達性 ⭐⭐⭐⭐⭐ 修正後は確実に0到達

改善案

1. 移動平均フィルタ追加

#define FILTER_SIZE 4
uint16_t adc_buffer[FILTER_SIZE];
uint8_t adc_index = 0;

uint16_t ReadADC1_Filtered(uint8_t channel) {
    adc_buffer[adc_index] = ReadADC1(channel);
    adc_index = (adc_index + 1) % FILTER_SIZE;
    
    uint32_t sum = 0;
    for (int i = 0; i < FILTER_SIZE; i++) {
        sum += adc_buffer[i];
    }
    return sum / FILTER_SIZE;
}

2. 適応型ヒステリシス

// 中間値では大きく、端では小さく
uint16_t adaptive_hyst(uint16_t value) {
    if (value < 10 || value > 245) {
        return 1;  // 端では高精度
    }
    return AD_HYST;  // 中間では安定重視
}

3. 加速度対応スムージング

// 変化量が大きいほど速く追従
int16_t diff = d5 - Od[i];
if (diff > 10) {
    Od[i] += 3;      // 大きな変化は速く
} else if (diff > 0) {
    Od[i]++;         // 小さな変化はゆっくり
} else if (diff < -10) {
    Od[i] -= 3;
} else if (diff < 0) {
    Od[i]--;
}

まとめ

フェーダーのバタつき対策は3段階のアプローチが効果的です:

┌─────────────────────────────────────────────────────────────┐
│  ADC読み取り   →   値計算   →   出力                       │
│       ↓              ↓            ↓                        │
│  ヒステリシス   デッドバンド   スムージング                 │
│  (入力安定化)   (低輝度対策)   (急変防止)                   │
└─────────────────────────────────────────────────────────────┘
対策 効果 注意点
ヒステリシス ノイズ無視 端の例外処理必須
デッドバンド LED安定動作 上昇時のみ加算
スムージング 滑らかな変化 リフレッシュレート考慮

これらを組み合わせることで、安定かつ応答性の良いフェーダー制御が実現できます。


参考

  • STM32F10x Standard Peripheral Library
  • DMX512 Protocol Specification (ANSI E1.11)

タグ: STM32 ADC ノイズ対策 DMX512 照明制御 組み込み C言語

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?