Урок 8. Цифровая фильтрация сигналов в программах для Ардуино.

Arduino UNO R3

Узнаем о другом способе обработки сигнала кнопки для фильтрации помех и устранении дребезга контактов.

Предыдущий урок     Список уроков     Следующий урок

В уроке 6 мы рассмотрели один из способов устранения дребезга контактов кнопки и написали программу для его реализации.

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

 

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

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

А ведь такая ситуация вполне реальная.

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

Удачный пример – охранная сигнализация.

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

И так:

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

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

 

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

Алгоритм усреднения достаточно прост и выглядит так. Нам необходим счетчик среднего значения сигнала и константа AVERAGE_TIME  – время усреднения.

алгоритм цифровой фильтрации сигнала

  • В цикле, с определенным периодом (например, 2 мс) мы считываем состояние сигнала.
  • Если оно низкого уровня, то вычитаем из счетчика 1. Если высокого – прибавляем 1.
  • Содержимое счетчика ограничиваем снизу на уровне 0 и сверху на уровне константы, определяющей время усреднения.
  • Таким образом, счетчик содержит среднее значение уровня сигнала.
  • Когда содержимое счетчика достигает 0 принимается решение, что контакты замкнулись.
  • Когда содержимое счетчика достигает значения ограничения – константы AVERAGE_TIME,  принимается решение о том, что контакты разомкнулись.

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

Цифровая фильтрация сигнала контактов

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

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

 

Реализация алгоритма цифровой фильтрации сигнала в программе для Ардуино.

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

Назовем метод filterAvarage (фильтрация по среднему значению).

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

  • scanState()  - проверка состояния (был создан в передыдущем уроке);
  • filterAvarage() – фильтрация по среднему значению.

В таком случае нам не надо создавать новые переменные. Мы можем использовать те же признаки, те же переменные. Ограничение – можно вызывать только один из методов для каждого объекта. С точки зрения объектно-ориентированного программирования это не совсем хорошо, но помним, что ресурсы нашего контроллера ограничены. Проблем такая универсальность нам не создаст.

Добавим метод  filterAvarage() в описании класса Button.

class Button {
  public:
    void  filterAvarage(); // метод фильтрации сигнала по среднему значению
    . . . . . . . . . . . . .            // другие члены класса
};

Напишем код для нового метода.

// метод фильтрации сигнала по среднему значению
// при сигнале низкого уровня flagPress= true
// при сигнале высокого уровня flagPress= false
// при изменении состояния с высокого на низкий flagClick= true
void Button::filterAvarage() {

 if ( flagPress != digitalRead(_pin) ) {
     //  состояние кнопки осталось прежним
     if ( _buttonCount != 0 ) _buttonCount--; // счетчик подтверждений - 1 с ограничением на 0
  }
  else {
     // состояние кнопки изменилось
     _buttonCount++;   // +1 к счетчику подтверждений

     if ( _buttonCount >= _timeButton ) {
      // состояние сигнала достигло порога _timeButton
      flagPress= ! flagPress; // инверсия признака состояния
     _buttonCount= 0;  // сброс счетчика подтверждений

      if ( flagPress == true ) flagClick= true; // признак клика кнопки      
     }   
  }
}

Комментировать не буду.

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

/*  Программа sketch_8_1 урока 8
 *  К плате подключены 2 кнопки и светодиод
 *  Каждое нажатие кнопки 1 меняет состояние светодиода на плате Ардуино
 *  Каждое нажатие кнопки 2 меняет состояние светодиода на макетной плате */

#define LED_1_PIN 13     // светодиод 1 подключен к выводу 13
#define BUTTON_1_PIN 12  // кнопка 1 подключена к выводу 12
#define BUTTON_2_PIN 11  // кнопка 2 подключена к выводу 11
#define LED_2_PIN 10     // светодиод 2 подключен к выводу 10

// Описание класса обработки сигналов кнопок
class Button {
  public:
    Button(byte pin, byte timeButton);  // конструктор  
   boolean flagPress;    // признак кнопка в нажатом состоянии
    boolean flagClick;    // признак нажатия кнопки (клик)
    void  scanState();    // метод проверки состояние сигнала
    void  filterAvarage(); // метод фильтрации сигнала по среднему значению
    void setPinTime(byte pin, byte timeButton); // метод установки номера вывода и времени (числа) подтверждения
  private:
    byte  _buttonCount;    // счетчик подтверждений состояния кнопки  
    byte _timeButton;      // время подтверждения состояния кнопки
    byte _pin;             // номер вывода кнопки
};

boolean ledState1;         // переменная состояния светодиода1
boolean ledState2;         // переменная состояния светодиода2

Button button1(BUTTON_1_PIN, 15);  // создание объекта для кнопки1
Button button2(BUTTON_2_PIN, 15);  // создание объекта для кнопки2
  
void setup() {
  pinMode(LED_1_PIN, OUTPUT);           // определяем вывод светодиода1 как выход
  pinMode(LED_2_PIN, OUTPUT);           // определяем вывод светодиода2 как выход
}

// бесконечный цикл с периодом 2 мс
void loop() {

  button1.filterAvarage();  // вызов метода фильтрации по среднему сигнала кнопки 1
  button2.scanState();  // вызов метода сканирования состояния сигнала кнопки 2

 
  // блок управления светодиодом1
  if ( button1.flagClick == true ) {
    // кнопка была нажата
   button1.flagClick= false;         // сброс признака клика кнопки
    ledState1= ! ledState1;             // изменение состояние светодиода
    digitalWrite(LED_1_PIN, ledState1);  // вывод состояния светодиода1   
  }

  // блок управления светодиодом2
  if ( button2.flagClick == true ) {
    // кнопка была нажата
    button2.flagClick= false;         // сброс признака клика кнопки
    ledState2= ! ledState2;             // изменение состояние светодиода
    digitalWrite(LED_2_PIN, ledState2);  // вывод состояния светодиода2   
  }

/*
    // проверка признака кнопка нажата
    digitalWrite(LED_1_PIN, button1.flagPress);
    digitalWrite(LED_2_PIN, button2.flagPress);
*/

  delay(2);  // задержка на 2 мс
}

// метод фильтрации сигнала по среднему значению
// при сигнале низкого уровня flagPress= true
// при сигнале высокого уровня flagPress= false
// при изменении состояния с высокого на низкий flagClick= true
void Button::filterAvarage() {

 if ( flagPress != digitalRead(_pin) ) {
     //  состояние кнопки осталось прежним
     if ( _buttonCount != 0 ) _buttonCount--; // счетчик подтверждений - 1 с ограничением на 0
  }
  else {
     // состояние кнопки изменилось
     _buttonCount++;   // +1 к счетчику подтверждений

     if ( _buttonCount >= _timeButton ) {
      // состояние сигнала достигло порога _timeButton
      flagPress= ! flagPress; // инверсия признака состояния
     _buttonCount= 0;  // сброс счетчика подтверждений

      if ( flagPress == true ) flagClick= true; // признак клика кнопки      
     }   
  }
}

// метод проверки состояния кнопки
// при нажатой кнопке flagPress= true
// при отжатой кнопке flagPress= false
// при нажатии на кнопку flagClick= true
void Button::scanState() {

 if ( flagPress != digitalRead(_pin) ) {
     //  состояние кнопки осталось прежним
     _buttonCount= 0;  // сброс счетчика подтверждений
  }
  else {
     // состояние кнопки изменилось
     _buttonCount++;   // +1 к счетчику подтверждений

     if ( _buttonCount >= _timeButton ) {
      // состояние кнопки не мянялось в течение времени _timeButton
      // состояние кнопки стало устойчивым
      flagPress= ! flagPress; // инверсия признака состояния
     _buttonCount= 0;  // сброс счетчика подтверждений

      if ( flagPress == true ) flagClick= true; // признак клика кнопки      
     }   
  }
}
// метод установки номера вывода и времени подтверждения
void Button::setPinTime(byte pin, byte timeButton)  {

  _pin= pin;
  _timeButton= timeButton;
  pinMode(_pin, INPUT_PULLUP);  // определяем вывод кнопки как вход
}

// конструктор класса Button
Button::Button(byte pin, byte timeButton) {

  _pin= pin;
  _timeButton= timeButton;
  pinMode(_pin, INPUT_PULLUP);  // определяем вывод кнопки как вход
}

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

Наиболее дотошливые читатели могут проверить работу алгоритма. Для этого надо искусственно сделать время усреднения очень большим. Время усреднения будет равным 5 секунд, если Вы:

  • при создании объектов-кнопок второй параметр зададите максимальным
    Button button1(BUTTON_1_PIN, 250);  // создание объекта для кнопки 1
    Button button2(BUTTON_2_PIN, 250);  // создание объекта для кнопки 2
  • время цикла зададите 20 мс
    delay(20);  // задержка на 20 мс.

Теперь удалите блоки управления светодиодами от признаков-кликов кнопок и сделайте управление светодиодами от признаков кнопка нажата.

digitalWrite(LED_1_PIN, button1.flagPress);
digitalWrite(LED_2_PIN, button2.flagPress);

Теперь можете проверить, что при нажатии на кнопки, оба светодиода зажигаются с задержкой примерно 5 секунд. Опять кнопки работают одинаково.

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

 

Мы закончили создание класса Button – кнопка. Кнопка оказалась не таким уж и простым элементом. В следующем уроке оформим этот класс библиотекой.

Предыдущий урок     Список уроков     Следующий урок

11 комментариев на «Урок 8. Цифровая фильтрация сигналов в программах для Ардуино.»

  1. Как подручными средствами можно специально навести помехи и проверить работоспобность метода в так сказать «реальных» условиях? Или это слишком муторно? Заранее спасибо!

    • Я уже точно не помню. Вроде я привожу в уроке методику проверки алгоритма.

    • трансформатор под нагрузкой рядом с проводами размести) чем ближе будет к проводам тем сильней помехи

  2. Несколько раз пытаюсь усвоить урок Смотрю на осциллограммы и не могу понять что вы вообще делаете программным способом устраняя импульсные помехи….Если считывать цифровое значение с кнопки то оно уже будет только или 1 или 0 и других значений принимать не будет и при помехе эпюры в цифровом значении не будут отличаться от дребезга .Вот если аналоговый сигнал считывать с кнопки тогда возникает возможность выделить полезный сигнал из помехи…

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

      • позвольте выражу мысли в слух.
        Если за промежуток времени появился лог 0 то кнопка нажата. , что нам собственно и нужно , а зачем нам считать лог 1? Лог 1 дадут нам представление не об уровне сигнала а его длительности…

        • Вы невнимательно прочли матчасть. При длинных проводах, да ещё и не экранированных, на линии связи контроллера с кнопкой может возникнуть наведённый электрический импульс, который контроллер уловит и отработает по программе, если не применять программную (или иную) фильтрацию. Вы же не хотите чтоб Ваше устройство срабатывало 50 раз в секунду, получая наводку от осветительной сети!? А программу написать проще, чем городить огород на дополнительных электронных компонентах. В общем, это наш путь — путь наименьшего сопротивления.

  3. здравствуйте. как нужно написать эту логику.

    if (кнопка нажата или отжата более 10 секунд )
    то на выходе pin 12 LOW
    else
    pin 12 HIGH

    • Здравствуйте! В последующих уроках есть примеры. Если не ошибаюсь в уроке 11.

  4. А вот как раз то, что я хотел от обработчика нажатий кнопки.
    Теперь прога — нраица!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *