Программный антидребезг с обратной связью

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

Введение

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

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

Идея состоит в следующем: ждем, пока кнопка будет нажата. Далее, через время Δt выполняем повторное чтение. Если кнопка по-прежнему находится в нажатом состоянии, то считаем, что кнопка нажата устойчиво, и выполняем установку соответствующего флага. Время Δt выбирается заведомо больше времени дребезга контактов, что-то в диапазоне 10-100мс.

Я немного модифицировал этот алгоритм для удобной реализации на конечных автоматах [2]. Алгоритм на СИ выгладит примерно так.

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

//Читаем состояние всех кнопок и помещаем результат в одну переменную
//где каждый бит - это текущее состояние GPIO кнопки
static uint32_t GetCurrentValues(void)
{
  uint32_t r = (STATE_BUTT_1 << 0)
             | (STATE_BUTT_2 << 1)
             | (STATE_BUTT_3 << 2)
             | (STATE_BUTT_4 << 3)
             | (STATE_BUTT_5 << 4)
             .........
             | (STATE_BUTT_16 << 15);

  return r;
}

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

//Timer - программный либо аппаратный счетчик,
//который увеличивает свое значение на 1
//через равные промежутки времени,
//например, каждую 1мс

switch (state)
{
case 0:
  if (Timer >= dT)
  {
    value1 = GetCurrentValues();
    Timer = 0;
    state = 1;
  }
  break;

case 1:
  if (Timer >= dT)
  {
    value2 = GetCurrentValues();
    PressedButtons = value1 & value2; //Результат
    Timer = 0;
    state = 0;
  }
  break;
}

Здесь весь прикол заключается в том, что алгоритм антидребезга для 32-х входов выполняется одной строчкой:

PressedButtons = value1 & value2

т.е. если на предыдущем и текущем моменте чтения было получено значение 1, операция «лог. И» даст 1, иначе 0. И это все одновременно для всех 32-х  бит! Круто. Далее, выполняем побитовое обращение к переменной PressedButtons, и читаем состояние кнопок далее по тексту программы.

Алгоритм можно улучшить:

switch (state)
{
case 0:
  value1 = 0;
  value2 = 0;
  Timer = 0;
  state = 1;
  break;

case 1:
  if (Timer >= dT)
  {
    value1 = GetCurrentValues();

    PressedButtons = value1 & value2;

    Timer = 0;
    state = 2;
  }
  break;

case 2:
  if (Timer >= dT)
  {
    value2 = GetCurrentValues();

    PressedButtons = value1 & value2;

    Timer = 0;
    state = 1;
  }
  break;
}

Здесь мы обновляем значение PressedButtons в два раза чаще.

Добавляем обратную связь

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

№   value1   value2  rez
1    0        0       0
2    0        1       0  <<
3    1        0       0  <<
4    1        1       1

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

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

№    value1  value2  old_rez  rez
1    0       0       0        0   
2    0       0       1        0  
3    0       1       0        0  << 
4    0       1       1        1  << 
5    1       0       0        0  << 
6    1       0       1        1  << 
7    1       1       0        1 
8    1       1       1        1	 

где old_rez — предыдущее состояние кнопки, которое зарегистрировал алгоритм антидребезга.

Состояния 3, 4, 5, 6 — неустойчивые, поэтому в этих случаях в качестве результата rez используется предыдущий результат old_rez.

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

rez = ((!value1) & value2 & old_rez) 
     | (value1 & (!value2) & old_rez) 
     | (value1 & value2 & (!old_rez)) 
     | (value1 & value2 & old_rez);

Немного страшно? Но это ничего. По запросу «Упрощение логических выражений онлайн» находим утилиту [4], которая все это дело упрощает до состояния:

rez = (value1 & value2) 
    | (value1 & old_rez) 
    | (value2 & old_rez);

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

static uint32_t calc(uint32_t a, uint32_t b, uint32_t old)
{
  return (a & b) | (a & old) | (b & old);
}

//Вызывается в бесконечном цикле в main():
void Buttons_Process(void)
{
  static uint32_t value1;
  static uint32_t value2;

  switch (state)
  {
  case 0:
    // Инициализация
    value1 = 0;
    value2 = 0;
    PressedButtons = 0;
    Timer = 0;
    state = 1;
    break;

  case 1:
    if (Timer >= dT)
    {
      value1 = GetCurrentValues();

      PressedButtons = calc(value1, value2, PressedButtons);

      Timer = 0;
      state = 2;
    }
    break;

  case 2:
    if (Timer >= dT)
    {
      value2 = GetCurrentValues();

      PressedButtons = calc(value1, value2, PressedButtons);

      Timer = 0;
      state = 1;
    }
    break;
  }
}

Обратная связь здесь и заключается в том, что результат PressedButtons зависит от его предыдущего значения: PressedButtons = calc(value1, value2, PressedButtons)

В итоге мы получили продвинутую систему программного антидребезга, которая может одновременно обрабатывать 32 кнопки (64, если использовать uint64_t), при этом отнимает совсем мало процессорного времени. Логические операции — сила! 😀

На этом все, всем спасибо за внимание, и до новых встреч!))

Литература

  1. Дребезг контактов [Электронный ресурс] // Википедия. URL: https://ru.wikipedia.org/wiki/%D0%94%D1%80%D0%B5%D0%B1%D0%B5%D0%B7%D0%B3_%D0%BA%D0%BE%D0%BD%D1%82%D0%B0%D0%BA%D1%82%D0%BE%D0%B2
  2. Татарчевский Владимир: «Применение Switch-технологии при разработке прикладного программного обеспечения для микроконтроллеров. Часть 1» // Компоненты и технологии, №2’2007
  3. Составление логического выражения по таблице истинности и его упрощение // URL: https://www.лена24.рф/Информатика_10_кл_Босова/20.3.html
  4. Онлайн-утилита упрощения логических выражений [Электронный ресурс] // URL: // https://www.kontrolnaya-rabota.ru/s/mathlogic
Метки: . Закладка Постоянная ссылка.

Обсуждение закрыто.