Робототехника · Микроконтроллеры · 7 класс
Кнопка и цифровой вход
Одна кнопка должна включать и выключать светодиод. Почему вывод «плавает», а кнопка «нажимается дважды»?
Ситуация
Если в автобусе нажать кнопку «остановка», перед водителем загорается лампочка. Вы хотите сделать так же: одна кнопка — светодиод загорается, нажали ещё раз — гаснет. Вы подключаете кнопку к выводу 2 Arduino, светодиод — к выводу 13 и… светодиод сам по себе мигает — хотя вы кнопки и не касались! Если поставить резистор, он успокоится. Потом вы пишете программу-переключатель — и иногда от одного нажатия светодиод переключается дважды и остаётся выключенным. Почему вывод «плавает»? Почему кнопка «нажимается дважды»? С помощью осциллографа и `millis()` мы найдём и причину, и лекарство.
Тема
Кнопка и цифровой вход: INPUT_PULLUP, дребезг (debounce). Включаем и выключаем лампу одной кнопкой: почему кнопка нажимается «дважды»
Ключевые понятия
- digitalRead и подтягивающий резистор (pull-up)
- INPUT_PULLUP
- Дребезг (debounce)
Формулы
Пороги `digitalRead()` (ATmega328P, 5 В): выше 3 В — HIGH, ниже 1,5 В — LOW, между ними — неопределённость (вход с гистерезисом ≈ 0,5 В сохраняет прежнее состояние).
Ток, который течёт через подтягивающий (или стягивающий) резистор при нажатой кнопке, ничтожен. Внутренний подтягивающий резистор 20–50 кОм: 0,1–0,25 мА.
RC-фильтр: если контакт размыкается на миг, конденсатор не успевает зарядиться до 3 В — дребезг до вывода не доходит.
Программный антидребезг: после принятого изменения изменения внутри окна игнорируются; окно должно быть длиннее дребезга (обычно 20–50 мс).
Ошибка: вывод переведён в OUTPUT HIGH, кнопка замкнула его на землю — ток в 5 раз больше предела, чип сгорает.
Теория
Что такое цифровой вход
`pinMode(2, INPUT)` делает вывод входом: теперь он не подаёт напряжение, а измеряет его. `digitalRead(2)` сводит напряжение к двум состояниям: около 5 В — HIGH (1), около 0 В — LOW (0). Границы определённые: при 5 В ATmega328P читает всё выше 3 В как HIGH, а ниже 1,5 В — как LOW; диапазон 1,5–3 В — «никто не знает». Вывод-вход почти не потребляет тока (меньше 1 мкА): он только «смотрит».
«Плавающий» вход
Если подключить кнопку между выводом 2 и GND и не ставить резистор, то при нажатии вывод соединяется с землёй — LOW, это определённо. Но когда кнопка не нажата, вывод ни к чему не подключён — он «плавает» в воздухе. Вход с высоким сопротивлением превращается в антенну: сетевая наводка 50 Гц, соседние провода, ваша рука раскачивают его от 0 до 5 В. `digitalRead` выдаёт случайные HIGH и LOW — светодиод мигает сам по себе, а если поднести руку, ещё сильнее. Это не «сломанная Arduino», а неопределённый вход.
Pull-down, pull-up и INPUT_PULLUP
Лекарство — «притянуть» вывод через резистор к известному напряжению. Pull-down (стягивающий): 10 кОм между выводом и GND, кнопка между выводом и 5V: в покое LOW, при нажатии HIGH (через резистор течёт 0,5 мА). Pull-up (подтягивающий): 10 кОм между выводом и 5V, кнопка между выводом и GND: в покое HIGH, при нажатии LOW — логика обратная, зато один контакт кнопки на земле — это безопасно и удобно. В ATmega328P такой резистор уже есть внутри чипа (20–50 кОм): `pinMode(2, INPUT_PULLUP)` его включает — внешний резистор не нужен. Поэтому в программах Arduino «нажата = LOW» — обычное дело.
Как устроена кнопка
Внутри тактовой кнопки — металлический купол: при нажатии он прогибается и замыкает два контакта, а когда отпускаете — возвращается, как пружина. Четыре вывода — две пары: внутри каждой пары они всегда соединены, при нажатии пары замыкаются между собой. На макетной плате кнопку ставят поперёк центральной канавки; чтобы не перепутать, какая пара где, провода подключают к диагональным выводам — тогда подключение верно для любой кнопки.
Дребезг (bounce) и как с ним бороться
Купол при касании контакта успокаивается не сразу — в течение 1–10 мс он несколько раз касается и отскакивает. Для глаза это одно нажатие, а на осциллографе — несколько фронтов. `loop()` крутится сотни тысяч раз в секунду и принимает каждый фронт за «новое нажатие»: переключатель дважды меняет состояние и остаётся выключенным, а в мониторе порта «нажато» выводится дважды. Способы борьбы: программный — окно с помощью `millis()`: после принятого изменения новые в течение OYNA мс игнорируются (окно должно быть длиннее дребезга: 20–50 мс); аппаратный — RC-фильтр: 10 кОм + 1 мкФ, = 10 мс, конденсатор не успевает зарядиться до 3 В при коротких размыканиях, а триггер Шмитта на входе даёт один чистый фронт.
О модели
Напряжение на выводе считается с шагом 50 мкс, вход — триггер Шмитта (3 В / 1,5 В). Дребезг: при каждом нажатии и отпускании 2 или 4 лишних замыкания-размыкания, в сумме 2–12 мс (случайно, при повторном показе повторяется). Шум плавающего вывода — 50 Гц + медленный случайный дрейф (для наглядности). Программа продвигается каждую миллисекунду; `digitalRead()` при каждом вызове видит следующую точку примерно через 60 мкс — настоящий `loop()` тоже «заглядывает» так часто. При коротком замыкании чип накапливает ток, превышающий предел 40 мА, по времени и сгорает.
Знаете ли вы?
- У каждого входного вывода ATmega328P есть внутренний подтягивающий резистор, включаемый из программы, — по даташиту 20–50 кОм. Поэтому в Arduino для кнопки внешний резистор не обязателен: достаточно `pinMode(pin, INPUT_PULLUP)`.
- Инженер-разработчик микроконтроллеров Джек Гэнссл (Jack Ganssle) в 2004 году измерил осциллографом 18 разных кнопок и переключателей: у большинства дребезг контактов длился 1–2 мс, у худших — больше 6 мс; поэтому в большинстве программ окно антидребезга берут 20–50 мс.
Что внутри эксперимента
Настоящая плата в рисованной изометрии: вы поворачиваете её, ставите детали, собираете цепь, пишете и загружаете код — ток и напряжение считаются сразу, а при ошибке деталь сгорает. Внутри — задания, теория, тест и экскурсия.
Другие эксперименты раздела
- Arduino Uno: первая программа — 7 класс
- Аналоговый вход и «Монитор порта» — 7 класс
- ШИМ: яркость и звук — 8 класс
- millis() и многозадачность — 8 класс