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

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

Идеализированная осциллограмма входного сигнала переключателя. Сигнал начинается с высокого уровня (разомкнутый переключатель), падает до низкого, несколько раз колеблется в течение нескольких миллисекунд, затем устанавливается на низком уровне (замкнутый переключатель).

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

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

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 сверху, B снизу. На двух пластинах накапливаются равные и противоположные заряды +Q и -Q, когда к терминалам приложено напряжение V.

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

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

Q = C × V

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

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

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

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

τ = R × C

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

График, показывающий рост напряжения конденсатора по экспоненциальной кривой от 0 В к шине питания. Время τ = RC отмечено на оси x там, где кривая достигает 63 % напряжения питания.

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

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

График, показывающий падение напряжения конденсатора по экспоненциальной кривой от Vmax к 0 В. Время τ = RC отмечено на оси x там, где кривая падает до 37 % начального напряжения.

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

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

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

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

Вход переключателя с аппаратным подавлением дребезга. Vcc соединяется через подтягивающий резистор 10 кΩ вниз к узлу. Этот узел соединяется с землёй через переключатель в одной ветви и через последовательный резистор 10 кΩ к выводу в другой ветви. Конденсатор 100 нФ между выводом и землёй завершает фильтр нижних частот.

Аппаратное подавление дребезга: 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-фильтр подавляет самые сильные всплески, а программное окно подавления дребезга охватывает то, что осталось.