Skip to Content

Устранение дребезга контактов на основе вертикальных счетчиков

При разработке встраиваемых систем очень часто возникает необходимость в устранении дребезга кнопок или цифровых входов. Это можно сделать аппаратно, применив ФНЧ на входе, либо программным способом. Хорошо известен программный метод устранения дребезга одновременно на нескольких входах на основе вертикальных счетчиков. Несмотря на широкое распространение этого метода, очень часто в литературе он представлен без подробного объяснения принципа работы. Попоробуем объяснить работу вертикальных счетчиков более полно и доступно. Вам потребуется базовые знания двоичной арифметики и программирования на С/С++.

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

Что такое дребезг контактов

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

Посмотрим на картинки которые были сняты анализатором Saleae logic подключенным к обычной кнопке. Здесь отлично виден дребезг, который продолжается чуть больше 4 мс.

Дребезг при нажатии кнопки

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

Увеличенный дребезг при нажатии

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

Дребезг при отпускании кнопки

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

Вертикальные счетчики

Вертикальные счетчики, это массив счетчиков обслуживаемых в программе. Этот термин рассматривается в дополнение к "горизонтальным счетчикам". Вы можете представить обычный байт как горизонтальный счетчик. Увеличение байтовой переменной происходит в диапазоне от 0 до 255. Представим, для начала, наш байт в виде списка битовых массивов.

Проинкрементируем наш байт 7 раз. В таблице Вы видите как процессор выполняет эти действия. Каждая строка таблицы имеет старший бит слева и младший бит справа и представляет одно из состояний которые принимает счетчик.

b7 b6 b5 b4 b3 b2 b1 b0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 0
0 0 0 0 0 0 1 1
0 0 0 0 0 1 0 0
0 0 0 0 0 1 0 1
0 0 0 0 0 1 1 0
0 0 0 0 0 1 1 1

Младший бит или b0 просто переключается между 0 и 1. Следубщий бит переключается только если b0 равен 1, или, точнее, по формуле: b1,i+1 = b1,i xor b0,i. Следующий бит b2 переключается если оба b0 и b1 были равны 1. Такая же схема переключения применяется и для остальных бит.

В вертикальных счетчиках эту таблицу надо повернуть на 90 градусов. Каждый бит счетчика окажется в новой строке, т.е. в другом байте или переменной. Если у вас есть трех-битный счетчик, то его биты распределятся между тремя переменными, например, cnt0, cnt1 и cnt2. Переменные, это некие битовые сущности. Наименьшая сущность которую можно хранить в памяти микроконтроллера - это байт. В вертикальном счетчике каждый бит переменной представляет один цифровой вход или кнопку. В cnt0 находятся все b0 для 8 входов, в cnt1 - все b1 и т.д.

  in7 in6 in5 in4 in3 in2 in1 in0
cnt2 (b2) 1 1 1 1 0 0 0 0
cnt1 (b1) 1 1 0 0 1 1 0 0
cnt0 (b0) 1 0 1 0 1 0 1 0

В 32-битном микроконтроллере вы сможете обрабатывать на дребезг одновременно 32 входа используя всего пару переменных и очень эффективную арифметику.

Программный антидребезг

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

Реализация с объяснениями

Мы покажем пример реализации антидребезга до 8 входов с использованием двух-уровневого вертикального счетчика. Функция принимает байт сырых данных считанных с порта ввода-вывода и возвращает состояние порта после применения антидребезга. Этот код очень просто переделывается для обработки более чем 8 входов, просто измените разрядность используемых переменных.

unsigned char debounce(unsigned char sample)
{
    static unsigned char state, cnt0, cnt1;
    unsigned char delta, toggle;

    delta = sample ^ state;

    cnt1 = cnt1 ^ cnt0;
    cnt0 = ~cnt0;

    cnt0 &= delta;
    cnt1 &= delta;

    toggle = cnt0 & cnt1;
    state ^= toggle;

    return state;
}
Строка 1
Аргумент функции просто содержит состояние входов считанное с порта ввода-вывода (состояние с дребезгом), переменная sample.
Строка 3
Функции требуется 2 статических переменных для хранения вертикального счетчика и одна для хранения текущего состояния входов (после антидребезга, устоявшееся состояние).
Строка 6
Переменная delta хранит логическую 1 в бите соответствующего каждму входу, состояние которого отличается от конечного, устоявшегося состояния. Здесь выполняется побитовый XOR переменных sample и state. XOR, как известно сбрасывает в 0 те биты состояние которых совпадает в обеих меременных и устанавливает в 1 те биты состояние которых отличается.
Строки 8 и 9
Эти строки обновляют наш вертикальный счетчик. Строка 9 переключает все биты счетчика cnt0 (наши "вертикальные младшие биты"), а строка 8 переключает биты в cnt1 (наши "вертикальные старшие биты") в зависимости от предыдущего состояния cnt0. Т.к. cnt1 зависит от предыдущего состояния cnt0, то cnt1 изменяется раньше чем cnt0.
Строки 11 и 12
Любые биты в сэмпле которые совпадают с устоявшимся состоянием (state) сбрасываются. Таким образом наш вертикальный счетчик (cnt0 и cnt1) увеличивается только для тех входов или бит состояние которых отличается от устоявшегося состояния. А также здесь получается эффект мгновенного сброса счетчика в нулевое состояние, если новое состояние некоторого бита не совпадает с нго же предыдущим состоянием, т.е. состояние бита поменялось.
Т.е. счетчики инкрементируются только для тех битов, состояние которых отличается от устоявшегося состояния. Но если биты будут отличаться от самих себя при следующем чтении, т.е. вход еще "дребезжит", то счетчик немедленно сбросится в 0.

Обратите внимание, что операции над строками 11 и 12 можно комбинировать с операциями в строках 8 и 9 для получения более компактного кода.

Строка 14
Переменная toggle устанавливает 1 бит в каждой позиции где переменные cnt0 и cnt1 содержат логическую 1, т.е. когда наш вертикальный счетчик досчитал до 3. Т.к. вертикальный счетчик сбрасывается в 0 при любом дребезге на входе, то если он досчитал до 3, это значит что сигнал был стабильным в течение 3-х последовательных чтений порта. Здесь имеется в виду что сигнал должен отличаться от стабильного состояния, т.е. вход изменил свое состояние, но при этом сам сигнал остается стабильным с течение 3-х чтений входа. Таким образом переменная toggle содержит логическую 1 в тех битах которые должны попасть в выходное стабильное состояние.
Строка 15
Те биты, которые были определены как "изменившиеся" переключают биты в переменной state. Это будет новое устоявшееся состояние. Эта операция может быть объединена со строкой 14, что поможет сэкономить одну локальнкю переменную.
Строка 17
Функция возвращает устоявшееся состояние.

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

Компактная реализация

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

unsigned char debounce(unsigned char sample)
{
    static unsigned char state, cnt0, cnt1;
    unsigned char delta;

    delta = sample ^ state;
    cnt1 = (cnt1 ^ cnt0) & delta;
    cnt0 = ~cnt0 & delta;
    state ^= (cnt0 & cnt1);

    return state;
}

Обратите внимание на отсутствие циклов, даже если мы обрабатываем до 8 входов за раз (и это может быть легко расширено до 16 или даже 32 входов, не делая процедуру более продолжительной или более сложной).

Улучшенная версия

В окончательной версии производятся два улучшения: вертикальный счетчик считает до 4 (вместо 3) и возвращает флаг с изменениями, а также состояние (debounced) всех входов. Новая функция немного длиннее предыдущих версий.

	unsigned char debounce(unsigned char sample, unsigned char *toggle)
{
    static unsigned char state, cnt0, cnt1;
    unsigned char delta;

    delta = sample ^ state;
    cnt1 = (cnt1 ^ cnt0) & delta;
    cnt0 = ~cnt0 & delta;

    *toggle = delta & ~(cnt0 | cnt1);
    state ^= *toggle;

    return state;
}
Суть изменения заключается в строке 10. Вместо того, чтобы проверять, какие биты являются равными 1 в cnt0 и cnt1, здесь проверяется, какие биты в вертикальных счетчиках равны нулю. Если после того, как счетчик достиг 3, если вы еще раз увеличите его, то счетчик переполнится и сбросится в 0. Проверяя это переполнение, вы на самом деле считаете до 4.

Выражение между круглыми скобками «(cnt0 | cnt1)» будет иметь логическую 1 в позициях, где какая-либо из вертикальных счетных переменных тоже содержит 1. Этот результат сначала отрицается, «~(cnt0 | cnt1)», так что теперь единицы появляются там где были нули и наоборот. Затем производим логическое И с переменной delta что очищает любые биты которые отличаются от установившегося состояния. Поэтому переменная toggle будет содержать "1" для каждого бита, который был изменен и для которого вертикальный счетчик переполнился и стал 0. Переполнение происходит после четырех последовательных (то есть «стабильных») выборок, которые отличаются от старого установившегося состояния.

Заключительные замечания

Представленная здесь функция подавления дребезга должна вызываться на регулярной основе, например, в прерывании таймера. Предлагаемый интервал составляет от 5 мс до 10 мс, так что кнопка будет определяться нажатой или отпущенной от 20 мс до 40 мс после стабилизации состояния. Эти тайминги не являются какой то догмой, но задержки переключения более 50 мс уже будут заметны для пользователя.

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

Источник: https://www.compuphase.com/electronics/debouncing.htm

Комментарии

Отправить комментарий

Содержание этого поля является приватным и не предназначено к показу.
  • Syntax highlight code surrounded by the {syntaxhighlighter SPEC}...{/syntaxhighlighter} tags, where SPEC is a Syntaxhighlighter options string or "class="OPTIONS" title="the title".
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Доступны HTML теги: <a> <p> <span> <s> <strike> <div> <h1> <h2> <h3> <h4> <h5> <h6> <img> <map> <area> <hr> <br> <br /> <ul> <ol> <li> <dl> <dt> <dd> <table> <caption> <tbody> <tr> <td> <em> <b> <u> <i> <strong> <del> <ins> <sub> <sup> <quote> <blockquote> <pre> <address> <code> <cite> <embed> <object> <param> <strike>
  • Использовать как разделитель страниц.

Подробнее о форматировании

CAPTCHA
Введите текст что Вы видите на картинке. Регистр букв не важен.