Due + 5 кнопок. Контроль нажатия кнопок и обработка событий.

5N62V
Offline
Зарегистрирован: 25.02.2016

Всем бобра! :)

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

Ну я и рискнул подумать, что не зря в прошлом году книгу по С++ скурил, надо как-то отбивать инвестиции. Решил написать свое. Критерии просты: регистрировать короткое нажатие, длинное нажатие, обрабатывать их, и по сути - все на этом. Обработчики нажатия, поскольку проект громоздкий, должны быть отдельными файлами, но это уже потом. Пока написал костяк. Вроде работает.

Хедер файл:



#define KEY_UP 51
#define KEY_DOWN 47
#define KEY_LEFT 44
#define KEY_RIGHT 45
#define KEY_OK 46


class Button {
  private:
    uint32_t timer;
    uint8_t pin;
    uint8_t shortPressTime;
    uint16_t longPressTime;

  public:
    bool shortPress;
    bool longPress;
    void (* handler)();

    Button(uint8_t p,  void (* h)() = NULL, uint16_t l = 2000, uint8_t s = 60) : pin(p), handler(h), longPressTime(l), shortPressTime(s)
    {
      shortPress = false; longPress = false;
    }

    bool isPress() {// читаем состояние пина, работает ровно в 10 раз быстрее digitalRead
      return !(REG_PIOC_PDSR & (1 << pin));
    }

    bool isPressed() {// была ли нажата кнопка, если да - то или коротким или длинным нажатием
      bool state = isPress();// прочли состояние пина
      if (!state and timer == 0)return false;//если не нажато - то не нажато :)
      else if (state && timer == 0) {//если обнаружили нажатие - запускаем таймер, но не спешим рефлексировать
        timer = millis();
        return false;
      }
      else if (!state && timer != 0) {//если обнаружили отпускание, то анализируем что это было
        uint32_t time = millis() - timer;
        timer = 0;
        if (time > longPressTime) {//длинное нажатие
          longPress = true; return true;
        }
        else if (time > shortPressTime) {//короткое нажатие
          shortPress = true; return true;
        }
      }
      return false;//какая-то фигня
    }

    void process(){// запуск обработчика 
      (* handler)();
    }
};

ну и пример применения.  Прототипы обработчиков и объявление объектов перенесу в хедер, сами обработчики в силу большой зависимости от контекта применения - в отдельные файлы. В лупе будет одно обращение к коротенькой фу-ции, и все.

#include "myBotton.h"

void keyUPhandler();
void keyDOWNhandler();
void keyLEFThandler();
void keyRIGHThandler();
void keyOKhandler();

Button* btn[5] = {
  new Button (12, &keyUPhandler),//up
  new Button (16, &keyDOWNhandler),//down
  new Button (19, &keyLEFThandler),//left
  new Button (18, &keyRIGHThandler),//right
  new Button (17, &keyOKhandler)//ok
};

void setup() {
  SerialUSB.begin(115200);
  pinMode(KEY_UP, INPUT_PULLUP);
  pinMode(KEY_DOWN, INPUT_PULLUP);
  pinMode(KEY_LEFT, INPUT_PULLUP);
  pinMode(KEY_RIGHT, INPUT_PULLUP);
  pinMode(KEY_OK, INPUT_PULLUP);
  delay(2000);
}

void loop() {
  checkButtons();//проверяем кнопки. Краткость - это то что надо
}

void checkButtons() {// сканируем была ли нажата любая кнопка. Если да - обрабатываем
  for (uint8_t i = 0; i < 5; i++) {
    if (btn[i]->isPressed())btn[i]->process();
  }
}

void keyUPhandler() {
  if (btn[0]->shortPress) {
    btn[0]->shortPress = false;//сбрасываем флаг
    SerialUSB.println("UP short");//тут код обработчика короткого нажатия
  }
  if (btn[0]->longPress) {
    btn[0]->longPress = false;.. сбрасываем флаг
    SerialUSB.println("UP long");//тут код обработчика длинного нажатия

  }
}

void keyDOWNhandler() {
  if (btn[1]->shortPress) {
    btn[1]->shortPress = false;
    SerialUSB.println("DOWN short");
  }
  if (btn[1]->longPress) {
    btn[1]->longPress = false;
    SerialUSB.println("DOWN long");
  }
}

void keyLEFThandler() {
  if (btn[2]->shortPress) {
    btn[2]->shortPress = false;
    SerialUSB.println("LEFT short");
  }
  if (btn[2]->longPress) {
    btn[2]->longPress = false;
    SerialUSB.println("LEFT long");
  }
}

void keyRIGHThandler() {
  if (btn[3]->shortPress) {
    btn[3]->shortPress = false;
    SerialUSB.println("RIGHT short");
  }
  if (btn[3]->longPress) {
    btn[3]->longPress = false;
    SerialUSB.println("RIGHT long");
  }
}

void keyOKhandler() {
  if (btn[4]->shortPress) {
    btn[4]->shortPress = false;
    SerialUSB.println("OK short");
  }
  if (btn[4]->longPress) {
    btn[4]->longPress = false;
    SerialUSB.println("OK long");
  }
}

Хочу трезвой критики. Глум приветствуется! :) Глум с дружеским пинком в сторону как правильно делать - втройне! :) Я недавно в ООП, и нет ничего удивительного, что пугаюсь собственной тени. 

ПС. #include "myBotton.h" - дада, уже знаю :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Только честно, правда нужна критика или, как это часто бывает, только похвала? А то здесь на форуме не раз и не два, возьмёшься добросовестно человеку писать, потратишь час, обращая внимание на каждую мелочь и объясняя её, в результате получишь: "мудак и зануда, лишь бы прикопаться к чему-нибудь". Правда, было не раз и устал уже от такого.

Rumata
Offline
Зарегистрирован: 29.03.2019

5N62V пишет:

ПС. #include "myBotton.h" - дада, уже знаю :)

Да, правильно - "myBottom.h" :)

 

5N62V
Offline
Зарегистрирован: 25.02.2016

ЕвгенийП пишет:

Только честно, правда нужна критика или, как это часто бывает, только похвала? А то здесь на форуме не раз и не два, возьмёшься добросовестно человеку писать, потратишь час, обращая внимание на каждую мелочь и объясняя её, в результате получишь: "мудак и зануда, лишь бы прикопаться к чему-нибудь". Правда, было не раз и устал уже от такого.

Евгений, с удовольствием приму все Ваши замечания, постараюсь осознать и в будущем применять!:) Придирайтесь на здоровье! :)

nik182
Offline
Зарегистрирован: 04.05.2015

Эх. Кнопочки. Если уж заморачиваться на что то особенное - добавить в таймер 0 прерывание на comprA или В, не трогая остальные настройки, и в нем анализировать нажатие кнопок, глушить дребезг и выдавать длинные, короткие нажатия. На этом можно сделать короткий и совершенно не зависающий кнопочный сервер с расходом 3 строки на кнопку.   

5N62V
Offline
Зарегистрирован: 25.02.2016

nik182 пишет:

Эх. Кнопочки. Если уж заморачиваться на что то особенное - добавить в таймер 0 прерывание  

Признаюсь, я думал над этим.   Я не до конца, как это часто бывает, описал свою задачу. Забыл-с некоторые детали. :) Одновременное нажатие кнопок тоже должно обрабатываться (заход в подрежимы), и получается надо разные таймеры запускать. А их может быть меньше чем кнопок. 

 

5N62V
Offline
Зарегистрирован: 25.02.2016

Rumata пишет:

Да, правильно - "myBottom.h" :)

И от истины не далеко! :)

nik182
Offline
Зарегистрирован: 04.05.2015

И зачем разные таймеры? Вот для наны. Кнопки на D2 D3 на землю. Из двух кнопок короткое, длинное нажатие и пулемёт коротких нажатий 200 мс при удержании. Всего 6 комбинаций. Две кнопки и совместное нажатие. Работают все миллисы. При не нажатых кнопках отъедает около 2 микросекунд каждую миллисекунду и чуть больше каждую десятую. Работает и в делее. В его елдe можно получать нажатие кнопки без обработки. Защита от дребезга присутствует. Не зависит от загруженности лупа.

unsigned long blinkt, previousMillis; // период моргания и время последнего обновления состояния светодиода
volatile bool KeyPressed = false; // Было ли нажатие кнопок
volatile byte np, key, k1cnt, k2cnt, k3cnt;

// Прерывание вызывается один раз в миллисекунду
SIGNAL(TIMER0_COMPA_vect)
{
  np++;
  if (np > 9)
  {
    np = 0; // Проверяем кнопки каждые 10 мс.
    if (k1cnt > 5) { //Если кнопка нажата больше 50 мс
      if ((PIND & 4) == 0) {// Если всё ещё нажата
        k1cnt++;
        if (k1cnt > 120) { // Если нажата больше 1200 мс выдаем короткое нажатие и пока нажата выдаем пулемётом короткие нажатия через 200мс
          k1cnt -= 20;
          key = 1; KeyPressed = true;
        };
      }
      else {
        if ((k1cnt > 5) & (k1cnt < 120)) {
          key = 1;
          KeyPressed = true;
        }; // отпущена - короткое
        if ((k1cnt > 60) & (k1cnt < 120) ) {
          key = 0x11;
          KeyPressed = true;
        };// больше 600 мс -  длинное
        k1cnt = 0;
      };
    }
    else if ((PIND & 4) == 0) k1cnt++;

    if (k2cnt > 5) {
      if ((PIND & 8) == 0) {
        k2cnt++;
        if (k2cnt > 120) {
          k2cnt -= 20;
          key = 2; KeyPressed = true;
        };
      }
      else {
        if ((k2cnt > 5) & (k2cnt < 60)) {
          key = 2;
          KeyPressed = true;
        };
        if ((k2cnt > 60) & (k2cnt < 120) ) {
          key = 0x12;
          KeyPressed = true;
        };
        k2cnt = 0;
      };
    }
    else if (( PIND & 8) == 0) k2cnt++;

    if (k3cnt > 5) {
      if ((PIND & 12) == 0) {
        k3cnt++;
        if (k3cnt > 120) {
          k3cnt -= 20;
          key = 3; KeyPressed = true;
        };
      }
      else {
        if ((k3cnt > 5) & (k3cnt < 60)) {
          key = 3;
          KeyPressed = true;
        };
        if ((k3cnt > 60) & (k3cnt < 120) ) {
          key = 0x13;
          KeyPressed = true;
        };
        k3cnt = 0;
      };
    }
    else if (( PIND & 12) == 0) k3cnt++;
  }
}

// Читает последнюю нажатую кнопку
inline char ReadKey(void)
{
  KeyPressed = false;
  return key;
}

// Ждём нажатие кнопки и получаем значение
char KeyP(void)
{
  while (!KeyPressed) ;
  KeyPressed = false;
  return key;
};

void setup()
{
  // Timer0  примерно посередине  вызываем "Compare A"
  OCR0A = 0xAF;
  TIMSK0 |= _BV(OCIE0A);
  pinMode(13, OUTPUT);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  blinkt = 250;
  Serial.begin(115200);
  Serial.println("Start");
}

void loop()
{
  if (KeyPressed)
  {
    Serial.println(key, HEX);
    switch (ReadKey()) {
      case 0x1 : blinkt = 500; break;
      case 0x2 : blinkt = 750; break;
      case 0x3 : blinkt = 1000; break;
      case 0x11 : blinkt = 1250; break;
      case 0x12 : blinkt = 1500; break;
      case 0x13 : blinkt = 2000;
    }

  }
  if (millis() - previousMillis >= blinkt)
  {
    previousMillis = millis();
    digitalWrite(13, !digitalRead(13));
  }
}

  

5N62V
Offline
Зарегистрирован: 25.02.2016

nik182 пишет:

И зачем разные таймеры? Вот для наны. Кнопки на D2 D3 на землю. Из двух кнопок короткое, длинное нажатие и пулемёт коротких нажатий 200 мс при удержании. Всего 6 комбинаций. Две кнопки и совместное нажатие. Работают все миллисы. При не нажатых кнопках отъедает около 2 микросекунд каждую миллисекунду и чуть больше каждую десятую. Работает и в делее. В его елдe можно получать нажатие кнопки без обработки. Защита от дребезга присутствует. Не зависит от загруженности лупа.

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

...еще подумал. Подход нравится все больше :) стоит  переписать его под Due.

nik182
Offline
Зарегистрирован: 04.05.2015

А миллис не пугает? Это дополнительное прерывание того же таймера, который обслуживает миллис. Миллис тоже дёргает все процессы раз в миллисекунду. С таким подходом прерываний в 2 раза больше. 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Хорошо, давайте попробуем. У меня нет DUE, поэтому я заменил строку №26 в «.h» файле на

return ! digitalRead(pin);

и исправил опечатку в строке №43 в CPP файле. В результате, у меня получились фот такие файлы и работал я с ними на UNO в IDE 1.8.12 с опциями «из коробки». В дальнейшем я обсуждаю именно их и по ним привожу номера строк.

Файл «Button.h»

#ifndef BUTTON_H
#define BUTTON_H

#define KEY_UP 51
#define KEY_DOWN 47
#define KEY_LEFT 44
#define KEY_RIGHT 45
#define KEY_OK 46


class Button {
  private:
    uint32_t timer;
    uint8_t pin;
    uint8_t shortPressTime;
    uint16_t longPressTime;

  public:
    bool shortPress;
    bool longPress;
    void (* handler)();

    Button(uint8_t p,  void (* h)() = NULL, uint16_t l = 2000, uint8_t s = 60) : pin(p), handler(h), longPressTime(l), shortPressTime(s)
    {
      shortPress = false; longPress = false;
    }

    bool isPress() {// читаем состояние пина, работает ровно в 10 раз быстрее digitalRead
      return ! digitalRead(pin);
    }

    bool isPressed() {// была ли нажата кнопка, если да - то или коротким или длинным нажатием
      bool state = isPress();// прочли состояние пина
      if (!state and timer == 0)return false;//если не нажато - то не нажато :)
      else if (state && timer == 0) {//если обнаружили нажатие - запускаем таймер, но не спешим рефлексировать
        timer = millis();
        return false;
      }
      else if (!state && timer != 0) {//если обнаружили отпускание, то анализируем что это было
        uint32_t time = millis() - timer;
        timer = 0;
        if (time > longPressTime) {//длинное нажатие
          longPress = true; return true;
        }
        else if (time > shortPressTime) {//короткое нажатие
          shortPress = true; return true;
        }
      }
      return false;//какая-то фигня
    }

    void process(){// запуск обработчика 
      (* handler)();
    }
};

#endif	//	BUTTON_H

Файл «5N62V.cpp»

#include "Button.h"

void keyUPhandler();
void keyDOWNhandler();
void keyLEFThandler();
void keyRIGHThandler();
void keyOKhandler();

Button* btn[5] = {
  new Button (12, &keyUPhandler),//up
  new Button (16, &keyDOWNhandler),//down
  new Button (19, &keyLEFThandler),//left
  new Button (18, &keyRIGHThandler),//right
  new Button (17, &keyOKhandler)//ok
};

void setup() {
  Serial.begin(115200);
  pinMode(KEY_UP, INPUT_PULLUP);
  pinMode(KEY_DOWN, INPUT_PULLUP);
  pinMode(KEY_LEFT, INPUT_PULLUP);
  pinMode(KEY_RIGHT, INPUT_PULLUP);
  pinMode(KEY_OK, INPUT_PULLUP);
  delay(2000);
}

void loop() {
  checkButtons();//проверяем кнопки. Краткость - это то что надо
}

void checkButtons() {// сканируем была ли нажата любая кнопка. Если да - обрабатываем
  for (uint8_t i = 0; i < 5; i++) {
    if (btn[i]->isPressed())btn[i]->process();
  }
}

void keyUPhandler() {
  if (btn[0]->shortPress) {
    btn[0]->shortPress = false;//сбрасываем флаг
    Serial.println("UP short");//тут код обработчика короткого нажатия
  }
  if (btn[0]->longPress) {
    btn[0]->longPress = false;	// сбрасываем флаг
    Serial.println("UP long");//тут код обработчика длинного нажатия

  }
}

void keyDOWNhandler() {
  if (btn[1]->shortPress) {
    btn[1]->shortPress = false;
    Serial.println("DOWN short");
  }
  if (btn[1]->longPress) {
    btn[1]->longPress = false;
    Serial.println("DOWN long");
  }
}

void keyLEFThandler() {
  if (btn[2]->shortPress) {
    btn[2]->shortPress = false;
    Serial.println("LEFT short");
  }
  if (btn[2]->longPress) {
    btn[2]->longPress = false;
    Serial.println("LEFT long");
  }
}

void keyRIGHThandler() {
  if (btn[3]->shortPress) {
    btn[3]->shortPress = false;
    Serial.println("RIGHT short");
  }
  if (btn[3]->longPress) {
    btn[3]->longPress = false;
    Serial.println("RIGHT long");
  }
}

void keyOKhandler() {
  if (btn[4]->shortPress) {
    btn[4]->shortPress = false;
    Serial.println("OK short");
  }
  if (btn[4]->longPress) {
    btn[4]->longPress = false;
    Serial.println("OK long");
  }
}

Я почти не буду обсуждать саму работу с кнопкой, а сосредоточусь по большей части на «умении готовить» ООП.

Итак, поехали, для начала компиляция. Если Вы включите печать всех сообщений, то получите кучу предупреждений:

In file included from D:\Soft\5N62V_0\5N62V_0.ino:1:0:

sketch\Button.h: In constructor 'Button::Button(uint8_t, void (*)(), uint16_t, uint8_t)':

sketch\Button.h:21:22: warning: 'Button::handler' will be initialized after [-Wreorder]

     void (* handler)();

                      ^

sketch\Button.h:16:14: warning:   'uint16_t Button::longPressTime' [-Wreorder]

     uint16_t longPressTime;

              ^~~~~~~~~~~~~

sketch\Button.h:23:5: warning:   when initialized here [-Wreorder]

     Button(uint8_t p,  void (* h)() = NULL, uint16_t l = 2000, uint8_t s = 60) : pin(p), handler(h), longPressTime(l), shortPressTime(s)

     ^~~~~~

sketch\Button.h:16:14: warning: 'Button::longPressTime' will be initialized after [-Wreorder]

     uint16_t longPressTime;

              ^~~~~~~~~~~~~

sketch\Button.h:15:13: warning:   'uint8_t Button::shortPressTime' [-Wreorder]

     uint8_t shortPressTime;

             ^~~~~~~~~~~~~~

sketch\Button.h:23:5: warning:   when initialized here [-Wreorder]

     Button(uint8_t p,  void (* h)() = NULL, uint16_t l = 2000, uint8_t s = 60) : pin(p), handler(h), longPressTime(l), shortPressTime(s)

     ^~~~~~

Скетч использует 3380 байт (10%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 306 байт (14%) динамической памяти, оставляя 1742 байт для локальных переменных. Максимум: 2048 байт.

Дело в том, что инициализация членов класса перед выполнением конструктора должна производиться в том же порядке, в каком они описаны в классе, а Вы написали её в другом. Если переписать заголовок конструктора вот так

Button(uint8_t p, void (* h)() = NULL, uint16_t l = 2000, uint8_t s = 60) : pin(p), shortPressTime(s), longPressTime(l), handler(h)

(т.е. переставить местами инциализаторы в соответствии с порядком объявления членов), то все предупреждения уйдут.

Ну, смотрим. Память программ: 3380 байтов, память данных – 306 байтов. Но тут мы должны понимать, что первое, что делается в программе (ещё до setup) – это хапается память под пять кнопок операторами new (строки №№ 10-14). Сколько там? sizeof(Button) равен 12 + 2 служебных байте на каждый new, итого 14*5 = 70 байтов. Таким образом, к моменту вызова setup имеем, что занято 3380 байтов памяти программ и 376 байтов памяти данных. Запомним эти цифры.

Сразу же вопрос, а зачем на использовать new для создания объекта? Это было бы оправдано, если бы мы собирались этот объект потом уничтожать (с помощью delete). Но в объекте кнопки я не могу представить себе ситуации, где нужно этот объект динамически создавать и уничтожать. Не, ну, теоретически можно придумать ситуацию, когда памяти настолько не хватает, что неиспользуемые в данный момент кнопки уничтожатся, но, согласитесь, это надумано. Проще и выгоднее (и по памяти программ и по памяти данных) объявлять кнопки статически безо всяких new). Вот так будет выглядеть .cpp файл.

#include "Button.h"

void keyUPhandler();
void keyDOWNhandler();
void keyLEFThandler();
void keyRIGHThandler();
void keyOKhandler();

Button btn[5] = {
  Button (12, &keyUPhandler),//up
  Button (16, &keyDOWNhandler),//down
  Button (19, &keyLEFThandler),//left
  Button (18, &keyRIGHThandler),//right
  Button (17, &keyOKhandler)//ok
};

void setup() {
  Serial.begin(115200);
  pinMode(KEY_UP, INPUT_PULLUP);
  pinMode(KEY_DOWN, INPUT_PULLUP);
  pinMode(KEY_LEFT, INPUT_PULLUP);
  pinMode(KEY_RIGHT, INPUT_PULLUP);
  pinMode(KEY_OK, INPUT_PULLUP);
  delay(2000);
}

void loop() {
  checkButtons();//проверяем кнопки. Краткость - это то что надо
}

void checkButtons() {// сканируем была ли нажата любая кнопка. Если да - обрабатываем
  for (uint8_t i = 0; i < 5; i++) {
    if (btn[i].isPressed())btn[i].process();
  }
}

void keyUPhandler() {
  if (btn[0].shortPress) {
    btn[0].shortPress = false;//сбрасываем флаг
    Serial.println("UP short");//тут код обработчика короткого нажатия
  }
  if (btn[0].longPress) {
    btn[0].longPress = false; // сбрасываем флаг
    Serial.println("UP long");//тут код обработчика длинного нажатия

  }
}

void keyDOWNhandler() {
  if (btn[1].shortPress) {
    btn[1].shortPress = false;
    Serial.println("DOWN short");
  }
  if (btn[1].longPress) {
    btn[1].longPress = false;
    Serial.println("DOWN long");
  }
}

void keyLEFThandler() {
  if (btn[2].shortPress) {
    btn[2].shortPress = false;
    Serial.println("LEFT short");
  }
  if (btn[2].longPress) {
    btn[2].longPress = false;
    Serial.println("LEFT long");
  }
}

void keyRIGHThandler() {
  if (btn[3].shortPress) {
    btn[3].shortPress = false;
    Serial.println("RIGHT short");
  }
  if (btn[3].longPress) {
    btn[3].longPress = false;
    Serial.println("RIGHT long");
  }
}

void keyOKhandler() {
  if (btn[4].shortPress) {
    btn[4].shortPress = false;
    Serial.println("OK short");
  }
  if (btn[4].longPress) {
    btn[4].longPress = false;
    Serial.println("OK long");
  }
}

Компилируем с ним и видим, что:

память программ: 2608 (мы сэкономили 772 байта)
память данных: 346 (сэкономили 30 байтов)

Теперь смотрим на Ваш конструктор.

Button(uint8_t p, void (* h)() = NULL, uint16_t l = 2000, uint8_t s = 60) : pin(p), shortPressTime(s), longPressTime(l), handler(h)
{
	shortPress = false; longPress = false;
}

и замечаем, что в нём нет ни одного параметра (и вообще ничего), что не было бы известно ещё на этапе компиляции! В таком случае, мы должны признать, что выполнять этот конструктор во время выполнения – излишняя роскошь! Зачем? Пусть компилятор сам всё просчитает и сгенерирует уже готовый кусок памяти с нужными значениями! Так мы выиграем немного памяти программ и заодно чуть-чуть времени выполнения, поскольку всё, что делается в конструкторе, сделает за нас компилятор.

Переписываем конструктор так, чтобы он выполнялся во время компиляции, т.е. делаем его constexpr. При этом надо помнить две вещи: 1) constexpr конструктор должен инициализировать все, без исключения члены класса; и 2) в С++11, который стоит «из коробки» constexpr конструктор должен быть пустым. Если поставить опцию С++17, то этого требования не будет, но мы же «из коробки» всё делаем. Итак, заменяем Ваш конструктор на вот такой

constexpr Button(uint8_t p, void (* h)() = NULL, uint16_t l = 2000, uint8_t s = 60) : timer(0),
	pin(p), shortPressTime(s), longPressTime(l), shortPress(false), longPress(false), handler(h) {}

и снова компилируем. Получаем:

память программ: 2548 (сэкономили ещё 60 байтов, а всего уже 838 байтов)
память данных: 346 (ничего не изменилось, экономия – 30 байтов)

Смотрим дальше. Опять же теоретически можно придумать ситуацию, когда для разных кнопок нам захочется задать разные интервалы для длинного и короткого нажатий. Но это теоретически (если будем иметь отдельные кнопки для шустриков и тормознутых). В реальности эти интервалы одинаковы для всех кнопок в устройстве, разве не так?. Тогда зачем хранить эти значения для каждого экземпляра персонально? Почему не хранить один раз для всех кнопок? Для этого достаточно объявить их static и убрать инициализацию из конструктора. Делаем это. Теперь наш .h файл получился таким

#ifndef BUTTON_H
#define BUTTON_H

#define KEY_UP 51
#define KEY_DOWN 47
#define KEY_LEFT 44
#define KEY_RIGHT 45
#define KEY_OK 46

class Button {
  private:
    uint32_t timer;
    uint8_t pin;
    const static uint8_t shortPressTime = 60;
    const static uint16_t longPressTime = 2000;

  public:
    bool shortPress;
    bool longPress;
    void (* handler)();

    constexpr Button(uint8_t p, void (* h)() = NULL) : timer(0),
      pin(p), shortPress(false), longPress(false), handler(h) {}

    bool isPress() {// читаем состояние пина, работает ровно в 10 раз быстрее digitalRead
      return ! digitalRead(pin);
    }

    bool isPressed() {// была ли нажата кнопка, если да - то или коротким или длинным нажатием
      bool state = isPress();// прочли состояние пина
      if (!state and timer == 0)return false;//если не нажато - то не нажато :)
      else if (state && timer == 0) {//если обнаружили нажатие - запускаем таймер, но не спешим рефлексировать
        timer = millis();
        return false;
      }
      else if (!state && timer != 0) {//если обнаружили отпускание, то анализируем что это было
        uint32_t time = millis() - timer;
        timer = 0;
        if (time > longPressTime) {//длинное нажатие
          longPress = true; return true;
        }
        else if (time > shortPressTime) {//короткое нажатие
          shortPress = true; return true;
        }
      }
      return false;//какая-то фигня
    }

    void process() { // запуск обработчика
      (* handler)();
    }
};

#endif // BUTTON_H

компилируем его и видим результаты наших трудов:

память программ: 2518 (сэкономили ещё 30 байтов, а всего уже 868 байтов)
память данных: 332 (сэкономили ещё 14 байтов, всего уж 44 байта)

Заметьте, мы пока не делали ничего "по сути". Просто чуть-чуть аккуратнее переписал то, что было. И уже получили неплохой гешефт.

Далее, маленькое замечание про метод isPressed. Все методы, объявленные прямо при описании класса считаются inline. Это значит, что компилятор не будет генерировать как таковую функцию, а будет вставлять её код прямо в место вызова. isPressed достаточно большая функция и вставка её в место вызова (если вызовов много, а у нас их много), приводит к раздуванию размера памяти программ. Но, умные компиляторы иногда игнорируют inline и таки генерируют функцию, а в места вызова вставляют переход на неё. В нашем случае так и происходит. Но, в принципе (в общем случае) это нужно иметь в виду и стараться большие и много раз вызываемые методы не делать inline, потому как компилятор, он, конечно, умный, но «на Аллаха надейся, а верблюда привязывай». Я не буду сейчас выносить её в .cpp файл, т.к. компилятор у нас её и сам выносит, но просто имейте в виду.

Четыре мелких замечания:

1.
Два свойства shortPress и longPress предполагают доступ извне. Однако Вы доступаетесь к ним не только на чтение, но и присваиваете им false чтобы не обработать одно нажатие дважды. Это коробит. Эти Ваши присваивания в основной программе не несут никакой логической нагрузки с точки зрения основной программы – они просто прикрывают недоработку в классе. По идее класс должен быть самодостаточным и всю свою внутреннюю кухню держать внутри себя. Можно ведь и забыть им false присвоить! Да и зачем столько присваиваний, которые перегружают основной код? Давайте перенесём сами свойства в private, а для доступа к ним сделаем inline методы, которые вернут значение, и сами же присвоят false. Тогда внутренняя кухня окажется спрятанной в классе и не надо будет помнить, что в основной программе им что-то присваивать нужно. У основной программы своих головных болей хватает. Если это делать, то надо соответственно поменять и обращение к ним из основной программы.

2.
Свойство handler используется в методе process. Никакого доступа к самой переменной handler извне класса не предполагается. Тогда какого чёрта она делает в разделе public? Переносим её в private.

3.
Методы isPress и process ничего не изменяют внутри класса. А раз ничего не меняют, их нужно объявлять const. Вообще, слово const для обозначения неизменяемой переменной (или метода не изменяющего ничего в классе) на вид выглядит бесполезным, но выручает при случайных ошибках (опечатках) и иногда (хотя и редко) позволяет оптимизатору гораздо лучше оптимизировать код. Оно ведь Вам ничего не стоит? Ну и пишите его. Не хотите что-то менять – скажите об этом компилятору.

4.
слово private - очень жёсткое. Если Вы захотите пронаследоваться от этого класса, то класс - наследник не будет иметь к этим переменным доступ. ТЕ переменные, к которым нужно позволить доступ наследникам лучше обзывать protected. Ситуация с наследованием совсем не "теоретическая". ну. например, потребовалась Вам кнопка "с фиксацией" а физически на плате обычная тактовая торчит. Никаких проблем, делаете программно, но т.к. основная функциональность уже реализована в данном классе, разумно класс "кнопка с фиксацией" пронаследовать от этого и "реюзать" вся имеющуюся функциональность.

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

После реализации этих замечаний код получается таким:

Файл «Button.h»

#ifndef BUTTON_H
#define BUTTON_H

#define KEY_UP 51
#define KEY_DOWN 47
#define KEY_LEFT 44
#define KEY_RIGHT 45
#define KEY_OK 46


class Button {
  private:
    const static uint8_t shortPressTime = 60;
    const static uint16_t longPressTime = 2000;
    uint32_t timer;
    uint8_t pin;
    void (* handler)();
  protected:
    bool shortPress;
    bool longPress;

  public:

    constexpr Button(uint8_t p,  void (* h)() = NULL) : timer(0),
      pin(p), handler(h), shortPress(false), longPress(false) {}

    bool isShortPress(void) {
      const bool res = shortPress;
      shortPress = false;
      return res;
    }

    bool isLongPress(void) {
      const bool res = longPress;
      longPress = false;
      return res;
    }

    bool isPress() const {// читаем состояние пина, работает ровно в 10 раз быстрее digitalRead
      return ! digitalRead(pin);
    }

    bool isPressed(void) {// была ли нажата кнопка, если да - то или коротким или длинным нажатием
      bool state = isPress();// прочли состояние пина
      if (!state and timer == 0)return false;//если не нажато - то не нажато :)
      else if (state && timer == 0) {//если обнаружили нажатие - запускаем таймер, но не спешим рефлексировать
        timer = millis();
        return false;
      }
      else if (!state && timer != 0) {//если обнаружили отпускание, то анализируем что это было
        uint32_t time = millis() - timer;
        timer = 0;
        if (time > longPressTime) {//длинное нажатие
          longPress = true; return true;
        }
        else if (time > shortPressTime) {//короткое нажатие
          shortPress = true; return true;
        }
      }
      return false;//какая-то фигня
    }

    void process() const { // запуск обработчика
      (* handler)();
    }
};

#endif	//	BUTTON_H

Файл «5N62V.cpp»

#include "Button.h"

void keyUPhandler();
void keyDOWNhandler();
void keyLEFThandler();
void keyRIGHThandler();
void keyOKhandler();

Button btn[5] = {
  Button (12, &keyUPhandler),//up
  Button (16, &keyDOWNhandler),//down
  Button (19, &keyLEFThandler),//left
  Button (18, &keyRIGHThandler),//right
  Button (17, &keyOKhandler)//ok
};

void setup() {
  Serial.begin(115200);
  //  Serial.println(sizeof(Button));
  pinMode(KEY_UP, INPUT_PULLUP);
  pinMode(KEY_DOWN, INPUT_PULLUP);
  pinMode(KEY_LEFT, INPUT_PULLUP);
  pinMode(KEY_RIGHT, INPUT_PULLUP);
  pinMode(KEY_OK, INPUT_PULLUP);
  delay(2000);
}

void loop() {
  checkButtons();//проверяем кнопки. Краткость - это то что надо
}

void checkButtons() {// сканируем была ли нажата любая кнопка. Если да - обрабатываем
  for (uint8_t i = 0; i < 5; i++) {
    if (btn[i].isPressed()) btn[i].process();
  }
}

void keyUPhandler() {
  if (btn[0].isShortPress()) {
    Serial.println("UP short");//тут код обработчика короткого нажатия
  }
  if (btn[0].isLongPress()) {
    Serial.println("UP long");//тут код обработчика длинного нажатия

  }
}

void keyDOWNhandler() {
  if (btn[1].isShortPress()) {
    Serial.println("DOWN short");
  }
  if (btn[1].isLongPress()) {
    Serial.println("DOWN long");
  }
}

void keyLEFThandler() {
  if (btn[2].isShortPress()) {
    Serial.println("LEFT short");
  }
  if (btn[2].isLongPress()) {
    Serial.println("LEFT long");
  }
}

void keyRIGHThandler() {
  if (btn[3].isShortPress()) {
    Serial.println("RIGHT short");
  }
  if (btn[3].isLongPress()) {
    Serial.println("RIGHT long");
  }
}

void keyOKhandler() {
  if (btn[4].isShortPress()) {
    Serial.println("OK short");
  }
  if (btn[4].isLongPress()) {
    Serial.println("OK long");
  }
}

По-прежнему:

память программ: 2518 (ничего не изменилось)
память данных: 332 (ничего не изменилось)

Так, ну простые косметические замечания в целом закончились. Я сделаю перерыв и "второй серией" напишу уже более тонкие и более сутевые вещи. Продолжение следует (если не поступит команды "не надо" :-)))

5N62V
Offline
Зарегистрирован: 25.02.2016

ЕвгенийП пишет:

к, ну простые косметические замечания в целом закончились. Я сделаю перерыв и "второй серией" напишу уже более тонкие и более сутевые вещи. Продолжение следует (если не поступит команды "не надо" :-)))

Шутите?! Надо, очень надо! :))

Я к сожалению сейчас на работе, и поэтому прочел Ваш пост по диаганоли. Оставлю на вечер разбор каждого абзаца.  Спасибо Вам за труд !  Даже просто столько текста написать  = уже прямо вау-вау! А тут еще и смысловое наполнение, которое предстоит осознать!  

Клапауций 9999
Offline
Зарегистрирован: 27.11.2020

по поводу громоздкости...
ну напиши сам обёртку для класса в Click.h
ненужные функции закомменти - если совсем не нужно.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

подпишусь

5N62V
Offline
Зарегистрирован: 25.02.2016

ЕвгенийП пишет:

ело в том, что инициализация членов класса перед выполнением конструктора должна производиться в том же порядке, в каком они описаны в классе, а Вы написали её в другом. 

  Вот что я умею качественно - так это косячить :)  Обещаю исправиться! Спасибо! 

ЕвгенийП пишет:

Сразу же вопрос, а зачем на использовать new для создания объекта? Это было бы оправдано, если бы мы собирались этот объект потом уничтожать (с помощью delete). 

И действительно... :) Это стереотипность мышления - где-то увидел, запомнил, дальше юзаю без вдумчивости. Учту!

ЕвгенийП пишет:
Переписываем конструктор так, чтобы он выполнялся во время компиляции, т.е. делаем его constexpr.

Это вообще что-то новенькое. Покурю завтра обязательно! :)

ЕвгенийП пишет:
... Тогда зачем хранить эти значения для каждого экземпляра персонально? Почему не хранить один раз для всех кнопок? Для этого достаточно объявить их static и убрать инициализацию из конструктора. 

Угу, посыл понятен, благодарю!

ЕвгенийП пишет:
Все методы, объявленные прямо при описании класса считаются inline.

Этого я тоже не знал - спасибо за науку!

ЕвгенийП пишет:
Четыре мелких замечания...

Все четыре замечания прочитаны, понятны и... и надо тренироваться. :)  Думать как ООПшник - это, надеюсь, скилл, который можно прокачать. Огрномное спасибо за помощь в прокачке! 

5N62V
Offline
Зарегистрирован: 25.02.2016

Клапауций 9999 пишет:
по поводу громоздкости... ну напиши сам обёртку для класса в Click.h ненужные функции закомменти - если совсем не нужно.
Я с уважением к Вашим трудам! :)  Если не трудно, что значит "обертка", "обернуть" ?

Rumata
Offline
Зарегистрирован: 29.03.2019

C нетерпением жду продолжение

andriano
andriano аватар
Онлайн
Зарегистрирован: 20.06.2015

5N62V пишет:

Если не трудно, что значит "обертка", "обернуть" ?

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

5N62V
Offline
Зарегистрирован: 25.02.2016

andriano пишет:

 Можно написать свои функции, которые будут вызывать уже имеющиеся, но с которыми Вам будет удобнее работать. Это и есть "обертка".

то есть это костыль, чтоб не переписывать класс так, чтоб им удобно было работать?

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

посмотри SomeSerial это пример применения обёртки

andriano
andriano аватар
Онлайн
Зарегистрирован: 20.06.2015

5N62V пишет:

andriano пишет:

 Можно написать свои функции, которые будут вызывать уже имеющиеся, но с которыми Вам будет удобнее работать. Это и есть "обертка".

то есть это костыль, чтоб не переписывать класс так, чтоб им удобно было работать?

Нет.

Клапауций 9999
Offline
Зарегистрирован: 27.11.2020

5N62V пишет:

Клапауций 9999 пишет:
по поводу громоздкости... ну напиши сам обёртку для класса в Click.h ненужные функции закомменти - если совсем не нужно.
Я с уважением к Вашим трудам! :)  Если не трудно, что значит "обертка", "обернуть" ?

ок. смотри, что нужно сделать, если начинаешь работать с проектом с нуля: 
 
скачиваешь последнюю версию отсюда https://github.com/Klapautsiy/titanium-bicycle-for-button/releases/tag/BUTTON-v14.1
читаешь описалово и исходники локально. 
 
по сути, что чем является - повторю здесь, хотя в описании и самих исходниках это есть: 
файл Click.h - вычислительное ядро класса кнопки. 
как бы этого должно быть достаточно... для одной кнопки(читай - экземпляра класса кнопки). 
когда кнопок более, чем одна - имеет смысл в некоем инструменте, который генерит для каждой аппаратной кнопки экземпляр класса, распространяет настройки на сгенерированные экземпляры класса, опрашивает состояния кнопок, отсылает данные каждому экземпляру класса, получает данные от каждого экземпляра класса... 
т.е., используется для удобства администрирования - можешь называет это костылём или панелью управления. 
 
далее... 
file Button.h - shell for to class Click, buttons. // экземпляры класса Click генерятся в одномерный массив, если кнопки подключены обычно.
file Matrix.h - shell for to class Click, matrix buttons. // экземпляры класса Click генерятся в двумерный массив, если тип подключения кнопок - матрица.
 
есть небольшой вики с картинко - там об этом, так же, сказано. https://github.com/Klapautsiy/titanium-bicycle-for-button/wiki/synopsis
есть примеры, как с этим работать в папке /examples
 
!ну, да - по сути так и не ответил, почему нужны дополнительные файлы для ядра - можно слить всё это в один файл и написать функцию настроек, где будет указываться тип подключения кнопок: обычный/матрица - на момент актуальности проекта мне казалось, что такое решение не имеет практической ценности:
вручную указывать тип подключения - #include <Matrix.h> или #include <Button.h> или функцией настроек в сетапе.
ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Вторая серия (первого сезона)

Здесь мы отталкиваемся от кодов, приведённых в конце поста #10

Для начала, давайте обратим внимание на то, что методы bool isShortPress(void) и  bool isLongPress(void) с точки зрения логики основной программы ничего не изменяют в классе - это ведь только вопросы "было нажатие или не было". Поэтому, по уму их бы надо сделать const. Зачем? Если Вы думаете, что это чистое эстетство, таки нет. Если метод объявлен как const, то его можно вызывать в контексте константного объекта, а если нет - то нельзя. Вызов именно этих методов в контексте константного объекта нам сегодня пригодится чуть позже. Пока же отметим лишь, что по логике основной программы они "лишь вопросы" - нечего им менять в классе и потому должны быть const - это просто правило хорошего тона в ООП - так принято, так "ООП-но".

Но тут засада! На самом-то деле они изменяют поля shortPress и longPress (присваивают им false), потому формально объявить их const нельзя.

Но, повторяю, пол логике и по "духу ООП" они должны быть const.

Для разрешения этой коллизии в языке есть специальный спецификатор mutable. Его можно применять только к переменным - нестатическим членам класса, не объявленным как const и не являющимися ссылками (к лямбдам ещё, но это отдельная песня). Переменная-член, объявленная, как mutable может изменять даже в кон тесте const объекта (например, методами, об]явленными как const).

Т.е. в объявление shortPress и longPress добавляем слово mutable, а методы isShortPress и isLongPress объявляем const.

-----------

Далее, Вы храните call-back функцию handler для вызова обработчика. Чуть ниже я поматерюсь на сам факт её использования, но пока обсудим что с нею не так (уж если и использовать).

При такой реализации, как у Вас, Вы вынуждены каждой кнопке назначать свой обработчик даже, если все они делают одно и тоже. Почему? Да просто потому, что когда функция-обработчик вызвана, у неё нет НИКАКОЙ возможности узнать какая именно кнопка её вызвала! Поэтому, если один и тот же обработчик будет назначен нескольким кнопкам - он не будет знать от кого вызван и что делать.

Давайте возьмём ещё более красноречивый пример: у меня тут валяются тактовые кнопки с подсветкой. И вот хочу я включать их светодиоды когда они нажаты. Казалось бы, заведи в классе кнопки ещё одно поле - пин светодиода и делай это для всех кнопок одним обработчиком - а вот хрен там! Придётся писать свой обработчик для каждой кнопки, хотя эти обработчики будут отличаться только номером пина светодиода!

Обычная практика в таких случаях - иметь параметр у обработчика - объект, который его вызывает. Т.е. мы описываем функцию обработчик с параметров - указателем на Button, а при вызове просто передаём ему this. Тогда обработчик будет иметь указатель на вызвавший его экземпляр кнопки и проблема снимается полностью. Разумеется, параметр должен быть константным (нечего менять вызвавший объект). Теперь Вы понимаете зачем мы добавляли const  к методам isShortPress и isLongPress - чтобы иметь возможность вызывать их по константному указателю.

В общем, я добавил в Ваш класс метод onPin, который возвращает номер своего пина и переписал Вашу основную программу с использованием всего одного обработчика, который печатает какое было нажатие на кнопку с номером пина N. Здесь реализовано всё, о чём говорилось выше. Смотрите, как это делается:

Файл «Button.h»

#ifndef BUTTON_H
#define BUTTON_H

#define KEY_UP 51
#define KEY_DOWN 47
#define KEY_LEFT 44
#define KEY_RIGHT 45
#define KEY_OK 46

class Button {
  private:
    const static uint8_t shortPressTime = 60;
    const static uint16_t longPressTime = 2000;
    uint32_t timer;
    const uint8_t pin;
    void (* const handler)(const Button *);
  protected:
    mutable bool shortPress;
    mutable bool longPress;

  public:

    constexpr Button(uint8_t p,  void (* const h)(const Button *) = NULL) : timer(0),
      pin(p), handler(h), shortPress(false), longPress(false) {}

    bool isShortPress(void) const {
      const bool res = shortPress;
      shortPress = false;
      return res;
    }

    bool isLongPress(void) const {
      const bool res = longPress;
      longPress = false;
      return res;
    }

    uint8_t onPin(void) const {
      return pin;
    }

    bool isPress() const {// читаем состояние пина
      return ! digitalRead(pin);
    }

    bool isPressed(void) {// была ли нажата кнопка, если да - то или коротким или длинным нажатием
      bool state = isPress();// прочли состояние пина
      if (!state and timer == 0)return false;//если не нажато - то не нажато :)
      else if (state && timer == 0) {//если обнаружили нажатие - запускаем таймер, но не спешим рефлексировать
        timer = millis();
        return false;
      }
      else if (!state && timer != 0) {//если обнаружили отпускание, то анализируем что это было
        uint32_t time = millis() - timer;
        timer = 0;
        if (time > longPressTime) {//длинное нажатие
          longPress = true; return true;
        }
        else if (time > shortPressTime) {//короткое нажатие
          shortPress = true; return true;
        }
      }
      return false;//какая-то фигня
    }

    void process() const { // запуск обработчика
      (* handler)(this);
    }
};

#endif	//	BUTTON_H

Файл «5N62V.cpp»

#include "Button.h"

void keyHandler(const Button *);

Button btn[5] = {
  Button (KEY_UP, keyHandler),//up
  Button (KEY_DOWN, keyHandler),//down
  Button (KEY_LEFT, keyHandler),//left
  Button (KEY_RIGHT, keyHandler),//right
  Button (KEY_OK, keyHandler)//ok
};

void setup() {
  Serial.begin(115200);
  //  Serial.println(sizeof(Button));
  pinMode(KEY_UP, INPUT_PULLUP);
  pinMode(KEY_DOWN, INPUT_PULLUP);
  pinMode(KEY_LEFT, INPUT_PULLUP);
  pinMode(KEY_RIGHT, INPUT_PULLUP);
  pinMode(KEY_OK, INPUT_PULLUP);
  delay(2000);
}

void loop() {
  checkButtons();//проверяем кнопки. Краткость - это то что надо
}

void checkButtons() {// сканируем была ли нажата любая кнопка. Если да - обрабатываем
  for (uint8_t i = 0; i < 5; i++) {
    if (btn[i].isPressed()) btn[i].process();
  }
}

void keyHandler(const Button * bt) {
  const bool sh = bt->isShortPress();
  const bool ln = bt->isLongPress();
  if (sh || ln) {
    Serial.print(sh ? F("short on pin #") : F("long on pin #"));
    Serial.println(bt->onPin());
  }
}

Так, едем дальше. Я обещал поматериться на сам факт использования call-back функции. Приступаю.

Механизм call-back функций очень полезен и даже необходим в не-ООП программах (на С, например). В ООП же он выглядит чужеродным, выбивается из общих концепций - типа "на корове седло". Т.е. это очень не-ООП-ная вещь. В ООП это дурной тон. Сам-то я эту технику использую, но я не фанат ООП (я вообще не "-фил" и не "-фоб" никаких техник программирования, Вы, наверное, заметили по моему отношению к goto и delay). Но, если Ваш код увидят true-фанаты ООП - их первая реакция будет: "программа неверно спроектирована и пришлось говнокодить костыли" и они будут правы потому, что техника call-back функций в ООП -  действительно костыль, свидетельствующий о неверном проектировании ООП-программы.

Для таких вещей в ООП существует механизм виртуальных функций. Конечно, виртуальные функции требуют дополнительных ресурсов, но не настолько много, как кричат всепропальщики и достаточно часто оно стоит того из-за дополнительных удобств. Вот сами подумайте, так ли часто Вы в каком-нибудь проекте расходуете все ресурсы под завязку? А если та же память осталась, Вы что её в другой проект перенесёте? Или на зиму засолите?

Ну, и последняя самая мелкая придирка.

В Вашем изначальном тексте встречается такая конструкция:

Button* btn[5] = { //  Что ещё за 5 !!!!!
  new Button (12, &keyUPhandler),//up
  new Button (16, &keyDOWNhandler),//down
  new Button (19, &keyLEFThandler),//left
  new Button (18, &keyRIGHThandler),//right
  new Button (17, &keyOKhandler)//ok
};
....
  for (uint8_t i = 0; i < 5; i++) {  // Что ещё за 5 !!!!!
    if (btn[i]->isPressed())btn[i]->process();
  }

разумеется, константа 5 здесь форменное безобразие. А ну как Вы в процессе работы над проектом передумаете и решите одну кнопку выбросить или добавить? Надо ведь не забыть поправить эту пятёрку во всех местах!

Вчера это можно было бы написать вот так:

Button* btn[] = {
  new Button (12, &keyUPhandler),//up
  new Button (16, &keyDOWNhandler),//down
  new Button (19, &keyLEFThandler),//left
  new Button (18, &keyRIGHThandler),//right
  new Button (17, &keyOKhandler)//ok
};
static const uint8_t totalBtns = sizeof(btn) / sizeof(btn[0]);
.....

  for (uint8_t i = 0; i < totalBtns; i++) {
    if (btn[i]->isPressed())btn[i]->process();
  }

но язык развивается, и сегодня такие вещи принято писать вот так (хотя, я никак не привыкну, но это уже про "старую собаку и новые фокусы"):

Button* btn[] = {
  new Button (12, &keyUPhandler),//up
  new Button (16, &keyDOWNhandler),//down
  new Button (19, &keyLEFThandler),//left
  new Button (18, &keyRIGHThandler),//right
  new Button (17, &keyOKhandler)//ok
};
.......
for (Button * bt:btn ) {
	if (bt->isPressed()) bt->process();
}

При таком подходе Вы можете и не знать сколько там кнопок.

5N62V
Offline
Зарегистрирован: 25.02.2016

ЕвгенийП пишет:

Вторая серия (первого сезона)

уря! реально ждал как сериал :))

.... А дальше как китайский болванчик, после каждого предложения:

да, сенсей!   Я понял, сенсей!  будет исполнено, сенсей! И т.д.  :))

Прочел три раза. Все доступно и понятно. Спасибо огромное! Мне даже переспросить нечего, настолько все разжевано.  У Вас, Евгений, предрасположенность к деланию очевидными неочевидных для слушателей вещей! С этим надо что-то делать.  :))

KindMan
Offline
Зарегистрирован: 19.12.2018

Спасибо Евгению Петровичу! Очень интересное чтиво! Преподавательский дар на лицо!

P. S. Повезло вам, 5N62V, в моих каракулях Евгений Петрович не ковырялся :(

5N62V
Offline
Зарегистрирован: 25.02.2016

KindMan пишет:
Повезло вам, 5N62V, в моих каракулях Евгений Петрович не ковырялся :(

Даже   и не знаю как я так Евгения спровоцировал! :) Евгений, как немногие, умеет объяснять доступно, и в то же время давать материал структурировано. Это я еще про PROGMEMy заметил. Хочется еще, ибо к хорошему привыкаешь быстро. Но я тут узреваю несправедливость: по ценности такая адресная, пусть и публичная, консультация тянет ну никак не меньше какого-нить проектика из раздела "ишу исполнителя".  А лайки и признательность , даже глубокая,  - такая себе, весьма условная мотивация, все нешкольники это понимают. 

И шо рабить в такой ситуации? Я уверен, многие на этом ресурсе заинтерисованы в качественном контенте, и система донатов была бы весьма кстати! Заодно поуменьшилось бы количество нетематического срача. :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

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

Дело в том, что если делать через виртуальные функции, то придется вернуться к изначальной идее ТС создавать элементы массива через new, т.к. виртуальность работает по указателям.

Вот два примера с виртуальными функциями.

Неправильный пример:

//
// Базовый класс с виртуальным методом
//
struct BaseClass {
	virtual void kaka(void) { Serial.println("BaseClass:kaka called"); }
};
//
//	Производный класс, переопределяющий виртуальный метод 
//	(оставляем его виртуальным)
//
struct ChildClass : public BaseClass {
	virtual void kaka(void) { Serial.println("ChildClass:kaka called"); }
};
//
//	Ещё производный класс, переопределяющий виртуальный метод 
//	(больше переопределять не будем - отменяем виртуальность)
//
struct GrandChildClass : public ChildClass {
	void kaka(void) { Serial.println("GrandChildClass:kaka called"); }
};

//
// Массив объявляем по базовому классу
//
static BaseClass items[] = {
	BaseClass(), 
	ChildClass(), 
	GrandChildClass()
};

void setup() {
  Serial.begin(57600);
  //
  //	Вызываем методы всех объектов
  for (BaseClass item:items) item.kaka();
}

void loop(void){}

//
// Вывод в мониторе порта
//
//BaseClass:kaka called
//BaseClass:kaka called
//BaseClass:kaka called
//

Казалось бы, при вызове метода kaka для всех элементов массива (строка №35) каждый должен напечатать своё, но, как видим, хрен там. Виртуальность не сработала.

Она работает при обращении через указатель.

Правильный пример:

//
// Базовый класс с виртуальным методом
//
struct BaseClass {
	virtual void kaka(void) { Serial.println("BaseClass:kaka called"); }
};
//
//	Производный класс, переопределяющий виртуальный метод 
//	(оставляем его виртуальным)
//
struct ChildClass : public BaseClass {
	virtual void kaka(void) { Serial.println("ChildClass:kaka called"); }
};
//
//	Ещё производный класс, переопределяющий виртуальный метод 
//	(больше переопределять не будем - отменяем виртуальность)
//
struct GrandChildClass : public ChildClass {
	void kaka(void) { Serial.println("GrandChildClass:kaka called"); }
};

//
// Массив объявляем по базовому классу
//
static BaseClass * items[] = {
	new BaseClass(), 
	new ChildClass(), 
	new GrandChildClass()
};

void setup() {
  Serial.begin(57600);
  //
  //	Вызываем методы всех объектов
  for (BaseClass * item:items) item->kaka();
}

void loop(void){}

//
// Вывод в мониторе порта
//
//BaseClass:kaka called
//ChildClass:kaka called
//GrandChildClass:kaka called
//

Как видите, здесь всё в порядке. Для каждого объекта вызвалась именно его функция kaka, определённая в производном классе, несмотря на то, что все вызывались по указателю item, который вообще-то описан как 


BaseClass * item

и, вроде бы, про производные классы ничего не знает.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

О_О 

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

ЕвгенийП пишет:

for (Button * bt:btn ) {
	if (bt->isPressed()) bt->process();
}

Женя! С СНГ тебя (Старый Новый Год) ;)))

Тогда уж:

for (auto * bt:btn ) {
	if (bt->isPressed()) bt->process();
}

==================================

Ну и, шоп два раза не вставать, хочу заступиться за колбэки. ;))

Женя, а ты под андроид не писал ничего? Не изучал АПИ от Гугля? ...Ну там колбэк на колбэке и сверху еще два ;))). Собственно весь апи на них построен.  Ты мог видеть стиль программирования, когда функцию с именем (или блямбу) описывают прямо внутри скобок аргументов другой функции? Это самый типичный стиль для java под андроид ...и не только. ;)) О! ты ж JS любишь вроде? тот же стиль сплошь и рядом. И именно для колбэков.

Я могу путать терминологию, но апи андроида и половина кода Веба на JS - разве не ООП-стайл? Или ты именно про С++ программирование в канонах ООП? Не касательно других языков?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Граф, да от меня калбэки защищать не надо, у меня у самого их ... А вот true-фанаты ООП рассуждают именно так, как я описал выше. И я согласен с ними, ведь если калбэк - не член класса, то с точки зрения ООП - действительно костыль. Вот я там советовал ТС this передавать, а виртуалке он сам передаётся, т.к. она здесь "родная", а калбэк - чужеродный и ему явно передать надо. Это просто вопрос "чистоты истинной веры". Примерно, как функциональщики кривят губы на побочные эффекты, а на замечание, что без побочных эффектов программировать практические вещи невозможно в принципе, кривят ещё больше, мол типа это проблемы "практического программирования". Азохен вей! :-)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

wdrakula пишет:

Тогда уж:

for (auto * bt:btn ) {
	if (bt->isPressed()) bt->process();
}

Не, использование auto я считаю дидактически неверным в изложении данного материала.

Там ведь суть в чём - указатель на базовый класс, но при этом вызывается метод производного - это и есть прямая демонстрация виртуальности. А так, как ты написал - auto скрывает (маскирует) этот факт и у читателя может создаться ложное впечатление, что bt при каждом проходе цикла имеет разные типы (на правильный класс указывает). Это не так, но такое впечатление может появиться.

5N62V
Offline
Зарегистрирован: 25.02.2016

ЕвгенийП пишет:

 у читателя может создаться ложное впечатление, что bt при каждом проходе цикла имеет разные типы (на правильный класс указывает). 

кстати да! мы, возмущенные читатели, категорически возмущены попытками создания у нас ложного впечатления! 

5N62V
Offline
Зарегистрирован: 25.02.2016

Клапауций 9999 пишет:

ок. смотри, что нужно сделать, если начинаешь работать с проектом с нуля: 

 
скачиваешь последнюю версию отсюда https://github.com/Klapautsiy/titanium-bicycle-for-button/releases/tag/BUTTON-v14.1
читаешь описалово и исходники локально. 
 
Ув Клаупаций, не хочу создавать у Вас ложное чувство, типа никто не отреагировал на Ваши пояснения. Это не так. Я честно сегодня читал и Click.h , и мини-вики, и примеры. У меня смешанные чувства, что в первую очередь объясняется моими скудными скилами по чтению чужого кода. Но я упрямый ;) 
qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

5N62V пишет:
кстати да! мы, возмущенные читатели, категорически возмущены попытками создания у нас ложного впечатления!

кстати да! мы, возмущенные читатели, категорически возмущены попытками создания у нас возмущения!

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

Клапауций 9999
Offline
Зарегистрирован: 27.11.2020

5N62V пишет:

Ув Клаупаций, не хочу создавать у Вас ложное чувство, типа никто не отреагировал на Ваши пояснения. Это не так. Я честно сегодня читал и Click.h , и мини-вики, и примеры. У меня смешанные чувства, что в первую очередь объясняется моими скудными скилами по чтению чужого кода. Но я упрямый ;) 

я всего то хотел узнать, что трудного в неиспользовании лишнего и применении, например:

if (test.event_click_Dn     (0) == 1) {}

или, я не понимаю, что за сущность - обработка нажания.

5N62V пишет:

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