digitalRead: обработка кнопки

Кнопка кажется простым датчиком: нажата или отпущена. Но вход микроконтроллера должен иметь определённый логический уровень в обоих состояниях, а механические контакты при переключении несколько раз быстро замыкаются и размыкаются. Правильная схема и обработка превращают эти физические особенности в одно надёжное событие.
digitalRead() возвращает HIGH или LOW. В режиме INPUT_PULLUP внутренняя подтяжка удерживает отпущенный вход в HIGH, а кнопка соединяет его с GND, поэтому нажатие читается как LOW.
Зачем это нужно
Кнопка может запускать робота, переключать режим, подтверждать калибровку или служить концевым выключателем. Ошибка обработки приводит к заметным проблемам:
- одно нажатие переключает режим несколько раз;
- неподключённый вход случайно меняет состояние;
- долгий
delay()задерживает реакцию; - программа выполняет действие постоянно, пока кнопку держат, хотя нужно один раз;
- логика оказывается инвертированной из-за
INPUT_PULLUP.
Эти же принципы применяются к герконам, концевикам и многим цифровым выходам модулей.
Схема подключения кнопки

В учебной схеме кнопка соединяет вход Arduino с GND. В коде включается режим INPUT_PULLUP, поэтому внешний подтягивающий резистор не нужен: подтяжка находится внутри микроконтроллера.
| Узел | Куда подключить | Что будет в коде |
|---|---|---|
| один вывод кнопки | к цифровому пину, например 7 | digitalRead(7) читает вход |
| второй вывод кнопки | к GND | при нажатии вход получает LOW |
| внутренняя подтяжка | включается программой | отпущенная кнопка даёт HIGH |
Главная идея
Цифровой вход не должен «висеть в воздухе». Подтягивающий резистор задаёт устойчивое состояние, когда кнопка разомкнута. При INPUT_PULLUP резистор уже находится внутри микроконтроллера:
питание → внутренняя подтяжка → вход → кнопка → GND
| Состояние кнопки | Электрическая связь | Результат digitalRead() | Смысл |
|---|---|---|---|
| отпущена | вход удерживается подтяжкой | HIGH | не нажата |
| нажата | вход соединён с GND | LOW | нажата |
Такая логика называется активным низким уровнем: действие соответствует LOW.
Подключение с INPUT_PULLUP
Один контакт кнопки подключают к выбранному GPIO, второй — к GND. Внешний подтягивающий резистор для этого варианта не нужен, потому что включается внутренний. Его точный номинал и электрические характеристики зависят от микроконтроллера.
Для четырёхконтактной тактовой кнопки важно знать внутреннее соединение ножек: два контакта с одной стороны часто уже соединены между собой. Проверить пары можно мультиметром при отключённом питании.
const int buttonPin = 7;
const int ledPin = 13;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
}
void loop() {
const bool pressed = digitalRead(buttonPin) == LOW;
digitalWrite(ledPin, pressed ? HIGH : LOW);
}
Переменная pressed переводит электрическую инверсию в понятный логический смысл. Дальше программа работает со словами «нажата/не нажата», а не запоминает, что LOW означает действие.
Схема с внешним pull-down тоже возможна: резистор удерживает вход у GND, а кнопка соединяет его с питанием. Тогда нажатие обычно читается как HIGH. Уровень питания должен быть допустим для GPIO конкретной платы.
Уровень и событие — разные задачи
Предыдущий код включает LED всё время, пока кнопка удерживается. Для переключения режима нужен не уровень, а момент перехода из «отпущена» в «нажата» — фронт события.
Простой детектор перехода:
const int buttonPin = 7;
const int ledPin = 13;
bool previousPressed = false;
bool ledState = false;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
}
void loop() {
const bool pressed = digitalRead(buttonPin) == LOW;
if (pressed && !previousPressed) {
ledState = !ledState;
digitalWrite(ledPin, ledState ? HIGH : LOW);
}
previousPressed = pressed;
}
Логическое выражение pressed && !previousPressed истинно только при переходе. Однако механический дребезг способен создать несколько таких переходов подряд, поэтому реальная кнопка всё равно может переключить LED несколько раз.
Что такое дребезг
Контакты не переходят мгновенно из одного устойчивого состояния в другое. Они упруго касаются и отскакивают, а вход видит короткую последовательность HIGH/LOW. Длительность зависит от конкретной кнопки, усилия, износа и электрической схемы.

Слева показана физическая цепь INPUT_PULLUP. Синяя последовательность в центре — условный, а не измеренный временной рисунок дребезга. Мятная линия справа — одно устойчивое логическое изменение после фильтрации; эти диаграммы не являются проводами.
Подавление дребезга бывает:
| Способ | Как работает | Компромисс |
|---|---|---|
| задержка после обнаружения | игнорирует вход некоторое время | блокирует код или скрывает быстрые события |
проверка стабильности по millis() | принимает состояние после устойчивого интервала | требует переменных состояния |
| RC-цепь и вход Шмитта | сглаживает сигнал аппаратно | нужен расчёт и дополнительные компоненты |
| библиотека | предоставляет готовый интерфейс | нужно изучить поведение и совместимость |
Для робота полезен неблокирующий программный вариант.
Неблокирующее подавление дребезга
Алгоритм хранит сырое показание, время его последнего изменения и подтверждённое устойчивое состояние:
const int buttonPin = 7;
const int ledPin = 13;
const unsigned long debounceMs = 30;
bool lastRawPressed = false;
bool stablePressed = false;
bool previousStablePressed = false;
bool ledState = false;
unsigned long rawChangedAtMs = 0;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
}
void loop() {
const bool rawPressed = digitalRead(buttonPin) == LOW;
const unsigned long now = millis();
if (rawPressed != lastRawPressed) {
lastRawPressed = rawPressed;
rawChangedAtMs = now;
}
if (now - rawChangedAtMs >= debounceMs) {
stablePressed = rawPressed;
}
if (stablePressed && !previousStablePressed) {
ledState = !ledState;
digitalWrite(ledPin, ledState ? HIGH : LOW);
}
previousStablePressed = stablePressed;
}
Интервал 30 мс — начальная учебная оценка, не стандарт для всех кнопок. Его выбирают по наблюдению реального сигнала и требованиям к задержке реакции. Слишком малый интервал пропустит дребезг, слишком большой сделает управление медленным.
Важно инициализировать переменные осознанно. Сразу после запуска программа некоторое время подтверждает начальное состояние. Если действие при включении критично, можно в setup() прочитать вход и согласовать все сохранённые состояния.
Кнопка переключает режим робота
Вместо непосредственного управления LED событие может менять числовой режим:
const int stoppedMode = 0;
const int manualMode = 1;
const int autoMode = 2;
int mode = stoppedMode;
void selectNextMode() {
if (mode == stoppedMode) {
mode = manualMode;
} else if (mode == manualMode) {
mode = autoMode;
} else {
mode = stoppedMode;
}
}
После подтверждённого фронта вызывается selectNextMode(). Отдельная функция применения режима должна безопасно остановить моторы при stoppedMode и только затем включать разрешённые действия. Кнопка подключается к логическому входу; аварийное отключение опасного механизма не следует строить только на программном опросе обычной кнопки.
Несколько кнопок
Для нескольких входов удобно хранить номера в массиве, но каждому входу нужны собственные состояния и время изменения. Одна общая переменная rawChangedAtMs заставит кнопки влиять друг на друга.
const int buttonPins[] = {6, 7, 8};
const int buttonCount = 3;
void setup() {
for (int i = 0; i < buttonCount; i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
}
void loop() {
for (int i = 0; i < buttonCount; i++) {
const bool pressed = digitalRead(buttonPins[i]) == LOW;
// Для полноценного debounce храни отдельное состояние каждого индекса.
}
}
В крупном проекте состояние кнопки можно оформить структурой и обрабатывать функцией либо использовать изученную библиотеку с подходящей лицензией и поддержкой архитектуры платы.
Диагностика входа
Первый тест — выводить сырое состояние не слишком часто:
const int buttonPin = 7;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
Serial.begin(9600);
}
void loop() {
Serial.println(digitalRead(buttonPin));
delay(50);
}
Этот тест показывает инверсию, но из-за задержки может не показать быстрый дребезг. Для детального анализа подходит логический анализатор или осциллограф с общей землёй и допустимыми уровнями.
| Симптом | Вероятная причина | Проверка |
|---|---|---|
| значение случайно меняется | вход плавает | включить подтяжку, проверить общий GND |
всегда LOW | кнопка или провод постоянно замкнуты | обесточить, прозвонить контакты |
нажатие даёт LOW, а код ждёт HIGH | не учтён INPUT_PULLUP | ввести переменную pressed |
| одно нажатие даёт несколько событий | дребезг | добавить подтверждение стабильности |
| реакция запаздывает | длинный debounce или блокирующий код | измерить время прохода loop() |
Что запомнить о кнопке
- Входу нужен определённый уровень в разомкнутом состоянии.
INPUT_PULLUPделает отпущенную кнопкуHIGH, нажатую —LOW.- Удерживаемый уровень и одно событие нажатия — разные задачи.
- Механический дребезг создаёт несколько быстрых переходов.
- Неблокирующий debounce подтверждает состояние после периода стабильности.
- Значение интервала подбирают по реальной кнопке и требуемой реакции.
Практика
Задание 1
Подключите кнопку между GPIO и GND, включи INPUT_PULLUP и выведите сырое состояние в Serial. Зафиксируй значения при отпускании и нажатии.
Задание 2
Реализуйте переключение LED по фронту без debounce. Наблюдай, возникает ли несколько переключений, и опишите условия опыта.
Задание 3
Добавьте неблокирующее подтверждение стабильности. Сравните несколько интервалов и выберите компромисс между дребезгом и задержкой.
Задание 4
Сделайте три режима учебного робота и переключайте их одним подтверждённым нажатием. Отдельно определите безопасное состояние приводов при старте.
Проверьте себя
- Почему цифровой вход нельзя оставлять плавающим?
- Что делает режим
INPUT_PULLUP? - Какое значение соответствует нажатию в этой схеме?
- Чем уровень кнопки отличается от фронта нажатия?
- Откуда появляется дребезг контактов?
- Какие состояния хранит неблокирующий debounce?
- Почему универсального интервала debounce нет?
- Как проверить четырёхконтактную кнопку до подключения?
Сначала ответьте без подсказки. Ответ можно считать полным, если вы:
- формулируете основную мысль своими словами;
- называете важные условия, ограничения или меры безопасности;
- для схемы, кода или расчёта показываете ход решения и ожидаемый результат.
Если один из пунктов объяснить не получается, найдите соответствующую главу статьи, перечитайте её и повторите ответ.
Словарь статьи
- Цифровой вход — GPIO, на котором программа читает логический уровень.
- Подтяжка — резистивная связь входа с питанием или землёй, задающая состояние покоя.
INPUT_PULLUP— режим входа с внутренней подтяжкой к питанию.- Активный низкий уровень — логика, где действие соответствует
LOW. - Фронт — момент перехода между устойчивыми состояниями.
- Дребезг — серия быстрых переключений механического контакта.
- Debounce — аппаратное или программное подавление дребезга.
- Плавающий вход — вход без гарантированного электрического уровня.
Связанные темы
- digitalWrite: мигание светодиодом — индикация состояния входа.
- Конструкция ветвления — реакция на нажатие.
- Циклы — регулярный опрос нескольких входов.
- Функции — отдельный обработчик состояния кнопки.
Источники
- Arduino: digitalRead() — чтение цифрового входа.
- Arduino: pinMode() — режим
INPUT_PULLUP. - Arduino Built-in Examples — Button, InputPullupSerial, Debounce и State Change Detection.
- Arduino: millis() — неблокирующее измерение интервала.
- Microchip AVR Hardware Design Considerations, AN2519 — рекомендации по входам и внешним сигналам.