Таймеры и программный антидребезг входов.

X-Dron
Offline
Зарегистрирован: 24.01.2015

16 лет занимаюсь программированием промышленной автоматики на PLC Omron, Siemens. С ардуино столкнулся месяца 2 назад. И сразу же стали бесить delay(), когда для того, чтобы правильно отработал какой-то один алгоритм, приходится тормозить весть проц и все другие аглоритмы.
Для того чтобы использовать фронты дискретных входов, нужно избавиться от дребезга контактов нужно собирать схемы с триггером Шмитта. А если таких входов штук 20???
В итоге написал библиотеку из 2-х классов Timer_P и DI.
Оттестировал. Вроде работает.
Описание:

Класс Timer_P имеет 3 публичных метода, публичные свойства отсутствуют.
  Timer_P() - конструктор класса, вызывается при декларации объекта класса Timer_P, аргументы отсутствуют.
  boolean Timer(boolean Condition, boolean Reset,int Mode, long Duration) - основной метод работы таймера
    Аргументы:
    - boolean Condition - условие запуска таймера
    - boolean Reset - условие принудительного сброса таймера
    - int Mode - режим работы таймера (0...4) см. временную диаграмму Timer_P.png
    - long Duration - длительность работы таймера в миллисекундах
    возвращает признак срабатывания таймера
  long GetRemains() - оставшееся время до срабатывания таймера в миллисекундах.
Timer_P работает с фронтами сигналов, поэтому очень чувствителен к дребезгу входных сигналов.
Для того, чтобы использовать Timer_P для входов Arduino рекомендуется "пропускать" их через класс DI.

Класс DI используется для программой фильтрации дребезга контакта.
Класс DI имеет 3 публичных метода, публичные свойства отсутствуют.
  DI(int PIN, long Duration)  конструктор класса, вызывается при декларации объекта класса DI.
    Аргументы:
    - int PIN - номер пина Arduino
    - long Duration - длительность времени фильтрации.
  boolean DI_Read() - считывание фильтрованного входа, возвращает фильтрованное значени входа.
  void DI_Refresh() - обновление входа, связанного с объектом класса.
Делать pinMode для входов используемых для DI не нужно. Они инициализируются при создании объекта класса DI.
При работе со входами используется INPUT_PULLUP-режим. Значения входов нормализовываются во время обработки класса.
Замкнутая кнопка - логическая 1 в DI.

Пример использования классов в скетче Arduino
-------------------------------------------------
    #include <DI.h>        //подключаем библиотеку DI - обработка дискретного входа из набора библиотек от X-Dron
    #include <Timer_P.h>    //подключаем библиотеку Timer_P - работа с таямерами из набора библиотек от X-Dron

    #define Condition_PIN 2    //PIN кнопки условия работы таймера
    #define Reset_PIN 3        //PIN кнопки сброса таймера
    #define QTimer_PIN 7        //PIN светодиода работы таймера
    #define QReset_PIN 8        //PIN светодиода сброса таймера
 
    Timer_P Timer_Test;                                //создание и инициализация объекта Timer_Test класса Timer_P
    // Создание объектов типа "Дистретный вход" они посажены описанные выше пины. Фильтр антидребезга 30мс.
    DI Condition_IN(Condition_PIN, 30);          
    DI Reset_IN(Reset_PIN, 30);
 
    void setup() {
      //Режимы выходов
      pinMode(QTimer_PIN, OUTPUT);
      pinMode(QReset_PIN, OUTPUT);
      //делать pinMode для входов не нужно. Они инициализируются при создании объекта класса DI см. DI.cpp
      //при работе со входами используется INPUT_PULLUP-режим. Значения входов нормализовываются во время обработки класса.
      //замкнутая кнопка - логическая 1.
    }
 
    void loop() {
      // Обновляем значение дискретных входов
      // Производится их считывание с пинов и фильтрация через внутреннюю переменную класса.
      Condition_IN.DI_Refresh();
      Reset_IN.DI_Refresh();
   
      //Считываем состояние фильтрованной кнопки сброса таймера и выводим его на лампочку.
      digitalWrite(QReset_PIN, Reset_IN.DI_Read());
   
      //Запускаем таймер по фильтрованной кнопке запуска таймера.
      //Сброс выполнения таймера по фильтрованной кнопке сброса таймера.
      //Режим работы таймера - Mode = 1 extended pulse
      //Время работы таймера - 1900мс.
      //Результат работы таймера выводим на светодиод работы таймера
      digitalWrite(QTimer_PIN, Timer_Test.Timer(Condition_IN.DI_Read(), Reset_IN.DI_Read(), 1, 1900));
   
      //Получаем значение отсрочки срабатывания таймера
      long T_Remains = Timer_Test.GetRemains();
    }
-------------------------------------------------

Библиотека находится по адресу
https://github.com/X-Dron/X-Dron_lib
Ссылка для скачивания https://github.com/X-Dron/X-Dron_lib/archive/master.zip
В примерах есть видео для разных режимов работы таймера. Временную диаграмму см. в  Timer_P.png
https://raw.githubusercontent.com/X-Dron/X-Dron_lib/master/Timer_P.png
Есть в планах написать еще один класс, который будет имитировать на Arduino входы-выходы как на промышленных контроллерах. Будет:
разбиение адресного поля на отдельные разделы (дискретные входы, дискретные выходы, аналоговые входы, аналоговые выходы).
- Считывание входов в начале цикла loop.
- Вывод выходов в конце цикла loop.
- Возможность настройки антидребезга по любому входному дискретному каналу.
Для мелких контроллеров это практически не актуально, а вот для меги может пригодиться.
Когда дойдут руки - не знаю. Наиболее интересный для себя проект - это все-таки беспроводной ввод-вывод.
 

dimax
dimax аватар
Онлайн
Зарегистрирован: 25.12.2013

X-Dron, было бы любопытно узнать, в чём отличие ваших библиотек от уже существующих.

X-Dron
Offline
Зарегистрирован: 24.01.2015

Хе. Библиотека DigitalTube с классом TM1637, например, работает с 4-х разрядным семисегментным индикатором, а класс Timer_P - создает разные импульсы. Библиотека DHT с классом DHT работает с датчиками температуры и влажности. Каков вопрос таков и ответ. Вы не указали, с какими библиотеками сравнивать, я других аналогичных не встречал. А если серьезно, то Безусловно, везде используются одни и те же принципы работы с основными базовыми наборами функций и атрибутов. Но можно всю жизнь писать на базовом уровне, а можно использовать библиотеки. Это как писать на ассемблере или на объектно-ориентированном языке. По поводу работы с кнопками есть закрепленная тема на форуме. Видел такой вариант фильтрации антидребезга

  if (buttonWasUp && !digitalRead(BUTTON_PIN)) {
    delay(10);
    if (!digitalRead(BUTTON_PIN))
       ; //типа сделан антидребезг, что-то можно сделать
  }
  buttonWasUp = digitalRead(BUTTON_PIN);

Т.е. ловим первый первый фронт нажатия, делаем паузу 10мс, снова считываем кнопку. В первых строках поста я описал, что delay() бесит. В системе регулирования паровой турбины за 10мс нужно обработать датчики положения сервоприводов, обработать регуляторы положения, выдать задания на позиционирование. А здесь 10мс фильтруем кнопку и все другое подождет. Я практически не сомневаюсь, что многие будут плеваться, т.к. если полностью отказаться от delay(), то методы программирования будут сильно отличаться. Но они гораздо ближе к тем, что используются в промышленных PLC. Простой пример. http://wiki.amperka.ru/%D0%BA%D0%BE%D0%BD%D1%81%D0%BF%D0%B5%D0%BA%D1%82-... Скетч сделан с использованием delay()

    #define BUZZER_PIN   12  // пин с пищалкой
    #define PLAYER_COUNT 2   // количество игроков-ковбоев
    // вместо перечисления всех пинов по-одному, мы объявляем пару
    // списков: один с номерами пинов с кнопками, другой — со
    // светодиодами. Списки также называют массивами (англ. array)
    int buttonPins[PLAYER_COUNT] = {3, 13};
    int ledPins[PLAYER_COUNT] = {9, 11};
     
    void setup()
    {
      pinMode(BUZZER_PIN, OUTPUT);
      for (int player = 0; player < PLAYER_COUNT; ++player) {
        // при помощи квадратных скобок получают значение в массиве
        // под указанным в них номером. Нумерация начинается с нуля
        pinMode(ledPins[player], OUTPUT);
        pinMode(buttonPins[player], INPUT_PULLUP);
      }
    }
     
    void loop()
    {
      // даём сигнал «пли!», выждав случайное время от 2 до 7 сек
      delay(random(2000, 7000));
      tone(BUZZER_PIN, 3000, 250); // 3 килогерца, 250 миллисекунд
     
      for (int player = 0; ; player = (player+1) % PLAYER_COUNT) {
        // если игрок номер «player» нажал кнопку...
        if (!digitalRead(buttonPins[player])) {
          // ...включаем его светодиод и сигнал победы на 1 сек
          digitalWrite(ledPins[player], HIGH);
          tone(BUZZER_PIN, 4000, 1000);
          delay(1000);
          digitalWrite(ledPins[player], LOW);
          break; // Есть победитель! Выходим (англ. break) из цикла
        }
      }
    }

Как задание для самостоятельного решения написано: В игре есть лазейка: кнопку можно зажать до сигнала «пли!» и таким образом сразу же выиграть. Дополните программу так, чтобы так выиграть было нельзя. Начинаешь ловить фронты - ловишь дребезг. Либо используй внешнюю обвязку в виде триггера Шмитта, или делай программно. Решение на моей библиотеке выглядит так, лазейки нет.

    #include <DI.h>           
    #include <Timer_P.h>
    #define BUZZER_PIN   11  // пин с пищалкой
    #define PLAYER_COUNT 2  // количество игроков-ковбоев
    int buttonPins[PLAYER_COUNT] = {2, 3};
    int ledPins[PLAYER_COUNT] = {7, 8};
    boolean Tour, Win[PLAYER_COUNT];
    
    DI Keys[PLAYER_COUNT];  //создание массива из класса DI - дискретный вход 
    Timer_P KeyTimers[PLAYER_COUNT], LedTimers[PLAYER_COUNT], StartDelayTimer; //создание экземпляров таймеров
    
    void setup()
    {
      pinMode(BUZZER_PIN, OUTPUT);
      Tour = false;    // инициализация цикла игры
      for (int player = 0; player < PLAYER_COUNT; player++) {
        pinMode(ledPins[player], OUTPUT); //инициализация выходов на светодиоды
        Keys[player].Init(buttonPins[player], 10); //инициализация дискретных входов класса DI, антидребезг 10мс 
      }
    }
     
    void loop()
    {
      if (StartDelayTimer.Timer((!Tour), false, 2, random(3000, 8000))) // если тур игры не запущен, то через промежуток 3..8 секунд 
      {
        Tour = true;                     //запускаем тур
        tone(BUZZER_PIN, 3000, 250);     //даем сигнал 
      }
      for (int player = 0; player < PLAYER_COUNT; player++)
      {
         Keys[player].DI_Refresh();       //Обновляем входа
         Win[player] = KeyTimers[player].Timer(Keys[player].DI_Read(), !Tour, 0, 1); //Определяем победителя
         //Формируется импульс длительностью до 1мс по нажатию клавиш. 
         if (Win[player])   //Если победитель есть 
         {
           Tour = false;    //то тур завершился
           tone(BUZZER_PIN, 4000, 1500); //даем сигнал
         }
         digitalWrite(ledPins[player], LedTimers[player].Timer(Win[player], false, 1, 1500)); //обновляем светодиоды  
      }
    }

Здесь используется обновленная библиотека, в репозитории еще вчерашняя. Обновлю через час-два. Добавился еще один конструктор класса DI и метод инициализации. Это дает возможность создавать массивы класса DI.

alex668
Offline
Зарегистрирован: 02.10.2016

с помощью этой библиотеки написан скетч на подсветку с двумя датчиками, ты помогал,кстати, спасибо

сейчас что то глюкануло и не работает, не мог бы еще раз помоч?

MagicianT
Offline
Зарегистрирован: 03.10.2015

Несколько некоректно проводить паралели с тригером Шмидта, с дребезгом эти тригеры не справляются, да они не для того и придуманы. У ардуино Шмидты кстати есть, на каждом порте с гистерезисом около 0.5В, т.е. в "1" при 2.7В, а обратно в "0" при 2.2В 

ssss
Offline
Зарегистрирован: 01.07.2016

X-Dron пишет:

16 лет занимаюсь программированием промышленной автоматики на PLC Omron, Siemens. С ардуино столкнулся месяца 2 назад. И сразу же стали бесить delay(), когда для того, чтобы правильно отработал какой-то один алгоритм, приходится тормозить весть проц и все другие аглоритмы.
Для того чтобы использовать фронты дискретных входов, нужно избавиться от дребезга контактов нужно собирать схемы с триггером Шмитта. А если таких входов штук 20???

Если вы так охрененно озабочены дребезгом, юзайте Кортекс от Nuvoton. Там на портах обработка дребезга хардварная и всё настраивается в регистрах порта при ините.

 

nik182
Онлайн
Зарегистрирован: 04.05.2015

Для подавления дребезга я использую следующую конструкцию

      if (k1cnt > 3){
        if ((PINB & dataPin)==0) k1cnt++; else {if (k1cnt > 40)  key=2; else key=1;} k1cnt=0;}
      else if ((PINB & dataPin)==0) k1cnt++;                

Висит либо в цикле на конструкции

    tm=millis();
    if(tm-tio > 20)
    {
      tio=tm;
         if (k1cnt > 3){
        if ((PINB & dataPin)==0) k1cnt++; else {if (k1cnt > 40)  key=2; else key=1;} k1cnt=0;}
      else if ((PINB & dataPin)==0) k1cnt++;                
   }

 либо в прерывании таймера, если цикл работает больше 20 мс. Но это как то редко случается.

Выдаёт короткоё и длинное нажатие. Легко позволяет расширить до быстрого повтора короткого нажатия при удержании больше определённого времени - часто использую при настройке параметров.  Это конечно не титановый велосипед, но меня полностью удволетворяет.  Занимает мало места и времени. Не имеет задержек при подавлении дребезга.  Из минусов - для каждой кнопки надо прописать свои параметры и писать строки.