Top.Mail.Ru

Arduino и дребезг контактов при подключении кнопки.

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

Теоретическая часть.

Начнем по отличнейшей традиции с разбора сути происходящих физических явлений, а в практической части статьи перейдем к конкретным действиям. Пусть кнопка подключена так же, как и в предыдущем уроке:

Схема подключения.

Собственно, в данном случае все довольно просто - как и все в нашем мире, кнопка, к сожалению, не идеальна, не идеальны и поверхности контактов, не идеальны и материалы. И одним из проявлений этой неидеальности как раз и является дребезг контактов. На практике представляет из себя следующее:

Arduino, дребезг контактов .

То есть, проще говоря, в идеальном случае кнопка должна работать так:

  • кнопка не нажата - имеем высокий уровень, то есть 5 В
  • нажимаем кнопку - уровень моментально становится низким и остается в этом состоянии, пока кнопка нажата
  • отпускаем кнопку - также молниеносно сигнал возвращается к стабильно высокому уровню

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

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

Но для начала посмотрим врагу в глаза, то есть воспроизведем дребезг контактов и воочию убедимся в его наличии. Я возьму Arduino Uno, но особой разницы, какую именно плату из семейства Ардуино использовать, нет. Повторим еще раз схему:

Подключение кнопки к Ардуино.

На макетной же плате можно осуществить коммутацию так:

Схема на макетной плате.

Создаем простенький проект, в котором выведем текущее состояние кнопки в Serial Monitor:

// Кнопка подключена к D3
int buttonPin = 3;


void setup() 
{
  // D3 работает в качестве входа
  pinMode(buttonPin, INPUT);
  Serial.begin(115200);
}


void loop() 
{
  int currentButtonState = digitalRead(buttonPin);
  Serial.print("Button state: ");
  Serial.println(currentButtonState);
}

Что мы увидим в результате? Все просто - поскольку мы выводим значения непосредственно из функции loop(), то результатом работы скетча будут быстро бегущие цифры. Допустим, кнопка не нажата, на входе логическая единица, соответственно это и увидим:

Пример дребезга контактов.

Суть же и смысл эксперимента в следующем. Нажимаем кнопку, значение изменилось на "0" (на входе низкий уровень напряжения). Все, казалось бы, верно, но вот если рассмотреть значения непосредственно в момент нажатия, то увидим как раз-таки нежелательные случайные скачки между "1" и "0", вызванные дребезгом контактов.

Практическая часть.

Ну что же, первопричину дребезга выяснили, на практике воспроизвели, дело за малым - осталось проблему решить 👍 И здесь сходу можно выделить несколько самых распространенных вариантов, начнем с аппаратного.

Аппаратное уничтожение дребезга контактов.

Аппаратный вариант не требует внесения изменений в программу, то есть в скетч, потому так и называется. В данном случае простейшее решение заключается в добавлении в схему нескольких компонентов, а точнее двух - короля фильтрации конденсатора и резистора.

Конденсатор.

Выглядит все это дело так, здесь также присутствует подтягивающий резистор R_2 :

Устранение дребезга на Arduino Uno.

И на макетной плате:

Подключение кнопки на макетной плате.

Конденсатор C_1 в сочетании с резистором R_1 образуют RC-цепочку (представляющую из себя фильтр нижних частот). Здесь, по ссылке, можно почитать подробнее о нюансах, связанных с RC-цепочками. А, поскольку сегодня у нас на повестке тема "Arduino", то погружаться в детали, а также дублировать их из упомянутой статьи я не буду, не люблю мешанину 🙂 Сейчас можно кратко выделить тот факт, что "традиционные" номиналы в данном случае имеют следующие значения:

  • емкость конденсатора: 100 нФ (0.1 мкФ)
  • сопротивление резистора: 1 КОм - 10 КОм

Все эти значения вытекают из того, что постоянная времени RC-цепи должна превышать время успокоения дребезга кнопки. Так, все, не углубляюсь в эту тему, переходим к программному способу устранения дребезга, только еще раз взглянем на модернизированную схему подключения компонентов к Arduino Uno:

Устранение дребезга контактов при помощи RC-цепи.

Таким образом, получили классическое и наиболее простое решение проблемы с наименьшими затратами на компоненты. В большинстве случаев этого будет достаточно.

Программное уничтожение дребезга контактов.

Здесь самый базовый вариант заключается в том, чтобы после изменения состояния кнопки выждать паузу, достаточную для окончания всех дребезговых процессов, сейчас на практике все будет абсолютно понятно. Расписываю максимально подробно, пусть в ущерб оптимальности, но во имя большей прозрачности:

// Кнопка подключена к D3
int buttonPin = 3;

// Текущее состояние кнопки
int currentButtonState = HIGH;

// Предыдущее состояние кнопки
int previousButtonState = HIGH;

// Величина задержки
constexpr int DEBOUNCE_DELAY_MS = 25;

void setup() 
{
  // D3 работает в качестве входа
  pinMode(buttonPin, INPUT);
  Serial.begin(115200);
}

void loop() 
{
  // Считываем состояние входа D3
  int currentButtonState = digitalRead(buttonPin);

  // Если считанное значение отличается от сохраненного (состояние кнопки изменилось)
  if (currentButtonState != previousButtonState)
  {
    // Банальная задержка для того, чтобы миновать переходные процессы/дребезг контактов
    delay(DEBOUNCE_DELAY_MS);

    // Теперь в теории дребезг миновал, спокойно считываем состояние
    currentButtonState = digitalRead(buttonPin);
  }

  // Текущее значение становится предыдущим
  previousButtonState = currentButtonState;

  // Далее программа продолжается, оперируем с currentButtonState
  Serial.println(currentButtonState);
}

В данном случае текущее значение будет храниться в переменной currentButtonState, а предыдущее - в previousButtonState. Считываем значение, если оно не равно сохраненному в previousButtonState, значит по всей видимости состояние кнопки изменилось (было нажатие, к примеру). В течение времени, которое задается в переменной DEBOUNCE_DELAY_MS (значение в миллисекундах), просто ожидаем, намертво остановившись на вызове delay(). За это время предполагаем, что переходные процессы завершились, уровень на входе стабилен, можно спокойно считать повторно значение в переменную и с ним уже работать дальше:

currentButtonState = digitalRead(buttonPin);

Решение простое до безобразия и до безобразия же неоптимальное, прямо скажем - скверное ) Причина проста - пока микроконтроллер мог заниматься полезной деятельностью, он бессмысленно висит в функции ожидания , то есть на вызове delay(DEBOUNCE_DELAY_MS). Да, во многих случаях это сработает, да, проблем не вызовет, но все-таки решение так себе как минимум. Поэтому рассмотрим еще один вариант - не сильно сложнее, не особо напряжный, но при этом гораздо оптимальнее и правильнее. Итак, смотрим код, потом подробно пройдемся по нему поэтапно:

// Кнопка подключена к D3
int buttonPin = 3;

// Результирующее состояние кнопки
int finalButtonState = HIGH;

// Текущее состояние кнопки
int currentButtonState = HIGH;

// Предыдущее состояние кнопки
int previousButtonState = HIGH;

// Счетчик миллисекунд
int debounceTimeMs = 0;

// Величина задержки
const int DEBOUNCE_DELAY_MS = 50;

void setup() 
{
  // D3 работает в качестве входа
  pinMode(buttonPin, INPUT);
  Serial.begin(115200);
}

void loop() 
{
  // Считываем состояние входа D3
  int currentButtonState = digitalRead(buttonPin);

  // Если считанное значение отличается от сохраненного (состояние кнопки изменилось)
  if (currentButtonState != previousButtonState)
  {
    // Сохраняем текущую временную отметку
    debounceTimeMs = millis();
  }

  // Если с момента обновления debounceTimeMs прошло более, чем DEBOUNCE_DELAY_MS мс
  if ((millis() - debounceTimeMs) >= DEBOUNCE_DELAY_MS) 
  {
    // Обновляем результирующее значение, с которым дальше и работаем
    finalButtonState = digitalRead(buttonPin);
  }

  // Обновляем значение
  previousButtonState = currentButtonState;

  // Далее программа продолжается, оперируем с finalButtonState
  Serial.println(finalButtonState);
}

В данном случае у нас уже три переменные для разных состояний кнопки:

  • currentButtonState - текущее состояние
  • previousButtonState - предыдущее
  • finalButtonState - результирующее, то которое мы и стремимся получить и очистить от всяческих нежелательных явлений в виде дребезга в данном случае

Итак, по-прежнему в самом начале функции loop() анализируем текущее состояние цифрового входа D3:

int currentButtonState = digitalRead(buttonPin);

Проверяем, изменилось ли эта величина относительно предыдущей, и, если изменение имело место, то в переменную debounceTimeMs сохраняем текущее количество миллисекунд с момента запуска платы:

if (currentButtonState != previousButtonState)
{
	// Сохраняем текущую временную отметку
	debounceTimeMs = millis();
}

Для получения этого значения используем стандартную функцию Arduino - millis(), которая возвращает количество миллисекунд с момента старта выполнения программы. Отлично, двигаемся дальше... А дальше мы только проверяем, прошло ли заданное время (DEBOUNCE_DELAY_MS) с момента, когда было обновлено значение debounceTimeMs:

if ((millis() - debounceTimeMs) >= DEBOUNCE_DELAY_MS)

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

Идея метода здесь проста, по факту мы получаем следующее: значение debounceTimeMs будет обновляться каждый раз, когда изменилось состояние на входе. Это может быть вызвано как дребезгом, помехами, шумами и т. п., так и собственно нажатием. В любом случае обновляем значение переменной, сохраняя в него текущее количество миллисекунд. А finalButtonState мы обновим тогда и только тогда, когда в течение DEBOUNCE_DELAY_MS (50 мс в данном примере) изменений на входе не было. То есть напряжение устаканилось и стабилизировалось, значит мы можем считывать состояние входа и использовать для наших нужд. Вот и все, если механизм будет не до конца ясен, пишите в комментарии, я нарисую временную диаграмму протекающих процессов 👍

Метод также не избавлен от недостатков, но здесь уже нет простаивания на delay(). Микроконтроллер занимается своей работой, проверяя при каждом заходе в loop(), прошло ли необходимое время или еще нет. Поэтому данное решение проблемы гораздо предпочтительнее первого, в чем мы и убедились наглядно. А вообще, в целом, конкретная реализация может отличаться в разных примерах, неизменной остается концепция - переждать нежелательные процессы, при этом позволив контроллеру заниматься другими задачами.

Так, ну и на этом заканчиваем, пожалуй, на сегодня, до скорого 🤝

Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x