Цифровые автоматы в классах по qwone

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Похоже надо открыть новую тему, что бы не смешавать скетчи со старой.

http://arduino.ru/forum/programmirovanie/klassy-arduino-po-qwone-dlya-ch...

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

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Ну начальный шаблон похож

/**/
unsigned long mill; // переменная под millis()
//--------------------------------
class Cl_Led {
  protected:
    byte pin;
  public:
    Cl_Led(byte p):pin(p) {}
    void init() {}
    void run() {}
};
//---Компоновка-----------------------------
Cl_Led Led(/*пин*/13);
//--------------------------------
void setup() {
  Led.init();
}

void loop() {
  mill = millis();
  Led.run();
}
/**/

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Мигание светодиода с равными полупериодами тоже

/**/
unsigned long mill; // переменная под millis()
//--------------------------------
enum {ssOFF = 0, ssON}; /*состояние ВЫКЛ/ВКЛ*/
class Cl_Led {
  protected:
    byte pin;
    unsigned long time;
    unsigned long past;
    byte state;
    void stand(byte s) {
      state = s;
      past = mill;
      switch (state) {
        case ssOFF:
          digitalWrite(pin, LOW);
          break;
        case ssON:
          digitalWrite(pin, HIGH);
          break;
      }
    }
  public:
    Cl_Led(byte p, unsigned long t): pin(p), time(t) {}
    void init() {
      pinMode(pin, OUTPUT);
      stand(ssOFF);
    }
    void run() {
      if (state == ssOFF && mill - past >= time) stand(ssON);
      if (state == ssON  && mill - past >= time) stand(ssOFF);
    }
};
//---Компоновка-----------------------------
Cl_Led Led(/*пин*/13,/*время перекл*/500);
//--------------------------------
void setup() {
  Led.init();
}

void loop() {
  mill = millis();
  Led.run();
}
/**/

ПС: Вот так тоже но запись получше

/**/
unsigned long mill; // переменная под millis()
//--------------------------------
enum {ssOFF = 0, ssON}; /*состояние ВЫКЛ/ВКЛ*/
class Cl_Led {
  protected:
    byte pin;
    unsigned long time;
    unsigned long past;
    enum onLed{ssOFF = 0, ssON}state; /*состояние ВЫКЛ/ВКЛ*/
    void stand(onLed s) {
      state = s;
      past = mill;
      switch (state) {
        case ssOFF:
          digitalWrite(pin, LOW);
          break;
        case ssON:
          digitalWrite(pin, HIGH);
          break;
      }
    }
  public:
    Cl_Led(byte p, unsigned long t): pin(p), time(t) {}
    void init() {
      pinMode(pin, OUTPUT);
      stand(ssOFF);
    }
    void run() {
      if (state == ssOFF && mill - past >= time) stand(ssON);
      if (state == ssON  && mill - past >= time) stand(ssOFF);
    }
};
//---Компоновка-----------------------------
Cl_Led Led(/*пин*/13,/*время перекл*/500);
//--------------------------------
void setup() {
  Led.init();
}

void loop() {
  mill = millis();
  Led.run();
}
/**/

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Мигание с различными полупериодами так же

/**/
unsigned long mill; // переменная под millis()
//--------------------------------
enum {ssOFF=0, ssON}; /*состояние ВЫКЛ/ВКЛ*/
class Cl_Led {
  protected:
    byte pin;
    unsigned long timeON, timeOFF;
    unsigned long past;
    byte state;
    void stand(byte s) {
      state = s;
      past = mill;
      switch (state) {
        case ssOFF:
          digitalWrite(pin, LOW);
          break;
        case ssON:
          digitalWrite(pin, HIGH);
          break;
      }
    }
  public:
    Cl_Led(byte p, unsigned long t1, unsigned long t2)
      : pin(p), timeON(t1), timeOFF(t2) {}
    void init() {
      pinMode(pin, OUTPUT);
      stand(ssOFF);
    }
    void run() {
      if (state == ssOFF && mill - past >= timeOFF) stand(ssON);
      if (state == ssON  && mill - past >= timeON)  stand(ssOFF);
    }
};
//---Компоновка-----------------------------
Cl_Led Led(/*пин*/13,/*время ВКЛ*/500,/*время ВЫКЛ*/100);
//--------------------------------
void setup() {
  Led.init();
}

void loop() {
  mill = millis();
  Led.run();
}
/**/

Поправочка

/**/
unsigned long mill; // переменная под millis()
//--------------------------------
class Cl_Led {
  protected:
    byte pin;
    unsigned long timeON, timeOFF;
    unsigned long past;
    enum onLed{ssOFF = 0, ssON} state; /*состояние ВЫКЛ/ВКЛ*/
    void stand(onLed s) {
      state = s;
      past = mill;
      switch (state) {
        case ssOFF:
          digitalWrite(pin, LOW);
          break;
        case ssON:
          digitalWrite(pin, HIGH);
          break;
      }
    }
  public:
    Cl_Led(byte p, unsigned long t1, unsigned long t2)
      : pin(p), timeON(t1), timeOFF(t2) {}
    void init() {
      pinMode(pin, OUTPUT);
      stand(ssOFF);
    }
    void run() {
      if (state == ssOFF && mill - past >= timeOFF) stand(ssON);
      if (state == ssON  && mill - past >= timeON)  stand(ssOFF);
    }
};
//---Компоновка-----------------------------
Cl_Led Led(/*пин*/13,/*время ВКЛ*/500,/*время ВЫКЛ*/100);
//--------------------------------
void setup() {
  Led.init();
}

void loop() {
  mill = millis();
  Led.run();
}
/**/

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

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

/**/
unsigned long mill; // переменная под millis()
//------Cl_Btn----------------------
enum {sbNONE = 0, sbClick}; /*состояние не изменилось/клик*/
class Cl_Btn {
  protected:
    const byte pin;
    byte state;
    bool bounce = 0;
    bool btn = 1, oldBtn;
    unsigned long past;
  public:
    Cl_Btn(byte p): pin(p) {}
    /*инициализация-вставить в setup()*/
    void init() {
      pinMode(pin, INPUT_PULLUP);
    }
    /*работа-вставить в loop()*/
    void run() {
      state = sbNONE;
      bool newBtn = digitalRead(pin);
      if (!bounce && newBtn != btn) {
        bounce = 1;
        past = mill;
      }
      if (bounce && mill - past >= 10) {
        bounce = 0 ;
        oldBtn = btn;
        btn = newBtn;
        if (!btn && oldBtn) state = sbClick;
      }
    }
    byte read() {
      return state;
    }
};
//--------------------------------
enum {ssSTOP = 0, ssOFF, ssON}; /*состояние ВЫКЛ/ГОРИТ/НЕГОРИТ*/
class Cl_Led {
  protected:
    byte pin;
    unsigned long timeON, timeOFF;
    unsigned long past;
    byte state;
    void stand(byte s) {
      state = s;
      past = mill;
      switch (state) {
        case ssSTOP:
          digitalWrite(pin, LOW);
        case ssOFF:
          digitalWrite(pin, LOW);
          break;
        case ssON:
          digitalWrite(pin, HIGH);
          break;
      }
    }
  public:
    Cl_Led(byte p, unsigned long t1, unsigned long t2)
      : pin(p), timeON(t1), timeOFF(t2) {}
    void init() {
      pinMode(pin, OUTPUT);
      stand(ssSTOP);
    }
    void run() {
      if (state == ssOFF && mill - past >= timeOFF) stand(ssON);
      if (state == ssON  && mill - past >= timeON)  stand(ssOFF);
    }
    void ON() {
      stand(ssON);
    }
};
//---Компоновка-----------------------------
Cl_Led Led(/*пин*/13,/*время ВКЛ*/500,/*время ВЫКЛ*/100);
Cl_Btn Btn1(/*пин*/2);
//--------------------------------
void setup() {
  Btn1.init();
  Led.init();
}

void loop() {
  mill = millis();
  Led.run();
  Btn1.run();
  if (Btn1.read() == sbClick)Led.ON();
}
/**/

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Управление с коротким и длиным нажатием.

/**/
unsigned long mill; // переменная под millis()
//------Cl_Btn----------------------
enum {sbNONE = 0, sbClick, sbLong}; /*состояние не изменилось/клик/долгое наж*/
class Cl_Btn {
  protected:
    const byte pin;
    byte state;
    bool bounce = 0;
    bool btn = 1, oldBtn;
    unsigned long past;
    const uint32_t time = 500 ;
    bool flag = 0;
    uint32_t past_flag = 0 ;
  public:
    Cl_Btn(byte p): pin(p) {}
    /*инициализация-вставить в setup()*/
    void init() {
      pinMode(pin, INPUT_PULLUP);
    }
    /*работа-вставить в loop()*/
    void run() {
      state = sbNONE;
      bool newBtn = digitalRead(pin);
      if (!bounce && newBtn != btn) {
        bounce = 1;
        past = mill;
      }
      if (bounce && mill - past >= 10) {
        bounce = 0 ;
        oldBtn = btn;
        btn = newBtn;
        if (!btn && oldBtn) {
          flag = 1;
          past_flag = mill;
        }
        if (!oldBtn && btn && flag && mill - past_flag < time ) {
          flag = 0;
          state = sbClick;
        }
      }
      if (flag && mill - past_flag >= time ) {
        flag = 0;
        state = sbLong;
      }
    }
    byte read() {
      return state;
    }
};
//--------------------------------
enum {ssSTOP = 0, ssOFF, ssON}; /*состояние ВЫКЛ/ГОРИТ/НЕГОРИТ*/
class Cl_Led {
  protected:
    byte pin;
    unsigned long timeON, timeOFF;
    unsigned long past;
    byte state;
    void stand(byte s) {
      state = s;
      past = mill;
      switch (state) {
        case ssSTOP:
          digitalWrite(pin, LOW);
        case ssOFF:
          digitalWrite(pin, LOW);
          break;
        case ssON:
          digitalWrite(pin, HIGH);
          break;
      }
    }
  public:
    Cl_Led(byte p, unsigned long t1, unsigned long t2)
      : pin(p), timeON(t1), timeOFF(t2) {}
    void init() {
      pinMode(pin, OUTPUT);
      stand(ssSTOP);
    }
    void run() {
      if (state == ssOFF && mill - past >= timeOFF) stand(ssON);
      if (state == ssON  && mill - past >= timeON)  stand(ssOFF);
    }
    void OFF() {
      stand(ssSTOP);
    }
    void ON() {
      stand(ssON);
    }
};
//---Компоновка-----------------------------
Cl_Led Led(/*пин*/13,/*время ВКЛ*/500,/*время ВЫКЛ*/100);
Cl_Btn Btn1(/*пин*/2);
//--------------------------------
void setup() {
  Btn1.init();
  Led.init();
}

void loop() {
  mill = millis();
  Led.run();
  Btn1.run();
  if (Btn1.read() == sbClick)Led.ON();
  if (Btn1.read() == sbLong)Led.OFF();
}
/**/

Ну пока все. Немного похоже на кнопки Клапы, но все же не они.

Ворота
Ворота аватар
Offline
Зарегистрирован: 10.01.2016

Квон, прошлая тем была свалкой неработающих кодов (я тебе в #270 прямо привёл пример на котором валится, но ты и не подумал поправить).

Решил ещё одну помойку для неработающего говнокода завести? 

Ворота
Ворота аватар
Offline
Зарегистрирован: 10.01.2016

Хотя, я забыл, ты же там в посте #35 прямо сказал, что принципиально не исправляешь ошибок. Извини. Продолжай гадить.

sadman41
Онлайн
Зарегистрирован: 19.10.2016

Может раздел сразу завести на форуме: "Ардуино по qwone"... И ссылать туда всех борзых новичков. Навек.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

/*Скетч Аналоговая клавиатура
*/
unsigned long mill;
//----------Cl_Key---------
// класс аналоговая клавиатура
enum {skNone = 0, skLeft, skRight, skUp, skDown, skSelect};
class Cl_Key {
    byte pin;// нога
    byte state;
    int val_old, val; // состояние на клавиатуре
    unsigned long past;
    int read_() { // считать состояние клавиатуры
      int value = analogRead(pin);
      if (value < 130) return skLeft;
      else if (value < 310) return skUp;
      else if (value < 500) return skDown;
      else if (value < 730) return skRight;
      else if (value < 900) return skSelect;
      return skNone;
    }
  public:
    Cl_Key(byte p): pin(p) {}
    void init() {
      val_old = read_();
    }
    void run() {
      state = skNone;
      if (mill - past >= 50) {
        past = mill;
        val_old = val;
        val = read_();
        if (val_old == skNone) {
          state = val;
        }
      }
    }
    byte read() {
      return state;
    }
};
//--------Компоновка-----------
Cl_Key Key(/*нога*/A0);
//----------main()---------
void setup() {
  Serial.begin(9600);
  Key.init();
}
void loop() {
  mill = millis();
  Key.run();
  if (Key.read() == skLeft)   Serial.println("Left");
  if (Key.read() == skRight)  Serial.println("Right");
  if (Key.read() == skUp)     Serial.println("Up");
  if (Key.read() == skDown)   Serial.println("Down");
  if (Key.read() == skSelect) Serial.println("Select");
}
/*Скетч использует 1850 байт (6%) памяти устройства. Всего доступно 30720 байт.
  Глобальные переменные используют 226 байт (11%) динамической памяти, оставляя 1822 байт для локальных переменных. Максимум: 2048 байт.
*/

 

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

Предлагаю использовать здесь новый термин : "квонокод"

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

/*LCD Key Shield*/
unsigned long mill;
//--------------------Cl_aKeyboard--------------------------------
#include <LiquidCrystal.h>
/*состояние-нет нажатий/правая/левая/вверх/вниз/влево/выбор*/
enum {skNone = 0, skRight, skUp, skDown, skLeft, skSelect};
class Cl_aKeyboard {
    const byte pin;
    int  data_next, data, data_old;
    bool bounce = 0; // антидребезговый флаг
    unsigned long past;
    byte state;/**/
  public:
    Cl_aKeyboard(byte p) : pin(p) {}
    void init() {
      data = analogRead (pin);
    }
    void run() {
      state = skNone;
      data_next = analogRead (pin);
      if (! bounce && data != data_next) { // если прошел фронт изм на выводн
        bounce = 1;                              // выставить флаг
        past = mill;                         // сделать временую засветку
      }
      else if ( bounce && mill - past >= 5 ) { // если прошло антидребезговое время
        bounce = 0;                                // то снять флаг
        data_old = data ;
        data = data_next ;
        if (data_old > 1000) {
          if (data < 60)       state = skRight;
          else if (data < 200) state = skUp;
          else if (data < 400) state = skDown;
          else if (data < 600) state = skLeft;
          else if (data < 800) state = skSelect;
        }
      }
    }
    byte read() {
      return state;
    }
};
//---------------Компоновка----------------------------------------------
LiquidCrystal lcd(/*RS*/8,/*Enable*/9,/*DB4*/4,/*DB5*/5,/*DB6*/6,/*DB7*/7);
void lcd_setup() {
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("LCD Key Shield");
  lcd.setCursor(0, 1);
  lcd.print("Press Key:None  ");
}
void Do_Right() {
  lcd.setCursor(10, 1);
  lcd.print ("Right ");
}
void Do_Up() {
  lcd.setCursor(10, 1);
  lcd.print ("Up    ");
}
void Do_Down() {
  lcd.setCursor(10, 1);
  lcd.print ("Down  ");
}
void Do_Left() {
  lcd.setCursor(10, 1);
  lcd.print ("Left  ");
}
void Do_Select() {
  lcd.setCursor(10, 1);
  lcd.print ("Select");
}

Cl_aKeyboard Keyboard(/*пин*/A0);
//----------------- -----------------------
void setup() {
  lcd_setup();
  Keyboard.init();
}
void loop() {
  mill = millis();
  Keyboard.run();
  if (Keyboard.read() == skRight ) {
    Do_Right();
  }
  if (Keyboard.read() == skUp ) {
    Do_Up();
  }
  if (Keyboard.read() == skDown ) {
    Do_Down();
  }
  if (Keyboard.read() == skLeft ) {
    Do_Left();
  }
  if (Keyboard.read() == skSelect ) {
    Do_Select();
  }
}
/**/

 

Ворота
Ворота аватар
Offline
Зарегистрирован: 10.01.2016

DetSimen пишет:

Предлагаю использовать здесь новый термин : "квонокод"

Надо дать определение. Предлагаю такое:

"Квонокод - разновидность говнокода, написанного коллегой qwone. Отличительная особенность - широкое использование ключевого слова "class" при полном отсутствии объектно-ориентированного подхода".

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Ворота пишет:

DetSimen пишет:

Предлагаю использовать здесь новый термин : "квонокод"

Надо дать определение. Предлагаю такое:

"Квонокод - разновидность говнокода, написанного коллегой qwone. Отличительная особенность - широкое использование ключевого слова "class" при полном отсутствии объектно-ориентированного подхода".

Это оперделение требует корректировки:

"Квонокод - разновидность программ, написанных коллегой qwone. Отличительная особенность - широкое использование ключевого слова "class" при полном отсутствии объектно-ориентированного говнокода".

"

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Теперь попробуем заменить аналоговую клавиатару на клавиатуру на Serial

/**/
unsigned long mill;
//--------------------Cl_sKeyboard--------------------------------
/*состояние-нет нажатий/правая/левая/вверх/вниз/влево/выбор*/
enum {skNone = 0, skRight, skUp, skDown, skLeft, skSelect};
struct com_t {
  byte com;
  byte state;
};
const byte numCom = 5;
com_t MAP[numCom] {
  {'r', skRight}, {'u', skUp}, {'d', skDown}, {'l', skLeft}, {'s', skSelect}
};
class Cl_sKeyboard {
  protected:
    byte state = skNone;
  public:
    Cl_sKeyboard() {}
    void init() {
      Serial.begin(9600);
    }
    void run() {
      state = skNone;
      if (Serial.available() > 0) {
        byte a = Serial.read();
        for (int i = 0; i < numCom; i++) {
          if (a == MAP[i].com) state = MAP[i].state;
        }
      }
    }
    byte read() {
      return state;
    }
};
//---------------Компоновка----------------------------------------------
#include <LiquidCrystal.h>
LiquidCrystal lcd(/*RS*/8,/*Enable*/9,/*DB4*/4,/*DB5*/5,/*DB6*/6,/*DB7*/7);
void lcd_setup() {
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("LCD Key Shield");
  lcd.setCursor(0, 1);
  lcd.print("Press Key:None  ");
}
void Do_Right() {
  lcd.setCursor(10, 1);
  lcd.print ("Right ");
}
void Do_Up() {
  lcd.setCursor(10, 1);
  lcd.print ("Up    ");
}
void Do_Down() {
  lcd.setCursor(10, 1);
  lcd.print ("Down  ");
}
void Do_Left() {
  lcd.setCursor(10, 1);
  lcd.print ("Left  ");
}
void Do_Select() {
  lcd.setCursor(10, 1);
  lcd.print ("Select");
}

Cl_sKeyboard Keyboard;
//----------------- -----------------------
void setup() {
  lcd_setup();
  Keyboard.init();
}
void loop() {
  mill = millis();
  Keyboard.run();
  if (Keyboard.read() == skRight ) {
    Do_Right();
  }
  if (Keyboard.read() == skUp ) {
    Do_Up();
  }
  if (Keyboard.read() == skDown ) {
    Do_Down();
  }
  if (Keyboard.read() == skLeft ) {
    Do_Left();
  }
  if (Keyboard.read() == skSelect ) {
    Do_Select();
  }
}
/*Скетч использует 2820 байт (8%) памяти устройства. Всего доступно 32256 байт.
  Глобальные переменные используют 287 байт (14%) динамической памяти, оставляя 1761 байт для локальных переменных. Максимум: 2048 байт.
*/

ПС: Попозже я ее оптимизирую и переведу на PROGMEM

Ну где-то так

/**/
unsigned long mill;
//--------------------Cl_sKeyboard--------------------------------
/*состояние-нет нажатий/правая/левая/вверх/вниз/влево/выбор*/
enum {skNone = 0, skRight, skUp, skDown, skLeft, skSelect};
struct com_t {
  byte com;
  byte state;
};
const byte numCom = 5;
const com_t MAP[numCom] PROGMEM = {
  {'r', skRight}, {'u', skUp}, {'d', skDown}, {'l', skLeft}, {'s', skSelect}
};
class Cl_sKeyboard {
  protected:
    byte state = skNone;
  public:
    Cl_sKeyboard() {}
    void init() {
      Serial.begin(9600);
    }
    void run() {
      state = skNone;
      if (Serial.available() > 0) {
        byte a = Serial.read();
        for (int i = 0; i < numCom; i++) {
          if (a == pgm_read_byte(&(MAP[i].com))) {
            state = pgm_read_byte(&(MAP[i].state));
            break;
          }
        }
      }
    }
    byte read() {
      return state;
    }
};
//---------------Компоновка----------------------------------------------
Cl_sKeyboard Keyboard;
//----------------- -----------------------
void setup() {
  Keyboard.init();
}
void loop() {
  mill = millis();
  Keyboard.run();
  if (Keyboard.read() == skRight ) {
    Serial.println("Right");
  }
  if (Keyboard.read() == skUp ) {
    Serial.println("Up");
  }
  if (Keyboard.read() == skDown ) {
    Serial.println("Down");
  }
  if (Keyboard.read() == skLeft ) {
    Serial.println("Left");
  }
  if (Keyboard.read() == skSelect ) {
    Serial.println("Select");
  }
}
/*Скетч использует 1690 байт (5%) памяти устройства. Всего доступно 30720 байт.
  Глобальные переменные используют 217 байт (10%) динамической памяти, оставляя 1831 байт для локальных переменных. Максимум: 2048 байт.
*/

 

Logik
Онлайн
Зарегистрирован: 05.08.2014

Искуство иллюзии в исполнении квона - навязчивая публичная демонстрация владения ООП и цифрового автомата  при полном их отсутствии в коде.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Переделал меню отсюда #251 . Работает. Но надо ее еще обдумывать.

/**/
unsigned long mill;
typedef void (*pDo)();
//--------------------Cl_sKeyboard--------------------------------
/*состояние-нет нажатий/+/-/exe*/
enum {skNone = 0, skPlus, skMimus, skExe};
struct com_t {
  byte com;
  byte state;
};
const byte numCom = 3;
const com_t MAP[numCom] PROGMEM = {
  {'+', skPlus}, {'-', skMimus}, {'e', skExe}
};
class Cl_sKeyboard {
  protected:
    byte state = skNone;
  public:
    Cl_sKeyboard() {}
    void init() {
      Serial.begin(9600);
    }
    void run() {
      state = skNone;
      if (Serial.available() > 0) {
        byte a = Serial.read();
        for (int i = 0; i < numCom; i++) {
          if (a == pgm_read_byte(&(MAP[i].com))) {
            state = pgm_read_byte(&(MAP[i].state));
            break;
          }
        }
      }
    }
    byte read() {
      return state;
    }
};
//------Cl_Display----------------------
// класс регулярный вывод на дисплей
class Cl_Display {
  protected:
    pDo Do;//обработчик
    bool refreshON = 1; //сигнал обновить
  public:
    /*конструктор*/
    Cl_Display(pDo D): Do(D) {}
    /*инициализация-вставить в setup()*/
    void init() {
    }
    /*работа-вставить в loop()*/
    void run() {
      if (refreshON) {
        refreshON = 0;
        Do();
      }
    }
    void refresh() {
      refreshON = 1;
    }
    /*записать новый обработчик*/
    void write( pDo D) {
      Do = D;
    }
};
//-----Компоненты Меню-------------------------
void ExitMenu() {};
int Value1, Value2, Value3, Value4, Value5, Value6, Value7, Value8, Value9;
byte screen ; byte line ; bool fEdit;
void initMenu( byte screen);/*предварительное объявление*/
//-----sItemMenu----------------
enum {
  ROOT,
  HOLDER,
  CONFIGINT
};
struct sItemMenu {
  byte type;
  byte num;
};
const sItemMenu dbItemMenu[] PROGMEM = {
  /*type,num*/
  {ROOT, 0},     /*#0 ROOT[0]    1-3*/
  {HOLDER, 0},   /*#1 HOLDER[0]  4-6*/
  {HOLDER, 1},   /*#2 HOLDER[1]  7-9 */
  {CONFIGINT, 0},/*#3 var 1  CONFGINT[0]*/

  {HOLDER, 2},     /* #4 HOLDER[2]  10-12   */
  {CONFIGINT, 1},  /* #5 var 2  CONFGINT[1] */
  {CONFIGINT, 2},  /* #6 var 3  CONFGINT[2] */

  {CONFIGINT, 3},  /* #7 var 4  CONFGINT[3] */
  {CONFIGINT, 4},  /* #8 var 5  CONFGINT[4] */
  {CONFIGINT, 5},  /* #9 var 6  CONFGINT[5] */

  {CONFIGINT, 6},  /* #10 var 7  CONFGINT[6]*/
  {CONFIGINT, 7},  /* #11 var 8  CONFGINT[7]*/
  {CONFIGINT, 8}  /* #12 var 9  CONFGINT[8]*/
};
void pgm_viev(char *name) {/*вывод из PROGMEM*/
  char buffer[15];
  strcpy_P(buffer, name);
  Serial.print(buffer);
}
//------sItemRoot-------------------
struct sItemRoot {
  const byte top;
  const byte bottom;
  const pDo Do;
};
const sItemRoot dbItemRoot[] PROGMEM = {
  {1, 3, ExitMenu} /*ROOT[0]*/
};
byte readTopItemRoot(byte the) { /* <- top[the] */
  return pgm_read_byte(&dbItemRoot[the].top);
}
byte readBottomItemRoot(byte the) { /* <- top[the] */
  return pgm_read_byte(&dbItemRoot[the].bottom);
}
void DoItemRoot(byte the) {  /* Do[the] */
  pDo Do = pgm_read_word(&dbItemRoot[the].Do);
  Do();
}
//------sItemHolder-------------------
struct sItemHolder {
  const byte father;
  const byte top;
  const byte bottom;
  const char *name;
};
const char txtItemHolder0[] PROGMEM = "R0 HOLDER 1";
const char txtItemHolder1[] PROGMEM = "R0 HOLDER 2";
const char txtItemHolder2[] PROGMEM = "H0 HOLDER 3";
const sItemHolder dbItemHolder[] PROGMEM = {
  /*father,top,bottom,name*/
  {0,  4,  6, txtItemHolder0}, /*#0 HOLDER[0]*/
  {0,  7,  9, txtItemHolder1}, /*#1 HOLDER[1]*/
  {1, 10, 12, txtItemHolder2}  /*#2 HOLDER[2]*/
};
byte readFatherItemHolder(byte the) { /* <- father[the] */
  return pgm_read_byte(&dbItemHolder[the].father);
}
byte readTopItemHolder(byte the) { /* <- top[the] */
  return pgm_read_byte(&dbItemHolder[the].top);
}
byte readBottomItemHolder(byte the) { /* <- top[the] */
  return pgm_read_byte(&dbItemHolder[the].bottom);
}
char * readItemHolderName(byte the) { /* <- name[the] */
  return (char *)pgm_read_byte(&dbItemHolder[the].name);
}
//-------sItemConfigInt----------------
struct sItemConfigInt {
  int * pointer;
  byte addr;
  int   maxValue;
  int   minValue;
  int   setValue;
  const char *name;
};
const byte numItemConfigInt = 9; /*кол-во элементов ConfigInt */
const char txtItemConfigInt0[] PROGMEM = "R0 var 1=";
const char txtItemConfigInt1[] PROGMEM = "H0 var 2=";
const char txtItemConfigInt2[] PROGMEM = "H0 var 3=";
const char txtItemConfigInt3[] PROGMEM = "H1 var 4=";
const char txtItemConfigInt4[] PROGMEM = "H1 var 5=";
const char txtItemConfigInt5[] PROGMEM = "H1 var 6=";
const char txtItemConfigInt6[] PROGMEM = "H2 var 7=";
const char txtItemConfigInt7[] PROGMEM = "H2 var 8=";
const char txtItemConfigInt8[] PROGMEM = "H2 var 9=";
const sItemConfigInt dbItemConfigInt[] PROGMEM = {
  /*pointer,addr,max,min,set,name*/
  { &Value1,  0, 100, 10, 10, txtItemConfigInt0}, /*CONFGINT[0]*/
  { &Value2,  2, 100, 10, 20, txtItemConfigInt1}, /*CONFGINT[1]*/
  { &Value3,  4, 100, 10, 30, txtItemConfigInt2}, /*CONFGINT[2]*/
  { &Value4,  6, 100, 10, 40, txtItemConfigInt3}, /*CONFGINT[3]*/
  { &Value5,  8, 100, 10, 50, txtItemConfigInt4}, /*CONFGINT[4]*/
  { &Value6, 10, 100, 10, 60, txtItemConfigInt5}, /*CONFGINT[5]*/
  { &Value7, 12, 100, 10, 70, txtItemConfigInt6}, /*CONFGINT[6]*/
  { &Value8, 14, 100, 10, 80, txtItemConfigInt7}, /*CONFGINT[7]*/
  { &Value9, 16, 100, 10, 90, txtItemConfigInt8}  /*CONFGINT[8]*/
};
int readItemConfigInt(byte the) {    /*<-VAL[the]*  ++ */
  int * pointer = pgm_read_word(&dbItemConfigInt[the].pointer);
  return * pointer;
}
void readItemConfigIntfromPROGMEM(byte the) { /*VAL[the]<-PROGMEM[the] ++ */
  int * pointer = pgm_read_word(&dbItemConfigInt[the].pointer);
  *pointer      = pgm_read_word(&dbItemConfigInt[the].setValue);
}
void plusItemConfigInt(byte the) {/*if (VAL[the]<max[the]) ++VAL[the]*/
  int * pointer = pgm_read_word(&dbItemConfigInt[the].pointer);
  byte maxValue = pgm_read_byte(&dbItemConfigInt[the].maxValue);
  if (* pointer < maxValue) *pointer += 1;
}
void minusItemConfigInt(byte the) {/*if (VAL[the]>min[the]) --VAL[the]*/
  int * pointer = pgm_read_word(&dbItemConfigInt[the].pointer);
  byte minValue = pgm_read_byte(&dbItemConfigInt[the].minValue);
  if (* pointer > minValue ) *pointer -= 1;
}
char * readItemConfigIntName(byte the) { /* <- name[the] */
  return (char *)pgm_read_byte(&dbItemConfigInt[the].name);
}
#include <EEPROM.h>
void readItemConfigIntfromEEPROM(byte the) { /*VAL[the]<-EEPROM[the]*/
  int * pointer = pgm_read_word(&dbItemConfigInt[the].pointer);
  byte addr = pgm_read_byte(&dbItemConfigInt[the].addr);
  EEPROM.get(addr, *pointer);
}
void writeItemConfigIntToEEPROM(byte the) { /*EEPROM[the]<-VAL[the]*/
  int * pointer = pgm_read_word(&dbItemConfigInt[the].pointer);
  byte addr = pgm_read_byte(&dbItemConfigInt[the].addr);
  EEPROM.put(addr, *pointer);
}
void readAllItemConfigIntfromEEPROM() { /*VAL[All]<-EEPROM[All]*/
  for (int i = 0; i < numItemConfigInt; ++i) {
    readItemConfigIntfromEEPROM(i);
  }
}
void AllItemConfigIntPROGMEMtoEEPROM() { /*EEPROM[All]<-PROGMEM[the][All]*/
  for (int i = 0; i < numItemConfigInt; ++i) {
    int ConfigInt = pgm_read_word(&dbItemConfigInt[i].setValue);
    byte addr = pgm_read_byte(&dbItemConfigInt[i].addr);
    EEPROM.put(addr, ConfigInt);
  }
}
//-------------------
void initMenu( byte scr) {
  screen = scr;
  byte type = pgm_read_byte(&dbItemMenu[screen].type);
  byte num  = pgm_read_byte(&dbItemMenu[screen].num);
  if (type == ROOT)   line = pgm_read_byte(&dbItemRoot[num].top);
  if (type == HOLDER) line = pgm_read_byte(&dbItemHolder[num].top);
  fEdit = 0;
}
//-------------------
void VievMenu(byte screen, byte line, byte fEdit) {
  byte type = pgm_read_byte(&dbItemMenu[screen].type);
  byte num  = pgm_read_byte(&dbItemMenu[screen].num);
  if (type == ROOT) {
    byte top    = pgm_read_byte(&dbItemRoot[num].top);
    byte bottom = pgm_read_byte(&dbItemRoot[num].bottom);
    Serial.println();
    for (int i = top; i <= bottom; ++i) {
      Serial.println();
      if (i == line) {
        if (fEdit)Serial.print("*");
        else Serial.print(">");
      }
      else Serial.print(" ");
      byte t = pgm_read_byte(&dbItemMenu[i].type);
      byte n = pgm_read_byte(&dbItemMenu[i].num);
      if (t == CONFIGINT) {
        char * name = readItemConfigIntName(n);
        pgm_viev(name);
        int ItemValue = readItemConfigInt(n);
        Serial.print(ItemValue);
      }
      if (t == HOLDER) {
        char * name = readItemHolderName(n);
        pgm_viev(name);
      }
    }
  }
  if (type == HOLDER) {
    byte top    = pgm_read_byte(&dbItemHolder[num].top);
    byte bottom = pgm_read_byte(&dbItemHolder[num].bottom);
    Serial.println();
    for (int i = top; i <= bottom; ++i) {
      Serial.println();
      if (i == line) {
        if (fEdit)Serial.print("*");
        else Serial.print(">");
      }
      else Serial.print(" ");
      byte t = pgm_read_byte(&dbItemMenu[i].type);
      byte n = pgm_read_byte(&dbItemMenu[i].num);
      if (t == HOLDER) {
        char * name = readItemHolderName(n);
        pgm_viev(name);
      }
      if (t == CONFIGINT) {
        char * name = readItemConfigIntName(n);
        pgm_viev(name);
        int ItemValue = readItemConfigInt(n);
        Serial.print(ItemValue);
      }
    }
  }
}
//-----Компоновка------------------------------
void DoViev() {
  VievMenu(screen, line, fEdit);
}
Cl_Display Display(/*обработчик*/DoViev);
void DoMinus() {/*-*/
  Display.refresh();
  if (fEdit) { /*реж редактирования*/
    byte t = pgm_read_byte(&dbItemMenu[line].type);
    byte n = pgm_read_byte(&dbItemMenu[line].num);
    if (t == CONFIGINT) {
      minusItemConfigInt(n);
    }
  }
  else {/*реж передвиж*/
    byte type = pgm_read_byte(&dbItemMenu[screen].type);
    byte num = pgm_read_byte(&dbItemMenu[screen].num);
    if (type == ROOT) {
      byte top    = pgm_read_byte(&dbItemRoot[num].top);
      if (line > top ) --line;
    }
    if (type == HOLDER) {
      byte top    = pgm_read_byte(&dbItemHolder[num].top);
      if (line > top ) --line;
      else if (line == top ) {
        screen = pgm_read_byte(&dbItemHolder[num].father);
        byte t = pgm_read_byte(&dbItemMenu[screen].type);
        byte n = pgm_read_byte(&dbItemMenu[screen].num);
        if (t == ROOT) line = pgm_read_byte(&dbItemRoot[n].top);
        else         line = pgm_read_byte(&dbItemHolder[n].top);
      }
    }
  }
}
void DoPlus() {/*+*/
  Display.refresh();
  if (fEdit) { /*реж редактирования*/
    byte t = pgm_read_byte(&dbItemMenu[line].type);
    byte n = pgm_read_byte(&dbItemMenu[line].num);
    if (t == CONFIGINT) {
      plusItemConfigInt(n);
    }
  }
  else {/*реж передвиж*/
    byte type = pgm_read_byte(&dbItemMenu[screen].type);
    byte num = pgm_read_byte(&dbItemMenu[screen].num);
    if (type == ROOT) {
      byte bottom = pgm_read_byte(&dbItemRoot[num].bottom);
      if (line < bottom ) ++line;
    }
    if (type == HOLDER) {
      byte bottom = pgm_read_byte(&dbItemHolder[num].bottom);
      if (line < bottom ) ++line;
    }
  }
}
void DoExe() {/*exe*/
  Display.refresh();
  byte t = pgm_read_byte(&dbItemMenu[line].type);
  byte n = pgm_read_byte(&dbItemMenu[line].num);
  if (fEdit) { /*реж редактирования*/
    if (t == CONFIGINT) {
      writeItemConfigIntToEEPROM(n);
      fEdit = 0;
    }
    return;
  }
  else {     /*реж передвиж*/
    if (t == HOLDER   ) initMenu(line);
    if (t == CONFIGINT) fEdit = 1;
  }
}
Cl_sKeyboard Keyboard;
//--main()-------------------
void setup() {
  Serial.begin(9600);
  //AllItemConfigIntPROGMEMtoEEPROM();/*первичная настройка EEPROM из PROGMEM*/
  readAllItemConfigIntfromEEPROM(); /*прочитать из памяти*/
  initMenu(0);/*установить меню в начало*/
  Display.init();
  Keyboard.init();
}

void loop() {
  mill = millis();
  Display.run();
  Keyboard.run();
  if (Keyboard.read() == skMimus ) DoMinus();
  if (Keyboard.read() == skPlus  ) DoPlus();
  if (Keyboard.read() == skExe   ) DoExe();
}
/*Скетч использует 3554 байт (11%) памяти устройства. Всего доступно 30720 байт.
  Глобальные переменные используют 221 байт (10%) динамической памяти, оставляя 1827 байт для локальных переменных. Максимум: 2048 байт.
*/

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

qwone пишет:

Теперь попробуем заменить аналоговую клавиатару на клавиатуру на Serial

/**/
ПС: Попозже я ее оптимизирую и переведу на PROGMEM

Ну где-то так

/**/

Ни чё не понял .. а где там "конченный автомат"? Вы же не считаете, что раз обозвали метод run() и создали поле state то он сразу превратил класс в КА?

Я бы для начала, переформулировал задачу под решение методом конечных автоматов, а уже потом делал реализацию. Тут задача, вообще - какая?

Судя по тексту: есть 5 условных кнопок/команд: "влево", "вправо", "вверх", "вниз" и "выбор", принимаемых из Serial. По "нажатию кнопки" (приходу команды) выполнить свое действие. Где тут задача для КА? :)

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Arhat109-2 пишет:
Судя по тексту: есть 5 условных кнопок/команд: "влево", "вправо", "вверх", "вниз" и "выбор", принимаемых из Serial. По "нажатию кнопки" (приходу команды) выполнить свое действие. Где тут задача для КА? :)
Вот зачем мне жевать по 100 раз одно и тоже и забивать тему флудом. Если Вам не нужна моя концепция автомата, то и не нужны мои объяснения. У вас есть свое мнение об автомате. Замечательно. Я то тут причем.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

qwone пишет:

Arhat109-2 пишет:
Судя по тексту: есть 5 условных кнопок/команд: "влево", "вправо", "вверх", "вниз" и "выбор", принимаемых из Serial. По "нажатию кнопки" (приходу команды) выполнить свое действие. Где тут задача для КА? :)
Вот зачем мне жевать по 100 раз одно и тоже и забивать тему флудом. Если Вам не нужна моя концепция автомата, то и не нужны мои объяснения. У вас есть свое мнение об автомате. Замечательно. Я то тут причем.

Есть "общепринятое" понятие об КА как системе с конечным набором состояний. В этой задаче состояние одно: принять сигнал - сделать действие. Но если у Вас своя "концепция", то умолкаю..

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Ну да, а кнопки или что-то еще подобное,по вашему, это система с бесконечным набором состояний. Кнопку , а точнее программную часть тоже можно называть автоматом. А то что она переключается под действием механических сигналов, а не электрических, то это пусть Вас не смущает. Вот переключаю светодиод через Serial кнопки. Похоже по-ходу сумел организовать систему собитий. Так что Logik попозже будет попу рвать, говоря что я организовал не событийные события.

/**/
unsigned long mill;
//--------------------Cl_sKeyboard--------------------------------
/*события-нет /вкл/выкл/мигать*/
enum class event_t {None = 0, OFF, ON, Blink};
struct com_t {
  byte com;
  event_t event;
};
const byte numCom = 3;
const com_t MAP[numCom] PROGMEM = {
  {'l', event_t::OFF}, {'L', event_t::ON}, {'B', event_t::Blink}
};
class Cl_sKeyboard {
    event_t  event;
  public:
    bool is(event_t  e) {
      return e == event;
    }
    Cl_sKeyboard() {}
    void init() {
      Serial.begin(9600);
    }
    void run() {
      event = event_t::None;
      if (Serial.available() > 0) {
        byte a = Serial.read();
        for (int i = 0; i < numCom; i++) {
          if (a == pgm_read_byte(&(MAP[i].com))) {
            event = (event_t)pgm_read_byte(&(MAP[i].event));
            break;
          }
        }
      }
    }
};
//-----------------------------------------------------------------------
class Cl_Led {
  public:
    enum event_t {onNone = 0, onOFF, onON, onBlink};/*событие нет/Выкл/Вкл/Блинк*/
    event_t event;
  protected:
    byte pin;
    unsigned long past;
    unsigned long time = 500;
    enum state_t {sNone = 0, sOFF, sON, sBlink, sON2, sOFF2} state;/*состояние нет/Выкл/Вкл/Блинк/БлинкВКЛ/БлинкВыкл*/
    bool is(state_t s) {
      return s == state;
    }
    void stand(state_t s) {
      state = s;
      past = mill;
      switch (state) {
        case sNone:
          digitalWrite(pin, LOW);
          break;
        case sOFF:
          event = onOFF;
          digitalWrite(pin, LOW);
          break;
        case sON:
          event = onON;
          digitalWrite(pin, HIGH);
          break;
        case sBlink:
          event = onBlink;
          state = sON2;
        case sON2:
          digitalWrite(pin, HIGH);
          break;
        case sOFF2:
          digitalWrite(pin, LOW);
          break;
      }
    }
  public:
    bool is(event_t e) {
      return e == event;
    }
    Cl_Led(byte p): pin(p) {}
    void init() {
      pinMode(pin, OUTPUT);
      stand(sNone);
    }
    void run() {
      event = onNone;
      if (is(sON2) && mill - past >= time )stand(sOFF2);
      if (is(sOFF2) && mill - past >= time)stand(sON2);

    }
    void ON() {
      stand(sON);
    }
    void OFF() {
      stand(sOFF);
    }
    void blink() {
      stand(sBlink);
    }
};
//---------------Компоновка----------------------------------------------
Cl_sKeyboard Keyboard;
Cl_Led Led(/*пин*/13);
//----------------- -----------------------
void setup() {
  Keyboard.init();
  Led.init();
}
void loop() {
  mill = millis();
  Led.run();
  Keyboard.run();
  if (Keyboard.is(event_t::OFF)) {
    Led.OFF();
  }
  if (Keyboard.is(event_t::ON)) {
    Led.ON();
  }
  if (Keyboard.is(event_t::Blink)) {
    Led.blink();
  }

  if (Led.is(Cl_Led::onOFF)) {
    Serial.println("Led OFF");
  }
  if (Led.is(Cl_Led::onON)) {
    Serial.println("Led ON");
  }
  if (Led.is(Cl_Led::onBlink)) {
    Serial.println("Led Blink");
  }
}
/*Скетч использует 2372 байт (7%) памяти устройства. Всего доступно 30720 байт.
  Глобальные переменные используют 229 байт (11%) динамической памяти, оставляя 1819 байт для локальных переменных. Максимум: 2048 байт.
*/

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

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

Кнопку можно натянуть на конечный автомат, ибо как миниум 2 состояния у неё есть. Только стоит ли, кроме как "примера для"? :)

P.S. И мне кажется, что разумней не столько демонстрировать огромный код на классах (что сразу показывает проигрыш решения - стока писать лично мне - лениво), сколько попытаться показать КАК можно переформулировать "обыкновенное ТЗ" в постановку под КА.

Как пример: "светофор имеет 3 лампочки, которые загораются по порядку и горят 30сек - красный, 2сек - желтый и 30сек - зеленый". Можно решать с delay(), но можно переформулировать задачу:

"Светофор может находится в трех состояниях: 30сек. включен красный, 2сек включен желтый и 30сек включен зеленый. Изменением состояний управляет системное время millis(). Переходы между состояниями: выключить зеленый + включить красный, выключить красный + включить желтый, .."

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

P.P.S. правильная постановка задачи - 50% решения.

DetSimen
DetSimen аватар
Онлайн
Зарегистрирован: 25.01.2017

Архат, не святотацтвуй (с его точки зрения), ты же виишь, Пуха перекрестили, теперь он послушник Секты Свидетелей Всемогущества Святого Конечного Автомата.  Вот пусь пока живет в счастливом неведении, главное, чтоб квартиру на них не переписал.  

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Так сам вроде как в этой секте .. думаю в ней "раскол" не нужен.. :)

Преподавал по лету КА детишкам, оказалось что переформулировка задачи (как выше) приводит к существенно лучшему восприятию и построению алгоритмов и внезапно, оказывается: -"Вы им (12лет) преподаете конечные автоматы? Это же очень сложно!" .. совсем не так, а очень даже просто и им понятней чем как преодолеть delay() в коде.

У qwone, КМК, есть небольшое недопонимание КА: кроме состояний, есть граф переходов и именно он реализует "методы переходов", а слушатель новостей всего лишь .. поставляет новость о переходе или .. не поставляет (ничего не происходит - состояние неизменно).

Отсюда: класс КА должен содержать не только переменную state, но и иметь МЕТОДЫ переходов (возможно и матрицу переходов) а также СЛУШАТЕЛЯ новостей. Далее сюда в С++ настырно просится "базовый" класс КА с виртуальными наследуемыми методами .. а это есть безусловное зло для микроконтроллера с гарвардской архитектурой.

Отсюда: реализация на "С" базовых методов КА есть достаточное действо и скрипач (С++) тут не нужен. :)

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Итак пусть нам надо такой Автомат

И скетч к нему.

/**/
unsigned long mill;
//-------------------------------------
class Cl_AAA {
  protected:
    /*сщстояние нейтраль/ A/ B/ C/ D/ E*/
    enum state_t : byte {sNone = 0, sA, sB, sC, sD, sE} state = sNone;
    unsigned long time = 500;
    unsigned long past;
    void stand(state_t s) {
      state = s;
      past = mill;
      switch ((byte)state) {
        case sA:
          Serial.println("state=sA");
          break;
        case sB:
          Serial.println("state=sB");
          break;
        case sC:
          Serial.println("state=sC");
          break;
        case sD:
          Serial.println("state=sD");
          break;
        case sE:
          Serial.println("state=sE");
          break;
      }
    }
    bool is(state_t s) {
      return state == s && mill - past >= time;
    }
  public:
    Cl_AAA() {}
    void init() {
      Serial.begin(9600);
      stand(sA);
    }
    void run() {
      if (is(sA)) stand(sB);
      if (is(sB)) stand(sC);
      if (is(sC)) stand(sD);
      if (is(sD)) stand(sE);
      if (is(sE)) stand(sA);
    }
};
//-----------------------------------
Cl_AAA AAA;
//----------------------------------
void setup() {
  AAA.init();
}
void loop() {
  mill = millis();
  AAA.run();
}
/**/

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Но полезная переменная внутри автомата - состояние совершенно бесполезна для устройств снаружи. Ну зачем нам знать в каком состоянии находится автомат. Нам важнее знать когда он перешел и сделать все обработчики этого события снаружи. Так что приходится вводить такую переменную событие

/**/
unsigned long mill;
//-------------------------------------
class Cl_AAA {
  public:
    /*событие нейтраль/ A/ B/ C/ D/ E */
    enum event_t : byte {eNone = 0, eA, eB, eC, eD, eE};
    bool is(event_t e) { //<-проверка на это событие
      return event == e;
    }
  protected:
    /*состояние нейтраль/ A/ B/ C/ D/ E */
    enum state_t : byte {sNone = 0, sA, sB, sC, sD, sE} state = sNone;
    event_t  event = eNone;
    unsigned long time = 500;
    unsigned long past;
    void stand(state_t s) {
      state = s;
      past = mill;
      switch ((byte)state) {
        case sA:
          event = eA;//<-устанавливаем событие в A
          break;
        case sB:
          event = eB;
          break;
        case sC:
          event = eC;
          break;
        case sD:
          event = eD;
          break;
        case sE:
          event = eE;
          break;
      }
    }
    bool is(state_t s) {
      return state == s && mill - past >= time;
    }
  public:
    Cl_AAA() {}
    void init() {
      stand(sA);
    }
    void run() {
      event = eNone;//<--сбрасываем событие
      if (is(sA)) stand(sB);
      if (is(sB)) stand(sC);
      if (is(sC)) stand(sD);
      if (is(sD)) stand(sE);
      if (is(sE)) stand(sA);
    }
};
//-----------------------------------
Cl_AAA AAA;
//----------------------------------
void setup() {
  Serial.begin(9600);
  AAA.init();
}
void loop() {
  mill = millis();
  AAA.run();
  if (AAA.is(Cl_AAA::eA))Serial.println("state=sA");
  if (AAA.is(Cl_AAA::eB))Serial.println("state=sB");
  if (AAA.is(Cl_AAA::eC))Serial.println("state=sC");
  if (AAA.is(Cl_AAA::eD))Serial.println("state=sD");
  if (AAA.is(Cl_AAA::eE))Serial.println("state=sE");
}
/**/

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

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

qwone пишет:

Итак пусть нам надо такой Автомат

Допустим, ниже обобщение без КА, и с возможностью юзать любые интервалы между командами - например, подгружая следующую команды из файла макроописания, например, чтобы сделать алгоритм работы чего-либо - сменяемым, без перезакачки прошивки:

class Command
{
	uint16_t mDelay;
	public:
		Command() {}
		
	uint16_t getDelay() 
	{ 
		return mDelay; 
	}
	
	void run() 
	{ 
		Serial.print("run after ");
		Serial.println(mDelay);
	}
	
	Command* next() 
	{
		// загружаем следующую команду
		mDelay = random(200,2000);
		
		static uint8_t stop = 0;
		// тупо останавливаемся на каком-либо шаге
		if(++stop > 99)
			return NULL;
		
		return this;
	}
	
};

class Execution
{
	unsigned long past;
	Command* mCmd;
	
	public:
	
	Execution(Command* c)
	{
		past = 0;
		mCmd = c->next();
	}
	
	void run()
	{
		if(!mCmd)
			return;
		
		if(millis() - past > mCmd->getDelay())
		{
			mCmd->run();
			mCmd = mCmd->next();
			past = millis();
		}
	}
	
	
	
	
};

Command runner;
Execution exec(&runner);

void setup()
{
	
}
void loop()
{
	exec.run();
}

Виртуальные методы и наследование - спецом не вводил, но при желании и необходимости - оно можно.

Пух, я вот одного не пойму - к чему твои вырожденные примеры, к тому же такие, где мухи с котлетами в одном фарше? У тебя, например, переменная mill - глобальная, накохер? При том, что в одном классе аж две четырёхбайтовых переменных - накохер? В приведённом тобой выше примере КА - накохер сам КА, если его там - просто не надо, это тупой перебор интервалов, при этом весь интерес, что по истечению интервала просто выполняется другой кусок кода - накохер там КА? Кмк, ты сам уже запутался в КА и в посылах - зачем они нужны. Знаешь, это как купил молоток - и пока всё им не переколотишь - не остановишься, и похер - гвоздь торчит или шуруп - всё заколотим к ебенематери.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Чуть переписал скетч что выше. Теперь можно задавать время перескока в создании объекта.Косметическая доработка.

/**/
unsigned long mill;
//-------------------------------------
class Cl_AAA {
  public:
    /*событие нейтраль/ A/ B/ C/ D/ E */
    enum event_t : byte {eNone = 0, eA, eB, eC, eD, eE};
    bool is(event_t e) { //<-проверка на это событие
      return event == e;
    }
  protected:
    event_t  event = eNone;
    /*состояние нейтраль/ A/ B/ C/ D/ E */
    enum state_t : byte {sNone = 0, sA, sB, sC, sD, sE} state = sNone;
    unsigned long time = 500;
    unsigned long past;
    void stand(state_t s) {
      state = s;
      past = mill;
      switch ((byte)state) {
        case sA:
          event = eA;//<-устанавливаем событие в A
          break;
        case sB:
          event = eB;
          break;
        case sC:
          event = eC;
          break;
        case sD:
          event = eD;
          break;
        case sE:
          event = eE;
          break;
      }
    }
    bool is(state_t s) {
      return state == s && mill - past >= time;
    }
    bool is(state_t s, unsigned long t) {
      return state == s && mill - past >= t;
    }
  public:
    Cl_AAA(unsigned long t = 500): time(t) {
    }
    void init() {
      stand(sA);
    }
    void run() {
      event = eNone;//<--сбрасываем событие
      if (is(sA, time)) stand(sB);
      if (is(sB, time)) stand(sC);
      if (is(sC, time)) stand(sD);
      if (is(sD, time)) stand(sE);
      if (is(sE, time)) stand(sA);
    }
};
//-----------------------------------
Cl_AAA AAA(/*время перескока*/200);
//----------------------------------
void setup() {
  Serial.begin(9600);
  AAA.init();
}
void loop() {
  mill = millis();
  AAA.run();
  if (AAA.is(Cl_AAA::eA))Serial.println("state=sA");
  if (AAA.is(Cl_AAA::eB))Serial.println("state=sB");
  if (AAA.is(Cl_AAA::eC))Serial.println("state=sC");
  if (AAA.is(Cl_AAA::eD))Serial.println("state=sD");
  if (AAA.is(Cl_AAA::eE))Serial.println("state=sE");
}
/**/

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

DIYMan, Вас зовут видно Потапов из Ералаша.https://www.youtube.com/watch?v=nZAvX2BvpPc

Разумеется я здесь на простых примерах раскладываю опять же простые концепции.  Но я не мешаю Вам открыть свою тему и вести уже разговоры на сложные темы. Вот сколько вы открыли тем и что. Почему Вы лезите сюда, а не в Ваши темы. А знаю - ВЫ ЗДЕСЬ САМЫЙ УМНЫЙ.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

О как, млять. Ещё один ЧСВ-шник у нас завёлся, робяты. Шо деется-то :) Да др@чи тут один, пжалста - кроме тебя твои никчёмные поделия никому не интересны, как видишь. А я так, забежал, грешным делом подумав - может, прислушается и уже наконец-то начнёт чему-то учиться, вместо плясок на месте? Ошибался, жестоко. Прощенья просим, барин.

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

   

semaawp
semaawp аватар
Offline
Зарегистрирован: 29.05.2017

qwone пишет:

   


Видел тут на сайте много разных классов, появился вопрос. Может я чего то непойму, но это ведь классы C++? Просто я ковырял GNU C reference manual, и под термином class я видел только классы памяти. Это ведь разные вещи?

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

semaawp, classы в Си это мощный инструмент. И да в этой теме используются классы причем на 2% . Я не вижу смысла выкладывать все про классы здесь . Это очень много, есть соответствующая литература, а и я не смогу достаточно все это приподнести. А если я начну объяснять здесь, то от форумчан будет ну очень много критики. 

semaawp
semaawp аватар
Offline
Зарегистрирован: 29.05.2017

Основное отличие языка программирования С++ от С состоит в том, что в С нет классов, а следовательно язык С не поддерживает ООП, в отличие от С++.

Я вот это хотел узнать, но все равно спасибо

IvanP
Offline
Зарегистрирован: 19.11.2018

Скопипастить - это я еще могу, даже кое что поправить, а вот с нуля писать под классы, уже учиться видимо поздновато. Решил слелать автоматику для горелки на отработке самому. Понравились классы qwone , хоть гуру его склоняют тут как хотят, но главное они работают и занимают мало места.

Что хочется от ТС - чтобы помог переписать мой скетч под свои классы, может и восьмую атмегу влезет. Заранее спасибо.

//**/
#define pinds1 14  //пины для подключения Далласов
#define pinds2 15
#define timeDallas 1000 //тут определяем время через которое меряем  температуру далласа 1 секунда
#define timePurge 5000 //тут определяем время продувки 5 секунд
#define timeIgnition 3000 //тут определяем время розжига 3 секунд
#define set_temp_water 550 //тут температура котла
#define hysteresis 100 // гистерезис на нагрев котла
unsigned long mill;// переменная для millis()
unsigned long millDallas;
unsigned long millPurge;
unsigned long millIgnition;
byte valIgnition=0;
bool FlagDallas = 0;

bool rejim = 0;// реЖим работы 0-ожидание, 1-Auto
typedef void (*pDo)() ;// тип -функция обработчик
//----------------------------------------
#include <EEPROM.h>
//#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <OneWire.h>

int temp[2];//текущая температура в термометрах  
int tempCorrect[2]={0,0};//температура корректировки текущего термометра  
//Если на 1-шине сидит один датчик, то его ID знать не требуется!
OneWire ds1(pinds1);
OneWire ds2(pinds2);

OneWire *pinds[]={&ds1,&ds2}; 
#define START_CONVERT 0          
#define READ_TEMP 1

enum event_State : byte {eNone = 0, eExpectation=1, eHeating=2, ePurge=3, eIgnition=4, eCombustion=5, eAutoExpectation=6, eError=7};// режимы работы КА

event_State appState=eNone;



LiquidCrystal_I2C lcd(0x3F, 16, 2);  // инициализация библиотеки дисплея



// чтение
int EEPROM_int_read(int addr) {
  byte raw[2];
  for (byte i = 0; i < 2; i++) raw[i] = EEPROM.read(addr + i);
  int &num = (int&)raw;
  return num;
}
// запись
void EEPROM_int_write(int addr, int num) {
  byte raw[2];
  (int&)raw = num;
  for (byte i = 0; i < 2; i++) EEPROM.write(addr + i, raw[i]);
}
// обновить
void EEPROM_int_update(int addr, int num) {
  if (EEPROM_int_read(addr) != num) EEPROM_int_write(addr, num);
}
//------Cl_Display----------------------
// класс регулярный вывод на дисплей
class Cl_Display {
  protected:
    pDo Do;//обработчик
    bool refreshON = 1; //сигнал обновить
  public:
    /*конструктор*/
    Cl_Display(pDo D): Do(D) {}
    /*инициализация-вставить в setup()*/
    void init() {
      /* my  */
      //  lcd.begin();          // Старт дисплея
      lcd.init();          // Старт дисплея
      lcd.backlight();
      lcd.print("helloy");
      delay(2000);
      /* my  */
    }
    /*работа-вставить в loop()*/
    void run() {
      if (refreshON) {
        refreshON = 0;
        Do();
      }
    }
    void refresh() {
      refreshON = 1;
    }
    /*записать новый обработчик*/
    void write( pDo D) {
      Do = D;
    }
};
//------Cl_Btn----------------------
// класс кнопка
enum {sbNONE = 0, sbClick, sbLong}; /*состояние не изменилось/клик/долгое наж*/
class Cl_Btn {
  protected:
    const byte pin;
    pDo Do;//обработчик
    bool bounce = 0;
    bool btn = 1, oldBtn;
    unsigned long past;
    const uint32_t time = 4000 ;  //500 ;
    bool flag = 0;
    uint32_t past_flag = 0 ;

  public:
    byte state;
    /*конструктор*/
    Cl_Btn(byte p, pDo D): pin(p), Do(D) {}
    /*инициализация-вставить в setup()*/
    void init() {
      pinMode(pin, INPUT_PULLUP);
    }
    /*работа-вставить в loop()*/
    void run() {
      state = sbNONE;

      bool newBtn = digitalRead(pin);
      if (!bounce && newBtn != btn) {
        bounce = 1;
        past = mill;
      }
      if (bounce && mill - past >= 10) {
        bounce = 0 ;
        oldBtn = btn;
        btn = newBtn;
        past = mill;

        if (!btn && oldBtn) {
          flag = 1;
          past_flag = mill;

        }
        if (!oldBtn && btn && flag && mill - past_flag < time ) {
          flag = 0;
          state = sbClick;
          Do();
        }

        if (flag && mill - past_flag >= time ) {
          flag = 0;
          state = sbLong;
          rejim = !rejim;
          //          Do();

        }

      }
    }
    /*записать новый обработчик*/
    void write( pDo D) {
      Do = D;
    }
    byte read() {
      return state;
    }

};

//------Cl_BtnR----------------------
class Cl_BtnR : public Cl_Btn {      /* класс кнопка с повтором при удерж кнопки*/
  protected:
  public:
    /*конструктор*/
    Cl_BtnR(byte p, pDo D): Cl_Btn(p, D) {}  ////проверить
    /*работа-вставить в loop()*/
    void run() {
      bool newBtn = digitalRead(pin);
      if (!bounce && newBtn != btn) {
        bounce = 1;
        past = mill;
      }
      if (bounce && mill - past >= 10) {
        bounce = 0 ;
        oldBtn = btn;
        btn = newBtn;
        past = mill;
        if (!btn && oldBtn) Do();
      }
      if (!newBtn && !btn && mill - past >= 300) {
        past = mill;
        Do();
      }
    }
};

class Cl_Check : public Cl_Btn {      /* класс датсик - это тоже кнопка */
  protected:
  public:
    /*конструктор*/
    Cl_Check(byte p, pDo D): Cl_Btn(p, D)  {}  ////проверить
    /*работа-вставить в loop()*/
    void run() {
      state = sbNONE;

      bool newBtn = digitalRead(pin);
      if (!bounce && newBtn != btn) {
        bounce = 1;
        past = mill;
      }
      if (bounce && mill - past >= 10) {
        bounce = 0 ;
        oldBtn = btn;
        btn = newBtn;
        past = mill;
        state = sbLong; 
        if (!btn && oldBtn) { //тут датчик погас ?
                state = sbNONE; 
                Do();
         }

              
     }
   }

   
};


//-----Компоновка----------------------
// база данных настроечных переменных
const int maxNumber = 20;
int number = 0;
int  var0,  var1,  var2,  var3,  var4,  var5,  var6,  var7,  var8, var9; /*настроечные переменные !!!!!*/
int var10, var11, var12, var13, var14, var15, var16, var17, var18, var19;
int * const pVar[maxNumber] PROGMEM = {
  &var0 ,  &var1,  &var2,  &var3,  &var4,  &var5,  &var6,  &var7,  &var8,  &var9,
  &var10, &var11, &var12, &var13, &var14, &var15, &var16, &var17, &var18, &var19
}; /*указатели на переменные*/
const char name0[] PROGMEM = "var0"; /*имя*/
const char name1[] PROGMEM = "var1";
const char name2[] PROGMEM = "var2";
const char name3[] PROGMEM = "var3";
const char name4[] PROGMEM = "var4";
const char name5[] PROGMEM = "var5";
const char name6[] PROGMEM = "var6";
const char name7[] PROGMEM = "var7";
const char name8[] PROGMEM = "var8";
const char name9[] PROGMEM = "var9";
const char name10[] PROGMEM = "shim3";
const char name11[] PROGMEM = "var11";
const char name12[] PROGMEM = "var12";
const char name13[] PROGMEM = "var13";
const char name14[] PROGMEM = "var14";
const char name15[] PROGMEM = "var15";
const char name16[] PROGMEM = "var16";
const char name17[] PROGMEM = "var17";
const char name18[] PROGMEM = "var18";
const char name19[] PROGMEM = "var19";
const char* const name[maxNumber] PROGMEM = {
  name0 , name1 ,  name2,  name3,  name4,  name5,  name6,  name7,  name8,  name9,
  name10, name11, name12, name13, name14, name15, name16, name17, name18, name19
};
const int   addr[maxNumber] PROGMEM = { /*адресс*/
  0  , 2  , 4  , 6  , 8  , 10 , 12 , 14 , 16 , 18,
  20 , 22 , 24 , 26 , 28 , 30 , 32 , 34 , 36 , 38
};
const int minVar[maxNumber] PROGMEM = { /*минимум*/
  10 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 10,
  10 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 10 , 10
};
const int maxVar[maxNumber] PROGMEM = { /*максимум*/
  100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
  100, 100, 100, 100, 100, 100, 100, 100, 100, 100
};
//
char buffer[10];
int var_, minVar_, maxVar_;
char *name_;





/*вывевети на экран*/
void vievVar() {
//  Serial.print("temp[0]=");
////  Serial.print(": ");
//  Serial.print(var_);
//  Serial.println();
  lcd.setCursor(0, 0);                 // устанавливаем курсор в 2-ом столбце, 3 строка (начинается с 0)
     lcd.print("T1=");
    lcd.print(float (temp[0])/10,1);// печать температуры на дисплей temp
 
//    lcd.setCursor(0, 1);
//    lcd.print(Upit,1);// печать температуры на дисплей temp
    
    lcd.print(" T2=");
    lcd.print(float(temp[1])/10);// печать температуры на дисплей temp
    
    if (rejim){
   lcd.setCursor(15, 1);
    lcd.print("A");
    }else
    {
    lcd.setCursor(15, 1);
    lcd.print(" ");    
    }
    lcd.setCursor(0, 1);
//  lcd.setCursor(0, 1);
//    //    lcd.print("H=");
  lcd.print(buffer);           // печать влажности на дисплей
  lcd.print("=");
//  lcd.print(rejim);

  lcd.print(var_);
  lcd.print("    ");

}
/*прочитать var*/
void loadVar() {
  var_ = *(int*)pgm_read_word(&pVar[number]);
  strcpy_P(buffer, (char*)pgm_read_word(&(name[number])));
  minVar_ = pgm_read_byte_near(minVar + number);
  maxVar_ = pgm_read_byte_near(maxVar + number);
}
/*прочитать все из EEPROM*/
void loadAll() {
  for (int i = 0; i < maxNumber; ++i) {
    *(int*)pgm_read_word(&pVar[i]) = EEPROM_int_read(pgm_read_byte_near(addr + i));
  }
  loadVar();
};
/*записать var*/
void saveVar() {
  *(int*)pgm_read_word(&pVar[number]) = var_;
  EEPROM_int_update(pgm_read_byte_near(addr + number), var_);
}

// дисплей
Cl_Display Display(/*обработчик*/vievVar);
/*увеличить var1*/
void incVar() {
  Display.refresh();
  ++var_;
  if (var_ > maxVar_) var_ = maxVar_;
}
/*уменьшить var1*/
void decVar() {
  Display.refresh();
  --var_;
  if (var_ < minVar_) var_ = minVar_;
}
// кнопки
Cl_BtnR Btn1(/*пин*/3,/*обработчик*/incVar);/*инкрем*/
Cl_BtnR Btn2(/*пин*/4,/*обработчик*/decVar);/*декримент*/
void DoBtn3() {
  Display.refresh();
  saveVar();
  ++number;
  if (number >= maxNumber) number = 0;
  loadVar();
}
Cl_Btn Btn3(/*пин*/5,/*обработчик*/DoBtn3);/*Next*/

//---------------------------------------------------------------
// класс исполнительный механизм
#define ON_ HIGH
class Cl_Isp_Meh { 
  protected:
    const byte pin;
    pDo Do;//обработчик

  public:
    /*конструктор*/
    Cl_Isp_Meh(byte p): pin(p) {}
    /*инициализация- вставить в setup*/
    void init() {
       pinMode(pin, OUTPUT);
       digitalWrite(pin, ON_);
    }
    /*работа- вставить в loop*/
    void run() {}
    /*включить*/
    void ON() {
        digitalWrite(pin, !ON_);    }
    /*выключить*/
    void OFF() {
       digitalWrite(pin, ON_);    }
};
void DoCheck() {
 // if (Check.read()==sbNONE) SetState(eError);
 // Serial.println("Transporter Swap");
}
Cl_Check Check(/*пин счетчика*/10,/*обработчик*/&DoCheck);

Cl_Isp_Meh ClapanOil(/*пин*/6);//пин маслянного клапана
Cl_Isp_Meh ClapanAir(/*пин*/7);//пин воздушного клапана
Cl_Isp_Meh MotorSecondaryAir(/*пин*/11);//пин включения мотора вентилятора вторичного воздуха
Cl_Isp_Meh HeatingOil(/*пин*/8);//пин нагревателя
Cl_Isp_Meh pinIgnition(/*пин*/9);//пин реле розжига
Cl_Isp_Meh pinAuto(/*пин*/13);//пин индикации автоматического режима
Cl_Isp_Meh pinError(/*пин*/17);//пин индикации аварии
//--------------------------------------------

//-----main----------------------
void setup() {
  Serial.begin(9600);
  TCCR2B = TCCR2B & 0b11111000 | 0x07; //настроим второй таймер на шим
  
  loadAll();
  Display.init();
  Btn1.init();
  Btn2.init();
  Btn3.init();
  Check.init();
ClapanOil.init();
ClapanAir.init();
MotorSecondaryAir.init();
HeatingOil.init();
pinIgnition.init();
pinAuto.init();
pinError.init();
  
  for (int i=0; i<2; i++){
  tempProcess(START_CONVERT,*pinds[i]);//конвентируем Dallas 
 }

millDallas=millis();
millPurge=millis();
millIgnition=millis();
}

void SetState(event_State newState){
  if (appState == newState) return;     // если мы уже в этом состоянии - выход
  appState = newState;      // запомним новое состояние


  switch (appState)
  {
  case eNone:   // если мы ПЕРЕШЛИ в это состояние, это ошибка
    SetState(eError);
    break;

  case eExpectation:  // в режиме ожидания 
    ClapanOil.OFF();
    ClapanAir.OFF();
    MotorSecondaryAir.OFF();
    HeatingOil.OFF();
    pinIgnition.OFF();
    pinAuto.OFF();
    pinError.OFF();  
      break;
  case eHeating:  // в режиме продувка 
    ClapanOil.OFF();
    ClapanAir.OFF();
    MotorSecondaryAir.OFF();
    HeatingOil.ON();
    pinIgnition.OFF();
    pinAuto.ON();
    pinError.OFF();  
      break;
  case ePurge:  // в режиме продувка 
    ClapanOil.OFF();
    ClapanAir.OFF();
    MotorSecondaryAir.ON();
    HeatingOil.ON();
    pinIgnition.OFF();
    pinAuto.ON();
    pinError.OFF();  
      break;
  case eIgnition:  // в режиме розжиг 
    ClapanOil.ON();
    ClapanAir.ON();
    MotorSecondaryAir.ON();
    HeatingOil.ON();
    pinIgnition.ON();
    pinAuto.ON();
    pinError.OFF();  
      break;
  case eCombustion:  // в режиме горение 
    ClapanOil.ON();
    ClapanAir.ON();
    MotorSecondaryAir.ON();
    HeatingOil.ON();
    pinIgnition.OFF();
    pinAuto.ON();
    pinError.OFF();  
      break;
  case eAutoExpectation:  // в режиме Ожидание в автомате 
    ClapanOil.OFF();
    ClapanAir.OFF();
    MotorSecondaryAir.OFF();
    HeatingOil.OFF();
    pinIgnition.OFF();
    pinAuto.ON();
    pinError.OFF();  
      break;

  case eError:     // если ошибка
    ClapanOil.OFF();
    ClapanAir.OFF();
    MotorSecondaryAir.OFF();
    HeatingOil.OFF();
    pinIgnition.OFF();
    pinAuto.OFF();
    pinError.ON();  
//    cli();      // входим в клинч
//    abort();    // выход только по Reset
    break;
  default:
    break;
  }
  
}

void loop() {
  mill = millis();
  Display.run();
  Btn1.run();
  Btn2.run();
  Btn3.run();
  Check.run();
 
  if (Btn3.read() == sbLong) Display.refresh();
    if (FlagDallas)
    {
      thermostat ();
      Display.refresh();
      millDallas = mill;
      FlagDallas = 0;
    }
    if (mill - millDallas >=  timeDallas) { 
      FlagDallas = 1; 
    }
    //пока время возьмем как умеем, позже перепишу взяв параметры из меню
    if (rejim){
      if (appState == eExpectation) SetState(eHeating); //  если только включили автоматический режим, включаем нагрев
      
      if (appState == eHeating){ //если нагрелось больше 65 градусов и не горит включаем продувку
        if (temp[0]>=650 && Check.read()==sbNONE) SetState(ePurge);
        millPurge=mill; //засекаем вреня на продувку
  
        }
        
       if (appState == ePurge && mill-millPurge>=timePurge){
        SetState(eIgnition); //продули пытаемся зажечь
        millIgnition=mill; //засекаем вреня на розжиг
         valIgnition++; //увеличиваем счетчик циклов розжига
         if (valIgnition>3) SetState(eError); //Больше трех раз не зажгли ошибка
        }
        
       if (appState == eIgnition && Check.read()==sbLong){ SetState(eCombustion);
       }// подожгли 
       if (appState == eIgnition && Check.read()==sbNONE && mill-millIgnition>=timeIgnition) SetState(ePurge); //если не загорелось, возвращаемся в продувку
       
        if (appState == eCombustion && Check.read()==sbLong){//если горит 
          if (temp[1] >= set_temp_water + hysteresis) {//и температура воды меньше установленной + гистерезис
           SetState(eAutoExpectation); // включаем ожидание
          }
        }
        if (appState == eAutoExpectation && temp[1] <= set_temp_water - hysteresis) SetState(eHeating); //если уала ниже чем установленная температура - гистерезис
       //возвращаемся к нагреву
    }else{ 
      SetState(eExpectation);
      
      }
    
}

//enum event_State : byte {eNone = 0, eExpectation=1, eHeating=2, ePurge=3, eIgnition=4, eCombustion=5, eAutoExpectation=6, eError=7};// режимы работы КА


void thermostat () {
  for (int i=0; i<2; i++){
  temp[i]= tempProcess(READ_TEMP,*pinds[i])+tempCorrect[i];//читаем Температуру и корректировку к ней
  tempProcess(START_CONVERT,*pinds[i]);
   } 
}
//============================== 
int tempProcess(boolean ch,OneWire &ds){
  int t=0; 
  if(!ch){
    ds.reset(); 
    ds.write(0xCC);
    ds.write(0x44);
  }
  else{
    ds.reset();
    ds.write(0xCC);
    ds.write(0xBE);
    t= ds.read(); 
    t = t | (ds.read()<<8); 
    //return  t>>4;//целые *C, десятые отброшены
    //return  (t+8)>>4;//целые *С с округлением
    return  (t*10)>>4;//целое в десятых *C (214=>21,4*C)
  }
}

void beeper(int duration) {
//  tone(beeper_pin, 2000, duration);
}
//Скетч использует 10 676 байт (67%) памяти устройства. Всего доступно 15 872 байт.
//Глобальные переменные используют 680 байт динамической памяти.

 

IvanP
Offline
Зарегистрирован: 19.11.2018

Еще вентиляторы и насосы будут работать от шим, сам пытался под них класс написать, но почему - то не работает

class Cl_Isp_Meh_Shim  {      // класс исполнительный механизм с Шим на 3 или 11 выводе
  protected:
    const byte pin;
    pDo Do;//обработчик

  int var_shim;
  int nomer_shim;
  public:
    /*конструктор*/
    Cl_Isp_Meh_Shim(byte p,int nomer, pDo D): pin(p),nomer_shim(nomer), Do(D) {}
    /*работа-вставить в loop()*/
    
    void run() {
     var_shim=*(int*)pgm_read_word(&pVar[nomer_shim]);
     analogWrite(pin, map(var_shim,0,100,0,255));
   }
};

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

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

IvanP
Offline
Зарегистрирован: 19.11.2018
горелка состоит из:
1.Проточного нагревателя масла, со встроенным контроллером температуры , мы по своему датчику temp[0]
контролируем нагрелось масло до минимальной указанной в настройках, и режим аварии, если температура масла
превысила максимальную из настроек.
2.Клапан маслянный.
3.Клапан первичного воздуха, подается от компрессора, мощность подачи первичного воздуха нами не регулируется.
4.Вентилятор вторичного воздуха, нами регулируется для режима продувки, розжига и рабочего режима.
5.Датчик температуры воды в котле temp[1], по нему контролируем работу котла в автоматическом режиме.
6.Датчик огня, понятно горит или не горит.
 
имеем два основных режима ручной и авто.
В ручном можем задавать настройки и проверять работу отдельных устройств.
В автоматическом:
1. по температуре temp[1] контролируем, если температура воды в котле упала ниже заданной в настройках,
начинаем розжиг горелки. 
2. включаем режим нагрева масла в проточном нагревателе.
3. Если достигли заданной температуры масла, включаем продувку на 100% мошности вентилятора продувки 
и на время задаваемое в настройках.
4.Вентилятор продувки включаем на мощьность для розжига из настроек.Подаем напряжение на трансформатор розжига
на время заданное в настройках, или меньшее, если загорелось раньше. Если не загорелось, идем к пункту 3.
 т.е пытаемся разжечь заново. Если три раза не зажгли переходим в режим аварии, из которого выход только в ручную.
5.Вентилятор продувки включаем на мощность для рабочего режима из настроек.
6.Греем пока температура воды в котле temp[1] не превысит максимальную из настроек.
7.Если в рабочем режиме погасло, пытаемся трижды разжеч возвращаясь к пункту 3. не получилось - авария.
8. Если температура воды в котле превысила максимальную из настроек, гасим котел и переходим в режим ожидания
 в автоматическом режиме.
9. переходим к пункту 1.
 
ну как-то так... в перспективе отправлять сообщения о работе котла по gsm модему хоянину по СМС или на сервер по GPRS.
qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016
/**/
template <typename T> inline Print & operator << (Print &s, T n) {
  s.print(n);
  return s;
}
typedef void (*pDo)();
pDo run;
bool state;
unsigned long past;
void stand(byte s) {
  state = s;
  past = millis();
  switch (state) {
    case false:
      run = []{
        if (millis() - past >= 500) {
          stand(true);
          Serial << "ON\n";
        }
      };
      break;
    case true:
      run = []{
        if (millis() - past >= 500) {
          stand(false);
          Serial << "OFF\n";
        }
      };
      break;
  }
}
void setup() {
  Serial.begin(9600);
  stand(false);
}

void loop() {
  run();
}

Blink

/**/
typedef void (*pDo)();
//------------------
const byte pinLed = 13;
pDo run;
bool state;
unsigned long past;
const unsigned long time = 500;
void stand(byte s) {
  state = s;
  past = millis();
  switch (state) {
    case false:
      digitalWrite(pinLed, false);
      run = [] {
        if (millis() - past >= time) {
          stand(true);
        }
      };
      break;
    case true:
      digitalWrite(pinLed, true);
      run = [] {
        if (millis() - past >= time) {
          stand(false);
        }
      };
      break;
  }
}
//-------------------------------
void setup() {
  pinMode(pinLed, OUTPUT);
  stand(false);
}

void loop() {
  run();
}

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Возьмем код отсюда https://www.drive2.ru/b/2867939/ и перепишем как следует. Итак исходник

//Управляем роботом с помощью BLUETOOTH.
//В качестве управляющего устройства используем ANDROID устройство с программой Bluetooth RC Controller.

// *********************** Установка выводов моторов ************************
int MotorLeftSpeed = 5; // Левый (А) мотор СКОРОСТЬ — ENA
int MotorLeftForward = 4; // Левый (А) мотор ВПЕРЕД — IN1
int MotorLeftBack = 2; // Левый (А) мотор НАЗАД — IN2
int MotorRightForward = 8; // Правый (В) мотор ВПЕРЕД — IN3
int MotorRightBack = 7; // Правый (В) мотор НАЗАД — IN4
int MotorRightSpeed = 6; // Правый (В) мотор СКОРОСТЬ — ENB

// ********************** Для управления по блютуз ****************************
char btCommand = 'S';// Задаем переменную BLUETOOTH команда — по умолчанию "S" — СТОП

// ********************************** SETUP ***********************************
void setup ()
{
  Serial.begin (9600); //Включаем серйиный порт — скрость 9600

  //****************** Настраиваем параметры выводов ARDUINO ******************
  pinMode (MotorLeftForward, OUTPUT);
  pinMode (MotorLeftBack, OUTPUT);
  pinMode (MotorLeftSpeed, OUTPUT);

  pinMode (MotorRightForward, OUTPUT);
  pinMode (MotorRightBack, OUTPUT);
  pinMode (MotorRightSpeed, OUTPUT);
}
// ****************** Задаем основные направления движения ******************
void forward (int a) // ВПЕРЕД
{
  digitalWrite (MotorRightBack, LOW);
  digitalWrite (MotorRightForward, HIGH);
  analogWrite (MotorRightSpeed, 150);
  digitalWrite (MotorLeftBack, LOW);
  digitalWrite (MotorLeftForward, HIGH);
  analogWrite (MotorLeftSpeed, 150);
  delay (a * 50);
}
void right (int b) // ПОВОРОТ ВПРАВО (одна сторона)
{
  digitalWrite (MotorLeftBack, LOW);
  digitalWrite (MotorLeftForward, HIGH);
  analogWrite (MotorLeftSpeed, 200);
  digitalWrite (MotorRightBack, LOW);
  digitalWrite (MotorRightForward, LOW);
  delay (b * 50);
}
void left (int c) // ПОВОРОТ ВЛЕВО (одна сторона)
{
  digitalWrite (MotorRightBack, LOW);
  digitalWrite (MotorRightForward, HIGH);
  analogWrite (MotorRightSpeed, 200);
  digitalWrite (MotorLeftBack, LOW);
  digitalWrite (MotorLeftForward, LOW);
  delay (c * 50);
}
void turnR (int d) // РАЗВОРОТ ВПРАВО (два стороны)
{
  digitalWrite (MotorRightBack, HIGH);
  digitalWrite (MotorRightForward, LOW);
  digitalWrite (MotorLeftBack, LOW);
  digitalWrite (MotorLeftForward, HIGH);
  delay (d * 50);
}
void turnL (int e) // РАЗВОРОТ ВЛЕВО (два стороны)
{
  digitalWrite (MotorRightBack, LOW);
  digitalWrite (MotorRightForward, HIGH);
  digitalWrite (MotorLeftBack, HIGH);
  digitalWrite (MotorLeftForward, LOW);
  delay (e * 50);
}
void stopp (int f) // СТОП
{
  digitalWrite (MotorRightBack, LOW);
  digitalWrite (MotorRightForward, LOW);
  digitalWrite (MotorLeftBack, LOW);
  digitalWrite (MotorLeftForward, LOW);
  delay (f * 50);
}
void back (int g) // НАЗАД
{
  digitalWrite (MotorRightBack, HIGH);
  digitalWrite (MotorRightForward, LOW);
  digitalWrite (MotorLeftBack, HIGH);
  digitalWrite (MotorLeftForward, LOW);;
  delay (g * 50);
}
// ********************* Управление по БЛЮТУЗ ****************************
void loop()
{
  if (Serial.available() > 0)// В случае если в "Серийном порту" есть информация
  {
    btCommand = Serial.read(); // Считываем инвормацию с "Серийного порта"
    Serial.println(btCommand); // Печатаем инвормацию с "Серийного порта" — необходио в слуае отладки программы
    switch (btCommand)// Отрабатывем в соотвествии с поступившей командой
    {
      case 'F':
        forward (3); // едем ВПЕРЕД
        break;
      case 'B':
        back (3); // едем НАЗАД
        break;
      case 'R':
        right (3); // поворачиваем ВПРАВО
        break;
      case 'L':
        left (3); // поворачиваем ВЛЕВО
        break;
      case 'S':
        stopp (3); // СТОП!
    }
  }
}

 

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

IvanP пишет:

горелка состоит из:
1.Проточного нагревателя масла, со встроенным контроллером температуры , мы по своему датчику temp[0]
контролируем нагрелось масло до минимальной указанной в настройках, и режим аварии, если температура масла
превысила максимальную из настроек.
2.Клапан маслянный.
3.Клапан первичного воздуха, подается от компрессора, мощность подачи первичного воздуха нами не регулируется.
4.Вентилятор вторичного воздуха, нами регулируется для режима продувки, розжига и рабочего режима.
5.Датчик температуры воды в котле temp[1], по нему контролируем работу котла в автоматическом режиме.
6.Датчик огня, понятно горит или не горит.
 
имеем два основных режима ручной и авто.
В ручном можем задавать настройки и проверять работу отдельных устройств.
В автоматическом:
1. по температуре temp[1] контролируем, если температура воды в котле упала ниже заданной в настройках,
начинаем розжиг горелки. 
2. включаем режим нагрева масла в проточном нагревателе.
3. Если достигли заданной температуры масла, включаем продувку на 100% мошности вентилятора продувки 
и на время задаваемое в настройках.
4.Вентилятор продувки включаем на мощьность для розжига из настроек.Подаем напряжение на трансформатор розжига
на время заданное в настройках, или меньшее, если загорелось раньше. Если не загорелось, идем к пункту 3.
 т.е пытаемся разжечь заново. Если три раза не зажгли переходим в режим аварии, из которого выход только в ручную.
5.Вентилятор продувки включаем на мощность для рабочего режима из настроек.
6.Греем пока температура воды в котле temp[1] не превысит максимальную из настроек.
7.Если в рабочем режиме погасло, пытаемся трижды разжеч возвращаясь к пункту 3. не получилось - авария.
8. Если температура воды в котле превысила максимальную из настроек, гасим котел и переходим в режим ожидания
 в автоматическом режиме.
9. переходим к пункту 1.
 
ну как-то так... в перспективе отправлять сообщения о работе котла по gsm модему хоянину по СМС или на сервер по GPRS.

Честно - круто! А где Ваша программа? Или это задача на 100500 лет?

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

"Спрячем управление гусиницами в класс" и упростим управление ними

/**/
class Cl_Motor {
  protected :
    byte plusPin, minusPin, speedPin;
  public:
    Cl_Motor(byte f, byte b, byte s)
      : plusPin(f), minusPin(b), speedPin(s) {}
    void init() {
      pinMode(plusPin, OUTPUT);
      pinMode(minusPin, OUTPUT);
      stop();
    }
    void forward(byte s = 150) {
      digitalWrite (plusPin, LOW);
      digitalWrite (minusPin, HIGH);
      analogWrite (speedPin, s);
    }
    void back(byte s = 150) {
      digitalWrite (plusPin, HIGH);
      digitalWrite (minusPin, LOW);
      analogWrite (speedPin, s);

    }
    void stop() {
      digitalWrite (plusPin, LOW);
      digitalWrite (minusPin, LOW);
    }
};
//---------------------------
Cl_Motor Left (/*пин IN1*/4,/*пин IN2*/2,/*пин ENA*/5);
Cl_Motor Right(/*пин IN3*/8,/*пин IN4*/7,/*пин ENB*/6);
//--------------------------
void setup() {
  Left.init();
  Right.init();
}

void loop() {
}

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

И теперь вчерне накидаем код. В работе пока не проверял

/**/
class Cl_Motor {
  protected :
    byte plusPin, minusPin, speedPin;
  public:
    Cl_Motor(byte f, byte b, byte s)
      : plusPin(f), minusPin(b), speedPin(s) {}
    void init() {
      pinMode(plusPin, OUTPUT);
      pinMode(minusPin, OUTPUT);
      stop();
    }
    void forward(byte s = 150) {
      digitalWrite (plusPin, LOW);
      digitalWrite (minusPin, HIGH);
      analogWrite (speedPin, s);
    }
    void back(byte s = 150) {
      digitalWrite (plusPin, HIGH);
      digitalWrite (minusPin, LOW);
      analogWrite (speedPin, s);

    }
    void stop() {
      digitalWrite (plusPin, LOW);
      digitalWrite (minusPin, LOW);
    }
};
Cl_Motor Left (/*пин IN1*/4,/*пин IN2*/2,/*пин ENA*/5);
Cl_Motor Right(/*пин IN3*/8,/*пин IN4*/7,/*пин ENB*/6);
//------------------------------------------------
typedef void (*pDo)();
pDo run = [] {};
// состояния танка: стоп,вперед,назад,поворот налево,поворот направо,разворот по часовой,разворот против часовой
enum state_t {sStop, sForward, sBackward, sLeft, sRight, sTurnLeft, sTurnRight} state;
void stand(state_t s, unsigned long t = 100) {
  static unsigned long time;
  static unsigned long past;
  time = t;
  past = millis();
  switch (s) {
    case sStop:
      Left.stop();
      Right.stop();
      run = [] {};
      break;
    case sForward:
      Left.forward();
      Right.forward();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
    case sBackward:
      Left.back();
      Right.back();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
    case sLeft:
      Left.stop();
      Right.forward();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
    case sRight:
      Left.forward();
      Right.stop();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
    case sTurnLeft:
      Left.back();
      Right.forward();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
    case sTurnRight:
      Left.forward();
      Right.back();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
  }
}
//--------------------------
void setup() {
  Serial.begin (9600); //Включаем серйиный порт — скрость 9600
  Left.init();
  Right.init();
  stand(sStop);
}
void loop() {
  run();
  if (Serial.available() > 0)// В случае если в "Серийном порту" есть информация
  {
    char btCommand = Serial.read(); // Считываем инвормацию с "Серийного порта"
    Serial.println(btCommand); // Печатаем инвормацию с "Серийного порта" — необходио в слуае отладки программы
    switch (btCommand)// Отрабатывем в соотвествии с поступившей командой
    {
      case 'F':
        stand(sForward, 300);// едем ВПЕРЕД 0.3 сек
        break;
      case 'B':
        stand(sBackward, 300);//// едем НАЗАД 0.3 сек
        break;
      case 'R':
        stand( sRight, 300);//// поворачиваем ВПРАВО 0.3 сек
        break;
      case 'L':
        stand(sLeft, 300);//// поворачиваем ВЛЕВО 0.3 сек
        break;
      case 'S':
        stand(sStop);//// СТОП!
    }
  }
}

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Отладил скетч. Теперь можно в сериал отправлять не только по одной команде а группу и танк будет отрабатывать их.

/**/
template <typename T> inline Print & operator << (Print &s, T n) {
  s.print(n);
  return s;
}
class Cl_Motor {
  protected :
    byte plusPin, minusPin, speedPin;
  public:
    Cl_Motor(byte f, byte b, byte s)
      : plusPin(f), minusPin(b), speedPin(s) {}
    void init() {
      pinMode(plusPin, OUTPUT);
      pinMode(minusPin, OUTPUT);
      stop();
    }
    void forward(byte s = 150) {
      digitalWrite (plusPin, LOW);
      digitalWrite (minusPin, HIGH);
      analogWrite (speedPin, s);
    }
    void back(byte s = 150) {
      digitalWrite (plusPin, HIGH);
      digitalWrite (minusPin, LOW);
      analogWrite (speedPin, s);

    }
    void stop() {
      digitalWrite (plusPin, LOW);
      digitalWrite (minusPin, LOW);
    }
};
Cl_Motor Left (/*пин IN1*/4,/*пин IN2*/2,/*пин ENA*/5);
Cl_Motor Right(/*пин IN3*/8,/*пин IN4*/7,/*пин ENB*/6);
//------------------------------------------------
typedef void (*pDo)();
pDo run = [] {};
// состояния танка: стоп,вперед,назад,поворот налево,поворот направо,разворот по часовой,разворот против часовой
enum state_t {sStop = 0, sForward, sBackward, sLeft, sRight, sTurnLeft, sTurnRight} state;
void stand(state_t s, unsigned long t = 100) {
  static unsigned long time;
  static unsigned long past;
  state = s;
  time = t;
  past = millis();
  switch (s) {
    case sStop:
      Serial << "sStop\n";
      Left.stop();
      Right.stop();
      run = [] {};
      break;
    case sForward:
      Serial << "sForward\n";
      Left.forward();
      Right.forward();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
    case sBackward:
      Serial << "sBackward\n";
      Left.back();
      Right.back();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
    case sLeft:
      Serial << "sLeft\n";
      Left.stop();
      Right.forward();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
    case sRight:
      Serial << "sRight\n";
      Left.forward();
      Right.stop();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
    case sTurnLeft:
      Serial << "sTurnLeft\n";
      Left.back();
      Right.forward();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
    case sTurnRight:
      Serial << "sTurnRight\n";
      Left.forward();
      Right.back();
      run = [] {
        if (millis() - past >= time)stand(sStop);
      };
      break;
  }
}
//--------------------------
void setup() {
  Serial.begin (9600); //Включаем серйиный порт — скрость 9600
  Left.init();
  Right.init();
  stand(sStop);
}
void loop() {
  run();
  if (state == sStop) {
    if (Serial.available() > 0) // В случае если в "Серийном порту" есть информация
    {
      char btCommand = Serial.read(); // Считываем инвормацию с "Серийного порта"
      Serial << btCommand ; // Печатаем инвормацию с "Серийного порта" — необходио в слуае отладки программы
      switch (btCommand)// Отрабатывем в соотвествии с поступившей командой
      {
        case 'F':
          stand(sForward, 300);// едем ВПЕРЕД 0.3 сек
          break;
        case 'B':
          stand(sBackward, 300);// едем НАЗАД 0.3 сек
          break;
        case 'R':
          stand( sRight, 300);//поворачиваем ВПРАВО 0.3 сек
          break;
        case 'L':
          stand(sLeft, 300);// поворачиваем ВЛЕВО 0.3 сек
          break;
        case 'S':
          stand(sStop);// СТОП!
      }
    }
  }
}

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Попробую показать как писать Меню на ардуине (разумеется по моей методике). Итак  переменные.

/**/
//--переменые---------------------

byte var1, var2, var3, var4; //<- эти переменные
const byte maxVar1 = 50, maxVar2 = 50, maxVar3 = 50, maxVar4 = 50;
const byte minVar1 = 10, minVar2 = 10, minVar3 = 10, minVar4 = 40;

void setup() {
}
void loop() {
}

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Переменные будут читаться с ЕЕPROM и меню туда записываться.

/**/
//--переменые---------------------
#include <EEPROM.h>
byte var1, var2, var3, var4; //<- эти переменные
const byte maxVar1 = 50, maxVar2 = 50, maxVar3 = 50, maxVar4 = 50;
const byte minVar1 = 10, minVar2 = 10, minVar3 = 10, minVar4 = 40;
const int adr1 = 0, adr2 = 1, adr3 = 2, adr4 = 4; //
void initVar() {
  var1 = EEPROM.read(adr1);
  if (var1 > maxVar1)var1 = maxVar1;
  if (var1 < minVar1)var1 = minVar1;
  EEPROM.update(adr1, var1);
  var2 = EEPROM.read(adr2);
  if (var2 > maxVar2)var2 = maxVar2;
  if (var2 < minVar2)var2 = minVar2;
  EEPROM.update(adr2, var2);
  var3 = EEPROM.read(adr3);
  if (var3 > maxVar3)var3 = maxVar3;
  if (var3 < minVar3)var3 = minVar3;
  EEPROM.update(adr3, var3);
  var4 = EEPROM.read(adr4);
  if (var4 > maxVar4)var4 = maxVar4;
  if (var4 < minVar4)var4 = minVar4;
  EEPROM.update(adr4, var4);
}
void setup() {
  initVar();
}
void loop() {
}

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Добавим первичную разметку экранов в Меню - экран для оперативного отображения информации . Обновление раз в 1 секунду. И 4 экрана для редактирования переменных которые были упомянуты ранее. И да если долго находится на экране редактирования, то автоматический возврат на главный экран.

/**/
//--переменые---------------------
#include <EEPROM.h>
byte var1, var2, var3, var4; //<- эти переменные
const byte maxVar1 = 50, maxVar2 = 50, maxVar3 = 50, maxVar4 = 50;
const byte minVar1 = 10, minVar2 = 10, minVar3 = 10, minVar4 = 40;
const int adr1 = 0, adr2 = 1, adr3 = 2, adr4 = 4; //
void initVar() {
  var1 = EEPROM.read(adr1);
  if (var1 > maxVar1)var1 = maxVar1;
  if (var1 < minVar1)var1 = minVar1;
  EEPROM.update(adr1, var1);
  var2 = EEPROM.read(adr2);
  if (var2 > maxVar2)var2 = maxVar2;
  if (var2 < minVar2)var2 = minVar2;
  EEPROM.update(adr2, var2);
  var3 = EEPROM.read(adr3);
  if (var3 > maxVar3)var3 = maxVar3;
  if (var3 < minVar3)var3 = minVar3;
  EEPROM.update(adr3, var3);
  var4 = EEPROM.read(adr4);
  if (var4 > maxVar4)var4 = maxVar4;
  if (var4 < minVar4)var4 = minVar4;
  EEPROM.update(adr4, var4);
}
//--меню--------------------------
template <typename T> inline Print & operator << (Print &s, T n) {
  s.print(n);
  return s;
}
const byte strMain = 0; // главный экран
const byte str1    = 1; // 1-й экран
const byte str2    = 2; // 2-й экран
const byte str3    = 3; // 3-й экран
const byte str4    = 4; // 4-й экран
byte pos;
unsigned long pastMenu;
void setPos(byte p) {
  pastMenu = millis();
  pos = p;
  switch (p) {
    case strMain:
      Serial << "Main screen\n";
      break;
    case str1:
      Serial << "1 screen\n";
      break;
    case str2:
      Serial << "2 screen\n";
      break;
    case str3:
      Serial << "3 screen\n";
      break;
    case str4:
      Serial << "4 screen\n";
      break;
  }
}
void runMenu() {
  switch (pos) {
    case strMain:
      if (millis() - pastMenu >= 1000) setPos(strMain);
      break;
    default:
      if (millis() - pastMenu >= 10000) setPos(strMain);
  }
}
//--main------------------------------
void setup() {
  Serial.begin(9600);
  initVar();
  setPos(strMain);
}
void loop() {
  runMenu();
}

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Добавим в меню кнопки.Пока две для переключения между экранами.

/**/
//--переменые---------------------
#include <EEPROM.h>
byte var1, var2, var3, var4; //<- эти переменные
const byte maxVar1 = 50, maxVar2 = 50, maxVar3 = 50, maxVar4 = 50;
const byte minVar1 = 10, minVar2 = 10, minVar3 = 10, minVar4 = 40;
const int adr1 = 0, adr2 = 1, adr3 = 2, adr4 = 4; //
void initVar() {
  var1 = EEPROM.read(adr1);
  if (var1 > maxVar1)var1 = maxVar1;
  if (var1 < minVar1)var1 = minVar1;
  EEPROM.update(adr1, var1);
  var2 = EEPROM.read(adr2);
  if (var2 > maxVar2)var2 = maxVar2;
  if (var2 < minVar2)var2 = minVar2;
  EEPROM.update(adr2, var2);
  var3 = EEPROM.read(adr3);
  if (var3 > maxVar3)var3 = maxVar3;
  if (var3 < minVar3)var3 = minVar3;
  EEPROM.update(adr3, var3);
  var4 = EEPROM.read(adr4);
  if (var4 > maxVar4)var4 = maxVar4;
  if (var4 < minVar4)var4 = minVar4;
  EEPROM.update(adr4, var4);
}
//---кнопки-----------------------------
typedef void (*pDo)();
class Cl_btn {
  protected:
    byte pin;
    bool state;
    unsigned long past;
    void set(bool s) {
      state = s;
      past = millis();
      switch (s) {
        case false:
          break;
        case true:
          Do();
          break;
      }
    }
  public:
    Cl_btn(byte p): pin(p) {}
    pDo Do = [] {};
    void init() {
      pinMode(pin, INPUT_PULLUP);
      set(false);
    }
    void run() {
      if (millis() - past >= 100)
        switch (state) {
          case false:
            if (!digitalRead(pin))set(true);
            break;
          case true:
            if (digitalRead(pin))set(false);
            if (millis() - past >= 300)set(false);
            break;
        }
    }
};
const int numBtn = 2;
Cl_btn Btn[numBtn] = {
  Cl_btn(/*пин*/2),  //кнопка верх
  Cl_btn(/*пин*/3)   //кнопка вниз
};
//--меню--------------------------
template <typename T> inline Print & operator << (Print &s, T n) {
  s.print(n);
  return s;
}
const byte strMain = 0; // главный экран
const byte str1    = 1; // 1-й экран
const byte str2    = 2; // 2-й экран
const byte str3    = 3; // 3-й экран
const byte str4    = 4; // 4-й экран
byte pos;
unsigned long pastMenu;
void setPos(byte p) {
  pastMenu = millis();
  pos = p;
  switch (p) {
    case strMain:
      Serial << F("Main screen\n");
      Serial << F("var1:") << var1 << F("  ");
      Serial << F("var2:") << var2 << F("\n");
      Serial << F("var3:") << var3 << F("  ");
      Serial << F("var4:") << var4 << F("\n");
      Btn[0].Do = [] {};  //кнопка верх
      Btn[1].Do = [] {setPos(str1);};  //кнопка вниз
      break;
    case str1:
      Serial << F("1 screen\n");
      Serial << F("var1:") << var1 << F("\n");
      Btn[0].Do = [] {setPos(strMain);};  //кнопка верх
      Btn[1].Do = [] {setPos(str2);};  //кнопка вниз
      break;
    case str2:
      Serial << F("2 screen\n");
      Serial << F("var2:") << var2 << F("\n");
      Btn[0].Do = [] {setPos(str1);};  //кнопка верх
      Btn[1].Do = [] {setPos(str3);};  //кнопка вниз
      break;
    case str3:
      Serial << F("3 screen\n");
      Serial << F("var3:") << var3 << F("\n");
      Btn[0].Do = [] {setPos(str2);};  //кнопка верх
      Btn[1].Do = [] {setPos(str4);};  //кнопка вниз
      break;
    case str4:
      Serial << F("4 screen\n");
      Serial << F("var4:") << var4 << F("\n");
      Btn[0].Do = [] {setPos(str3);};  //кнопка верх
      Btn[1].Do = [] {};  //кнопка вниз
      break;
  }
}
void runMenu() {
  switch (pos) {
    case strMain:
      // если находимся на главном экране то обновлять каждую секунду
      if (millis() - pastMenu >= 1000) setPos(strMain);
      break;
    default:
      // если пользователь не нажимает на кнопки, то вернуться на главный экран
      if (millis() - pastMenu >= 10000) setPos(strMain);
  }
}
//--main------------------------------
void setup() {
  Serial.begin(9600);
  initVar();
  setPos(strMain);
  for (int i = 0; i < numBtn; i++)Btn[i].init();
}
void loop() {
  runMenu();
  for (int i = 0; i < numBtn; i++)Btn[i].run();
}

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Добавим в Меню сенсоры. К примеру потенциометры на пины A0 и А1 для имитации работы внешних датчиков.

/**/
//--переменые---------------------
#include <EEPROM.h>
byte var1, var2, var3, var4; //<- эти переменные
const byte maxVar1 = 50, maxVar2 = 50, maxVar3 = 50, maxVar4 = 50;
const byte minVar1 = 10, minVar2 = 10, minVar3 = 10, minVar4 = 40;
const int adr1 = 0, adr2 = 1, adr3 = 2, adr4 = 4; //
void initVar() {
  var1 = EEPROM.read(adr1);
  if (var1 > maxVar1)var1 = maxVar1;
  if (var1 < minVar1)var1 = minVar1;
  EEPROM.update(adr1, var1);
  var2 = EEPROM.read(adr2);
  if (var2 > maxVar2)var2 = maxVar2;
  if (var2 < minVar2)var2 = minVar2;
  EEPROM.update(adr2, var2);
  var3 = EEPROM.read(adr3);
  if (var3 > maxVar3)var3 = maxVar3;
  if (var3 < minVar3)var3 = minVar3;
  EEPROM.update(adr3, var3);
  var4 = EEPROM.read(adr4);
  if (var4 > maxVar4)var4 = maxVar4;
  if (var4 < minVar4)var4 = minVar4;
  EEPROM.update(adr4, var4);
}
//---------сенсор----------------------------
class Cl_sens {
  protected:
    const byte pin;
    byte val;
    unsigned long past;
  public:
    Cl_sens(byte p): pin(p) {}
    void init() {
      pinMode(pin, INPUT);
      val = analogRead(pin) / 16;
      past = millis();
    }
    void run() {
      if (millis() - past >= 500) {
        val = analogRead(pin) / 16;
        past = millis();
      }
    }
    byte read() {
      return val;
    }
};
const int numSens = 2;
Cl_sens Sens[numSens] = {
  Cl_sens(/*пин*/A0),  //сенсор 1
  Cl_sens(/*пин*/A1),  //сенсор 2
};
//---кнопки-----------------------------
typedef void (*pDo)();
class Cl_btn {
  protected:
    byte pin;
    bool state;
    unsigned long past;
    void set(bool s) {
      state = s;
      past = millis();
      switch (s) {
        case false:
          break;
        case true:
          Do();
          break;
      }
    }
  public:
    Cl_btn(byte p): pin(p) {}
    pDo Do = [] {};
    void init() {
      pinMode(pin, INPUT_PULLUP);
      set(false);
    }
    void run() {
      if (millis() - past >= 100)
        switch (state) {
          case false:
            if (!digitalRead(pin))set(true);
            break;
          case true:
            if (digitalRead(pin))set(false);
            if (millis() - past >= 300)set(false);
            break;
        }
    }
};
const int numBtn = 2;
Cl_btn Btn[numBtn] = {
  Cl_btn(/*пин*/2),  //кнопка верх
  Cl_btn(/*пин*/3)   //кнопка вниз
};
//--меню--------------------------
template <typename T> inline Print & operator << (Print &s, T n) {
  s.print(n);
  return s;
}
const byte strMain = 0; // главный экран
const byte str1    = 1; // 1-й экран
const byte str2    = 2; // 2-й экран
const byte str3    = 3; // 3-й экран
const byte str4    = 4; // 4-й экран
byte pos;
unsigned long pastMenu;
void setPos(byte p) {
  pastMenu = millis();
  pos = p;
  switch (p) {
    case strMain:
      Serial << F("Main screen\n");
      Serial << F("Sens1:") << Sens[0].read() << F("\n");
      Serial << F("Sens2:") << Sens[1].read() << F("\n");
      Btn[0].Do = [] {};  //кнопка верх
      Btn[1].Do = [] {setPos(str1);};  //кнопка вниз
      break;
    case str1:
      Serial << F("1 screen\n");
      Serial << F("var1:") << var1 << F("\n");
      Btn[0].Do = [] {setPos(strMain);};  //кнопка верх
      Btn[1].Do = [] {setPos(str2);};  //кнопка вниз
      break;
    case str2:
      Serial << F("2 screen\n");
      Serial << F("var2:") << var2 << F("\n");
      Btn[0].Do = [] {setPos(str1);};  //кнопка верх
      Btn[1].Do = [] {setPos(str3);};  //кнопка вниз
      break;
    case str3:
      Serial << F("3 screen\n");
      Serial << F("var3:") << var3 << F("\n");
      Btn[0].Do = [] {setPos(str2);};  //кнопка верх
      Btn[1].Do = [] {setPos(str4);};  //кнопка вниз
      break;
    case str4:
      Serial << F("4 screen\n");
      Serial << F("var4:") << var4 << F("\n");
      Btn[0].Do = [] {setPos(str3);};  //кнопка верх
      Btn[1].Do = [] {};  //кнопка вниз
      break;
  }
}
void runMenu() {
  switch (pos) {
    case strMain:
      // если находимся на главном экране то обновлять каждую секунду
      if (millis() - pastMenu >= 1000) setPos(strMain);
      break;
    default:
      // если пользователь не нажимает на кнопки, то вернуться на главный экран
      if (millis() - pastMenu >= 10000) setPos(strMain);
  }
}
//--main------------------------------
void setup() {
  Serial.begin(9600);
  initVar();
  for (int i = 0; i < numSens; i++)Sens[i].init();
  for (int i = 0; i < numBtn; i++)Btn[i].init();
  setPos(strMain);
}
void loop() {
  for (int i = 0; i < numSens; i++)Sens[i].run();
  for (int i = 0; i < numBtn; i++)Btn[i].run();
  runMenu();
}

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Добавим кнопку Select и режим редактирования 4 переменных в Меню

/**/
//--переменые---------------------
#include <EEPROM.h>
byte var1, var2, var3, var4; //<- эти переменные
const byte maxVar1 = 50, maxVar2 = 50, maxVar3 = 50, maxVar4 = 50;
const byte minVar1 = 10, minVar2 = 10, minVar3 = 10, minVar4 = 40;
const int adr1 = 0, adr2 = 1, adr3 = 2, adr4 = 4; //
void initVar() {
  var1 = EEPROM.read(adr1);
  if (var1 > maxVar1)var1 = maxVar1;
  if (var1 < minVar1)var1 = minVar1;
  EEPROM.update(adr1, var1);
  var2 = EEPROM.read(adr2);
  if (var2 > maxVar2)var2 = maxVar2;
  if (var2 < minVar2)var2 = minVar2;
  EEPROM.update(adr2, var2);
  var3 = EEPROM.read(adr3);
  if (var3 > maxVar3)var3 = maxVar3;
  if (var3 < minVar3)var3 = minVar3;
  EEPROM.update(adr3, var3);
  var4 = EEPROM.read(adr4);
  if (var4 > maxVar4)var4 = maxVar4;
  if (var4 < minVar4)var4 = minVar4;
  EEPROM.update(adr4, var4);
}
//---------сенсор----------------------------
class Cl_sens {
  protected:
    const byte pin;
    byte val;
    unsigned long past;
  public:
    Cl_sens(byte p): pin(p) {}
    void init() {
      pinMode(pin, INPUT);
      val = analogRead(pin) / 16;
      past = millis();
    }
    void run() {
      if (millis() - past >= 500) {
        val = analogRead(pin) / 16;
        past = millis();
      }
    }
    byte read() {
      return val;
    }
};
const int numSens = 2;
Cl_sens Sens[numSens] = {
  Cl_sens(/*пин*/A0),  //сенсор 1
  Cl_sens(/*пин*/A1),  //сенсор 2
};
//---кнопки-----------------------------
typedef void (*pDo)();
class Cl_btn {
  protected:
    byte pin;
    bool state;
    unsigned long past;
    void set(bool s) {
      state = s;
      past = millis();
      switch (s) {
        case false:
          break;
        case true:
          Do();
          break;
      }
    }
  public:
    Cl_btn(byte p): pin(p) {}
    pDo Do = [] {};
    void init() {
      pinMode(pin, INPUT_PULLUP);
      set(false);
    }
    void run() {
      if (millis() - past >= 100)
        switch (state) {
          case false:
            if (!digitalRead(pin))set(true);
            break;
          case true:
            if (digitalRead(pin))set(false);
            if (millis() - past >= 300)set(false);
            break;
        }
    }
};
const int numBtn = 3;
Cl_btn Btn[numBtn] = {
  Cl_btn(/*пин*/2),  //кнопка верх
  Cl_btn(/*пин*/3),  //кнопка вниз
  Cl_btn(/*пин*/4)   //кнопка Select
};
//--меню--------------------------
template <typename T> inline Print & operator << (Print &s, T n) {
  s.print(n);
  return s;
}
const byte strMain = 0; // главный экран
const byte str1    = 1; // 1-й экран
const byte Estr1   = 2; // 1-й экран(редактирование)
const byte str2    = 3; // 2-й экран
const byte Estr2   = 4; // 2-й экран(редактирование)
const byte str3    = 5; // 3-й экран
const byte Estr3   = 6; // 3-й экран(редактирование)
const byte str4    = 7; // 3-й экран
const byte Estr4   = 8; // 3-й экран(редактирование)
byte pos;
byte editVar;
unsigned long pastMenu;
void setPos(byte p) {
  pastMenu = millis();
  pos = p;
  switch (p) {
    case strMain:
      Serial << F("Main screen\n");
      Serial << F("Sens1:") << Sens[0].read() << F("\n");
      Serial << F("Sens2:") << Sens[1].read() << F("\n");
      Btn[0].Do = [] {};  //кнопка верх
      Btn[1].Do = [] {setPos(str1);};  //кнопка вниз
      Btn[2].Do = [] {};  //кнопка Select
      break;
    case str1:
      Serial << F("1 screen\n");
      Serial << F("var1:") << var1 << F("\n");
      Btn[0].Do = [] {setPos(strMain);};  //кнопка верх
      Btn[1].Do = [] {setPos(str2);};  //кнопка вниз
      Btn[2].Do = [] {                 //кнопка Select
        editVar = var1;
        setPos(Estr1);
      };
      break;
    case Estr1:
      Serial << F("1 screen\n");
      Serial << F("Edit var1:") << editVar << F("\n");
      Btn[0].Do = [] {     //кнопка верх
        if (editVar < maxVar1) editVar++;
        setPos(Estr1);
      };
      Btn[1].Do = [] {       //кнопка вниз
        if (editVar > minVar1) editVar--;
        setPos(Estr1);
      };
      Btn[2].Do = [] {  //кнопка Select
        EEPROM.write(adr1, editVar);
        var1 = editVar;
        setPos(str1);
      };
      break;
    case str2:
      Serial << F("2 screen\n");
      Serial << F("var2:") << var2 << F("\n");
      Btn[0].Do = [] {setPos(str1);};  //кнопка верх
      Btn[1].Do = [] {setPos(str3);};  //кнопка вниз
      Btn[2].Do = [] {                 //кнопка Select
        editVar = var2;
        setPos(Estr2);
      };
      break;
    case Estr2:
      Serial << F("2 screen\n");
      Serial << F("Edit var2:") << editVar << F("\n");
      Btn[0].Do = [] {
        if (editVar < maxVar2) editVar++;
        setPos(Estr2);
      };  //кнопка верх
      Btn[1].Do = [] {
        if (editVar > minVar2) editVar--;
        setPos(Estr2);
      };  //кнопка вниз
      Btn[2].Do = [] {    //кнопка Select
        EEPROM.write(adr2, editVar);
        var2 = editVar;
        setPos(str2);
      };
      break;
    case str3:
      Serial << F("3 screen\n");
      Serial << F("var3:") << var3 << F("\n");
      Btn[0].Do = [] {setPos(str2);};  //кнопка верх
      Btn[1].Do = [] {setPos(str4);};  //кнопка вниз
      Btn[2].Do = [] {                 //кнопка Select
        editVar = var3;
        setPos(Estr3);
      };
      break;
    case Estr3:
      Serial << F("3 screen\n");
      Serial << F("Edit var3:") << editVar << F("\n");
      Btn[0].Do = [] {   //кнопка верх
        if (editVar < maxVar3) editVar++;
        setPos(Estr3);
      };
      Btn[1].Do = [] {   //кнопка вниз
        if (editVar > minVar3) editVar--;
        setPos(Estr3);
      };
      Btn[2].Do = [] {  //кнопка Select
        EEPROM.write(adr3, editVar);
        var3 = editVar;
        setPos(str3);
      };
      break;
    case str4:
      Serial << F("4 screen\n");
      Serial << F("var4:") << var4 << F("\n");
      Btn[0].Do = [] {setPos(str3);};  //кнопка верх
      Btn[1].Do = [] {};              //кнопка вниз
      Btn[2].Do = [] {                //кнопка Select
        editVar = var4;
        setPos(Estr4);
      };
      break;
    case Estr4:
      Serial << F("4 screen\n");
      Serial << F("Edit var3:") << editVar << F("\n");
      Btn[0].Do = [] {   //кнопка верх
        if (editVar < maxVar4) editVar++;
        setPos(Estr3);
      };
      Btn[1].Do = [] {   //кнопка вниз
        if (editVar > minVar4) editVar--;
        setPos(Estr3);
      };
      Btn[2].Do = [] {  //кнопка Select
        EEPROM.write(adr4, editVar);
        var4 = editVar;
        setPos(str4);
      };
      break;
  }
}
void runMenu() {
  switch (pos) {
    case strMain:
      // если находимся на главном экране то обновлять каждую секунду
      if (millis() - pastMenu >= 1000) setPos(strMain);
      break;
    default:
      // если пользователь не нажимает на кнопки, то вернуться на главный экран
      if (millis() - pastMenu >= 10000) setPos(strMain);
  }
}
//--main------------------------------
void setup() {
  Serial.begin(9600);
  initVar();
  for (int i = 0; i < numSens; i++)Sens[i].init();
  for (int i = 0; i < numBtn; i++)Btn[i].init();
  setPos(strMain);
}
void loop() {
  for (int i = 0; i < numSens; i++)Sens[i].run();
  for (int i = 0; i < numBtn; i++)Btn[i].run();
  runMenu();
}

 

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

Теперь сделаем вывод на экран LCD1602 I2C  . Кнопка Select на Главном экране вкл/выкл подсветку экрана

/**/
//--переменые---------------------
#include <EEPROM.h>
byte var1, var2, var3, var4; //<- эти переменные
const byte maxVar1 = 50, maxVar2 = 50, maxVar3 = 50, maxVar4 = 50;
const byte minVar1 = 10, minVar2 = 10, minVar3 = 10, minVar4 = 40;
const int adr1 = 0, adr2 = 1, adr3 = 2, adr4 = 4; //
void initVar() {
  var1 = EEPROM.read(adr1);
  if (var1 > maxVar1)var1 = maxVar1;
  if (var1 < minVar1)var1 = minVar1;
  EEPROM.update(adr1, var1);
  var2 = EEPROM.read(adr2);
  if (var2 > maxVar2)var2 = maxVar2;
  if (var2 < minVar2)var2 = minVar2;
  EEPROM.update(adr2, var2);
  var3 = EEPROM.read(adr3);
  if (var3 > maxVar3)var3 = maxVar3;
  if (var3 < minVar3)var3 = minVar3;
  EEPROM.update(adr3, var3);
  var4 = EEPROM.read(adr4);
  if (var4 > maxVar4)var4 = maxVar4;
  if (var4 < minVar4)var4 = minVar4;
  EEPROM.update(adr4, var4);
}
//---------сенсор----------------------------
class Cl_sens {
  protected:
    const byte pin;
    byte val;
    unsigned long past;
  public:
    Cl_sens(byte p): pin(p) {}
    void init() {
      pinMode(pin, INPUT);
      val = analogRead(pin) / 16;
      past = millis();
    }
    void run() {
      if (millis() - past >= 500) {
        val = analogRead(pin) / 16;
        past = millis();
      }
    }
    byte read() {
      return val;
    }
};
const int numSens = 2;
Cl_sens Sens[numSens] = {
  Cl_sens(/*пин*/A0),  //сенсор 1
  Cl_sens(/*пин*/A1),  //сенсор 2
};
//---кнопки-----------------------------
typedef void (*pDo)();
class Cl_btn {
  protected:
    byte pin;
    bool state;
    unsigned long past;
    void set(bool s) {
      state = s;
      past = millis();
      switch (s) {
        case false:
          break;
        case true:
          Do();
          break;
      }
    }
  public:
    Cl_btn(byte p): pin(p) {}
    pDo Do = [] {};
    void init() {
      pinMode(pin, INPUT_PULLUP);
      set(false);
    }
    void run() {
      if (millis() - past >= 100)
        switch (state) {
          case false:
            if (!digitalRead(pin))set(true);
            break;
          case true:
            if (digitalRead(pin))set(false);
            if (millis() - past >= 300)set(false);
            break;
        }
    }
};
const int numBtn = 3;
Cl_btn Btn[numBtn] = {
  Cl_btn(/*пин*/2),  //кнопка верх
  Cl_btn(/*пин*/3),  //кнопка вниз
  Cl_btn(/*пин*/4)   //кнопка Select
};
//--LCD1602-----------------------------
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
bool backlightState = 0;
void invertBL() {
  if (!backlightState) {
    backlightState = 1;
    lcd.backlight();// Включаем подсветку дисплея
  }
  else {
    backlightState = 0;
    lcd.noBacklight();// Выключаем подсветку дисплея
  }
}

//--меню--------------------------
template <typename T> inline Print & operator << (Print &s, T n) {
  s.print(n);
  return s;
}
const byte strMain = 0; // главный экран
const byte str1    = 1; // 1-й экран
const byte Estr1   = 2; // 1-й экран(редактирование)
const byte str2    = 3; // 2-й экран
const byte Estr2   = 4; // 2-й экран(редактирование)
const byte str3    = 5; // 3-й экран
const byte Estr3   = 6; // 3-й экран(редактирование)
const byte str4    = 7; // 4-й экран
const byte Estr4   = 8; // 4-й экран(редактирование)
byte pos;
byte editVar;
unsigned long pastMenu;
void setPos(byte p) {
  pastMenu = millis();
  pos = p;
  switch (p) {
    case strMain:
      lcd.clear();
      lcd << F("T1:") << Sens[0].read() << F("   ");
      lcd.setCursor(0, 1);
      lcd << F("T2:") << Sens[1].read() << F("   ");
      Btn[0].Do = [] {};  //кнопка верх
      Btn[1].Do = [] {setPos(str1);};  //кнопка вниз
      Btn[2].Do = [] {invertBL();};  //кнопка Select
      break;
    case str1:
      lcd.clear();
      lcd << F("var1=") << var1 << F("  ");
      Btn[0].Do = [] {setPos(strMain);};  //кнопка верх
      Btn[1].Do = [] {setPos(str2);};  //кнопка вниз
      Btn[2].Do = [] {                 //кнопка Select
        editVar = var1;
        setPos(Estr1);
      };
      break;
    case Estr1:
      lcd.clear();
      lcd << F("var1-") << editVar << F("  ");
      Btn[0].Do = [] {     //кнопка верх
        if (editVar < maxVar1) editVar++;
        setPos(Estr1);
      };
      Btn[1].Do = [] {       //кнопка вниз
        if (editVar > minVar1) editVar--;
        setPos(Estr1);
      };
      Btn[2].Do = [] {  //кнопка Select
        EEPROM.write(adr1, editVar);
        var1 = editVar;
        setPos(str1);
      };
      break;
    case str2:
      lcd.clear();
      lcd << F("var2=") << var2 << F("  ");
      Btn[0].Do = [] {setPos(str1);};  //кнопка верх
      Btn[1].Do = [] {setPos(str3);};  //кнопка вниз
      Btn[2].Do = [] {                 //кнопка Select
        editVar = var2;
        setPos(Estr2);
      };
      break;
    case Estr2:
      lcd.clear();
      lcd << F("var2-") << editVar << F("  ");
      Btn[0].Do = [] {
        if (editVar < maxVar2) editVar++;
        setPos(Estr2);
      };  //кнопка верх
      Btn[1].Do = [] {
        if (editVar > minVar2) editVar--;
        setPos(Estr2);
      };  //кнопка вниз
      Btn[2].Do = [] {    //кнопка Select
        EEPROM.write(adr2, editVar);
        var2 = editVar;
        setPos(str2);
      };
      break;
    case str3:
      lcd.clear();
      lcd << F("var3=") << var3 << F("  ");
      Btn[0].Do = [] {setPos(str2);};  //кнопка верх
      Btn[1].Do = [] {setPos(str4);};  //кнопка вниз
      Btn[2].Do = [] {                 //кнопка Select
        editVar = var3;
        setPos(Estr3);
      };
      break;
    case Estr3:
      lcd.clear();
      lcd << F("var3-") << editVar << F("  ");
      Btn[0].Do = [] {   //кнопка верх
        if (editVar < maxVar3) editVar++;
        setPos(Estr3);
      };
      Btn[1].Do = [] {   //кнопка вниз
        if (editVar > minVar3) editVar--;
        setPos(Estr3);
      };
      Btn[2].Do = [] {  //кнопка Select
        EEPROM.write(adr3, editVar);
        var3 = editVar;
        setPos(str3);
      };
      break;
    case str4:
      lcd.clear();
      lcd << F("var4=") << var4 << F("  ");
      Btn[0].Do = [] {setPos(str3);};  //кнопка верх
      Btn[1].Do = [] {};              //кнопка вниз
      Btn[2].Do = [] {                //кнопка Select
        editVar = var4;
        setPos(Estr4);
      };
      break;
    case Estr4:
      lcd.clear();
      lcd << F("var4-") << editVar << F("  ");
      Btn[0].Do = [] {   //кнопка верх
        if (editVar < maxVar4) editVar++;
        setPos(Estr3);
      };
      Btn[1].Do = [] {   //кнопка вниз
        if (editVar > minVar4) editVar--;
        setPos(Estr4);
      };
      Btn[2].Do = [] {  //кнопка Select
        EEPROM.write(adr4, editVar);
        var4 = editVar;
        setPos(str4);
      };
      break;
  }
}
void runMenu() {
  switch (pos) {
    case strMain:
      // если находимся на главном экране то обновлять каждую секунду
      if (millis() - pastMenu >= 1000) setPos(strMain);
      break;
    default:
      // если пользователь не нажимает на кнопки, то вернуться на главный экран
      if (millis() - pastMenu >= 10000) setPos(strMain);
  }
}
//--main------------------------------
void setup() {
  //Serial.begin(9600);
  initVar();
  for (int i = 0; i < numSens; i++)Sens[i].init();
  for (int i = 0; i < numBtn; i++)Btn[i].init();
  lcd.init();
  invertBL();
  setPos(strMain);
}
void loop() {
  for (int i = 0; i < numSens; i++)Sens[i].run();
  for (int i = 0; i < numBtn; i++)Btn[i].run();
  runMenu();
}