Глиф Учебники

Урок 6. Кнопка — digitalRead и подтяжка INPUT_PULLUP

Курс «Arduino» · урок 6 из 10 · 4 минуты чтения

Светодиод — это выход: плата сама решает, подать на него напряжение или нет. Кнопка — вход: плата читает, есть ли напряжение на выводе. В этом уроке кнопка будет включать светодиод, а потом — переключать его каждым нажатием.

Как плата читает вход

digitalRead(вывод) возвращает HIGH или LOW. На Uno и Nano напряжение выше примерно 3 В читается как HIGH, ниже примерно 1,5 В — как LOW. Прежде чем читать, вывод назначают входом в setup.

Подвох в том, что вход, который никуда не подключён, висит в воздухе: на нём наводки от проводов и даже от руки, и digitalRead возвращает то HIGH, то LOW случайным образом. Кнопка, пока не нажата, ни к чему вывод не подключает, — значит, нужен резистор, который в это время держит вход в определённом состоянии. Его называют подтягивающим (pull-up, к плюсу) или стягивающим (pull-down, к GND).

INPUT_PULLUP — подтяжка внутри платы

В микроконтроллере уже есть подтягивающие резисторы (около 20–50 кОм) — их включает режим INPUT_PULLUP. Тогда хватает одной кнопки без резисторов:

Пока кнопка отпущена, внутренний резистор держит вход на 5 В — digitalRead возвращает HIGH. Нажали — вывод соединился с GND, читается LOW. Логика получается обратной: нажата — LOW.

Без подтяжки INPUT внутри платы подтяжки нет наводки вывод 2 кнопка GND то HIGH, то LOW Отпущена INPUT_PULLUP внутри платы 5 В 20–50 кОм вывод 2 кнопка GND digitalRead → HIGH Нажата INPUT_PULLUP внутри платы 5 В 20–50 кОм вывод 2 кнопка GND digitalRead → LOW
Без подтяжки вход висит и читается случайно; с INPUT_PULLUP резистор внутри платы держит вывод 2 на 5 В (HIGH), а нажатая кнопка соединяет его с GND (LOW).

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

Arduino Uno INPUT_PULLUP вывод 2 GND кнопка поперёк канавки ножки по диагонали не нужны
Кнопка стоит поперёк канавки макетной платы: от пятёрки одной ножки провод идёт к выводу 2, от пятёрки ножки по диагонали — к GND.

Светодиод горит, пока кнопка нажата

Arduino
const int BUTTON = 2;

void setup() {
  pinMode(BUTTON, INPUT_PULLUP);     // вход с внутренней подтяжкой к 5 В
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  if (digitalRead(BUTTON) == LOW) {  // нажата: вывод соединён с GND
    digitalWrite(LED_BUILTIN, HIGH);
  } else {
    digitalWrite(LED_BUILTIN, LOW);
  }
}

if (…) { … } else { … } — условие: если кнопка нажата, выполняется первый блок, иначе второй. == — сравнение, не путайте с = (присваивание). Подробнее об условиях — в курсе C++.

Переключение по нажатию

Часто нужно другое: нажал — включилось, нажал ещё раз — выключилось. Для этого надо ловить не состояние кнопки, а момент нажатия — переход из HIGH в LOW. Скетч помнит прошлое состояние кнопки и сравнивает с новым.

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

HIGH LOW вывод 2, INPUT_PULLUP отпущена нажата дребезг — несколько мс без паузы три «нажатия» вместо одного с паузой delay(50) изменение снова LOW — одно нажатие
При нажатии контакты несколько миллисекунд дребезжат, и без паузы скетч видит три нажатия; с паузой delay(50) повторное чтение даёт LOW — одно нажатие.
Arduino
const int BUTTON = 2;

bool ledOn = false;           // горит ли светодиод сейчас
int lastState = HIGH;         // прошлое состояние кнопки: HIGH — отпущена

void setup() {
  pinMode(BUTTON, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int state = digitalRead(BUTTON);
  if (state != lastState) {            // состояние изменилось
    delay(50);                         // переждать дребезг
    state = digitalRead(BUTTON);       // и прочитать снова
    if (state != lastState) {
      lastState = state;
      if (state == LOW) {              // это нажатие, а не отпускание
        ledOn = !ledOn;                // поменять на противоположное
        digitalWrite(LED_BUILTIN, ledOn ? HIGH : LOW);
        Serial.println(ledOn ? "Включено" : "Выключено");
      }
    }
  }
}

Что нового:

Откройте монитор порта — каждое нажатие печатает одну строку. Если строк при одном нажатии две, увеличьте паузу до 80–100 мс.

Подтяжка внешним резистором

Можно обойтись без INPUT_PULLUP и поставить резистор самостоятельно. Вариант со стягивающим резистором даёт привычную логику «нажата — HIGH»:

В скетче тогда pinMode(BUTTON, INPUT);, а нажатие — это digitalRead(BUTTON) == HIGH. Ток через резистор при нажатии: 5 / 10 000 = 0,0005 А = 0,5 мА — совсем немного.

Частые ошибки

Коротко

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

1. Кнопка подключена между выводом 2 и GND, в setup — pinMode(2, INPUT_PULLUP). Что вернёт digitalRead(2) при нажатии?
2. Почему вход без подтяжки читается случайно?
3. Что такое дребезг контактов?
4. Где объявить переменную, которая должна помнить значение между повторами loop()?