マイコンにおけるチャタリング&ノイズ対策

チャタリングとは、例えばマウスのクリックがなぜかダブルクリックになる現象です。

マウスに使われているタクトスイッチの劣化など機械的な要因から発生するものですが、様々な防止方法があり、「ハードウェア」「ファームウェア」「ソフトウェア」でそれぞれ異なります。

本記事では「ファームウェア」にあたるマイコンのプログラムに焦点をあてます。

なぜチャタリングは起こるか

スイッチの動作イメージを掴めば何となく理解できます。スイッチと一言で言っても色々ありますが、今回はマイクロスイッチを使います。

このマイクロスイッチは家にあった適当なもの

上記の写真のスイッチでは3つの端子が確認できますね。

下の端子から電気を流すと右上の端子にそのまま電気が流れますが、スイッチ上部の赤い突起を押すと右下の端子に電気が流れるようになります。

もちろん写真では電気が流れてません

つまり、右下の端子から電気が流れてきたらスイッチが押されたと判断できます。

こんな単純な構造ではないはずですが、説明のために色々省いてます

上記の図のうち、チャタリングが起こるタイミングは(2)と(4)です。この2つだけはONでもOFFでもない状態になっています。その他はON/OFFの区別がつきます。(1)と(5)は完全に端子とくっついていて、(3)も端子から完全に離れているからです。

チャタリング中では本当にほんの一瞬だけ電気が通ったり通らなかったりするのです。リアルタイムに電気信号を受け取り続けるプログラムからすれば、超高速でONとOFFが繰り返されているように見えます。

チャタリングというと押す時に発生するイメージがありますが、離す時にも発生するんですね。ざっくりとした説明ではありましたが、これがチャタリングの正体です。

チャタリングをプログラムでどう防ぐか

その前に「チャタリング時間」について説明しなければいけません。

チャタリングノイズは押す時と離す時に発生する

チャタリング時間とは、チャタリングが発生してから落ち着くまでの時間です。スイッチによって異なり、マイクロスイッチであれば数ミリ秒であることが多いです。

これから述べるチャタリング対策では全て「チャタリングが落ち着いてから入力を受け取る」という処理をしますが、言い換えれば「チャタリング時間よりも長い時間待ってから入力を受け取る」ことになります。なので、まずは使用するスイッチのチャタリング時間を知る必要があるのです。

開発元のメーカーのサイトなどに記載されていることもあるようですが、分からない場合はまず仮の時間を想定し、どの程度でチャタリングが起こらなくなるか調査(プログラム上の値を適当な数値に設定していく)する必要があります。

さて、本題ですが、チャタリング対策のアルゴリズムは大きく分けて3つあります。

ディレイ方式

読み込んだスイッチがONなら一定時間待ち、再度読み込んだ際にまたONであれば入力を受け付ける方式です。

メリット

  • 3つの方式の中では最も単純で実装が簡単
  • 予め入力遅延の秒数が分かる

デメリット

  • 「2つ以上のスイッチの同時押しはしない」という前提でなければ使えない
while (1) {
  // 0:OFF 1:ON
  if (SW_A == 1) {
    delay(5); // 5ミリ秒何もせず待つ
    if (SW_A == 1) {
      // 入力受け付け
    }
  }
}

while文の中のif文でスイッチがONであるかどうかを延々と確認しているイメージです。

特に特殊なことはしていないので、組み込み初心者でも簡単に実装できるはずです。

上記のコードはあくまで例ですが、察しがいい方はSW_A、SW_B、SW_Cとあった場合にスイッチの同時押しができないことに気が付くかもしれません。

delay関数の実行時、CPUは他の処理を行えなくなるので、スイッチを押したらnミリ秒経つまで他のスイッチが動作しないのです。

スイッチ押下時に処理を止めてもいい・同時押しは判定しない等の条件下でなければ使えません。

delay(5); // CPUは5ミリ秒間何もできない

しかし、「きっかり5ミリ秒待つ」という処理ができるのは強みです。この場合、スイッチを押してから入力が確定するまで5ミリ秒かかるので、入力遅延は5ミリ秒となります。実は、これ以外の方式では正確な遅延秒数を導き出すことができないので、入力遅延が数ミリ秒レベルで厳しいシステムにいいのではないでしょうか。

簡単に実装できる割に扱い方は難しい方式です。

ゲージ判定方式

一般的な用語ではないと思われますが、割り込みを使わずに実装できるので一つの方式として紹介します。

メリット

  • 複数のスイッチの同時押しが可能
  • 長押しを判定できる
  • 外来ノイズに対策できる
  • 割り込みを使わないので実装が簡単

デメリット

  • スイッチの数だけカウンタが必要なので、それを確保するメモリが必要
  • 「きっかりnミリ秒待つ」という動作ができず、入力遅延を非常に求めづらい
カウントアップ=ゲージを貯めていくイメージ

まずはスイッチごとにカウンタを用意します。押されたらそのスイッチに対応するカウンタをカウントアップし、ある一定回数以上の値(一致検出回数)に達したら入力を受けつけるというものです。

カウントアップ中は入力として受け取らないので、チャタリング時間を超えるディレイを取って入力を受け取ればチャタリングを防げます。また「カウンタの値がn回以上で長押しと判定」とすることも可能です。

外来ノイズにも色々あるが、一瞬だけ入るノイズならばこうなる

外来ノイズにも強いです。通常であればノイズが発生したら入力を開始してしまうところ、この方式では(閾値に到達しなければ)カウントアップの被害のみで済みます。

なお、この場合の外来ノイズとは、関係のない回路やリード線などから何らかの現象で誤って電気が流入してしまうような現象です。想像しやすい例を挙げるならば、ボタンを強打すると部品が揺れてリード線同士が接触してしまい、両方のリード線に電気が流れてしまう等が考えられます。その場合の物理的な対策としては、リード線が接触しても大丈夫なようにシールドを施すなどしますが、対策が難しいものであればこの方式の採用を考えるべきでしょう。

while (1) {
  // 一致検出回数 = 50
  if (COUNTER[SW_A] == 50 && COUNTER[SW_B] == 50) {
    // AとBの同時入力
    // something...
  }
  else if (COUNTER[SW_A] == 50) {
    // Aの入力
    // something...
  }
  else if (COUNTER[SW_B] == 50) {
    // Bの入力
    // something...
  }
  // 以下カウントアップ
  if (sw_a_pushed == 1 && COUNTER[SW_A] < 50) {
    countUpA();
  }
  if (sw_b_pushed == 1 && COUNTER[SW_B] < 50) {
    countUpB();
  }
}

この方式の欠点は、1回のカウントにどれだけの時間がかかるか分かりにくいところです。「他の処理を実行→ONならカウントする処理を実行→他の処理を実行」を繰り返しているので、他の処理でif文の中に入り、カウントが遅れてしまうということが考えられます。

上記の例に挙げたコードであれば、下記の4ケースで1カウント当たりの時間が異なります。

  • 「sw_a_pushed」と「sw_b_pushed」ともに1である
  • 「sw_a_pushed」が1である
  • 「sw_b_pushed」が1である
  • 「sw_a_pushed」と「sw_b_pushed」ともに1でない

50カウント目でようやく入力になりますから、各ボタンの入力遅延が異なったりするのです。ボタンを押すのは気まぐれな人間ですし、チャタリングでカウントされなかったりしますし、プログラム中の全ての条件分岐を網羅する必要があるので、入力遅延を求めることはとても難しいです。

「このプログラムの入力遅延は最大nミリ秒です!」などと宣言することが難しくなってしまいますね。入力遅延を定められる(入力遅延を知りたい)開発ケースではあまり取りたくない方法です。


ちなみに、コードはあくまで例ですので悪しからず。

実際には次のアルゴリズムで作り、スイッチを押した際と離した際のチャタリングの両方を対策しましょう。

  • 入力していないとき、ONならカウンタをカウントアップし、一致検出回数nに達したら入力を始める
  • 入力しているとき、OFFならカウンタをカウントダウンし、0に達したら入力をやめる

実装の際はカウンタのオーバーフローに気を付けてください。

サンプリング方式

割り込みを使う方式です。組み込み初心者には扱いづらいですが、仕組みは単純です。

メリット

  • 複数のスイッチの同時押しが可能
  • 予め大まかな入力遅延の秒数が分かる
  • チャタリングノイズに最も強い

デメリット

  • 入力遅延にブレが発生しやすい
  • 外来ノイズに弱い
自然な波形になる

タイマ割り込みを使って一定時間(サンプリング周期)ごとにスイッチの状態(ON/OFF)を読み出すというものです。この方式では、サンプリング周期が訪れたタイミングで全てのスイッチの状態をメモリへ書き込み、プログラムはメモリ上のスイッチの状態を取得します。

そうすれば次のスイッチの状態の読み出し時までに処理系は同じ値を読み取るわけですから、図に表せば安定した信号になります。もちろんチャタリング時間よりも長いサンプリング周期を取れば理論上はチャタリングが起こりません。

入力遅延についても、ディレイ方式ほどの正確さはないものの求めることができます。

サンプリング周期 = 10msとした場合

  • 最良のケース:遅延「約 0 ms」 スイッチを押した直後にサンプリング周期が訪れる
  • 最悪のケース:遅延「約10 ms」 サンプリング周期が訪れた直後にスイッチを押す

つまり、スイッチを押したタイミングによって0~10msのブレが発生します。平均遅延秒数は「サンプリング周期 / 2」で5msなので、これを基準にします。


ディレイ方式やゲージ判定方式に比べて大きなデメリットが少なく、チャタリング対策アルゴリズムしては最も適しているのですが、1つ問題があります。

それは、外来ノイズに弱いことです。

ノイズが入って一瞬だけONになったタイミングに運悪くサンプリング周期が差し掛かると、入力を受け付けてしまうのです。

入力時間もサンプリング周期分になる

これはサンプリング方式にゲージ判定方式を加えることで解決できます。

サンプリング(+ゲージ判定)方式

実は最も一般的な方式です。説明の都合上分けて説明していますが、サンプリング方式と言うと一般的にはこれを指すので注意してください。

メリット

  • 複数のスイッチの同時押しが可能
  • 長押しを判定できる
  • 予め大まかな入力遅延の秒数が分かる
  • 外来ノイズを対策できる

デメリット

  • スイッチの数だけカウンタが必要なので、それを確保するメモリが必要
  • 入力遅延にブレが発生しやすい

まずはスイッチごとにカウンタを用意します。タイマ割り込みを使って一定時間(サンプリング周期)ごとにスイッチの状態(ON/OFF)を読み出し、ONであればそのカウンタがカウントアップされ、ある一定以上の値(一致検出回数)に達したら入力を受け付けるというものです。

基本的には2つの方式のいいとこ取りで、一般的なチャタリング対策や外来ノイズ対策としては完成形であると思われます。

ゲージ判定方式に比べるとカウンタの増減がサンプリング周期ごとになるので、必要になる一致検出回数が少なくなり、メモリに厳しい環境なら少ないビットでカウンタを動作させる等のテクニックも活用できます(一致検出回数 = 3回ならスイッチ1つ辺り2ビットで処理可能)。

サンプリング周期 = 10ms / 一致検出回数 = 3回
前提 ケース 遅延秒数
全て一致 最良 約20ms
全て一致 最悪 約30ms
1回不一致 最良 約30ms
1回不一致 最悪 約40ms
2回不一致 最良 約40ms
2回不一致 最悪 約50ms

上記の表の通り、不一致になるとサンプリング周期分の遅れが生じます。実際には一致したり不一致だったりするので、「ブレ」が発生します。

平均遅延秒数は、不一致になった場合を除くのであれば「サンプリング周期 * 一致検出回数 - (サンプリング周期 / 2)」で求められます。不一致になった場合を考慮して、実際には「サンプリング周期 * 一致検出回数」くらいがいいかもしれませんが、ここまで来るとケースバイケースや好みと言った言葉でお茶を濁したくなりますね。


ところで、調べてみると「慣例的にはサンプリング周期 = 10ms : 一致検出回数 = 3回である」というような情報が散見されました。これは参考程度に留めておくべきでしょう。

とあるゲーム用コントローラのメンテ中

D2MV等のマイクロスイッチ(新品)であれば「サンプリング周期 = 3ms / 一致検出回数 = 2回」でも問題なく動作しました。ただし、スイッチの劣化も鑑みるならもう少し長めに設定するべきでしょう。

とりあえず設定してみる数値程度の認識で、問題なければそのまま使ってもいいと思います。人体に影響を及ぼすシステムでなければ脳死で10ms / 3回でいいかもしれない

まとめ

アルゴリズム実装コスト同時押しチャタリングノイズ外来ノイズ遅延
ディレイ方式××
ゲージ判定方式×
サンプリング方式×
サンプリング
(+ゲージ判定)方式

一通り紹介しましたが、ほとんどは「サンプリング(+ゲージ判定)方式」で事足りるはずです。また、ファームウェアではなくハードウェアで対策する手もあります。

ハードウェアによるチャタリング対策の概要はこちら(マルツオンライン)

遅延について

チャタリング対策に入力遅延はつきものです。遅延を増やすほどチャタリングが起こりにくくなるので、「どの頻度までチャタリングを許すか」「どの程度まで遅延を許すか」が争点になります。

一般的なマウスであればチャタリングが起こらないように入力遅延を多めにするでしょうが、ヘビーゲーマー向けのマウスであれば少なめにして応答速度を高めるという策も取れるでしょう。

ちなみに、格闘ゲームの専用コントローラにはあえてチャタリング対策を行っていないものがあるそうです。もちろん遅延はゼロなので、遅延を許容できないヘビーゲーマーからは好まれるんですね。

記事の内容的に身も蓋もありませんが、モノによってはそういう実装もあるということも頭の片隅に留めておくといいかもしれません。

REVIVE USBを使ったチャタリング対策ツール

REVIVE USBという自作USB入力デバイス向け基板のファームウェアを製作しました。

オープンソースであり、メーカーからファームウェアのソースコードを取得できるので、自分で好きなように弄れるという代物です。元々チャタリング対策がされていなかったので、その機能を追加したファームウェアを開発し、その際に調べた結果をこの記事に書いたという経緯があります。

サンプリング周期と一致検出回数を設定できる

この記事で言うところの「サンプリング(+ゲージ判定)方式」で作っています。

基板にマイクロスイッチを接続してキーボードに設定するとたまにチャタリングを確認できます(「a」が「aa」になる)が、本ファームウェアを使って設定するとそれを防ぐことができます。

このツールを使えばファームウェア再書き込みやUSBコネクタの着脱等の操作なく、設定ボタンを押すだけでサンプリング周期/一致検出回数を変えられるので、スイッチのチャタリング時間の調査にも便利です。

開発元に倣ってオープンソースなので、チャタリングについて調べたい方はどうぞ。

[Amazon.co.jp] REVIVE USB

0 件のコメント :

コメントを投稿