Дребезг контактов и его подавление (debounce)
Ты нажал кнопку один раз. А Arduino насчитала пять нажатий. Добро пожаловать в мир дребезга — и его укрощения.
Дребезг (bounce) — это когда металлические контакты кнопки при нажатии несколько миллисекунд «звенят», быстро замыкаясь и размыкаясь. Для нас это одно нажатие, для Arduino — десяток.
Сейчас сделаем счётчик нажатий, который считает честно — одно нажатие = +1, без призрачных срабатываний. Это классическая задача, отделяющая новичка от уверенного пользователя.
Что происходит физически
Идеал (как мы думаем): ____|‾‾‾‾‾‾‾‾‾‾|____
Реальность (дребезг): ____|‾|_|‾|_|‾‾‾‾‾|____
^^^^^^^
контакты "звенят" ~1-20 мс
Если просто считать каждое изменение с LOW на HIGH, ты насчитаешь все эти «звоны». Решение: после первого изменения подождать ~30 мс и убедиться, что сигнал устаканился.
Debounce без delay: конечный автомат
Мы не используем delay (он бы заморозил всю программу). Вместо этого запоминаем время последнего изменения через millis() — счётчик миллисекунд с момента включения платы.
const int BUTTON = 2;
int lastReading = HIGH;
int stableState = HIGH;
unsigned long lastChange = 0;
const unsigned long DEBOUNCE_MS = 30;
int count = 0;
void setup() {
pinMode(BUTTON, INPUT_PULLUP);
Serial.begin(9600);
}
void loop() {
int reading = digitalRead(BUTTON);
if (reading != lastReading) {
lastChange = millis(); // сигнал дёрнулся - засекаем время
}
if (millis() - lastChange > DEBOUNCE_MS) {
if (reading != stableState) { // прошло 30 мс, состояние новое
stableState = reading;
if (stableState == LOW) { // именно нажатие
count++;
Serial.println(count);
}
}
}
lastReading = reading;
}
Как работает под капотом
millis() возвращает число миллисекунд с момента старта (тип unsigned long — большое число). Идея debounce: каждый раз, когда сигнал меняется, мы «обнуляем таймер». Состояние считается настоящим только если оно держалось дольше 30 мс без изменений. Звон длится единицы миллисекунд, поэтому он не успевает «пережить» порог — мы его игнорируем.
Вот та же логика как чистая модель на Python — конечный автомат debounce, прогоняем через «зашумлённый» сигнал:
# Та же логика на Python: debounce как конечный автомат
DEBOUNCE = 30
def debounce(samples):
# samples: список (время_мс, сырое_значение), 0=нажата,1=отпущена
stable = 1; last = 1; last_change = 0; presses = 0
for t, raw in samples:
if raw != last:
last_change = t # сигнал дёрнулся
if t - last_change > DEBOUNCE:
if raw != stable:
stable = raw
if stable == 0: # подтверждённое нажатие
presses += 1
last = raw
return presses
# Дребезг: звон в начале, потом устойчивое нажатие на 50мс
signal = [(0,1),(2,0),(4,1),(6,0),(8,1),(10,0),(60,0),(120,1)]
print("Насчитано нажатий:", debounce(signal)) # должно быть 1
Запусти — несмотря на пять «дёрганий» в начале, автомат насчитает ровно одно нажатие. Магия порога времени.
Частые ошибки
- Считают каждое изменение без задержки — счётчик прыгает на 5–10 за нажатие.
- Используют int для millis(). Нужен unsigned long, иначе через ~32 секунды переполнение.
- Ставят delay(30) вместо millis-логики — работает, но замораживает остальную программу.
Best practices
- Порог 20–50 мс — золотая середина: дребезг гасится, а реакция остаётся быстрой.
- Для нескольких кнопок есть библиотека Bounce2 — она делает то же самое в одну строку.
- Всегда храни время в unsigned long и сравнивай через вычитание
millis() - lastChange.
Аппаратный способ и когда он нужен
Дребезг можно гасить не только кодом, но и железом — добавив к кнопке конденсатор. Конденсатор — это крошечный «бак» для заряда: он не даёт напряжению меняться мгновенно, поэтому быстрые «звоны» сглаживаются ещё до того, как доберутся до пина. Такой RC-фильтр (резистор + конденсатор) применяют, когда важна максимальная надёжность или когда сигнал читает не программа, а другая микросхема. Но для большинства любительских проектов программный debounce проще и гибче: ничего не паять, порог легко поменять в коде.
Запомни главную мысль шире самой кнопки: реальные сигналы грязные. Кнопки дребезжат, датчики шумят, провода ловят наводки. Зрелый подход к электронике — всегда закладывать, что вход «врёт», и фильтровать его: debounce для кнопок, усреднение для датчиков, проверка диапазона для чисел. Этот образ мышления отличает работающее устройство от того, что «иногда глючит, и непонятно почему».
Итоги
Дребезг — это физический «звон» контактов. Победа — в логике: засекаем millis() при каждом изменении и доверяем состоянию, только если оно продержалось дольше порога. Это первый твой конечный автомат. Дальше перейдём от «вкл/выкл» к измерению напряжения — аналоговому вводу.