Найти в Дзене
Реальная Сталь

Ардуино. Программирование с нуля до ЧПУ. Борьба с дребезгом контактов

На прошлом занятии мы поработали с переменными, создали функцию, подключили кнопку с помощью INPUT_PULLUP ТУТ.

На первый взгляд все просто, но выяснили о существовании неприятного физического явления ДРЕБЕЗГ КОНТАКТОВ, то есть при нажатии или отпускании кнопки(концевика, переключателя, энкодера итп), прежде чем принять устойчивое состояние 0 или 1, с выхода кнопки вылетает сотни, тысячи 0 и 1. Длительность этого явления от 5 до сотен миллисекунд. Это зависит от качества и длительности эксплуатации контактного компонента.

Кнопка и ардуино это два аппаратных компонента. Ардуино выполняет программу своим чередом, кнопка дребезжит своим. Нужно их "подружить".

Что бы отследить это явление нужен осцилограф, но мы начинающие электронщики, у нас пока его нет, будем выкручиваться с помощью монитора порта Ардуино IDE. Настроим его на максимальную скорость приема 250000 бод.

Ставим задачу:

  • Крутимся в loop, опрашиваем Pin кнопки. Отлавливаем нажатие
  • При нажатии, то есть при первом логическом 0, начинаем крутится в цикле for
  • В каждом цикле for опять опрашиваем состояние кнопки и в зависимости от состояния выводим в монитор 0 или 1 столько раз, сколько зададим циклов, например 226 раз for(byte i=0; i<=225; i++) {в теле for крутимся 226 раз, потом программа идет дальше}
  • дальше бесконечный цикл for(;;){условие выхода, отпускание, то есть первая логическая 1} идем дальше по программе.
  • дальше опять цикл фор, печатаем в монитор поведение при отпускании кнопки.
  • все пишем скетч
.
.

Прошиваем, смотрим монитор.

.
.
.
.

Монитор и Ардуино потрудились и честно вывели 256 символов 0 и 1 при нажатии и столько же при отпускании. Показали около 23 символов дребезга, затем наступил "покой".

Внимательный читатель спросит, почему так мало дребезга, ведь он дится около 100 мс (милисек) и вообще, куда он делся? Давайте разбираться. Дребезг гасится вот в этом участке кода.

.                      участок кода вывода и  гашения дребезга
. участок кода вывода и гашения дребезга

Наш процессор работает на частоте 16 Мгц, то есть 16000000 тактов в секунду ,1000000 мкс (микросек).Делим 1000000 / 16000000 = 0,0625 мкс весит один такт процессора.

Каждая строка в нашей программе это команда процессору. Некоторые команды выполняются за один такт, некоторые "жрут" тысячи тактов. Например Serial.println("нажата"); в районе 2400 тактов,150 мкс.

Считаем. Учитесь считать. Даже а Ардуино нам придется считать, Serial.println("нажата") 150 мкс * 1, for (byte i=0; i <= 225; i++) 12 тактов * 0,0625 * 1=0,75 мкс, ButtonState = digitalRead(12) 58 тактов* 0,0625*226 циклов = 819 мкс, if (ButtonState == 0) 3 такта*0,0625 *226=45 мкс, Serial.println(0) 150 мкс* 226 = 33900 мкс, if (ButtonState == 1) = 45 мкс, Serial.println(1) = 33900 мкс

Итого 68860 мкс, почти 69 миллисекунды времени кушает Serial.println("нажата") и 226 циков for нашей программы. Еще монитор порта Ардуино IDE съест времени раз в 5 больше, это тоже программа. По этому такой кривой вывод. Кнопка дребезжит в реальном времени, а монитор порта выводит когда пришло его время вывода, назовем его моменты времени, относительно кнопки.

Примечание . Реальное время в программировании это миф, мы не можем разницу довести до нуля, а только уменьшать до бесконечно малого значения. Почему?

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

Начало времени нашего Ардуино 0.0625 мкс. Допустим мы можем уменьшить это значение, при этом увеличится скорость исполнения. Но если каким то чудом нам удастся довести это значение до абсолютного нуля, мы достигнем реального времени, но процессор остановиться, ему нечем будет "шагать". Звучит как бред, но это ТАК.

К стати, пока самую большую скорость, которую удалось развить человечеству, применяя кристалл на основе цезия 133 0,00000000000000000001 сек, зептосекунда.

Конечно в программе для реального устройства не применяется монитор порта, то есть Serial.println(). Придется применять какие то "долговыполняемые" команды, без полезно тратить драгоценное процессорное время, например деление переменной типа float. Можно отправить программу после нажатия кнопки "погулять" и выполнить что то полезное, но это очень зависит конкретики вашего кода.

Зачем нам такие сложности, просто применяем задержку delay(100), 100 микросекунд и все. Код выглядит так.

.
.

Все просто, но есть подводные камни. Смотрим строку "delay(100) // задержка остановка выполнения программы на 100 миллисекунд " Ключевое слово будет "ОСТАНОВКА выполнения программы", то есть прекращается опрос датчиков висящих на портах, прекращаются сигналы с портов итд..., а таймеры продолжают работать. И если у Вас что то запилено на таймерах будут глюки, которые трудно отловить и отладить. Возможно Вы пока не знаете про таймеры, но мы ими скоро займемся. В данной плате ардуино их два.

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

Есть более лояльная функция millis(). С помощью millis() мы не останавливаем выполнение всего скетча, а просто указываем сколько времени ардуино должна просто обходить именно тот блок кода, который мы хотим приостановить. Возвращает количество миллисекунд с момента запуска текущей программы на плате Arduino. Считает тип unsigned long от 1 до 4 294 967 295 мс. Это число переполнится (вернется к нулю) примерно через 50 дней.

Мы можем записать это количество миллисекунд в переменную, например timing = millis(), затем вычисляем разницу между текущим моментом и ранее сохраненной точкой отсчета. Если разница больше нужного значения, то выполняем код.
Если нет - ничего не делаем . Мы не останавливаем весь скетч, а просто пропускаем определенный участок кода.

Алгоритм скетча будет такой.

.
.

Рассмотрим. Начинаем оптимизацию кода. Смотрим строку " if (ButtonState == 0 && flag == 0 && millis() - timing > 200) { //если три условия истина, исполняем if ". Видим "&&". Это логическое "И". В простом понимании строка будет выглядеть так "if (ButtonState == 0 и flag == 0 и millis() - timing > 200)", то есть для общей истины должны все три условия иметь логическое состояние "истина", если хотя бы одно условие "лож" , то общее состояние "лож", условие if не исполняется.

Быть точнее у нас получилось "2И". может бить "3И" , ""

Крутимся в loop, при нажатии в переменную flag записываем 1, поднимаем флаг. В переменную timing записываем какое то значение времени. Затем в каждой итерации лупа проверяем millis() - timing > 200. Это и есть задержка на 200 мс. При отпускании то же самое.

В языке Ардуино так же есть || (логическое ИЛИ, 2ИЛИ...). Все просто, как табуретка. Общая истина - когда одно из условий истина

! (логическое отрицание). Инверсия. Истина, если операнд лож, и наоборот, например: if (!n) { // ...} // условие верно, если n - false (n равно 0).

Важно различать логический оператор "И" - && и битовый оператор "И" - &. Тоже самое относится к логическому оператору "ИЛИ" - || и битовому оператору "ИЛИ" - |. Скоро будем изучать

В Ардуино создано множество готовых библиотек, то есть готового кода для использования. Естественно есть библиотеки работы с кнопками. Что бы иметь представление, как работать с библиотеками забежим немного вперед.

Язык Ардуино хоть и Wiring, но является объектно ориентировочным, что нам дает огромные возможности.

Главные принципы в ООП – абстрагирование, инкапсуляция, наследование и полиморфизм.

Объект это осязаемый предмет или явление, имеющие четко выраженные границы, свойства и поведение.

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

Инкапсуляция это быстрая организация управляемой иерархии, чтобы было достаточно простой команды «что делать», без одновременного уточнения как именно делать.

Сделаем из всего этого вывод. Библиотека это капсула с кодом, записанная на каком то носителе, значит физически ощущаемый объект. Код, исполняемый в результате использования этой библиотеки, объект как явление. В скетче нужно подключить библиотеку и создать объект,который унаследует все свойства этой библиотеки(наследование). Затем выдавать команды через этот объект.

Существует библиотека, созданная сообществом ардуинщиков, Bounce2.h.

Скачайте ZIP-файл с библиотекой ТУТ . Затем скопируйте распакованную папку с библиотекой по адресу
обычно
«Документы/Arduino/libraries». Перегружаем Ардуино IDE Далее, чтобы подключить эту библиотеку к своему скетчу, кликните в IDE Arduino на Скетч > Подключить библиотеку > Bounce2 . В результате в верхней части скетча появится строчка хештег include Bounce2.h. Используем.

.                              Скетч реализации библиотеки   Bounce2.h
. Скетч реализации библиотеки Bounce2.h

Из выше сказанного и комментариев в скетче думаю все понятно.

Для борьбы с этим явлением придуманы и аппаратные и методы.

Аппаратными, то есть физическими методами, являются RC, LC фильтры, RS триггеры. Имеются специальные микросхемы, например микросхема MC14490 на 6 линий.

                           Цена на данный момент в районе 100 руб
Цена на данный момент в районе 100 руб

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

Давайте рассмотрим RC цепь с применением триггера Шмитта на ТТЛ, Транзисторно-транзисторная логика. Выше мы рассмотрели логические И, ИЛИ, НЕ. Существуют и другие логические истинности.

На картинке слева микросхема CD4001A . Ее архитектура основана на КМОП(полевые транзисторы), а значит имеет высокое входное сопротивление, позволяющее нам уменьшить емкость С1, а значит и размер. Состоит из 4 логических элементов ИЛИ-HЕ (NOR), значит можем подключит 2 кнопки. Импортозамещение К561ЛЕ5.

.
.

При подключении питания на выводе 1 и 2 D1.1 через R1 R2 устанавливается логическая 1. Смотрим таблицу истинности, 1 1 ,значит на выходе 3 D1.1 логический 0, значит на выводах 5 и 6 D1.2 тоже 0. Смотрим таблицу истинности 0 0, значит на выводе 4 D1.2 логическая 1. При нажатии кнопки конденсатор С1 начинает заряжается через R2. отрицательным потенциалом, минусом. Мы знаем, чем больше заряжен конденсатор, тем меньше его сопротивление. В какой то момент R2 пересилит сопротивление конденсатора и создаст логический 0, диаграмма 2 стрелка слева. При отжатии кнопки процесс перезарядки аналогичен, диаграмма 2, стрелка справа.

Давайте посчитаем время перехода логического состояния. Постоянная времени цепи заряда Т = R * С, где Т секунды, R ом, С фарады.

Т сек = 100000 ом *0,0000001 фарад = 0,01 сек = 10 мили сек

Смотрим диаграмму 2 , переход из логической 1 в логический 0 и обратно чуть ниже середины , значит

10 мс * 0,6 = 6 мс

Для хорошей кнопки пойдет. Для "замученой" нужно увеличит время, изменяя или сопротивление резистора или емкость кондера.

На рисунке справа CD4030BE, содержит 4 элемента "Исключающее ИЛИ"(XOR).Можно воткнуть 4 кнопки. Импортозамещение К561ЛП2. Сами разберетесь ? Вот Вам таблица истинности XOR.

.
.

Кстати INPUT_PULLUP, подтяжка внутреннего резистора, нужно заменить на INPUT, ножка Ардуино просто на прием.

Да, это дополнительные электронные компоненты на плату. Удобно применять когда нет возможности или желания делать это программно.

Это далеко не все методы борьбы с дребезгом. Продолжаются активные дискуссии на тему работы с кнопками

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