Кнопка — механическое устройство. В момент нажатия контакты ударяются друг о друга и дребезжат: за 5–20 мс состояние несколько раз меняется между 0 и 1. Микроконтроллер, читающий со скоростью миллионы раз в секунду, видит это как десятки нажатий.
Симптомы: одно нажатие засчитывается как 5, счётчик прыгает, меню проскакивает, реле щёлкает пачкой.
Три способа лечить:
Аппаратный: конденсатор 100 нФ параллельно кнопке. Он сглаживает фронт, RC с подтяжкой 10 кОм даёт τ = 1 мс, что уже срезает большую часть дребезга. Для тяжёлых случаев — RC-фильтр плюс триггер Шмитта (74HC14).
Софтовый: после изменения состояния подождать 10–20 мс и прочитать снова. Если состояние подтвердилось — считать нажатием. Просто, надёжно, работает почти всегда.
По прерыванию: не обрабатывать в ISR, а только ставить флаг, а разбираться в основном цикле. Иначе ISR будет вызываться десятки раз за нажатие.
Правильный софтовый вариант выглядит так: хранить предыдущее состояние и время последнего изменения; если прошло больше 20 мс и состояние совпало — принять.
Ещё одна ловушка: кнопки бывают с фиксацией (переключатели) и без. И контакт может «залипать» при износе — тогда помогает только замена.
Хорошая практика: обработку кнопки писать один раз в виде функции/класса и переиспользовать. Это скучно, но избавляет от повторного изобретения антидребезга в каждом проекте.
Дребезг — самая частая причина «кнопка срабатывает дважды», и лечится он в одну строку, если знать про него.
Включи кнопку с подтяжкой и посчитай нажатия без антидребезга. Потом добавь задержку 20 мс и пересчитай. Разница будет видна сразу.
Проверь себя
Почему антидребезг нельзя делать задержкой внутри прерывания?
Прерывание блокирует остальную работу, а задержка 20 мс — это вечность для микроконтроллера. Кроме того, за это время придёт ещё несколько прерываний от дребезга. Флаг + обработка в основном цикле.
-
Wikipedia: Switch debouncer
дребезг контактов
-
ESP-IDF: GPIO и прерывания
прерывания и дребезг