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

Тема в разделе "Глядите, что я сделал", создана пользователем X-Dron, 24 янв 2015.

  1. X-Dron

    X-Dron Гик

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

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

    Класс DI используется для программой фильтрации дребезга контакта.
    Класс DI имеет 3 публичных метода, публичные свойства отсутствуют.
      DI() конструктор класса без аргументов. Может быть использован для создания массива объектов класса DI. см. cowboys_X-Dron в примерах
      DI(int PIN, long Duration)  конструктор класса, вызывается при декларации объекта класса DI.
       Аргументы:
       - int PIN - номер пина Arduino
       - long Duration - длительность времени фильтрации.
      boolean DI_Read() - считывание фильтрованного входа, возвращает фильтрованное значени входа.
      void Init(int PIN, long Duration) - метод инициализации объектов класса DI. Используется при инициализации массива DI. см.cowboys_X-Dron в примерах
      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

    Есть в планах написать еще один класс, который будет имитировать на Arduino входы-выходы как на промышленных контроллерах. Будет:
    разбиение адресного поля на отдельные разделы (дискретные входы, дискретные выходы, аналоговые входы, аналоговые выходы).
    - Считывание входов в начале цикла loop.
    - Вывод выходов в конце цикла loop.
    - Возможность настройки антидребезга по любому входному дискретному каналу.
    Для мелких контроллеров это практически не актуально, а вот для меги может пригодиться.
    Когда дойдут руки - не знаю. Наиболее интересный для себя проект - это все-таки беспроводной ввод-вывод.
     

    Вложения:

    • Timer_P.png
      Timer_P.png
      Размер файла:
      44,5 КБ
      Просмотров:
      1.067
    Последнее редактирование: 31 янв 2015
    ИгорьК и Alex19 нравится это.
  2. Alex19

    Alex19 Гуру

    Интересное решение.

    Сам ярый противник delay, пользуюсь простой функцией таймера на основании mills(), такой же принцип как у Вас в библиотеке. А для подавления дребезга подтягивающими резисторами.

    Как я понял разницу режимов, можно увидеть на видео?
     
  3. X-Dron

    X-Dron Гик

    Подробная временнАя диаграмма режимов описана во вложенном файле поста. Этот же файл присутствует в библиотеке. Timer_P.png.
    Только сейчас заметил небольшую ошибку в диаграмме 4-го режима, сейчас подправлю.
    Подтягивающие резисторы помогают от наводок, а вот от дребезга не спасают. Пока не работаешь с фронтами это не заметно.
    В Эксперементе 14 вики, там где считаются нажатия кнопок, антидребезг сделан так.
    Код (Text):
      if (buttonWasUp && !digitalRead(BUTTON_PIN)) {
        delay(10);
        if (!digitalRead(BUTTON_PIN))
          clicks = (clicks + 1) % 10;
      }
      buttonWasUp = digitalRead(BUTTON_PIN);
    Т.е. ловим первый первый фронт нажатия, делаем паузу 10мс, снова считываем кнопку. Если она нажата, то увеличиваем количество нажатий на 1. А представим на миг, что все-таки дребезг продолжается и мы поймали отпущенное состояние, от нажатие не засчитается. delay() опять же бесит.
     
    Последнее редактирование: 25 янв 2015
  4. Alex19

    Alex19 Гуру

    Спасибо за пояснение, надеюсь у Вас хватит времени воплотить все задумки.

    Очень плохо разбираюсь в электронике (главное, что усвоил электроника - тонкий кабель, электрика - толстый:(), пока не было необходимости в ее изучении. Надеюсь закончу текущий проект и сяду за парту.
     
  5. X-Dron

    X-Dron Гик

    Обновил библиотеку.
    Добавил в классе DI:
    DI() конструктор класса без аргументов. Может быть использован для создания массива объектов класса DI. см. cowboys_X-Dron в примерах
    void Init(int PIN, long Duration) - метод инициализации объектов класса DI. Используется при инициализации массива DI. см.cowboys_X-Dron в примерах.
    http://wiki.amperka.ru/конспект-arduino:кнопочные-ковбои
    Как задание для самостоятельного решения написано:
    Начинаешь ловить фронты - ловишь дребезг. Либо используй внешнюю обвязку в виде триггера Шмитта, или делай программно. Решение на моей библиотеке выглядит так, лазейки нет. и без delay()
    Код (Text):
        #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)); //обновляем светодиоды
          }
        }
    В этом коде, на самом деле есть чит за первого игрока. Если, чисто случайно, нажали одновременно, то предпочтение отдается первому.
    Более справедливый void loop должен быть таким:
    Код (Text):
        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[0] || Win[1])  //Если победитель есть
          {
            Tour = false;    //то тур завершился
            tone(BUZZER_PIN, 4000, 1500); //даем сигнал
          }
          for (int player = 0; player < PLAYER_COUNT; player++)
            digitalWrite(ledPins[player], LedTimers[player].Timer(Win[player], false, 1, 1500)); //обновляем светодиоды
        }
    При одновременном нажатии дается ничья, загораются оба светодиода
     
    Последнее редактирование: 26 янв 2015
    ИгорьК нравится это.
  6. Alex19

    Alex19 Гуру

    Раз Вы так серьезно подошли к вопросу, возможно Вам будет полезна библиотека работы с ножками на прямую.

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

    Пока разобрался с цифровыми выходами. Но возможностей у нее больше.

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

    Код с FastIO, тут использовался оригинальная библиотека.
    Код (Text):

      WRITE(pin11, HIGH);  // +1 digitalWrite +4  // +0 WRITE
      WRITE(pin12, HIGH);  // +1 digitalWrite +4  // +0 WRITE
      WRITE(pin13, HIGH);  // +1 digitalWrite +8  // +0 WRITE
      WRITE(pin14, HIGH);
      WRITE(pin15, HIGH);
      WRITE(pin16, HIGH);
      WRITE(pin17, HIGH);
      WRITE(pin18, HIGH);
      WRITE(pin19, HIGH);
      WRITE(pin20, HIGH);
      WRITE(pin21, HIGH);
      WRITE(pin22, HIGH);
      WRITE(pin23, HIGH);
      WRITE(pin24, HIGH);
      WRITE(pin25, HIGH);
      // 15 digitalWrite + 72
      // 15 WRITE + 4

      bpin0 = READ(pin1);  // +1 digitalRead +8  // +4 READ
      bpin1 = READ(pin2);  // +1 digitalRead +4  // +0 READ
      bpin2 = READ(pin3);  // +1 digitalRead +4  // +0 READ
      bpin3 = READ(pin4);
      bpin4 = READ(pin5);
      bpin5 = READ(pin6);
      bpin6 = READ(pin7);
      bpin7 = READ(pin8);
      bpin8 = READ(pin9);
      bpin9 = READ(pin10);
      // 10 digitalRead + 48
      // 10 READ + 8
     
    Так же она позволяет уменьшить код, пример Blink without Delay, будет весить 630 байт, вместо 1 028 байт.

    Очень удобно, когда работаешь с большим кол-вом пинов. Удобна, тем, что используется более понятное наименования пина, просто 13 а не PINB7 (к примеру).

    Появится время детально разберу Вашу библиотеку и если понравится уволоку себе:), надеюсь не будете против.

    UPD. Прямой доступ к цифровым пинам для Arduino Due, вдруг кому-то будет любопытно
    Код (Text):

    inline void digitalWriteDirect(int pin, boolean val){
      if(val) g_APinDescription[pin].pPort -> PIO_SODR = g_APinDescription[pin].ulPin;
      else    g_APinDescription[pin].pPort -> PIO_CODR = g_APinDescription[pin].ulPin;
    }

    inline int digitalReadDirect(int pin){
      return !!(g_APinDescription[pin].pPort -> PIO_PDSR & g_APinDescription[pin].ulPin);
    }
    И тесты скорости выполнения
    Код (Text):

    /*digitalWriteDirect(pin11, HIGH);  // +1 digitalWrite +3 // +1 digitalWriteDirect +1
      digitalWriteDirect(pin12, HIGH);  // +1 digitalWrite +2 // +1 digitalWriteDirect +0
      digitalWriteDirect(pin13, HIGH);  // +1 digitalWrite +3 // +1 digitalWriteDirect +0
      digitalWriteDirect(pin14, HIGH);
      digitalWriteDirect(pin15, HIGH);
      digitalWriteDirect(pin16, HIGH);
      digitalWriteDirect(pin17, HIGH);
      digitalWriteDirect(pin18, HIGH);
      digitalWriteDirect(pin19, HIGH);
      digitalWriteDirect(pin20, HIGH);
      digitalWriteDirect(pin21, HIGH);
      digitalWriteDirect(pin22, HIGH);
      digitalWriteDirect(pin23, HIGH);
      digitalWriteDirect(pin24, HIGH);
      digitalWriteDirect(pin25, HIGH);*/
      // 15 digitalWrite + 38
      // 15 digitalWriteDirect + 5

      /*bpin0 = digitalReadDirect(pin1);  // +1 digitalRead +1 // +1 digitalReadDirect +1
      bpin1 = digitalReadDirect(pin2);  // +1 digitalRead +2 // +1 digitalReadDirect +0
      bpin2 = digitalReadDirect(pin3);  // +1 digitalRead +0 // +1 digitalReadDirect +1
      bpin3 = digitalReadDirect(pin4);
      bpin4 = digitalReadDirect(pin5);
      bpin5 = digitalReadDirect(pin6);
      bpin6 = digitalReadDirect(pin7);
      bpin7 = digitalReadDirect(pin8);
      bpin8 = digitalReadDirect(pin9);
      bpin9 = digitalReadDirect(pin10);*/
      // 10 digitalRead + 11
      // 10 digitalReadDirect + 7
     
    Было взято с arduino.cc уже не вспомню где.
     

    Вложения:

    • FastIO.txt
      Размер файла:
      45 КБ
      Просмотров:
      299
    Последнее редактирование: 30 янв 2015
    X-Dron нравится это.
  7. X-Dron

    X-Dron Гик

    Спасиб, за инфу. Если дойду до симуляции PLC на Mega, то попробую разобраться.
    Сейчас уже моя беспроводка стала приходить.
     
  8. Alex19

    Alex19 Гуру

    Не за что, все помогаем друг другу по мере сил.

    Он подходит не только для Mega, так же для UNO, Nano и т.д. вообщем на все платы с чипами ATmega168, ATmega328, ATmega328P, ATmega1280, ATmega2560 (в оригинале больше, но они очень редкие к примеру серия AT90USB, не нашел только ATmega32u4, его можно дописать изучив пин мапинг).

    Узнать какой чип, на конкретной ардуине, можно тут.
     
  9. X-Dron

    X-Dron Гик

    Обновил библиотеку. В класс Timer_P добавил 2 метода.
    Код (Text):
     
    void TimerV(boolean Condition, boolean Reset,int Mode, long Duration) - метод запуска и контроля таймера
        Аргументы:
        - boolean Condition - условие запуска таймера
        - boolean Reset - условие принудительного сброса таймера
        - int Mode - режим работы таймера (0...4) см. временную диаграмму Timer_P.png
        - long Duration - длительность работы таймера в миллисекундах
    boolean Q0() - возвращает признак срабатывания таймера.
     
    Методы TimerV и Q0 добавлены, чтобы можно было делать таймеры с самосбросом и для удобного использования признака срабатывания таймера.

    Меандр с выводом на лампочку с использованием библиотеки пишется так.
    Код (Text):
        #include <Timer_P.h>    //подключаем библиотеку Timer_P - работа с таямерами из набора библиотек от X-Dron
        #define LED_PIN 7        //PIN светодиода
        #define Period 2000      //Период меандра
        #define Duration 500    //Импульс меандра
       
        Timer_P Period_T, Pulse_T;  //создание и инициализация объекта Timer_Test класса Timer_P

        void setup() {
          //Режимы выходов
          pinMode(LED_PIN, OUTPUT);
        }
       
        void loop() {
          Period_T.TimerV(!Period_T.Q0(), 0, 2, Period); // Самосбрасывающийся таймер периода
          Pulse_T.TimerV(!Period_T.Q0(), 0, 1, Duration);// Импульс длительности Duration по началу периода
          digitalWrite(LED_PIN, Pulse_T.Q0()); //Выводим меандр на лампочку.
        }