3.11. デバウンス

スイッチは完全に開閉する接点として描かれますが、実際のスイッチの接点は2つの状態の間をきれいに切り替わるわけではありません。接点はチャタリングを起こし、安定するまでの数ミリ秒の間に何度も電気的な接触と断絶を繰り返します。ピンを読み取るGPIO入力はこれをエッジの連発として認識します。不用意なポーリングループは1回の実際の押下を複数の「押下」として数え、割り込みハンドラは1回の押下ごとに何度も実行されてしまいます。

スイッチ入力信号を示す理想化されたスコープのトレース。 信号はハイ(スイッチ開)から始まり、ローに落ち、 数ミリ秒の間に何度も行き来して跳ね返った後、 ロー(スイッチ閉)に安定します。

バウンスするスイッチは、安定するまでの間に高速な遷移の連発を生成します。

デバウンスとは、チャタリングをフィルタリングして、各物理的な押下が単一のイベントとして記録されるようにする手法です。これには2つのアプローチがあります。すなわちソフトウェア(ファームウェア内のタイミングルール)またはハードウェア(配線上の小さなフィルタ)です。両者は互いに排他的ではありません。

3.11.1. ソフトウェアデバウンス

考え方は、入力が最後に変化した時刻を記憶しておき、そのタイムスタンプから短い時間枠内のさらなる変化を拒否するというものです。接点のバウンスは通常10 ms未満で、実際の押下は50~100 msかかります。30~50 msの時間枠であれば、実際の押下を妨げることなくすべてのバウンスを捕捉できます。

ポーリングループでは、ピンを読み取り、最後の安定値と比較し、デバウンスの時間枠が経過した後にのみ変化を受け入れます。

import time
from machine import Pin

button = Pin("P0", Pin.IN, Pin.PULL_UP)
last_state  = 1
last_change = 0
DEBOUNCE_MS = 50

while True:
    now = time.ticks_ms()
    state = button.value()
    if state != last_state and time.ticks_diff(now, last_change) > DEBOUNCE_MS:
        last_change = now
        last_state = state
        if state == 0:
            do_action()
    time.sleep_ms(10)

割り込み駆動の読み取りでは、ハンドラ内で同じタイミングルールを適用し、実際の押下を micropython.schedule() を介してメインコンテキストに引き渡します(GPIO入力 を参照)。

import time
import micropython
from machine import Pin

button = Pin("P0", Pin.IN, Pin.PULL_UP)
last_irq = 0
DEBOUNCE_MS = 50

def handle_press(pin):
    do_action()

def on_press(pin):
    global last_irq
    now = time.ticks_ms()
    if time.ticks_diff(now, last_irq) < DEBOUNCE_MS:
        return
    last_irq = now
    micropython.schedule(handle_press, pin)

button.irq(handler=on_press, trigger=Pin.IRQ_FALLING)

ISRはタイムスタンプによってバウンスをフィルタリングしてコールバックをキューに入れます。handle_press はメインコンテキストに戻って実行され、そこではメモリ割り当てや低速なI/Oが安全に行えます。

3.11.2. ハードウェアデバウンス

ハードウェアデバウンスは、チャタリングがピンに到達する前に電気的にフィルタリングします。標準的な手段はコンデンサです。

コンデンサは電荷を蓄える2端子の部品です。物理的には、絶縁体(誘電体)で隔てられ、短い距離を空けて保持された2枚の導電板で構成されています。

2枚の平行な水平の板の間に誘電体(絶縁体)を挟んだ コンデンサの図。各板からリードが外部の端子に 接続されており、上がA、下がBです。端子間に電圧Vを 印加すると、2枚の板に等しく逆の電荷+Qと-Qが 蓄積されます。

平行板コンデンサ:絶縁層で隔てられた2つの導体。

端子間に電圧を印加すると、2枚の板に等しく逆の電荷が駆動されます。その関係は次のとおりです。

Q = C × V

ここで Q は蓄えられた電荷(クーロン)、V はコンデンサにかかる電圧、C はその静電容量(ファラド)です。静電容量はデバイスの構造によって決まり、静電容量が大きいほど同じ電圧でより多くの電荷を蓄えられます。

その結果として、コンデンサは電圧を瞬時に変化させることができません。流入または流出する電荷は経路上のあらゆる抵抗を通過しなければならず、その抵抗が電圧の変化速度を決めます。

3.11.2.1. RC時定数

抵抗を通じてコンデンサを充電すると、ステップ状ではなく、電源電圧へ向かって滑らかに指数関数的に上昇します。その上昇の特性時間がRC時定数です。

τ = R × C

1つの τ の後、コンデンサは電源電圧の約63 %に達します。5つの τ の後には99 %を超え、実用上「完全に充電された」状態になります。

コンデンサの電圧が0 Vから電源レールへ向かって 指数関数曲線に沿って上昇する様子を示すグラフ。 曲線が電源電圧の63 %に達するx軸上の点に 時間τ = RCが示されています。

コンデンサは指数関数曲線に沿って充電されます。τ = RC は最終電圧の63 %に達するまでの時間です。

抵抗を通じた放電はその鏡像をたどります。電圧は初期値からゼロへ向かって指数関数的に減少し、1つの τ の後には開始電圧の37 %まで下がり、5つの τ の後には1 %未満まで下がります。

コンデンサの電圧がVmaxから0 Vへ向かって 指数関数曲線に沿って下降する様子を示すグラフ。 曲線が開始電圧の37 %まで下がるx軸上の点に 時間τ = RCが示されています。

コンデンサは指数関数的な減衰に沿って放電します。τ = RC は開始電圧の37 %まで下がるまでの時間です。

3.11.2.2. デバウンス回路

入力ピンとグランドの間に直列抵抗を介してコンデンサを接続すると、ローパスフィルタが構成されます。高速なスパイクはその抵抗を通じてコンデンサを充電または放電させる時間がないため、ピンはスパイクの前の電圧に近い値を保ちます。低速な変化、つまり意図的な押下は、コンデンサを充電または放電させ、読み取り値がそれに追従します。

R1 はスイッチのハイ側をVccへプルアップし、バウンスする生のスイッチ信号を生成します。R2C がその信号をローパスフィルタリングしてピンに渡します。

ハードウェアデバウンス付きのスイッチ入力。Vccは 10 kΩのプルアップ抵抗を通じて接合点へ下ります。 その接合点は、一方の分岐でスイッチを通じてグランドへ、 もう一方の分岐で10 kΩの直列抵抗を通じてピンへ 接続されます。ピンとグランドの間の100 nF コンデンサがローパスフィルタを完成させます。

ハードウェアデバウンス:R2C が、生のスイッチ信号がピンに到達する前にローパスフィルタリングします。

標準的な値:R1 = 10 (プルアップ)、R2 = 10 (直列)、C = 100 nF

スイッチが開いているとき、電流はVcc → R1R2 → コンデンサ(直列)と流れ、τ_charge = (R1 + R2) × C = 2 ms でコンデンサをVccまで充電します。

スイッチが閉じると、スイッチノードがグランドにクランプされ、コンデンサは R2 のみを通じてそのグランドへ放電し、τ_discharge = R2 × C = 1 ms となります。

両方のエッジがRCフィルタリングされます。コンデンサはスイッチから R2 の下流にある独自のノードに位置するため、Vcc(開)と0 V(閉)の間できれいにスイングし、どちらの場合も定常状態で R1 を通って電流が流れる必要はありません。

3.11.3. どちらを選ぶか

  • ソフトウェアがデフォルトです。部品のコストがかからず、しきい値の調整が容易で、CPUが読み取るあらゆるピンで機能します。

  • ハードウェアは、バウンスがCPUのポーリングコード以外のもの、たとえば二重発火してはならない割り込み、ハードウェアカウンタ、独自のフィルタを持たないペリフェラルに到達する場合に、部品のコストに見合う価値があります。

ソフトウェアデバウンスとハードウェアデバウンスは平和に共存することもできます。小さなRCフィルタが最悪のスパイクを抑制し、ソフトウェアのデバウンス時間枠が残りをカバーします。