29 марта 2026 - Admin

Подключение кнопки к Ардуино

Кнопки Ардуино

Кнопка — простейшее устройство ввода, с помощью которого можно дать команду Ардуино. Есть некоторые особенности подключения кнопок к плате Ардуино, которые мы разберём в этой статье. Также мы познакомимся с примерами программного кода, реализующими реакцию микроконтроллера на нажатие кнопки.

Краткое содержание:

Виды кнопок

Вообще, кнопка - это нечто, что умеет замыкать и размыкать контакты. Но есть разные механические реализации этого нехитрого устройства, о которых полезно знать.

Во-первых, кнопки делятся по типу фиксации на:

  • Тактовые кнопки — без фиксации. Нажал - контакты замкнулись, отпустил - разомкнулись. Как пример - кнопка дверного звонка.
  • Кнопки с фиксацией — нажал один раз — контакт замкнулся, нажал ещё раз — разомкнулся. Такие кнопки можно встретить в качестве выключателей питания на бытовой технике
  • Клавишные кнопки — имеют два устойчивых положения, замкнуто/разомкнуто. Как пример - классический выключатель света.

Во-вторых, кнопки можно поделить по типу начального состояния:

  • Нормально-разомкнутая, иногда говорят "замыкающая" (на английском NO, normally open). В изначальном состоянии цепь разомкнута.
  • Нормально-замкнутая, иногда говорят "размыкающая" (NC, normally closed) - наоборот, в свободном состоянии замнута и размыкается при нажатии.

Обозначение кнопок на схеме

Обозначение некоторых типов кнопок на схемах: а) нормально-разомкнутая без фиксации б) нормально-замкнутая без фиксации в) нормально-разомкнутая с фиксацией

Есть и более сложные кнопки и переключатели. Например, которые управляют сразу несколькими группами контактов. Но, с точки зрения Ардуино, программирование всех видов кнопок весьма похоже: нужно просто "научить" микроконтроллер определять, замкнута ли та или иная пара контактов. Так что для простоты в данной статье мы рассмотрим работу с обычными тактовыми кнопками. Такие кнопки можно найти в каждом стандартном наборе Ардуино.

Контакты кнопки Ардуино

Стандартная кнопка из набора Ардуино. У каждого контакта есть пара выводов, для удобства закрепления кнопки на макетной плате. При нажатии на кнопку все 4 вывода оказываются замкнуты.

Подключение кнопки к плате Ардуино

На плате Ардуино есть пины (контакты), которые могуть работать как "вход" - то есть определять уровень напряжения, который на них подали. Режим пина задаётся в коде следующим образом:

// переключаем пин №7 в режим входа
pinMode(7, INPUT);

Поступим просто: подключим один контакт кнопки к положительному выводу питания, а другой - к пину Ардуино. Нажали кнопку - на пин попал высокий потенциал - осталось обработать это событие в программе.

Подключение кнопки к Ардуино без подтягивающего резистора

Неправильное подключение кнопки к Ардуино: нет подтягивающего резистора

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

Подтягивающий резистор

Чтобы этого не происходило, в схему добавляют так называемый подтягивающий резистор (в англоязычной литературе pull-up resistor):

Кнопка с подтягивающим резистором

Подключение кнопки с подтягивающим резистором

Теперь, когда кнопка разомкнута, пин Ардуино заземлён через резистор и защищён от непредвиденных помех.

Нужно также упомянуть, что схему можно собрать "наоборот" - подтянуть пин к высокому уровню, а при нажатии кнопки сажать его на землю. Такой вариант инвертированной логики мы как раз увидим чуть ниже.

Внутренняя подтяжка

На некоторых платах Ардуино есть встроенные подтягивающие резисторы номиналом около 20-50 кОм, которые можно активировать программно. Делается это через объявления режима INPUT_PULLUP для пина в коде скетча:

// включаем для пина №2 внутренний подтягивающий резистор
pinMode(2, INPUT_PULLUP);

Внутренняя подтяжка происходит к высокому уровню. Поэтому схему нужно собрать вот так, подсоединяя второй вывод кнопки к земле:

Подключение кнопки с внутренней подтяжкой

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

Простейший скетч: нажатие и отпускание

Теперь напишем скетч для этой схемы. Логика очень простая: при нажатии на кнопку будем зажигать встроенный светодиод, при отпускании кнопки - гасить его.

// номер пина, к которому подключена кнопка
const int buttonPin = 2;
// переменная для хранения состояния кнопки
int buttonState = 0;

void setup() {
  // настраиваем пин кнопки как вход с внутренним подтягивающим резистором
  pinMode(buttonPin, INPUT_PULLUP);
  // настраиваем пин встроенного светодиода как выход
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  // считываем состояние кнопки
  buttonState = digitalRead(buttonPin);

  if (buttonState == LOW) {
    // кнопка нажата - включаем светодиод
    digitalWrite(LED_BUILTIN, HIGH);
  } else {
    // кнопка отпущена - выключаем светодиод
    digitalWrite(LED_BUILTIN, LOW);
  }
}

Дребезг контактов: что это и почему мешает

Когда вы нажимаете или отпускаете кнопку, её контакты не замыкаются мгновенно. В течение нескольких миллисекунд они могут "подпрыгивать" - многократно замыкаться и размыкаться. Это явление называется дребезгом контактов (contact bounce).

График сигнала при дребезге контактов

Примерный график сигнала при дребезге контактов

На графике видно, что вместо одного чёткого перехода сигнал "скачет" между 0 и 1. Для Arduino это выглядит как серия быстрых нажатий и отпусканий, хотя вы нажали кнопку всего один раз.

Из-за дребезга программа может реагировать на одно нажатие как на несколько, что приводит к ошибкам: светодиод может мигать, счётчик увеличиваться на несколько единиц и т.д.

Программная фильтрация дребезга

Самый простой способ избавиться от дребезга — добавить небольшую задержку после обнаружения нажатия или отпускания кнопки. Такой подход называется программной фильтрацией дребезга (debounce).

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

// пример ПЛОХОГО КОДА!
// не используйте в своих проектах!

// номер пина, к которому подключена кнопка
const int buttonPin = 2;
// переменная для хранения состояния кнопки
int buttonState = 0;
// задержка для фильтрации дребезга, в милисекундах
const unsigned long debounceDelay = 20;

void setup() {
  // настраиваем режимы пинов 
  pinMode(buttonPin, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  // считываем состояние кнопки
  buttonState = digitalRead(buttonPin);

  if (buttonState == LOW) {
    // ждём немного, чтобы убедиться, что кнопка нажата
    delay(debounceDelay);
    // проверяем ещё раз состояние кнопки
    buttonState = digitalRead(buttonPin);
    if (buttonState == LOW) {
        // только теперь включаем светодиод
        digitalWrite(LED_BUILTIN, HIGH);
    } 
  } else {
    // кнопка отпущена - выключаем светодиод
    digitalWrite(LED_BUILTIN, LOW);
  }
}

У этого кода, как минимум, два недостатка:

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

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

Возможных реализаций может быть много, вот один из примеров корректного кода:

// номер пина кнопки и светодиода
const int buttonPin = 2;
const int ledPin = 13;

int buttonState = 0;         // подтверждённое состояние кнопки
int lastButtonState = 0;     // состояние кнопки в предыдущем цикле

unsigned long lastDebounceTime = 0;  // время последнего изменения состояния
unsigned long debounceDelay = 50;    // задержка для фильтрации (мс)

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  int reading = digitalRead(buttonPin);

  // если состояние изменилось — сбрасываем таймер
  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }

  // если прошло достаточно времени, считаем состояние стабильным
  if ((millis() - lastDebounceTime) > debounceDelay) {
    // если состояние изменилось — обновляем и реагируем
    if (reading != buttonState) {
      buttonState = reading;
      if (buttonState == LOW) {
        digitalWrite(ledPin, HIGH); // кнопка нажата — включаем светодиод
      } else {
        digitalWrite(ledPin, LOW);  // кнопка отпущена — выключаем светодиод
      }
    }
  }
  lastButtonState = reading;

  // ... тут можно выполнять другую полезную работу,
  // цикл не блокируется расположенным выше кодом ...

}

В этом примере кнопка считается "нажатой" только если её состояние стабильно не меняется в течение 50 мс. Это значение можно подобрать экспериментально — обычно достаточно 20–50 мс.

Аппаратная фильтрация (RC-цепочка)

Для более надёжной фильтрации дребезга можно использовать аппаратный способ — RC-цепочку (резистор + конденсатор). Такая схема сглаживает резкие колебания сигнала при нажатии кнопки.

Схема подключения RC-цепочки к Ардуино

Схема подключения RC-цепочки для устранения дребезга контактов

Что мы здесь видим. По-прежнему в схеме остаётся резистор подтяжки 20 кОм. В данном примере подтягиваем к высокому уровню. А вот сигнал с кнопки идёт не напрямую на пин Ардуино, а через интегрирующую RC-цепь. Более подробно эта тема рассмотрена в статье про свойства RC-цепи. Здесь же упомянем, что интегрирующая RC-цепь устраняет из сигнала высокие частоты - а это как раз и есть мешающий нам дребезг.

Номиналы деталей следует выбрать так, чтобы постоянная времени RC-цепи превышала период дребезга. В данном примере (резистор 10 кОм и конденстор 0.1 мкФ) постоянная времени будет равна 1 мс. При необходимости следует увеличить сопротивление резистора и/или ёмкость конденсатора.

Аппаратная фильтрация особенно полезна, если требуется высокая надёжность или если кнопка подключена к прерыванию (interrupt). В особо ответственных схемах RC-цепь используют в сочетании с триггером Шмитта, что даёт чёткий крутой фронт сигнала от кнопки.

Библиотеки для кнопок: краткий обзор

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

Библиотека Особенности Пример использования
Bounce2 Простая фильтрация дребезга, минимальный код
#include 
Bounce debouncer = Bounce();
const int buttonPin = 2;
const int ledPin = 13;

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  debouncer.attach(buttonPin);
  debouncer.interval(25); // Задержка фильтрации, мс
}

void loop() {
  debouncer.update();
  if (debouncer.fell()) { // Кнопка нажата
    digitalWrite(ledPin, HIGH);
  }
  if (debouncer.rose()) { // Кнопка отпущена
    digitalWrite(ledPin, LOW);
  }
}
OneButton Обработка коротких/длинных нажатий, двойных кликов
#include 
OneButton button(2, true); // true — кнопка к GND

void setup() {
  button.attachClick([]() {
    // Короткое нажатие
  });
  button.attachLongPressStart([]() {
    // Долгое нажатие
  });
}

void loop() {
  button.tick();
}
EasyButton Удобный интерфейс, поддержка событий
#include 
EasyButton button(2);

void setup() {
  button.begin();
  button.onPressed([]() {
    // Кнопка нажата
  });
}

void loop() {
  button.read();
}

Если у вас простая задача — достаточно программной фильтрации. Если требуется сложная логика (удержание, двойной клик, несколько кнопок) — используйте библиотеку.

Поделиться в соцсетях:

Добавить комментарий