3.11. 디바운싱¶
스위치는 완벽하게 열리거나 닫히는 접점으로 그려지지만, 실제 스위치의 접점은 두 상태 사이를 깔끔하게 전환하지 못합니다. 접점은 채터링(chatter) 을 일으킵니다 – 안정되기 전 몇 밀리초 동안 전기적 접촉을 여러 번 붙였다 떼었다 합니다. 핀을 읽는 GPIO 입력은 이를 에지가 연속으로 발생하는 것으로 봅니다. 부주의한 폴링 루프는 실제로는 한 번 누른 것을 여러 번의 “누름”으로 세고, 인터럽트 핸들러는 실제 누름 한 번당 여러 번 실행됩니다.
바운싱하는 스위치는 안정되기 전에 빠른 전이가 연속으로 발생합니다.¶
디바운싱(debouncing) 은 채터링을 필터링하여 물리적인 누름 한 번이 단일 이벤트로 등록되도록 하는 기법입니다. 이를 해결하는 두 가지 접근법이 있습니다 – 소프트웨어(펌웨어 내의 타이밍 규칙) 또는 하드웨어(배선상의 작은 필터)입니다. 이 둘은 상호 배타적이지 않습니다.
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단자 부품입니다. 물리적으로는 절연체(유전체(dielectric))로 분리된 채 짧은 거리를 두고 떨어진 두 개의 전도성 판으로 이루어져 있습니다.
평행판 커패시터: 절연층으로 분리된 두 개의 도체.¶
단자 양단에 전압을 인가하면 두 판에 크기가 같고 부호가 반대인 전하가 몰립니다. 그 관계는 다음과 같습니다
Q = C × V
여기서 Q는 저장된 전하(쿨롱), V는 커패시터 양단의 전압, C는 그 정전 용량(패럿)입니다. 정전 용량은 소자의 구조에 의해 결정되며, 정전 용량이 클수록 동일한 전압에서 더 많은 전하가 저장됩니다.
그 결과: 커패시터는 전압을 순간적으로 바꿀 수 없습니다. 들어오거나 나가는 전하는 경로상의 저항을 통과해야 하며, 그 저항이 전압이 얼마나 빠르게 변할 수 있는지를 결정합니다.
3.11.2.1. RC 시정수¶
저항을 통해 커패시터를 충전하면 계단 모양이 아니라 공급 전압을 향해 매끄럽게 지수적으로 상승합니다. 그 상승의 특성 시간이 RC 시정수입니다:
τ = R × C
한 번의 τ 가 지나면 커패시터는 공급 전압의 약 63 %에 도달합니다. 5 τ 후에는 99 %를 넘어 – 실용적으로 “완전히 충전된” 상태가 됩니다.
커패시터는 지수 곡선을 따라 충전됩니다. τ = RC 는 최종 전압의 63 %에 도달하는 시간입니다.¶
저항을 통한 방전은 그 반대 모양을 따릅니다: 전압은 초기 값에서 0을 향해 지수적으로 감소하여, 한 번의 τ 후에는 시작 전압의 37 %로 떨어지고, 5 τ 후에는 1 % 미만이 됩니다.
커패시터는 지수 감쇠를 따라 방전됩니다. τ = RC 는 시작 전압의 37 %로 떨어지는 시간입니다.¶
3.11.2.2. 디바운스 회로¶
직렬 저항을 거쳐 입력 핀과 접지 사이에 연결된 커패시터는 저역 통과 필터(low-pass filter) 를 형성합니다. 빠른 스파이크는 그 저항을 통해 커패시터를 충전하거나 방전할 시간이 없으므로, 핀은 스파이크 이전의 전압에 가깝게 유지됩니다. 느린 변화 – 의도적인 누름 – 는 커패시터를 충전하거나 방전시키고 읽기 값이 이를 따라갑니다.
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 필터가 최악의 스파이크를 억제하고, 소프트웨어 디바운스 윈도우가 남은 것을 처리합니다.