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つの導体。¶
端子間に電圧を印加すると、2枚の板に等しく逆の電荷が駆動されます。その関係は次のとおりです。
Q = C × V
ここで Q は蓄えられた電荷(クーロン)、V はコンデンサにかかる電圧、C はその静電容量(ファラド)です。静電容量はデバイスの構造によって決まり、静電容量が大きいほど同じ電圧でより多くの電荷を蓄えられます。
その結果として、コンデンサは電圧を瞬時に変化させることができません。流入または流出する電荷は経路上のあらゆる抵抗を通過しなければならず、その抵抗が電圧の変化速度を決めます。
3.11.2.1. RC時定数¶
抵抗を通じてコンデンサを充電すると、ステップ状ではなく、電源電圧へ向かって滑らかに指数関数的に上昇します。その上昇の特性時間がRC時定数です。
τ = R × C
1つの τ の後、コンデンサは電源電圧の約63 %に達します。5つの τ の後には99 %を超え、実用上「完全に充電された」状態になります。
コンデンサは指数関数曲線に沿って充電されます。τ = RC は最終電圧の63 %に達するまでの時間です。¶
抵抗を通じた放電はその鏡像をたどります。電圧は初期値からゼロへ向かって指数関数的に減少し、1つの τ の後には開始電圧の37 %まで下がり、5つの τ の後には1 %未満まで下がります。
コンデンサは指数関数的な減衰に沿って放電します。τ = RC は開始電圧の37 %まで下がるまでの時間です。¶
3.11.2.2. デバウンス回路¶
入力ピンとグランドの間に直列抵抗を介してコンデンサを接続すると、ローパスフィルタが構成されます。高速なスパイクはその抵抗を通じてコンデンサを充電または放電させる時間がないため、ピンはスパイクの前の電圧に近い値を保ちます。低速な変化、つまり意図的な押下は、コンデンサを充電または放電させ、読み取り値がそれに追従します。
R1 はスイッチのハイ側をVccへプルアップし、バウンスする生のスイッチ信号を生成します。R2 と C がその信号をローパスフィルタリングしてピンに渡します。
ハードウェアデバウンス:R2 と C が、生のスイッチ信号がピンに到達する前にローパスフィルタリングします。¶
標準的な値:R1 = 10 kΩ(プルアップ)、R2 = 10 kΩ(直列)、C = 100 nF。
スイッチが開いているとき、電流はVcc → R1 → R2 → コンデンサ(直列)と流れ、τ_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フィルタが最悪のスパイクを抑制し、ソフトウェアのデバウンス時間枠が残りをカバーします。