Практически всегда, любой кто начинает изучать работу с микроконтроллерами, и 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, который включает и выключает встроенный светодиод на 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-триггер состоит из двух логических элементов И-НЕ и имеет вход установки S (от англ. set — устанавливать) и вход сброса R (от Reset). На оба входа через токоограничивающие резисторы подано напряжение питания. На входе RS-триггера, который не подключен в данный момент к подвижному контакту, присутствует сигнал логической единицы.
Если подвижной контакт замыкает вход на землю, то но нем формируется уровень логического нуля. При нажатии и отпускании кнопки (либо при срабатывании другого механического датчика) при помощи подвижного контакта то один, то другой вход триггера подключается к земле.
Пусть контакт подключает вход S триггера к земле. Как только на вход триггера поступит первый логический ноль из пачки импульсов, которые вызваны дребезгом контактов, триггер переключится и на выходе устройства устанавливается логический ноль. Остальные импульсы уже не изменят состояния триггера.
Это состояние сменится на противоположное только тогда, когда подвижный контакт сомкнется в верхним контактом. Как только на вход R триггера поступит первый отрицательный импульс, триггер переключится и на выходе появится логическая единица. В этом состоянии триггер будет находиться до тех пор, пока при помощи подвижного контакта вход S опять не будет подключен к земле.
Работа схемы не зависит от числа и продолжительности импульсов, вызванных дребезгом контактов.
Еще одним способом борьбы с дребезгом контактов является использование RC-фильтров для сглаживания колебаний. Сглаженный сигнал затем подается на вход триггера Шмидта или другого логического элемента с высокоимпедансным входом. Ниже приведена схема с использованием КМОП-инвертера. На выходе триггера Шмидта мы будем иметь сигнал, избавленный от дребезга контактов. Ниже приведена схема для антидребезговой RC-цепочки.
Когда ключ переключается из разомкнутого состояния в замкнутое (нажатие кнопки), конденсатор C разряжается на землю через резистор R2. Выражение для напряжения на конденсаторе VC как функция времени t в момент разряда дается выражением (1)
(1)
В это время происходит переход из состояния логической единицы в состояние логического нуля. Мы можем сделать так, чтобы время этого перехода превысило время дребезга. Самым простым способом сделать это является установка VC = логическому нулю для триггера Шмидта, взяв стандартный или имеющийся в наличии конденсатор емкостью C и измерить время дребезга системы, используя осциллограф или логический анализатор. Тогда R2 можно выразить следующим образом:
(2)
здесь V0 — напряжение, соответствующее уровню логического нуля.
Когда ключ переключается из замкнутого состояния в разомкнутое (отпускание кнопки), конденсатор C заряжается до напряжения VDC через соединение R1 + R2. Выражение для VC(t) в процессе зарядки:
(3)
Это период времени, в течение которого происходит изменение уровня с логичекого нуля в логическую единицу. Мы можем сделать так, чтобы этот переход занял времени больше, чем происходит дребезг контактов. Используя то же самое время t что и ранее, а также значение R2, найденное из уравнения (2), мы можем записать выражение для R1:
(4)
Пример расчета: пусть время дребезга для кнопки, подключаемой к микроконтроллеру составляет 10 мкс. Напряжение питания VDC = + 5 В и в наличии имеется конденсатор емкостью 10 нФ. Тогда параметры элемнтов антидребезговой цепочки составят:
- t = 10 мкс, С = 10 нФ, V0 = 1.3 В, VDC = 5 В. Подставив эти значения в уравнение (2), получим R2 = 742 Ома.
- t = 10 мкс, С = 10 нФ, V1 = 3.7 В, VDC = 5 В. Подставив эти значения в уравнение (4), получим R1 = 2579 Ом.
- Используем неинвертирующий КМОП-буфер между кнопкой и входом микроконтроллера.
Программный способ борьбы с дребезгом контактов
Самый простой способ борьбы с дребезгом контактов программным способом — это использование задержек.
Дребезг контактов приводит к тому, что на входном пине вместо изменения состояния с единицы в ноль при нажатии кнопки, мы получим целую серию импульсов (как на осцилограмме выше). Чтобы избавиться от их паразитного влияния нужно обнаружить нажатие кнопки, приостановить выполнение программы и реализовать некоторую задержку. Время задержки необходимо выбрать таким образом, чтобы оно превышало дребезг контактов. Такую же процедуру задержки нужно реализовать и после обнаружения отпускания кнопки.
Библиотека Bounce2 для Arduino
Для борьбы с дребезгом контактов для Arduino существует специальная библиотека, которая называется Bounce2.
Скачать ее можно с репозитория GitHub или по ссылке ниже.
Если вы не знаете как устанавливать библиотеки в среде 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) для реализации антидребезга. Это проще для понимания и позволяет не знать длительность дребезга.
Определив
#define BOUNCE_LOCK-OUT
в файле Bounce.h можно включить альтернативный метод борьбы с дребезгом. Этот метод позволяет быстрее реагировать на изменение состояния кнопки, однако, требует установить продолжительность дребезга, а эта величина, как я отметил выше увеличивается с течением времени, а значит, потребуется вносить изменения в код, либо установить заведомо большее значение.
Приведу пример использования этой библиотеки:
#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]
Не думал что узнаю что то новое из этой темы...
Однако про RS триггер и библиотеку я не знал...спасибо)))
Добрый вечер Андрей! попробовал ваш код в конце статьи, работает безукоризненно), а как сделать так что-бы программа не ждала отпускания кнопки после длительного нажатия, а после скажем 2 секунд, переводила режим, не дожидаясь отпускания кнопки?
и ещё, что хотел спросить, где можно отлаживать скетч, что-бы смотреть как ведёт себя программа, где и как меняются переменные?
Заранее спасибо!
В 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 — просто выводите интересующие вас вещи в последовательный порт.
не выходит, пробовал даже ниже под этой записью, выключать светодиоды
Чуть скорректированный работающий вариант кода, сбрасывающий режим в ноль при длительном нажатии, не отпуская кнопку.
#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); // выводим сообщение о текущем режиме } }
Андрей! Всё супер))), у меня была мысль реализовать то, что вы в низу показали (если больше нуля и больше двух секунд) , но отбросил эту мысль считая,считая, что это не может изменить ход работы кода.
большое спасибо Андрей)))
а где можно посмотреть исходник библиотеки «Bounce2.»?
В статье в разделе Библиотека Bounce2 для Arduino есть ссылки на скачивание этой библиотеки в виде zip-архива и на репозиторий разработчика. Скачайте, распакуйте — там все исходники.
Спасибо!
У Вас ошибки в формулах. Если исходить, что исходные формулы (1) и (3) записаны верно, то получается:
формула (2) выведена неправильно, пример расчета п. 1 это подтверждает (натуральный логарифм надо ставить под дробью, рядом с емкостью);
формула (4) выведена неправильно (единица потерялась, а ее надо ставить внутри логарифма: ln (1-V0/Vdc).
Проверьте еще раз, пожалуйста, и исходные формулы тоже.
Вадим, вы совершенно правы. В тексте действительно были допущены опечатки. Спасибо за внимательность, исправил. Формулы (1) и (3) — верны.
Кстати!
За место:
"pinMode (2, INPUT); //
digitalWrite (2, HIGH); //
Можно просто написать одной строчкой "pinMode (2, INPUT_PULLUP);
Но INPUT_PULLUP это чисто ардуиновский аргумент пина так что в чистых плюсах работать не будет.
Андрей у вас просто замечательный сайт! Дай вам бог здоровья. Как жаль что я не знал о вас когда самостоятельно решал проблему с дребезгом. На российском комьюнити ардуинщиков нашел 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, и ардуина готова отреагировать на новый дребезг
}
Владислав, спасибо 🙂
Лучше сразу нацеливаться на многозадачность. Создавать прерывание по таймеру. И рисовать драйвер кнопки. При опросе ноги в каждое прерывание три 1 подряд — кнопка отжата, три 0 подряд — нажата, а иначе неопределенное состояние кнопки. И уже нажата кнопка или нет определять с определенной ячейки. Потом создать обработчик событий нажатия кнопки.
Да понимаю, сложновато получается. Но если у вас уже будет кнопок с клавиатуру. Да и задач на вашу систему много нагружено, то проще надстраивать систему.
Добрый день!
arduino.ru/forum/proekty/...hilda-lcd-knopki
Тут я сделал антидребезг 5 кнопок по аналоговому входу. Я не силен в программировании, прошу вас оптимизировать код и(или) создать библиотеку для шилда из 5 кнопок.
Суть метода, постоянно происходит опрос аналогового входа кнопок в теле программы и происходит сравнение между двумя замерами (интервал между замерами можно выставить от 5 до 30 мс это оптимальное время для антидребезга) уровня напряжения (от 0 до 1024 ед.) на аналоговом входе кнопок. Если уровни совпадают, то интерпретируется нажатие определенной кнопки, если нет, то происходит повторный замер и сравнение уровней...
Автору огромное спасибо!
Решил проблему с кнопкой с помощью 2х резисторов 10кОм и кондесантора 2мКф.
Никакого дребезга нету. Программно обрабатывать дребезг не есть хорошо думаю, так как ест процессорное время.