Перейти к содержанию

Кнопка: подтяжка, чтение 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).

DDxnPORTxnРежим вывода
00вход, третье состояние
01вход с подтяжкой
10выход, низкий уровень
11выход, высокий уровень

Есть и глобальный выключатель: бит 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).

Задание

  1. С кнопкой и подтяжкой в 20–50 кОм вход PD2 читается как 0 при нажатии. Какой уровень будет прочитан, если кнопку подключить так же, но забыть строку PORTD |= _BV(PORTD2);?
  2. Сколько миллисекунд пройдёт от начала устойчивого нажатия до переключения светодиода в программе с антидребезгом, если DEBOUNCE_MS равно 30, а сам цикл без задержки занимает пренебрежимо мало времени?
Ответ и решение
  1. Нажатая кнопка по-прежнему даст 0: она замыкает вывод на землю. А вот у отпущенной кнопки уровень станет неопределённым — вход без подтяжки ловит наводки и может читаться как угодно, поэтому светодиод будет переключаться сам.

  2. Около 30 мс: счётчик увеличивается раз в 1 мс и срабатывает на 30-м отсчёте подряд с новым уровнем. Если в начале нажатия был дребезг, отсчёт каждый раз начинается заново, и задержка будет больше на время дребезга.

Что дальше

В уроке 4 разберём, откуда ATmega328P берёт тактовую частоту, что такое фьюзы и почему _delay_ms может ошибаться в 16 раз. Регистры портов с битами — на странице регистров GPIO ATmega328P. Как выбрать внешний резистор для кнопки, отвечает вопрос «Какой резистор нужен кнопке Arduino», а сам принцип подтяжки — термин «Подтягивающий резистор».

Частые вопросы

Почему вход без подтяжки читается случайно?

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

Можно ли подключить кнопку к плюсу питания, а не к земле?

Можно, но тогда нужен внешний стягивающий резистор между выводом и землёй, например 10 кОм: внутри ATmega328P есть только подтягивающие резисторы к питанию. Схема на землю с внутренней подтяжкой проще — она не требует ни одной детали, кроме кнопки.

Сколько миллисекунд ждать при подавлении дребезга?

Джек Ганссл, измеривший дребезг 18 переключателей, рекомендует 20–50 мс: этого хватает почти для всех кнопок, а реакция за 50 мс воспринимается как мгновенная. Средний дребезг в его измерениях был около 1,6 мс, но отдельные экземпляры дребезжали намного дольше.

Источники

  1. 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 кОм
  2. 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
  3. 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 для проверки битов регистров

Нашли ошибку?