Отлавливаем импульсы с дебаунсером и передачей в интернет. Как?

Тема в разделе "Arduino & Shields", создана пользователем kvv213, 20 авг 2016.

Метки:
  1. kvv213

    kvv213 Гик

    Друзья,

    Доделываю один проект и дополировываю последние шероховатости. Вкратце, плата на Arduino отлавливает импульсы с "кнопки" и отправляет их в интернет на сервер.

    Импульсы с кнопки ловятся через Bounce2 (продолжение популярного дебаунсера). Все работает шикарно, импульсы ловятся, помехи нет. Но есть проблема с отловлей импульсов нажатия при передаче наловленного в сеть. Сам процесс передачи (используется Ethernet шилд) занимает секунды полторы две. Чего вполне достаточно для пропуска целого импульса, а то и двух.

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

    Подумалось, что можно обойтись малой кровью и задействовать прерывание с пина кнопки, а в прерывании вызвать обработчик Bounce2.Update. Но не тут-то было. Просто так не работает. Прерывание срабатывает, но импульс не ловится. Сдается мне, что проблемы может быть две:
    1. Не Volatile переменные в Bounce2.
    2. Неработающие функции millis в обработчике прерывания (а все известные мне дебаунсеры работают как раз на этих функциях).

    Собственно теперь думу крепку думаю, как проще поступить... В теории, можно объявить объект Bounce как volatile, дабы не ковырять все переменные внутри самой библиотеки и собственно не создавать новую библиотеку. Либо вообще использовать не библиотеку, а например использовать код Калинина отсюда http://mypractic.ru/urok-8-cifrovaya-filtraciya-signalov-v-programmax-dlya-arduino.html и внедрить его прямо в тело программы позаботившись о volatile....

    Буду рад свежим мыслям.

    PS. Еще полагаю, что во избежание ложных срабатываний, в код обработчика хорошо бы вставлять запрет на обработку прерывания в самом начале и разрешение в самом конце. Но практического подтверждения своим догадкам пока не нашел.
     
  2. Unixon

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

    Если невозможна многозадачность на прерываниях, разносите задачи по разными контроллерам.
     
    ostrov нравится это.
  3. qwone

    qwone Гик

    Говоря о кнопках забывают маленькую вещь. Это медленые устройства по отношению к процессору. Ну не может человек , сидящий на кнопке , стучать на ней со скоростью процессора. Опять же даже короткий импульс на кнопке (нажатие- отжатие) это ну очень большое время (ну очень много операций может сделать процессор) для процессора. Вот и получается, что "быстрый" процессор работая с "медленной" кнопкой отлавливает никому не нужный дребезг, кторый и устраняют .
    Говоря в итоге к сказаному. Если есть какой-то механизм, который опрашивает кнопку с частотой 0,2 сек, то мы всегда можем определить была ли кнопка нажата в это время или нет , был переход от ненажатая кнопка в нажатое состояние.
    Опять же кнопки инструмент , который позволяет человеку отдавать команды процесс И человек это тоже медленное устройство по отношению к процессору.
    Если вам нужна многозадачность и не хочется дергать прерывание воспользуйтесь этим
    Код (C++):
    uint8_t non_stop_program1(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }
    uint8_t non_stop_program2(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }
    uint8_t non_stop_program3(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }
    void setup() {
      Serial.begin(9600);
    }
    void loop() {
      if (non_stop_program1(500))  {
    // Здесь вы организовываете вывод на экран. Вызывается раз в 0.5 сек
        Serial.println("knok");
    }
      if (non_stop_program2(200))  {
    // Здесь вы организовываете ввод с клавиатуры. Вызывается раз в 0.2 сек
      }
      if (non_stop_program3(100))  {
    // Здесь сама работа устройства не зависимая от вашего ввода и показа на экран. Вызывается раз в 0.1 сек(может быть по желанию другим)
       }
    }
     
     
  4. kvv213

    kvv213 Гик

    Вариант, но слишком уж сложно :) Должно же быть какое-то красивое решение.
     
  5. kvv213

    kvv213 Гик

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

    Я вот думаю, а может быть в обработчике прерывания флаг какой поднять, мол был импульс. Конечно есть риск того, что кнопка нажата, началась отправка в интернет, дебаунсер фильтрует дребезг, а флаг поднимается. Но такой риск заметно меньше, чем не поймать импульс в середине отправки. Кстати этот же вариант можно сделать еще красивее - обработчик прерывания включать только в момент отправки в Интернет, а в остальное время пускай обычным способом работает.
     
  6. qwone

    qwone Гик

    Если и есть красивое решение, то скорее всего вы его не оцените. Красота это вещь сугубо индивидуальная. На вкус и цвет карандаши разные.
     
  7. qwone

    qwone Гик

    Ну да поднять флаг и тогда другой вычислительный поток будет долбить интернет.
    Вот посмотрите структуру программы
    Код (C++):
    /*  Схема подключения
    I2C LCD1602          GND -> GND
                          +5V -> +5V
                          SDA -> A4(SDA)
                          SCL -> A5(SCL)

    *  Ардуино энкодер   CLK -> A2   CLK_Pin   0 нажата 1 нет
                          DT  -> A1   DT_Pin    0 нажата 1 нет
                          SW  -> A0   SW_Pin    0 нажата 1 нет
                          GND -> GND
                          +5V -> +5V                    
    */

    // ============= Переменные  ====================================
    // системные переменные
    uint8_t mode_viev                 ; // 0 режим показа экранов / 1 режим редактирования переменной на экране
    uint8_t stat_blink_mode_viev      ; // мигалка mode_viev
    uint8_t screen_number             ; // номер текущего демонстрируемого окна
    const int number_of_screen = 3    ; // количество окон в системе
    // технологические переменные


    // ============= Подключение LCD1602 по I2C =====================
    #include <Wire.h>
    #include <LiquidCrystal_I2C.h>

    LiquidCrystal_I2C lcd(0x27,16,2);  // Устанавливаем дисплей

    void LCD_ini(){            //  инициализация дисплея
      mode_viev = 0          ; // установить режим редактирования
      stat_blink_mode_viev=0 ;
      screen_number  = 1     ; // показать экран 0
      lcd.init()             ;                  
      lcd.backlight()        ; // Включаем подсветку дисплея
    }

    // --- Экран 1 ---
    void Screen1_viev()   {    // показать  экран  1
      lcd.print("***") ;
    }
    // --- Экран 2 ---
    void Screen2_viev()   {    // показать  экран  2
      lcd.print("***") ;
    }
    // --- Экран 3 ---
    void Screen3_viev()   {    // показать  экран  3
      lcd.print("***") ;
    }
    // -- Панель --
    void  Panel_Viev()     {     // показать на экране режим и номер окна
      lcd.setCursor(15, 0) ;
      if (stat_blink_mode_viev&&mode_viev) lcd.print("E");   // показ значка обычный режим
                                      else lcd.print(" ");   // показ значка  режим редактирования
      lcd.setCursor(15, 1) ;
      lcd.print(screen_number)  ;
    }
    // -- Общий вывод на дисплей--
    void Viev(){
      lcd.clear();
      switch (screen_number) {
          case 1:Screen1_viev();break;  // показать экран устройствa 1
          case 2:Screen2_viev();break;  // показать экран устройствa 2
          case 3:Screen3_viev();break;  // показать экран устройствa 3
      }
      stat_blink_mode_viev = !stat_blink_mode_viev ;
      Panel_Viev();
    }
    // ========== обработчик энкодера ===========================
    const int CLK_Pin=A2  ;            // нога CLK кнопки 0 нажата 1 нет
    const int DT_Pin=A1   ;            // нога DT  кнопки 0 нажата 1 нет
    const int SW_Pin=A0   ;            // нога кнопки 0 нажата 1 нет

    uint8_t statCLK,statCLK_old ;      // переменные: состояние ноги CLK сейчас и раньше
    uint8_t SW ,SW_old  ;      // переменные: состояние ноги SW сейчас и раньше

    void Encoder_ini()         {       //  инициализация энкодера и кпопки в нем
      pinMode(SW_Pin, INPUT_PULLUP)  ; // вывод на ввод с подтягивающим резистром
      pinMode(CLK_Pin, INPUT);
      pinMode(DT_Pin, INPUT);
      statCLK_old = digitalRead(CLK_Pin);
      SW = digitalRead(SW_Pin);
    }
    uint8_t Encoder(){
      statCLK = digitalRead(CLK_Pin);
      if ((statCLK == 0)&&(statCLK_old ==1 )){ //  счет по фронту
         statCLK_old=statCLK;
         return (digitalRead(DT_Pin)+1); // 1 по часовой 2 против часовой
        };
      statCLK_old=statCLK;
      return 0; // изменений не произошло
    }
    uint8_t SW_botton() {
      SW_old = SW;
      SW = digitalRead(SW_Pin);
      if (!SW &&SW_old) return 1;  // срабатывание по нажатию кнопки
      else return 0;
      }
    // ==== ядро системы ======
    uint8_t non_stop_program1(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }
    uint8_t non_stop_program2(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }
    uint8_t non_stop_program3(uint16_t span) {
      static uint32_t future = 0;
      if (millis()<future) return 0;
      future += span;
      return 1;
    }
    // ==== главная часть =====
    void setup() {
       LCD_ini();          //  инициализация дисплея
       Encoder_ini();      //  инициализация энкодера и кпопки в нем
    }
    void loop() {
      if (non_stop_program1(500))  { // обновление раз в 0.5 сек информации на дисплее
        Viev();
    }
      if (non_stop_program2(200))  { //  Ввод с клавиатуры,анализ и выработка действия. Вызывается раз в 0.2 сек
          if (SW_botton()) {              // если произошло нажатие клавиши    
            // вот сюда и ставьте свою отправку в интернет
          }
      }
      if (non_stop_program3(100))  {
    // Здесь сама работа устройства не зависимая от вашего ввода и показа на экран. Вызывается раз в 0.1 сек(может быть по желанию другим)
       }
    }
     
     
  8. kvv213

    kvv213 Гик

    В общем решил сделать примерно следующим образом:
    Код (C++):
    volatile boolean interruptImpulse = false; // Catches impulse when Sketch is busy with Internet publishing

    void inter() {
      interruptImpulse = true;
    }

    void loop() {
      deBouncer.update(); // check for the impulse
      if (deBouncer.rose()) {
        addGASImpulse("");
      }
      delay(10);
      publisher(); // Update ThingSpeak
    }

    void publisher() { // uploads data to ThingSpeak
        attachInterrupt(digitalPinToInterrupt(GASMeterPin), inter, FALLING); //use interrupt in order not to oversee an impulse
    // тут какой-то код прет со страшной силой
          detachInterrupt(digitalPinToInterrupt(GASMeterPin)); // stop to use interrupts    
          if (interruptImpulse) { // Try to catch GAS meter impulse via interrupt
            interruptImpulse = false;
            addGASImpulse(F("(i)"));
    }
    }

    void addGASImpulse(String sVal){
        garageGASMeter++;
        Serial.print(F("Pressed "));
        Serial.print(sVal);
    }
    Проверил - ловит импульсы как от нажатия кнопки во время отправки в интернеты, так и вне ее. Конечно, это некий костыль, но вроде бы все остальные функции у меня выполняются более-менее быстро. Исключением можно назвать обработка запроса встроенным веб-сервером, там есть Delay(1) после чтения каждого из символов обращения к веб-серверу, но операция сия настолько редкая, что потребности ее обходить как-то нету совсем.