Определение установившегося состояния на входах. антидребезг.

Тема в разделе "Arduino & Shields", создана пользователем SergeiL, 30 июн 2021.

  1. SergeiL

    SergeiL Оракул Модератор

    Чтение состояния входов или считывание состояния кнопок стандартная ситуация при работе с микроконтроллером.

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

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

    Как все это работает:
    Данная функция запускается каждые 5-20мс. Все зависит от требуемой скорости реакции.
    На тестовой Меге с Амперковским Relay Shield вместо кнопок в разъем между землей и цифровым входом втыкал резистор. При опросе каждые 20мс никаких эффектов перещелкивания реле - не было, и в монитор порта валится только одно событие.
    А вот на 10мс пока резистор в разъем вставляешь, вытаскиваешь - пару раз реле может сработать.

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

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

    В случае Arduino, обе части могут выполняться последовательно в loop(), но я больше люблю вариант с таймером.

    Можно первую часть перенести в обработчик таймера, а вторую оставить в loop() или main() .

    Главные достоинство данного варианта – это компактность, скорость обработки и надежность.

    Собственно функция:
    Код (C++):

    //------------------ ФУНКЦИЯ УСТР. ДРЕБЕЗГА --------------------------------------
    // Глобальные переменные

    // биты в байтах ниже взводятся обработчике дребезга, можно в прерывании от таймера и сбрасываются программой обработчиком событий
    uint8_t LATCH_ON   = 0;     // вход перешел в 1
    uint8_t LATCH_OFF  = 0;     // вход вернулся в 0

    void Input_CHK(void)         // вызывается каждые 10 мсек, можно из таймера
    {
        static uint8_t SLATCH = 0;            // текущее устоявшееся значение входов после устранения дребезга
        static uint8_t VCBIT0 = 0;            // Вертикальный счетчик бит 0
        static uint8_t VCBIT1 = 0;            // Вертикальный счетчик бит 1

        uint8_t        SWKEYS = 0;            // состояние вводов
        uint8_t        VCTEMP = 0;            // промежуточная переменная
        uint8_t        VCMASK = 0;            // Маска

        SWKEYS = Input_Read();                         // загрузим значения входов в глобальную переменную SWKEYS

        VCMASK = SWKEYS ^ SLATCH;                   // скинем счетчики для установившихся и неактивных значений
        VCBIT0 &= VCMASK;
        VCBIT1 &= VCMASK;
                                                      //  Каждая '1' в SLATCH представляет установившееся значение
                                                           
        SLATCH ^= (VCTEMP = VCMASK & VCBIT0 & VCBIT1);

        if( VCTEMP )                                  // есть изменения входов, взведем флаги
      {
         LATCH_ON  |= VCTEMP &  SWKEYS;               // взведем биты нажатых кнопок и сработавших входов.
         LATCH_OFF |= VCTEMP & ~SWKEYS;               // Биты сбрасываются в обработчиках.
      }

        VCBIT1 ^= (VCMASK & VCBIT0);                   // инкрементируем счетчик.
        VCBIT0 ^= (VCMASK);
    }
     
    В ней вызывается функция Input_Read(); :
    Код (C++):

    //-------------------- Digital In Ports -------------------------------

    uint8_t DIG_In_Pins[] = {8, 9, 10, 11};   // 4 входа или кнопки.
    #define DIG_INPUT_QTY   (sizeof(DIG_In_Pins)/sizeof(DIG_In_Pins[0]))

    //------------------ Чтение цифровых входов --------------------------------------

    uint8_t  Input_Read(void)
    {
          uint8_t result = 0;                              // копим побитовое состояние входов
          uint8_t i,j;
          for (i=1,j=0; j < DIG_INPUT_QTY; j++,i=i<<1)     // пробежимся по всем входам
          {                  
                                                           // Если при нажатии кнопки на входе появляется VCC - в if оставляем == HIGH.
              if ( digitalRead(DIG_In_Pins[j]) == LOW )    // Если при нажатии кнопки на входе появляется GND - заменить HIGH на LOW
                result |= i;                               // Дальше всегда будем считать, что если бит в 1 - это кнопка нажата или на входе ON.
          }                                                //                                             0 - это кнопка отпущена или на входе OFF.
                                                           // в result "1" взводится в соответствующем бите, если на входе присутствует ON. номер бита сответствует номеру входа
                                                           // например, если на входе 2 - ( в define в самом верху определено, что это цифровой пин 0),
                                                           // (диапазон 0-7 InputPins[2]) присутсвует 1 - в result добавится 1 в 2-ом от нуля бите.  00000100
          return(result);
    }
    Цикл присутствует только в Input_Read(void) для того, чтобы собрать информацию с портов контроллера в одну переменную, где каждый разряд будет отвечать за определенный вход.
    Если обращаться к портам не через функции Ардуино, можно загрузить состояние всего порта одной командой, тогда потребность в функции Input_Read(void) отпадает совсем, обработка становится еще проще и короче.

    В функции void Input_CHK(void) циклов нет.
    То есть получая входные уровни из порта, мы помещаем их в переменную SWKEYS, где каждый бит соответствует определенному входу.
    Далее выполняются 9 строчек "магического кода" (информация в комментариях) в результате которых:
    • если сигнал 1 на входе порта присутствует 4 проверки, и предыдущее устойчивое состояние было 0 взводится соответствующий бит в глобальной переменной LATCH_ON.
    • если сигнал 0 на входе порта присутствует 4 проверки, и предыдущее устойчивое состояние было 1 взводится соответствующий бит в глобальной переменной LATCH_OFF.,
    Это и есть указанные выше флаги изменения состояния на входе.

    Далее например в loop() обработаем изменившиеся флаги:
    Код (C++):
       if (LATCH_ON != 0 || LATCH_OFF != 0  )          // Если есть изменения на входах - обработаем.
       {
            for (i=1,j=0; j < PIN_IN_NUMB; j++,i=i<<1)   // пробежимся по всем входам
            {
              if (LATCH_ON & i)                          // Если копка была нажата или вход перешел в ON
              {
                    Serial.print("  Input ");        
                    Serial.print(j);
                    Serial.println(" = ON");
                LATCH_ON &= ~i;                          // Обязательно сбросим бит, так как данное событие обработано
              }
              if (LATCH_OFF & i)                          // Если копка была отпущена или вход перешел в OFF  ЕСЛИ НУЖНО.
              {
                    Serial.print("  Input ");        
                    Serial.print(j);
                    Serial.println(" = OFF");
                LATCH_OFF &= ~i;                        // Обязательно сбросим бит, так как данное событие обработано
              }
            }
       }
     
    С переменными типа uint8_t параллельно обрабатывается до 8-ми входов.
    Если нужно больше входов - можно изменить типы переменных до uint16_t или uint32_t.
    Меняется только тип переменных, все остальное остается прежним.

    Ну а дальше будет два примера, один простенький с опросом входов в loop(),
    а второй с таймером.
     
    Daniil и KindMan нравится это.
  2. SergeiL

    SergeiL Оракул Модератор

    Пример 1
    Код (C++):
    #define PIN_IN_0 8     // для удобства назначим физическим портам ардуино логическое имя, которое занесем потом в массив
    #define PIN_IN_1 9
    #define PIN_IN_2 10     //  Логический вход 2: соответствует Цифровому входу 0 на Arduino
    #define PIN_IN_3 11

                          // количество используемых входов значение от 1 до 4.
                          // Если входов больше 8 ходов - меняем размер переменных участвующих в хранении результатов с byte на unsigned int или long

    uint8_t InputPins[] = { PIN_IN_0,PIN_IN_1,PIN_IN_2,PIN_IN_3};      // заносим номера пинов в массив, для обработки через for

    #define PIN_IN_NUMB (sizeof(InputPins)/sizeof(InputPins[0]))         // количество используемых входов значение от 1 до 8.


    uint8_t test_buff[] = {0,1,3,0,1,3,3,2,3,1,1,3,0,2,3,4,0,2,0,0,0,0,1,0,0};   // тестовый массив со значениями.
    #define TEST_BUFF_LEN (sizeof(test_buff)/sizeof(test_buff[0]))                            // количество тестовых чтений.

    #define PIN_READ_TO 10L             // 10 ms  периодичность проверки входов. Оптимальным является интервал 5-10мс. соответственно в результате сигнал о нажатии будет получен через 20-40мс  (в данном случае 40мс)

    unsigned long   LastPinReadMs;      // переменная для сохранения времени, когда были считаны значения с цифорвых входов
    char buff_t[10];

    //------------------ Чтение цифровых входов --------------------------------------

    char *sprintf_bin(char *buff, uint8_t value)
    {
      for (int i = 0; i < 8; i++)
      {
        if(value & (1 << i))
          *(buff+7-i)='1';
        else
          *(buff+7-i)='0';
      }
      *(buff+8)=0;
      return buff;
    }
    uint8_t Input_Read(void)
    {
          uint8_t result = 0;                              // копим побитовое состояние входов

    //  убрать блок, до и включая while(1); для чтения реальных входов

          static uint8_t counter = 0;
          if (counter < TEST_BUFF_LEN)
          {
       //     Serial.println();
            Serial.print("Inp=");
            Serial.print(test_buff[counter]);
            Serial.print("  ");

            sprintf_bin(buff_t,test_buff[counter]);
            Serial.println(buff_t);

            result = test_buff[counter++];
          }
          else
            while(1);
    /******************************/

    /*  раскомментировать    данный блок для чтения реальных входов
          uint8_t i,j;
          for (i=1,j=0; j < PIN_IN_NUMB; j++,i=i<<1)    // пробежимся по всем входам
          {                  
                                                        // Если при нажатии кнопки на входе появляется VCC - в if оставляем == HIGH.
              if ( digitalRead(InputPins[j]) == HIGH )  // Если при нажатии кнопки на входе появляется GND - заменить HIGH на LOW
                result |= i;                            // Дальше всегда будем считать, что если бит в 1 - это кнопка нажата или на входе ON.
          }                                             //                                             0 - это кнопка отпущена или на входе OFF.
                                                        // в result "1" взводится в соответствующем бите, если на входе присутствует ON. номер бита сответствует номеру входа
                                                        // например, если на входе 2 - ( в define в самом верху определено, что это цифровой пин 0),
                                                        // (диапазон 0-7 InputPins[2]) присутсвует 1 - в result добавится 1 в 2-ом от нуля бите.  00000100
      */
                                         
          return(result);
    }

    //------------------ ФУНКЦИЯ УСТР. ДРЕБЕЗГА --------------------------------------
    // Глобальные переменные

    // биты в байтах ниже взводятся обработчике дребезга, можно в прерывании от таймера и сбрасываются программой обработчиком событий
    uint8_t LATCH_ON   = 0;     // вход перешел в 1
    uint8_t LATCH_OFF  = 0;     // вход вернулся в 0

    void Input_CHK(void)         // вызывается каждые 10 мсек, можно из таймера
    {
        static uint8_t SLATCH = 0;            // текущее устоявшееся значение входов после устранения дребезга
        static uint8_t VCBIT0 = 0;            // Вертикальный счетчик бит 0
        static uint8_t VCBIT1 = 0;            // Вертикальный счетчик бит 1

        uint8_t        SWKEYS = 0;            // состояние вводов
        uint8_t        VCTEMP = 0;            // промежуточная переменная
        uint8_t        VCMASK = 0;            // Маска

        SWKEYS = Input_Read();                         // загрузим значения входов в глобальную переменную SWKEYS

        VCMASK = SWKEYS ^ SLATCH;                   // скинем счетчики для установившихся и неактивных значений
        VCBIT0 &= VCMASK;
        VCBIT1 &= VCMASK;
                                                      //  Каждая '1' в SLATCH представляет установившееся значение
        SLATCH ^= (VCTEMP = VCMASK & VCBIT0 & VCBIT1);

        if( VCTEMP )                                  // есть изменения входов, взведем флаги
      {
         LATCH_ON  |= VCTEMP &  SWKEYS;               // взведем биты нажатых кнопок и сработавших входов.
         LATCH_OFF |= VCTEMP & ~SWKEYS;               // Биты сбрасываются в обработчиках.
      }

        VCBIT1 ^= (VCMASK & VCBIT0);                   // инкрементируем счетчик.
        VCBIT0 ^= (VCMASK);
    }

    void Input_On(uint8_t number)
    {
      Serial.print("  Input ");                // выполним действие по нажатию на кнопку.  номер кнопку в переменной "number"
      Serial.print(number);
      Serial.println(" = ON");
    }

    void Input_Off(uint8_t number)
    {
      Serial.print("  Input ");                // ЕСЛИ НУЖНО! выполним действие по отпусканию кнопки.  номер кнопку в переменной "number"
      Serial.print(number);
      Serial.println(" = OFF");
    }

    void setup()
    {
        uint32_t strt_ms = millis();
        uint8_t j;

        for (j=0; j < PIN_IN_NUMB; j++)        // переключим нужные порты на ввод
            pinMode(InputPins[j], INPUT_PULLUP);
        Serial.begin(115200);

        while (millis()- strt_ms < 5000L ) // 5 sec wait for serial port. Needed for Leonardo only
        {
          if (Serial)
          {
            Serial.println("Serial Connected");
            break;
          }
        }
    }

    void loop()
    {
       uint8_t j,i;

       if ((millis() - LastPinReadMs) > PIN_READ_TO)      // выполняется каждые 10мс (PIN_READ_TO)
       {
          Input_CHK();                                    // читаем значение, игнорируем дребезг
          LastPinReadMs = millis();                       // запомним время
       }


       if (LATCH_ON != 0 || LATCH_OFF != 0  )          // Если есть изменения на входах - обработаем.
       {
            for (i=1,j=0; j < PIN_IN_NUMB; j++,i=i<<1)   // пробежимся по всем входам
            {
              if (LATCH_ON & i)                          // Если копка была нажата или вход перешел в ON
              {
                Input_On(j);
                LATCH_ON &= ~i;                          // Обязательно сбросим бит, так как данное событие обработано
              }
              if (LATCH_OFF & i)                          // Если копка была отпущена или вход перешел в OFF  ЕСЛИ НУЖНО.
              {
                Input_Off(j);
                LATCH_OFF &= ~i;                        // Обязательно сбросим бит, так как данное событие обработано
              }
            }
       }



      //   ваш код....

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

    Слева десятичное, (справа двоичное) значение поступающее на вход функции.

    Serial Connected
    Inp=0 00000000
    Inp=1 00000001..........Значение в 0 и 1 разряде прыгает
    Inp=3 00000011..........данные "1" отбрасываются
    Inp=0 00000000
    Inp=1 00000001
    Inp=3 00000011
    Inp=3 00000011
    Inp=2 00000010
    Inp=3 00000011..........Значение "1" в 1 разряде держится 4 считывания.
    Input 1 = ON...............Вход 1 перешел в состояние "1"
    Inp=1 00000001
    Inp=1 00000001
    Inp=3 00000011..........Значение "1" в 0 разряде держится 4 считывания.
    Input 0 = ON...............Вход 0 перешел в состояние "1"
    Inp=0 00000000
    Inp=2 00000010
    Inp=3 00000011
    Inp=4 00000100
    Inp=0 00000000
    Inp=2 00000010
    Inp=0 00000000..........Значение "0" в 0 разряде держится 4 считывания.
    Input 0 = OFF...............Вход 0 перешел в состояние "0"
    Inp=0 00000000
    Inp=0 00000000
    Inp=0 00000000..........Значение "0" в 1 разряде держится 4 считывания.
    Input 1 = OFF...............Вход 1 перешел в состояние "0"
    Inp=1 00000001
    Inp=0 00000000
    Inp=0 00000000
     
    Daniil и Airbus нравится это.
  3. SergeiL

    SergeiL Оракул Модератор

    Пример 2. Порезал из реально работающего, все проверено - работает.
    Код (C++):
    #include <TimerOne.h>

    // --------------------------------- Timers --------------------------
    #define TICKER_PERIOD      10   // ms

    #define SEC_COUNT_TO       1*(1000/TICKER_PERIOD)       //  1 sec  в единицах тикера
    // #define TEMP_CHK_TO        30                           //  30 sec  в секундах  (оставил для примера секундный таймер)
    // #define TEMP_CONV_TO       2*(1000/TICKER_PERIOD)       //  2 sec  в единицах тикера. время преобразования (оставил для примера еще один таймер)
    // uint16_t  Temperature_Chk_Counter = TEMP_CHK_TO;
    // uint8_t   Temperature_Conv_Counter = 0;



    //-------------------- Digital Out Ports -------------------------------

    uint8_t Relay_Pins[] = {4, 5, 6, 7};   //  реле подвешены на соответствующие входы
    #define RELAY_QTY (sizeof(Relay_Pins)/sizeof(Relay_Pins[0]))

    #define LED_PIN 13  // внутренний LED мигает, показывая, что устройство живо.
    bool ledStatus = false;

    //-------------------- Digital In Ports -------------------------------

    uint8_t DIG_In_Pins[] = {8, 9, 10, 11};   // 4 входа или кнопки.
    #define DIG_INPUT_QTY   (sizeof(DIG_In_Pins)/sizeof(DIG_In_Pins[0]))

    uint8_t InputState = 0;   // храним текущее состояне входов.  Потом придумаем, для чего

    //      76543210
    //      |||||||+---  0: In_00
    //      ||||||+----  1: In_01
    //      |||||+-----  2: In_02
    //      ||||+------  3: In_03
    //      |||+-------  4:
    //      ||+--------  5:
    //      |+---------  6:

    //-------------------------------- Functions -----------------------------------

    void init_io(void);
    void change_relay_state(uint8_t relay_numb, uint8_t relay_state);
    void Timer_Func(void);
    uint8_t  Input_Read(void);
    void Input_CHK(void);
    void Input_Process(void);
    void setup(void);
    void loop(void);

    void init_io(void)
    {
      uint8_t   i,j;
      uint8_t   state;

      for (i=0,j=1; i < RELAY_QTY; i++,j=j<<1)    // для упрощения убрано восстонавление реле из EEPROM
      {
        state = LOW;
        digitalWrite (Relay_Pins[i], state);
        pinMode(Relay_Pins[i], OUTPUT);
      }

      for (i=0; i < DIG_INPUT_QTY; i++) //
      {
          pinMode(DIG_In_Pins[i], INPUT_PULLUP);      
      }

      // Служебные пины
      digitalWrite(LED_PIN, LOW);
      pinMode(LED_PIN, OUTPUT);
    }

    void change_relay_state(uint8_t relay_numb, uint8_t relay_state) //
    {
        if (relay_numb < RELAY_QTY)
        {
          Serial.print(F("Relay: "));
          Serial.print(relay_numb);
          Serial.print(F("   State: "));
          Serial.println(relay_state);

          digitalWrite (Relay_Pins[relay_numb], relay_state);
        }
    }

    void Timer_Func(void)
    {
      static unsigned Sec_Counter = SEC_COUNT_TO;

      Sec_Counter--;

      Input_CHK();

      if (Sec_Counter == 0) // 1 Sec counter  все скундные таймеры закидываем сюда.
      {
        Sec_Counter = SEC_COUNT_TO;
     
        ledStatus=!ledStatus;
        digitalWrite (LED_PIN, ledStatus);        // мигает - значит работает...
    /*
        if ( DS_Quantity && Temperature_Chk_Counter )
        {
          Temperature_Chk_Counter--;
        }
    */

      }  // 1 Sec counter exit
    }


    //------------------ Чтение цифровых входов --------------------------------------

    uint8_t  Input_Read(void)
    {
          uint8_t result = 0;                              // копим побитовое состояние входов
          uint8_t i,j;
          for (i=1,j=0; j < DIG_INPUT_QTY; j++,i=i<<1)     // пробежимся по всем входам
          {                              
                                                           // Если при нажатии кнопки на входе появляется VCC - в if оставляем == HIGH.
              if ( digitalRead(DIG_In_Pins[j]) == LOW )    // Если при нажатии кнопки на входе появляется GND - заменить HIGH на LOW
                result |= i;                               // Дальше всегда будем считать, что если бит в 1 - это кнопка нажата или на входе ON.
          }                                                //                                             0 - это кнопка отпущена или на входе OFF.
                                                           // в result "1" взводится в соответствующем бите, если на входе присутствует ON. номер бита сответствует номеру входа
                                                           // например, если на входе 2 - ( в define в самом верху определено, что это цифровой пин 0),
                                                           // (диапазон 0-7 InputPins[2]) присутсвует 1 - в result добавится 1 в 2-ом от нуля бите.  00000100
          return(result);
    }

    //------------------ ФУНКЦИЯ УСТР. ДРЕБЕЗГА --------------------------------------

    // биты в байтах ниже взводятся обработчике дребезга, в прерывании от таймера и сбрасываются программой обработчиком событий  (здесь в Input_Process() )
    uint8_t LATCH_ON   = 0;     // вход перешел в 1
    uint8_t LATCH_OFF  = 0;     // вход вернулся в 0

    void Input_CHK(void)         // вызывается каждые 10 мсек, можно из таймера  В КОД МОЖНО НЕ ВНИКАТЬ, главное понять суть флагов LATCH_ON и LATCH_OFF
    {
        static uint8_t SLATCH = 0;            // текущее устоявшееся значение входов после устранения дребезга
        static uint8_t VCBIT0 = 0;            // Вертикальный счетчик бит 0
        static uint8_t VCBIT1 = 0;            // Вертикальный счетчик бит 1

        uint8_t        SWKEYS = 0;            // состояние вводов
        uint8_t        VCTEMP = 0;            // промежуточная переменная
        uint8_t        VCMASK = 0;            // Маска


        SWKEYS = Input_Read();         // загрузим значения входов в глобальную переменную SWKEYS

        VCMASK = SWKEYS ^ SLATCH;                     // скинем счетчики для установившихся и неактивных значений
        VCBIT0 &= VCMASK;
        VCBIT1 &= VCMASK;
                                                      //  Каждая '1' в SLATCH представляет установившееся значение
        SLATCH ^= (VCTEMP = VCMASK & VCBIT0 & VCBIT1);

        if( VCTEMP )                                  // есть изменения входов, взведем флаги
        {
           LATCH_ON  |= VCTEMP &  SWKEYS;             // взведем биты нажатых кнопок и сработавших входов.
           LATCH_OFF |= VCTEMP & ~SWKEYS;             // Биты сбрасываются в обработчиках.
        }
        VCBIT1 ^= (VCMASK & VCBIT0);                   // инкрементируем счетчик.
        VCBIT0 ^= (VCMASK);
    }

    void Input_Process(void)                              // сюда в будущем запихаем, что делать по срабатыванию входов. Сейчас просто включим или выключим соответствущее реле
    {
       uint8_t j,i;

       if (LATCH_ON != 0 || LATCH_OFF != 0  )          // Если есть изменения на входах - обработаем. Нет - выходим.
       {
            for (i=1,j=0; j < DIG_INPUT_QTY; j++,i=i<<1)   // пробежимся по всем входам
            {
              if (LATCH_ON & i)                          // Если копка была нажата или вход перешел в ON
              {
                Serial.print(F("  Input "));
                Serial.print(j);
                Serial.println(F(" = ON"));
             
                change_relay_state(j, 1);
             
                InputState |= i;
                LATCH_ON &= ~i;                          // Обязательно сбросим бит, так как данное событие обработано
              }
              if (LATCH_OFF & i)                          // Если копка была отпущена или вход перешел в OFF  ЕСЛИ НУЖНО.
              {
                Serial.print(F("  Input "));
                Serial.print(j);
                Serial.println(F(" = OFF"));

                change_relay_state(j, 0);
             
                InputState &= ~i;          
                LATCH_OFF &= ~i;                        // Обязательно сбросим бит, так как данное событие обработано
              }
            }
       }
    }

    void setup()
    {
      // Set console baud rate

      Serial.begin(115200);

      init_io();

      Timer1.initialize(1000L*(long)TICKER_PERIOD);
      Timer1.attachInterrupt(Timer_Func);
    }

    void loop()
    {
        Input_Process();
    /*  Оставил блок чтобы было понятно как работают таймеры
        if ( Temperature_Chk_Counter == 0 )             // пора зачитать температуру
        {
            Temperature_Chk_Counter = TEMP_CHK_TO;      // Перезапустим таймер периодичности проверки температуры
            MY_DBG(F("Start Temp Conersation"));        // Debug
            DS_Start_Conversion();                      // запустим преобразование температуры
            start_conv_flag=1;                          // flag запуска преобразования
            Temperature_Conv_Counter = TEMP_CONV_TO;    // запустим таймер на время преобразования
        }
    */

    }
     
     
    Daniil и Airbus нравится это.
  4. SergeiL

    SergeiL Оракул Модератор

    Как то так...
     
  5. Рокки1945

    Рокки1945 Гуру

    Прочитал до конца, пока не осмыслил до конца
     
  6. SergeiL

    SergeiL Оракул Модератор

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

    Для микроконтроллеров - по скорости, оптимальности, компактности, можно сказать и красоте - the best!
    Главное все входы обрабатываются за один короткий проход.

    UPD: В принципе можно использовать как черный ящик, скармливать значения на вход с портов ввода, и обрабатывать изменения на флагах. И не вникать, как оно там внутри работает. ;)

    Думаю ТС c темой "Срабатывание "кнопок" это помогло бы решить проблему.
    Такие варианты - решает:
    [​IMG]

    А простые debounce - чаще нет.
     
    Последнее редактирование: 3 июл 2021
  7. Рокки1945

    Рокки1945 Гуру

    а если например по простому - упало в нуль - в цикл и там в течении 20 млс - 20 опросов состояния кнопки - и там же счётчик - всё если счётчик равен 20 - то и значит кнопка точно нажата
    до кучи в этот же цикл засунуть проверку на + питания, если в течении 20 млс не было плюса и счётчик состояния кнопки равен 20 - значит точно нажатие кнопки - фиксируем его и обрабатываем
     
    Последнее редактирование: 3 июл 2021
  8. SergeiL

    SergeiL Оракул Модератор

    А вот это говорит, что не понял суть темы.
    То, что я описал работает без блокировки (без циклов), параллельно для 8 каналов если с uint8_t, и супер быстро. Быстрее уже некуда.
    Причем алгоритм правильный в плане пропуска помех и выявления установившегося состояния, а не компромисный.
    А в понятии простоты, так он проще в работе. А цикл проще только в понимании.
     
  9. parovoZZ

    parovoZZ Гуру

    млс - это что?

    А все эти 20 млс или чего там МК делать ничего не будет? А если он что-то выводит на дисплей, общается с кем-то снаружи, считывает данные с ИК приёмника и прочее - эти все подождут, да? У нас же, мать её, кнопка!
     
    Ariadna-on-Line нравится это.
  10. Рокки1945

    Рокки1945 Гуру

    А прерывания по входу а маску на порт?
     
  11. KindMan

    KindMan Гуру

    Вот Сергей привёл пример, как он использует, может и вы небольшой оформите, если думаете, что будет проще, эффективней? А то на словах не всё понятно.
     
    Рокки1945 нравится это.
  12. akl

    akl Гуру

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

    Код (C++):
    typedef struct _BUTTON {
      unsigned char pin;
      unsigned long debtime;
      unsigned long holdtime;
      unsigned char state;
      unsigned char flag;
      unsigned long timer;
      unsigned char r_push;
      unsigned char r_fast;
      unsigned char r_hold;
    } BUTTON;

    void readButtonPush(BUTTON *b, unsigned long* curr_time, unsigned char repeat) {
      b->state = !digitalRead(b->pin);
      b->r_push = 0;
      if (b->state && !b->flag) {
        b->flag = 1;
        b->timer = *curr_time;
        b->r_push = 1;
      }
      if (b->flag && *curr_time - b->timer > b->debtime) {
        b->timer = *curr_time;
        if (!b->state) {
          b->flag = 0;
        }
        else if (repeat) {
          b->r_push = 1;
        }
      }
    }

    void readButtonHold(BUTTON* b, unsigned long* curr_time) {
        b->state = !digitalRead(b->pin);
        b->r_hold = 0;
        b->r_fast = 0;
        b->r_push = 0;
        if (b->state && !b->flag) {
            b->flag = 1;
            b->timer = *curr_time;
            b->r_push=1;
        }
        if (!b->state && b->flag) {
            b->flag = 0;
            if (*curr_time - b->timer > b->debtime && *curr_time - b->timer < b->holdtime) {
                b->r_fast = 1;
            }
        }
        if (b->state && b->flag == 1 && *curr_time - b->timer > b->holdtime) {
            b->flag = 2;
            b->r_hold = 1;
        }
    }
    вот так их можно тестировать даже в виндосе
    Код (C++):
    #include <stdio.h>
    #include <stdlib.h>
    #include <windows.h>

    unsigned long millis(void){
        return GetTickCount();
    }
    unsigned char digitalRead(int key){
        if(GetKeyState(key)&0b10000000){return 0;}
        else{return 1;}
    }

    #define BUTT_1 '1'
    #define BUTT_2 '2'
    #define BUTT_3 '3'

    #define DEBTIME 50
    #define HOLDTIME 1000
    typedef struct _BUTTON {
      unsigned char pin;
      unsigned long debtime;
      unsigned long holdtime;
      unsigned char state;
      unsigned char flag;
      unsigned long timer;
      unsigned char r_push;
      unsigned char r_fast;
      unsigned char r_hold;
    } BUTTON;
    BUTTON bt1 = {BUTT_1, DEBTIME, HOLDTIME, 0};
    BUTTON bt2 = {BUTT_2, DEBTIME, HOLDTIME, 0};
    BUTTON bt3 = {BUTT_3, 100, HOLDTIME, 0};

    void readButtonPush(BUTTON *b, unsigned long* curr_time, unsigned char repeat) {
      b->state = !digitalRead(b->pin);
      b->r_push = 0;
      if (b->state && !b->flag) {
        b->flag = 1;
        b->timer = *curr_time;
        b->r_push = 1;
      }
      if (b->flag && *curr_time - b->timer > b->debtime) {
        b->timer = *curr_time;
        if (!b->state) {
          b->flag = 0;
        }
        else if (repeat) {
          b->r_push = 1;
        }
      }
    }
    void readButtonHold(BUTTON* b, unsigned long* curr_time) {
        b->state = !digitalRead(b->pin);
        b->r_hold = 0;
        b->r_fast = 0;
        b->r_push = 0;
        if (b->state && !b->flag) {
            b->flag = 1;
            b->timer = *curr_time;
            b->r_push=1;
        }
        if (!b->state && b->flag) {
            b->flag = 0;
            if (*curr_time - b->timer > b->debtime && *curr_time - b->timer < b->holdtime) {
                b->r_fast = 1;
            }
        }
        if (b->state && b->flag == 1 && *curr_time - b->timer > b->holdtime) {
            b->flag = 2;
            b->r_hold = 1;
        }
    }

    unsigned long mil;


    int main()
    {

    while(1){
        mil=millis();

        readButtonHold(&bt1,&mil);
        if(bt1.r_push){
            printf("%c%s\n",BUTT_1," push");
        }
        if(bt1.r_fast){
            printf("%c%s\n",BUTT_1," fast");
        }
        if(bt1.r_hold){
            printf("%c%s\n",BUTT_1," hold");
        }

        readButtonPush(&bt2,&mil,0);
        if(bt2.r_push){
            printf("%c%s\n",BUTT_2," push");
        }

        readButtonPush(&bt3,&mil,1);
        if(bt3.r_push){
            printf("%c%s\n",BUTT_3," push");
        }
    }
    return 0;
    }
    но они от ложных импульсов не защищают. по крайней мере в плане реакции на нажатие (на отпускание антидребезг все же помогает)

    подправил readButtonPush чтобы защищало от дребезга лучше.
    но это все таки офтоп, т.к. от шума (ложных сигналов) оно не защищает в принципе
     
    Последнее редактирование: 4 июл 2021
    SergeiL нравится это.
  13. Рокки1945

    Рокки1945 Гуру

    Код (C++):
    Приветствую!
    Задача: необходимо определить был мип или нажатие кнопки
    Моё решение с кондачка ну пусть оно имеет место быть:
    Допустим надо обслужить 3 кнопки на тиньке и защитить их от дребезжания (больше и не надо всего 6 рабочих) - 3 выхода ещё.
    ИНИЦИАЛИЗАЦИЯ:
    Подтяжка этих кнопок:
    DDRB &= ~(1<<0);
    PORTB |= 1<<0;
    DDRB &= ~(1<<1);
    PORTB |= 1<<1;
    DDRB &= ~(1<<2);
    PORTB |= 1<<2;
    Прерывания по изменению состояния на порте и их маска:
    sei();
    PCMSK = 0x7;
    GIMSK |= (1 << PCIE);
    Дальше пустой цикл, в нём тут и нет необходимости:
    while (1)
    {};
    ОБРАБОТЧИК ПРЕРЫВАНИЯ ПО ВХОДУ ПОРТА В:
    Запрет на эти самые прерывания:
    GIMSK &= ~(1 << PCIE);
    Настройка таймера 0 на 1 мс - будем заходить ровно 20 раз каждую милисекунду:
    TCCR0B &= 0xf8;
    TCCR0B |= 3;
    OCR0A=150-1;
    sei();
    TIMSK0 |= (1 << OCIE0A);
    TCCR0A &= 0x3F;
    TCCR0A |= 0x02;
    ОБРАБОТЧИК ПРЕРЫВАНИЯ ПО СРАВНЕНИЮ ТАЙМЕРА 0:
    счётчик этих самых прерываний - его количества :
    count = count + 1
    если счётчик меньше 21:
    if (COUNT < 21)
    { //опрос состояния входов и запись в переменные
    DDRB = DDRB & 0xFE;
    B0 = ((PINB & 0x01) == 0x01);
    DDRB = DDRB & 0xFD;
    B1 = ((PINB & 0x02) == 0x02);
    DDRB = DDRB & 0xFB;
    B2 = ((PINB & 0x04) == 0x04);
    //если нажата то инкремент переменной:
    if (B0 == 0)
    {INPUT_B0 = INPUT_B0 + 1;}
    if (B1 == 0)
    {INPUT_B1 = INPUT_B1 + 1;}
    if (B2 == 0)
    {INPUT_B2 = INPUT_B2 + 1;}}
    //как только счётчик больше 20 считываем значение переменных и меняем состояние соответствующих выходов
    if (INPUT_B0 >= 20)
    {OUT_1 = !OUT_1;
    DDRB = DDRB | 0x08;
    if ((OUT_1))
        PORTB = (PORTB & 0xF7) | 0x08;
    else
        PORTB = PORTB & 0xF7;}
    if (INPUT_B1 >= 20)
    {OUT_2 = !OUT_2;
    DDRB = DDRB | 0x10;
    if ((OUT_2))
        PORTB = (PORTB & 0xEF) | 0x10;
    else
        PORTB = PORTB & 0xEF;}
    //выход на ресет - ну это же не принципиально
    if (INPUT_B2 >= 20)
    {OUT_3 = !OUT_3;
    DDRB = DDRB | 0x20;
    if ((OUT_3))
        PORTB = (PORTB & 0xDF) | 0x20;
    else
        PORTB = PORTB & 0xDF;}
    //обнуление переменных
    INPUT_B0 = 0;
    INPUT_B1 = 0;
    INPUT_B2 = 0;
    COUNT = 0;
    //Прерывание: Запрещено TMR0COMPA
    TIMSK0 &= ~(1 << OCIE0A);
    //Прерывание: Разрешено PORTB
    sei();
    PCMSK = 0x7;
    GIMSK |= (1 << PCIE);
    Вот и всё!
     
    Последнее редактирование: 4 июл 2021
  14. Рокки1945

    Рокки1945 Гуру

    совсем запамятовал надо ли тут делать сброс накопившихся прерываний в регистре - так что-ли - CIFR |= (1<<INTF0);
     
  15. SergeiL

    SergeiL Оракул Модератор

    @Рокки1945 Ну вроде же не первый день, на форуме, а код вставить правильно?
    А по поводу алгоритма, что будет, если сигнал как на картинке в #6 ?
     
    Рокки1945 нравится это.
  16. SergeiL

    SergeiL Оракул Модератор

    Пока детально не проанализировал, а для примера под Win дребезг и не важен, его клавиатура уберет.
     
  17. akl

    akl Гуру

    в виндосе тоже можно симулировать дребезг - для этого можно например установить большое время антидребезга и дико долбить по кнопке
     
  18. Unixon

    Unixon Оракул Модератор

    При такой реализации у вас слишком большая вероятность пропустить некорректное состояние, всего 5 измерений делается.
    Идея совершенно верная, но лучше делать считывание чаще, просто в начале каждой итерации в главном цикле.
    Чтобы считывать быстрее, нужно избавиться от всех этих digitalRead(), подключить все кнопки к одному порту и читать через регистр.
    Если кнопок много - сгруппировать по 8шт по портам, их так собирать куда быстрее будет, чем по одной.
    На AVR вместо опроса портов можно использовать PCINT.
     
    SergeiL нравится это.
  19. Рокки1945

    Рокки1945 Гуру

    Извиняюсь.
    if (B0 == 0)
    {INPUT_B0 = INPUT_B0 + 1;}
    else:
    NPUT_B0 = INPUT_B0 - 1;
    ;)
     
    Последнее редактирование: 4 июл 2021
  20. SergeiL

    SergeiL Оракул Модератор

    Я так в основном и делаю. Об этом и писал в первом сообщении.
    Я использую данную функцию с AVR более 10лет, а с Ардуино, на практике, столкнулся только в 2015-16г. Когда появилась ESP8266.
    Просто на форуме в основном Ардуинщики, которые используют именно функции Ардуино, да и на Ардуино часть пинов портов изначально задействованы под альтернативные функции. Поэтому и предложил вариант адаптированный под Ардуино.

    Тут все от задачи зависит. Время удержания кнопки всегда значительно больше, тут ничего не пропустим. Наоборот уменьшение частоты опроса повысит вероятность снижения помех.
    Если проводные датчики, то мы тоже знаем их время удержания цепи в замкнутом состоянии. На него и настраиваемся.
    Я добавлял разрядность вертикального счетчика, при этом увеличивая частоту опроса.
    По сути, время принятия решения, после установившегося состояния, оставалось почти тем же. На кнопках и проводных датчиках типа: герконы, PIR, водяные счетчики, контактные датчики - я разницы не увидел.
    Поэтому и рекомендовал данный вариант. Но как сказал выше, тут все зависит от окружения.