3.11. Подавление дребезга

Переключатель изображают как идеальный замкнуто-разомкнутый контакт, но контакты реального переключателя не переходят между двумя состояниями чисто. Они дребезжат – замыкают и размыкают электрический контакт множество раз в течение нескольких миллисекунд, прежде чем установиться. GPIO-вход, считывающий вывод, видит это как пачку фронтов; неаккуратный цикл опроса насчитывает несколько «нажатий» на одно реальное нажатие, а обработчик прерывания срабатывает несколько раз на каждое фактическое нажатие.

An idealised scope trace showing a switch input signal. The signal starts high (open switch), drops low, bounces back and forth several times within a few milliseconds, then settles low (closed switch).

Дребезжащий переключатель создаёт пачку быстрых переходов перед тем, как установиться.

Подавление дребезга – это практика фильтрации дребезга так, чтобы каждое физическое нажатие регистрировалось как одно событие. Эту задачу решают два подхода – программный (правило тайминга в прошивке) или аппаратный (небольшой фильтр на проводе). Они не являются взаимоисключающими.

3.11.1. Программное подавление дребезга

Идея состоит в том, чтобы запоминать, когда вход последний раз менялся, и отклонять дальнейшие изменения в течение короткого окна после этой метки времени. Дребезг контактов обычно длится менее 10 мс; реальное нажатие занимает 50 – 100 мс; окно в 30 – 50 мс улавливает весь дребезг, не блокируя реальные нажатия.

В цикле опроса считайте вывод, сравните с последним стабильным значением и принимайте изменение только после того, как истекло окно подавления дребезга:

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 выполняется уже в основном контексте, где выделение памяти и медленный ввод-вывод безопасны.

3.11.2. Аппаратное подавление дребезга

Аппаратное подавление дребезга фильтрует дребезг электрически, ещё до того, как он достигнет вывода. Стандартный инструмент для этого – конденсатор.

Конденсатор – это двухполюсный компонент, который накапливает электрический заряд. Физически это две проводящие пластины, расположенные на небольшом расстоянии друг от друга и разделённые изолятором (диэлектриком).

A capacitor drawn as two parallel horizontal plates with a dielectric (insulator) between them. A lead connects each plate to an external terminal -- A on top, B on bottom. Equal and opposite charges +Q and -Q accumulate on the two plates when a voltage V is applied across the terminals.

Плоский конденсатор: два проводника, разделённые изолирующим слоем.

Приложение напряжения к его терминалам перемещает равные и противоположные заряды на две пластины; зависимость такова:

Q = C × V

где Q – накопленный заряд (кулоны), V – напряжение на конденсаторе, а C – его ёмкость (фарады). Ёмкость определяется конструкцией устройства; большая ёмкость означает больший накопленный заряд при том же напряжении.

Следствие: конденсатор не может мгновенно изменить своё напряжение. Заряд, втекающий или вытекающий, должен пройти через любое сопротивление на своём пути, и это сопротивление задаёт, насколько быстро может меняться напряжение.

3.11.2.1. Постоянная времени RC

Зарядка конденсатора через резистор даёт плавный экспоненциальный рост к напряжению питания, а не скачок. Характеристическое время этого роста – постоянная времени RC:

τ = R × C

По истечении одного τ конденсатор достигает примерно 63 % напряжения питания. После 5 τ он заряжен более чем на 99 % – «полностью заряжен» для практических целей.

A graph showing a capacitor's voltage rising along an exponential curve from 0 V toward the supply rail. The time τ = RC is marked on the x-axis where the curve reaches 63 % of the supply voltage.

Конденсатор заряжается по экспоненциальной кривой. τ = RC – это время достижения 63 % конечного напряжения.

Разрядка через резистор повторяет зеркальную картину: напряжение экспоненциально падает от своего начального значения к нулю, снижаясь до 37 % начального напряжения после одного τ и до менее 1 % после 5 τ.

A graph showing a capacitor's voltage falling along an exponential curve from Vmax toward 0 V. The time τ = RC is marked on the x-axis where the curve drops to 37 % of the starting voltage.

Конденсатор разряжается по экспоненциальному спаду. τ = RC – это время падения до 37 % начального напряжения.

3.11.2.2. Схема подавления дребезга

Конденсатор между входным выводом и землёй, подключённый через последовательный резистор, образует фильтр нижних частот. Быстрые всплески не успевают зарядить или разрядить конденсатор через этот резистор; вывод остаётся близким к тому напряжению, на котором он был до всплеска. Медленные изменения – осознанное нажатие – заряжают или разряжают конденсатор, и считываемое значение следует за ними.

R1 подтягивает верхнюю сторону переключателя к Vcc, формируя необработанный сигнал переключателя, который дребезжит. R2 и C затем фильтруют этот сигнал по нижним частотам на пути к выводу:

A switch input with hardware debouncing. Vcc connects through a 10 kΩ pull-up resistor down to a junction. That junction connects to ground through the switch on one branch, and through a 10 kΩ series resistor to the Pin on the other branch. A 100 nF capacitor between Pin and ground completes the low-pass filter.

Аппаратное подавление дребезга: R2 и C фильтруют необработанный сигнал переключателя по нижним частотам перед тем, как он достигнет вывода.

Типовые значения: R1 = 10 (подтягивающий), R2 = 10 (последовательный), C = 100 nF.

Когда переключатель разомкнут, ток течёт по пути Vcc → R1R2 → конденсатор (последовательно), заряжая конденсатор до Vcc с τ_charge = (R1 + R2) × C = 2 ms.

Когда переключатель замыкается, узел переключателя фиксируется на земле, и конденсатор разряжается через один лишь R2 на эту землю с τ_discharge = R2 × C = 1 ms.

Оба фронта фильтруются RC-цепью. Поскольку конденсатор находится на собственном узле, ниже по цепи от R2 относительно переключателя, он чисто переключается между Vcc (разомкнуто) и 0 В (замкнуто) – в установившемся режиме ни в одном из случаев через R1 не должен течь ток.

3.11.3. Выбор между ними

  • Программный способ используется по умолчанию. Он ничего не стоит в компонентах, порог легко настраивается, и он работает на любом выводе, который считывает CPU.

  • Аппаратный способ оправдывает затраты на детали, когда дребезг достигает чего-то иного, нежели код опроса CPU – прерывания, которое не должно срабатывать дважды, аппаратного счётчика, периферийного устройства без собственного фильтра.

Программное и аппаратное подавление дребезга также мирно сосуществуют: небольшой RC-фильтр подавляет самые сильные всплески, а программное окно подавления дребезга охватывает то, что осталось.