Перейти к основному содержимому

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

Кнопка на цифровом входе с внутренней подтяжкой

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

Коротко

digitalRead() возвращает HIGH или LOW. В режиме INPUT_PULLUP внутренняя подтяжка удерживает отпущенный вход в HIGH, а кнопка соединяет его с GND, поэтому нажатие читается как LOW.

Зачем это нужно

Кнопка может запускать робота, переключать режим, подтверждать калибровку или служить концевым выключателем. Ошибка обработки приводит к заметным проблемам:

  • одно нажатие переключает режим несколько раз;
  • неподключённый вход случайно меняет состояние;
  • долгий delay() задерживает реакцию;
  • программа выполняет действие постоянно, пока кнопку держат, хотя нужно один раз;
  • логика оказывается инвертированной из-за INPUT_PULLUP.

Эти же принципы применяются к герконам, концевикам и многим цифровым выходам модулей.

Схема подключения кнопки

Кнопка на цифровом входе Arduino UNO в Tinkercad

В учебной схеме кнопка соединяет вход Arduino с GND. В коде включается режим INPUT_PULLUP, поэтому внешний подтягивающий резистор не нужен: подтяжка находится внутри микроконтроллера.

УзелКуда подключитьЧто будет в коде
один вывод кнопкик цифровому пину, например 7digitalRead(7) читает вход
второй вывод кнопкик GNDпри нажатии вход получает LOW
внутренняя подтяжкавключается программойотпущенная кнопка даёт HIGH

Главная идея

Цифровой вход не должен «висеть в воздухе». Подтягивающий резистор задаёт устойчивое состояние, когда кнопка разомкнута. При INPUT_PULLUP резистор уже находится внутри микроконтроллера:

питание → внутренняя подтяжка → вход → кнопка → GND
Состояние кнопкиЭлектрическая связьРезультат digitalRead()Смысл
отпущенавход удерживается подтяжкойHIGHне нажата
нажатавход соединён с GNDLOWнажата

Такая логика называется активным низким уровнем: действие соответствует 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

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

Проверьте себя

  1. Почему цифровой вход нельзя оставлять плавающим?
  2. Что делает режим INPUT_PULLUP?
  3. Какое значение соответствует нажатию в этой схеме?
  4. Чем уровень кнопки отличается от фронта нажатия?
  5. Откуда появляется дребезг контактов?
  6. Какие состояния хранит неблокирующий debounce?
  7. Почему универсального интервала debounce нет?
  8. Как проверить четырёхконтактную кнопку до подключения?
Ориентиры для самопроверки

Сначала ответьте без подсказки. Ответ можно считать полным, если вы:

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

Если один из пунктов объяснить не получается, найдите соответствующую главу статьи, перечитайте её и повторите ответ.

Словарь статьи

  • Цифровой вход — GPIO, на котором программа читает логический уровень.
  • Подтяжка — резистивная связь входа с питанием или землёй, задающая состояние покоя.
  • INPUT_PULLUP — режим входа с внутренней подтяжкой к питанию.
  • Активный низкий уровень — логика, где действие соответствует LOW.
  • Фронт — момент перехода между устойчивыми состояниями.
  • Дребезг — серия быстрых переключений механического контакта.
  • Debounce — аппаратное или программное подавление дребезга.
  • Плавающий вход — вход без гарантированного электрического уровня.

Связанные темы

Источники