Закрыть
Подключение кнопки к Arduino

Подключение кнопки к Arduino

Практически всегда, любой кто начинает изучать работу с микроконтроллерами, и Arduino здесь не является исключением, после того, как помигает светодиодом пробует подключать кнопку и управлять с ее помощью миганием этого самого светодиода. Особенно сложного здесь ничего нет, но есть один нюанс, называемый «дребезг контактов». О том как правильно подключать кнопку к Arduino, что такое «дребезг контактов», как этот эффект проявляется и методах борьбы с ним и пойдет сегодня речь.

 

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

Простейшая схема подключения кнопки к микроконтроллеру выглядит следующим образом:

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

Если ключ S1 разомкнут (кнопка отпущена), то на цифровом входе Din микроконтроллера мы будем иметь напряжение 5В, соответствующее логической единице. При нажатой кнопке вход Din подключается к земле, что соответствует уровню логического нуля и все напряжение у нас упадет на резисторе R1, величину которого выбирают исходя из того, чтобы при нажатой кнопке через него протекал не слишком большой ток (обычно порядка 10÷100 кОм).

Если же просто подключить кнопку между цифровым входом и землей (без резистора R1, подключенного к +5В) или между входом и +5В, то в положении, когда кнопка не нажата на цифровом входе микроконтроллера будет присутствовать неопределенное напряжение (может соответствовать уровню 0, а может и 1) и мы бы считывали случайные состояния. Поэтому используется резистор R1, который, как говорят, «подтягивает» вход к +5В при отпущенной кнопке.

Считывая состояние цифрового входа микроконтроллера, мы сможем определить нажата кнопка (состояние логического 0) или же нет (будем получать на входе логическую единицу).

 

Подключение кнопки к Arduino

Микроконтроллеры Atmel AVR ATmega (на базе которых и строится Arduino) имеют встроенные программно подключаемые нагрузочные резисторы Rн величиной 20 кОм и мы можем воспользоваться ими, упростив схему подключения.

Подключение кнопки к Arduino
Подключение кнопки к Arduino

Подключается внутренний нагрузочный резистор путем записи логической единицы в требуемый бит порта.

Пример скетча Arduino, который включает и выключает встроенный светодиод на 13 пине, в зависимости от того, нажата или отпущена кнопка, подключенная ко второму пину, используя внутренний нагрузочный резистор:

void setup()
{
 pinMode(13, OUTPUT); //светодиод на 13 пине
 pinMode(2, INPUT); //2 пин - в режиме входа. Кнопка подключена к земле.
 digitalWrite(2, HIGH); //подключаем подтягивающий резистор
}
void loop()
{
 digitalWrite(13, !digitalRead(2)); // считываем состояние кнопки и переключаем светодиод
}

Здесь мы инвертируем значение, считанное с входного порта, путем использование логического НЕ, обозначаемого восклицательным знаком перед функцией digitalRead, так как при нажатой кнопке мы считываем 0, а для включения светодиода в порт нам нужно отправить 1.

 

Дребезг контактов

Все бы ничего, если бы мы жили в идеальном мире с идеальными кнопками. Реальные механические контакты, которые присутствуют в кнопках никогда не замыкаются и не размыкаются мгновенно. В течении непродолжительного промежутка времени происходит многократное замыкание и размыкание контактов ключа (кнопки) в результате чего на вход микроконтроллера поступает не единичный перепад напряжения, а целая пачка импульсов. Это явление носит название «дребезг контактов».

В примере выше, когда при помощи кнопки мы просто включали и выключали светодиод мы не заметили это, так как включение/выключение светодиода в момент «дребезга» происходило очень быстро и мы просто не увидели это глазом.

Типичная осциллограмма в момент отпуская кнопки выглядит следующим образом:

Осцилограмма с дребезгом контактов
Осцилограмма с дребезгом контактов

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

В результате дребезга контактов на входе вместо изменения состояния из нуля в единицу (кнопка отпущена) мы получим целую последовательность 010101010101. Это создает для нас ложные сигналы.

С эффектом дребезга контактов можно бороться либо аппаратными, либо программными методами. Рассмотрим их подробно.

 

Аппаратный способ борьбы с дребезгом контактов

Одним из схемотехнических способов борьбы с дребезгом контактов является использование RS-триггера. Эта схема используется в случае, когда кнопка или другой механический датчик выполнены в виде группы переключающихся контактов.

Схема антидребезга на основе RS-триггера
Схема антидребезга на основе RS-триггера

RS-триггер состоит из двух логических элементов И-НЕ и имеет вход установки S (от англ. set — устанавливать) и вход сброса R (от Reset). На оба входа через токоограничивающие резисторы подано напряжение питания. На входе RS-триггера, который не подключен в данный момент к подвижному контакту, присутствует сигнал логической единицы.

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

Пусть контакт подключает вход S триггера к земле. Как только на вход триггера поступит первый логический ноль из пачки импульсов, которые вызваны дребезгом контактов, триггер переключится и на выходе устройства устанавливается логический ноль. Остальные импульсы уже не изменят состояния триггера.

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

Работа схемы не зависит от числа и продолжительности импульсов, вызванных дребезгом контактов.

Еще одним способом борьбы с дребезгом контактов является использование RC-фильтров для сглаживания колебаний. Сглаженный сигнал затем подается на вход триггера Шмидта или другого логического элемента с высокоимпедансным входом. Ниже приведена схема с использованием КМОП-инвертера. На выходе триггера Шмидта мы будем иметь сигнал, избавленный от дребезга контактов. Ниже приведена схема для антидребезговой RC-цепочки.

RC-цепочка для подавления дребезга контактов
RC-цепочка для подавления дребезга контактов

Когда ключ переключается из разомкнутого состояния в замкнутое (нажатие кнопки), конденсатор C разряжается на землю через резистор R2. Выражение для напряжения на конденсаторе VC как функция времени t в момент разряда дается выражением (1)

(1)   \begin{equation*}V_C(t)=V_{DC}(\exp{\frac{-t}{R_2 C}}) \end{equation*}

В это время происходит переход из состояния логической единицы в состояние логического нуля. Мы можем сделать так, чтобы время этого перехода превысило время дребезга. Самым простым способом сделать это является установка VC = логическому нулю для триггера Шмидта, взяв стандартный или имеющийся в наличии конденсатор емкостью C и измерить время дребезга системы, используя осциллограф или логический анализатор. Тогда R2 можно выразить следующим образом:

(2)   \begin{equation*}R_2 = \frac{-t}{C \cdot ln(V_0 / V_{DC})} \end{equation*}

здесь V0 — напряжение, соответствующее уровню логического нуля.

Когда ключ переключается из замкнутого состояния в разомкнутое (отпускание кнопки), конденсатор C заряжается до напряжения VDC через соединение R1 + R2. Выражение для VC(t) в процессе зарядки:

(3)   \begin{equation*} V_C(t)=V_{DC}\left(1-\exp{\frac{-t}{(R_1+R_2)C}}\right)\end{equation*}

Это период времени, в течение которого происходит изменение уровня с логичекого нуля в логическую единицу. Мы можем сделать так, чтобы этот переход занял времени больше, чем происходит дребезг контактов. Используя то же самое время t что и ранее, а также значение R2, найденное из уравнения (2), мы можем записать выражение для R1:

(4)   \begin{equation*}R_1=\frac{-t}{C \cdot ln(1 - V_0 / V_{DC})}-R_2\end{equation*}

Пример расчета: пусть время дребезга для кнопки, подключаемой к микроконтроллеру составляет 10 мкс. Напряжение питания VDC = + 5 В и в наличии имеется конденсатор емкостью 10 нФ. Тогда параметры элемнтов антидребезговой цепочки составят:

  1. t = 10 мкс, С = 10 нФ, V0 = 1.3 В, VDC = 5 В. Подставив эти значения в уравнение (2), получим R2 = 742 Ома.
  2. t = 10 мкс, С = 10 нФ, V1 = 3.7 В, VDC = 5 В. Подставив эти значения в уравнение (4), получим R1 = 2579 Ом.
  3. Используем неинвертирующий КМОП-буфер между кнопкой и входом микроконтроллера.

 

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

Самый простой способ борьбы с дребезгом контактов программным способом — это использование задержек.

Дребезг контактов приводит к тому, что на входном пине вместо изменения состояния с единицы в ноль при нажатии кнопки, мы получим целую серию импульсов (как на осцилограмме выше). Чтобы избавиться от их паразитного влияния нужно обнаружить нажатие кнопки, приостановить выполнение программы и реализовать некоторую задержку. Время задержки необходимо выбрать таким образом, чтобы оно превышало дребезг контактов. Такую же процедуру задержки нужно реализовать и после обнаружения отпускания кнопки.

 

Библиотека Bounce2 для Arduino

Для борьбы с дребезгом контактов для Arduino существует специальная библиотека, которая называется Bounce2.

Скачать ее можно с репозитория GitHub или по ссылке ниже.

Библиотека Bounce2
82.1 KiB
2184 Downloads
Детали

Если вы не знаете как устанавливать библиотеки в среде Arduino IDE, то можете предварительно ознакомиться с установкой библиотек в Arduino IDE.

Эта библиотека включает следующие методы:

  • Bounce () — инициализация объекта Bounce
  • void interval (unsigned long interval) — устанавливает время антидребезга в миллисекундах
  • void attach (int pin) — устанавливает пин, к которому подключена кнопка и подключает на этом выводе встроенный подтягивающий резистор
  • int update () — поскольку Bounce не использует прерывания Arduino, вы «обновляете» объект до того, как считываете его состояние и это нужно делать постоянно (например, внутри loop). Метод update обновляет объект и возвращает TRUE (1), если состояние пина изменилось (кнопка была нажата или же, наоборот, отпущена) и FALSE (0) в противном случае. Вызов метода update внутри loop необходимо производить только один раз.
  • int read () — возвращает обновленное состояние пина

По умолчанию, библиотека Bounce использует интервал стабилизации (stable interval) для реализации антидребезга. Это проще для понимания и позволяет не знать длительность дребезга.

Параметр stable interval библиотеки Bounce
Параметр stable interval библиотеки Bounce

Определив

#define BOUNCE_LOCK-OUT

в файле Bounce.h можно включить альтернативный метод борьбы с дребезгом. Этот метод позволяет быстрее реагировать на изменение состояния кнопки, однако, требует установить продолжительность дребезга, а эта величина, как я отметил выше увеличивается с течением времени, а значит, потребуется вносить изменения в код, либо установить заведомо большее значение.

Параметр lock-out interval библиотеки Bounce
Параметр lock-out interval библиотеки Bounce

Приведу пример использования этой библиотеки:

#include <Bounce2.h>

Bounce bouncer = Bounce(); //создаем экземпляр класса Bounce

void setup()
{
 pinMode(2 ,INPUT); // кнопка на пине 2
 digitalWrite(2 ,HIGH); // подключаем встроенный подтягивающий резистор
 bouncer .attach(2); // устанавливаем кнопку
 bouncer .interval(5); // устанавливаем параметр stable interval = 5 мс
 Serial.begin(9600); //установка Serial-порта на скорость 9600 бит/сек
}
void loop()
{
 if (bouncer.update())
 { //если произошло событие
  if (bouncer.read()==0)
  { //если кнопка нажата
   Serial.println("pressed"); //вывод сообщения о нажатии
  }
  else Serial.println("released"); //вывод сообщения об отпускании
 }
}

И еще один небольшой практически полезный пример. Пусть у нас есть кнопка, при длительности нажатии которой меньше 2 секунд происходит изменение переменной current_mode, в которой хранится текущий режим работы некоторого устройства. В этом примере режим будет изменяться от 0 до 5. Один раз нажали — режим имеет номер 1. Еще раз нажали — 2. И так до пяти. После пяти при очередном нажатии текущий режим становится первым и опять по кругу. Если же удерживать кнопку в нажатом состоянии более 2 секунд переменной current_mode присваивается значение 0.

#include <Bounce2.h>

#define pressed_long 2000 // долговременное нажатие = 2 секунды
#define num_modes 5 // максимальный номер режима

short int max_mode = num_modes + 1; // вспомогательная переменная

Bounce bouncer = Bounce(); //создаем экземпляр класса Bounce

unsigned long pressed_moment; // момент нажатия кнопки
int current_mode = 0; // текущий режим

void setup()
{
 pinMode(2 ,INPUT); // кнопка на пине 2
 digitalWrite(2 ,HIGH); // подключаем встроенный подтягивающий резистор
 bouncer .attach(2); // устанавливаем кнопку
 bouncer .interval(5); // устанавливаем параметр stable interval = 5 мс
 Serial.begin(9600); //установка Serial-порта на скорость 9600 бит/сек
}
void loop()
{
 if (bouncer.update())
 { //если произошло событие
  if (bouncer.read()==0)
  { //если кнопка нажата
   pressed_moment = millis(); // запоминаем время нажатия
  }
  else
  { // кнопка отпущена
   if((millis() - pressed_moment) < pressed_long)
   { // если кнопка была нажата кратковременно
    current_mode++; // увеличиваем счетчик текушего режима
    current_mode %= max_mode; // остаток от целочисленного деления
    if (current_mode == 0) current_mode = 1; // режим меняется от 1 до num_modes
   }
   else
   { // кнопка удерживалась долго
    current_mode = 0;
    pressed_moment = 0; // обнуляем момент нажатия
   }
   Serial.println("Current mode:");
   Serial.println(current_mode); // выводим сообщение о текущем режиме
  }
 }
}

[add_ratings]

17 thoughts on “Подключение кнопки к Arduino

  1. Не думал что узнаю что то новое из этой темы...

    Однако про RS триггер и библиотеку я не знал...спасибо)))

  2. Добрый вечер Андрей! попробовал ваш код в конце статьи, работает безукоризненно), а как сделать так что-бы программа не ждала отпускания кнопки после длительного нажатия, а после скажем 2 секунд, переводила режим, не дожидаясь отпускания кнопки?

    и ещё, что хотел спросить, где можно отлаживать скетч, что-бы смотреть как ведёт себя программа, где и как меняются переменные?

    Заранее спасибо!

  3. В 28 строчке мы запомнили время pressed_moment, когда кнопка была нажата. Перед закрывающей скобкой loop в 47 строчке мы можем это использовать и записать:

    if((millis() - pressed_moment) > pressed_long) { // кнопка удерживалась долго current_mode = 0; pressed_moment = 0; // обнуляем момент нажатия }

    и строки 38-42 удалить за ненадобностью (если их не удалять — при отпускании кнопки режим будет сбрасываться в ноль повторно — это просто лишнее действие, но работать будет и в этом случае).

    Отладка при работе с Arduino производится просто: вставляете в том месте кода, где вы хотите посмотреть значения каких-то переменных, регистров, результатов выполнения функций и прочее в Serial порт, используя Serial.println или Serial.print. В реальном времени вы сможете наблюдать их, используя монитор порта. Только так. Второй вариант — изучать микроконтроллеры AVR, и писать/отлаживать программы, например, используя Atmel Studio. Но тут про простоту Arduino придется забыть, зато можно будет пошагово (по одной строчке кода) выполнять программу и смотреть, как результаты выполнения этой строчки кода изменяют регистры, ячейки памяти в микроконтроллере. А для Arduino — просто выводите интересующие вас вещи в последовательный порт.

    1. не выходит, пробовал даже ниже под этой записью, выключать светодиоды

  4. Чуть скорректированный работающий вариант кода, сбрасывающий режим в ноль при длительном нажатии, не отпуская кнопку.

    #include <Bounce2.h> #define pressed_long 2000 // долговременное нажатие = 2 секунды #define num_modes 5 // максимальный номер режима short int max_mode = num_modes + 1; // вспомогательная переменная Bounce bouncer = Bounce(); //создаем экземпляр класса Bounce unsigned long pressed_moment; // момент нажатия кнопки int current_mode = 0; // текущий режим void setup() { pinMode(2 ,INPUT); // кнопка на пине 2 digitalWrite(2 ,HIGH); // подключаем встроенный подтягивающий резистор bouncer .attach(2); // устанавливаем кнопку bouncer .interval(5); // устанавливаем параметр stable interval = 5 мс Serial.begin(9600); //установка Serial-порта на скорость 9600 бит/сек } void loop() { if (bouncer.update()) { //если произошло событие if (bouncer.read()==0) { //если кнопка нажата pressed_moment = millis(); // запоминаем время нажатия } else { // кнопка отпущена if((millis() - pressed_moment) < pressed_long) { // если кнопка была нажата кратковременно current_mode++; // увеличиваем счетчик текушего режима current_mode %= max_mode; // остаток от целочисленного деления if (current_mode == 0) current_mode = 1; // режим меняется от 1 до num_modes pressed_moment = 0; // обнуляем момент нажатия Serial.println("Current mode:"); Serial.println(current_mode); // выводим сообщение о текущем режиме } } } if((pressed_moment > 0) && ((millis() - pressed_moment) > pressed_long)) { // кнопка удерживалась долго current_mode = 0; pressed_moment = 0; // обнуляем момент нажатия Serial.println("Current mode:"); Serial.println(current_mode); // выводим сообщение о текущем режиме } }

  5. Андрей! Всё супер))), у меня была мысль реализовать то, что вы в низу показали (если больше нуля и больше двух секунд) , но отбросил эту мысль считая,считая, что это не может изменить ход работы кода.

    большое спасибо Андрей)))

    1. В статье в разделе Библиотека Bounce2 для Arduino есть ссылки на скачивание этой библиотеки в виде zip-архива и на репозиторий разработчика. Скачайте, распакуйте — там все исходники.

  6. У Вас ошибки в формулах. Если исходить, что исходные формулы (1) и (3) записаны верно, то получается:

    формула (2) выведена неправильно, пример расчета п. 1 это подтверждает (натуральный логарифм надо ставить под дробью, рядом с емкостью);

    формула (4) выведена неправильно (единица потерялась, а ее надо ставить внутри логарифма: ln (1-V0/Vdc).

    Проверьте еще раз, пожалуйста, и исходные формулы тоже.

    1. Вадим, вы совершенно правы. В тексте действительно были допущены опечатки. Спасибо за внимательность, исправил. Формулы (1) и (3) — верны.

  7. Кстати!

    За место:

    "pinMode (2, INPUT); //

    digitalWrite (2, HIGH); //

    Можно просто написать одной строчкой "pinMode (2, INPUT_PULLUP);

    Но INPUT_PULLUP это чисто ардуиновский аргумент пина так что в чистых плюсах работать не будет.

  8. Андрей у вас просто замечательный сайт! Дай вам бог здоровья. Как жаль что я не знал о вас когда самостоятельно решал проблему с дребезгом. На российском комьюнити ардуинщиков нашел 100% метод борьбы с таким негативным явлением. И... собственно хочу поделиться:

    statusKnopka = digitalRead (knopka); //считываем текущее значение кнопки

    statusLed = digitalRead (ledPin); //считываем текущее значение пина светодиода

    if (statusKnopka == LOW && flag == 0){ //если пришел первый дребезг и переменная flag = 0, то запустится подпрограмма и...

    digitalWrite (ledPin, !statusLed); //запишет на пин 8 противоположное значение текущего состояния светодиода

    flag = 1; //присвоит переменной flag значение 1, что не даст дребезгам кнопки зайти в эту подпрограмму еще раз

    }

    if (statusKnopka == HIGH && flag == 1){ //если кнопка оне нажата и переменная flag = 1, то запустится подпрограмма и...

    flag = 0; //обнулит переменную flag, и ардуина готова отреагировать на новый дребезг

    }

  9. Лучше сразу нацеливаться на многозадачность. Создавать прерывание по таймеру. И рисовать драйвер кнопки. При опросе ноги в каждое прерывание три 1 подряд — кнопка отжата, три 0 подряд — нажата, а иначе неопределенное состояние кнопки. И уже нажата кнопка или нет определять с определенной ячейки. Потом создать обработчик событий нажатия кнопки.

    Да понимаю, сложновато получается. Но если у вас уже будет кнопок с клавиатуру. Да и задач на вашу систему много нагружено, то проще надстраивать систему.

  10. Добрый день!

    arduino.ru/forum/proekty/...hilda-lcd-knopki

    Тут я сделал антидребезг 5 кнопок по аналоговому входу. Я не силен в программировании, прошу вас оптимизировать код и(или) создать библиотеку для шилда из 5 кнопок.

    Суть метода, постоянно происходит опрос аналогового входа кнопок в теле программы и происходит сравнение между двумя замерами (интервал между замерами можно выставить от 5 до 30 мс это оптимальное время для антидребезга) уровня напряжения (от 0 до 1024 ед.) на аналоговом входе кнопок. Если уровни совпадают, то интерпретируется нажатие определенной кнопки, если нет, то происходит повторный замер и сравнение уровней...

  11. Автору огромное спасибо!

    Решил проблему с кнопкой с помощью 2х резисторов 10кОм и кондесантора 2мКф.

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

Оставить ответ

Ваш email не будет опубликован.Обязательны поля помечены *