Кнопка: подтяжка, чтение PINx и дребезг
Кнопка — самый простой вход. Чтобы её чтение было надёжным, нужны две вещи: определённый уровень, когда кнопка отпущена, и защита от дребезга контактов, когда она нажимается.
Обновлено Редакция GAW
AVR с нуляурок 3 из 825 мин
Цель урока: Подключить кнопку к входу ATmega328P с внутренней подтяжкой, читать её через PINx и отличать одно нажатие от дребезга.
В уроке 2 выводы работали выходами. Теперь вывод станет входом: подключим кнопку между выводом 2 платы Uno (PD2) и землёй и будем включать светодиод L нажатием.
Вход с подтяжкой
После сброса вывод — вход без подтяжки (DDxn = 0, PORTxn = 0). Если кнопка отпущена, такой вход ни к чему не подключён и читается случайно. Решение — подтягивающий резистор к питанию. Он уже есть внутри ATmega328P: у входа запись единицы в бит PORTxn включает подтяжку (раздел 14.2.1 даташита). Её сопротивление — от 20 до 50 кОм (таблица DC-характеристик, раздел 30.2).
| DDxn | PORTxn | Режим вывода |
|---|---|---|
| 0 | 0 | вход, третье состояние |
| 0 | 1 | вход с подтяжкой |
| 1 | 0 | выход, низкий уровень |
| 1 | 1 | выход, высокий уровень |
Есть и глобальный выключатель: бит PUD в регистре MCUCR, записанный в единицу, отключает подтяжки на всех портах сразу (раздел 14.2.3). По умолчанию он равен нулю.
С подтяжкой отпущенная кнопка даёт на входе единицу, нажатая — ноль: логика инверсная. Ток через нажатую кнопку при 5 В — от 5 / 50 000 = 0,1 мА до 5 / 20 000 = 0,25 мА (расчёт наш).
Чтение кнопки
#include <avr/io.h>
int main(void)
{
DDRB |= _BV(DDB5); /* LED on PB5 */
PORTD |= _BV(PORTD2); /* PD2 stays an input, enable its pull-up */
for (;;) {
if (!(PIND & _BV(PIND2))) { /* low level: the button is pressed */
PORTB |= _BV(PORTB5);
} else {
PORTB &= ~_BV(PORTB5);
}
}
}
Тот же опрос можно записать макросом avr-libc: bit_is_clear(PIND, PIND2). Значение в PINx проходит через синхронизатор — защёлку и сам бит PINxn, — который защищает от метастабильности, но вносит задержку (раздел 14.2.4). Поэтому если прочитать вывод сразу после того, как программа его изменила, нужна пауза хотя бы в одну команду nop. В нашем цикле это неважно: между настройкой и чтением проходит много тактов.
Дребезг контактов
Пока светодиод просто повторяет кнопку, всё работает. Но попробуйте переключать светодиод на каждое нажатие — и он будет иногда срабатывать дважды. Причина — дребезг контактов: при замыкании контакты несколько раз отскакивают друг от друга, и вместо одного фронта на входе получается пачка импульсов. Джек Ганссл измерил 18 разных переключателей: у 16 из них дребезг длился в среднем около 1,6 мс, наибольший — 6,2 мс, а одна кнопка при отпускании дребезжала до 157 мс.
Программное решение — считать состояние изменившимся, только если новый уровень держится непрерывно всё время подавления. Ганссл рекомендует 20–50 мс. Опрашиваем вход раз в миллисекунду и считаем одинаковые отсчёты:
#define F_CPU 16000000UL
#include <avr/io.h>
#include <stdint.h>
#include <util/delay.h>
#define DEBOUNCE_MS 20 /* the new level must hold this long */
int main(void)
{
DDRB |= _BV(DDB5); /* LED on PB5 */
PORTD |= _BV(PORTD2); /* button on PD2 to GND, pull-up on */
uint8_t stable = 1; /* debounced level: 1 = released */
uint8_t count = 0;
for (;;) {
uint8_t raw = (PIND & _BV(PIND2)) ? 1 : 0;
if (raw != stable) {
if (++count >= DEBOUNCE_MS) {
stable = raw;
count = 0;
if (stable == 0) {
PINB = _BV(PINB5); /* a new press: toggle the LED */
}
}
} else {
count = 0; /* a bounce: start counting again */
}
_delay_ms(1);
}
}
Здесь задержка в 1 мс задаёт шаг опроса. В уроке 5 этот шаг будет давать таймер, и процессор освободится для другой работы. Аппаратные способы — RC-фильтр и защёлка — разобраны в термине «Дребезг контактов».
Безопасность
Если по ошибке настроить вывод с кнопкой выходом с высоким уровнем, нажатие замкнёт выход на землю, и ток ограничит только внутреннее сопротивление выходного каскада — легко превысить абсолютный максимум 40 мА (раздел 29.1). Поэтому при отладке мы советуем ставить последовательно с кнопкой резистор 220–330 Ом: при 5 В он ограничит ток 15–23 мА, на чтение входа с подтяжкой 20–50 кОм он не влияет, а выход от короткого замыкания защитит. Неиспользуемые выводы даташит советует не оставлять плавающими — например, включить на них подтяжку (раздел 14.2.6).
Задание
- С кнопкой и подтяжкой в 20–50 кОм вход PD2 читается как 0 при нажатии. Какой уровень будет прочитан, если кнопку подключить так же, но забыть строку
PORTD |= _BV(PORTD2);? - Сколько миллисекунд пройдёт от начала устойчивого нажатия до переключения светодиода в программе с антидребезгом, если
DEBOUNCE_MSравно 30, а сам цикл без задержки занимает пренебрежимо мало времени?
Ответ и решение
-
Нажатая кнопка по-прежнему даст 0: она замыкает вывод на землю. А вот у отпущенной кнопки уровень станет неопределённым — вход без подтяжки ловит наводки и может читаться как угодно, поэтому светодиод будет переключаться сам.
-
Около 30 мс: счётчик увеличивается раз в 1 мс и срабатывает на 30-м отсчёте подряд с новым уровнем. Если в начале нажатия был дребезг, отсчёт каждый раз начинается заново, и задержка будет больше на время дребезга.
Что дальше
В уроке 4 разберём, откуда ATmega328P берёт тактовую частоту, что такое фьюзы и почему _delay_ms может ошибаться в 16 раз. Регистры портов с битами — на странице регистров GPIO ATmega328P. Как выбрать внешний резистор для кнопки, отвечает вопрос «Какой резистор нужен кнопке Arduino», а сам принцип подтяжки — термин «Подтягивающий резистор».
Частые вопросы
Почему вход без подтяжки читается случайно?
Вход КМОП-микросхемы почти не потребляет тока, поэтому неподключённый вывод ловит наводки от пальцев, проводов и соседних дорожек и выдаёт то ноль, то единицу. Подтягивающий резистор держит на входе высокий уровень, пока кнопка не замкнёт вывод на землю.
Можно ли подключить кнопку к плюсу питания, а не к земле?
Можно, но тогда нужен внешний стягивающий резистор между выводом и землёй, например 10 кОм: внутри ATmega328P есть только подтягивающие резисторы к питанию. Схема на землю с внутренней подтяжкой проще — она не требует ни одной детали, кроме кнопки.
Сколько миллисекунд ждать при подавлении дребезга?
Джек Ганссл, измеривший дребезг 18 переключателей, рекомендует 20–50 мс: этого хватает почти для всех кнопок, а реакция за 50 мс воспринимается как мгновенная. Средний дребезг в его измерениях был около 1,6 мс, но отдельные экземпляры дребезжали намного дольше.
Источники
- Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet (DS40002061B) — разд. 14.2.1: PORTxn = 1 у входа включает подтяжку; разд. 14.2.3: бит PUD в MCUCR отключает все подтяжки; разд. 14.2.4: PINxn читается через синхронизатор, после записи в порт нужна команда nop; разд. 14.2.6: неиспользуемым выводам нужен определённый уровень; разд. 30.2: подтягивающий резистор вывода 20–50 кОм
- Jack Ganssle. A Guide to Debouncing, part 1 и part 2 — измерения 18 переключателей: у 16 в среднем около 1,6 мс дребезга, наибольший 6,2 мс, у одного при размыкании до 157 мс; время подавления 20–50 мс; часть 2 — https://www.ganssle.com/debouncing-pt2.htm
- AVR-LibC 2.3.2: avr/sfr_defs.h (github.com/avrdudes/avr-libc) — макросы bit_is_set, bit_is_clear и loop_until_bit_is_set для проверки битов регистров
Нашли ошибку?
Смотрите также
- Дребезг контактовСерия переключений вместо одного при нажатии кнопки
- Подтягивающий резисторPull-up и pull-down: определённый уровень на свободной линии
- Какой резистор нужен для кнопки Arduino?INPUT_PULLUP или внешние 10 кОм: схема, ток и расчёт
- ATmega328PРаспиновка, фьюзы, Optiboot, периферия, питание, схема и код на C