Многоуровневое меню для Ардуино и строкового ЖК дисплея 16x2

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

wdrakula пишет:

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

а, кто не умеет трахаться втроём стоя в гамаке на лыжах, тот непрофессиональный любовник, согласитесь(с)?

d_p
Offline
Зарегистрирован: 05.10.2016

И всё же, господа, помогите практическим советом. Как решить вопрос перемещение курсора?

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

Верхняя строка ("хлебные крошки") выводится в зависимости от переменной меню/подменю, нижняя строка выводится от той же переменной и переменной позиции курсора (функция?).

MENU>SUBMENU1

*ON   OFF     AUTO

При нажатии кнопки "вправо" получаем комманду на изменения переменной, отвечающей за позиционирование курсора:

MENU>SUBMENU1

  ON   *OFF     AUTO

Для включения или отключения (ON/OFF при нажатии кнопки "select") предполагаю, что нужно использовать функцию, которая от трех переменных (позиция в дереве меню, позиция курсора в текущем меню, нажатие кнопки "select") будет менять сигнал HIGH/LOW на логическом выходе ардуины, причем на том, на котором висит управление из текущего подменю. Я понимаю логику, и вижу как должна работать программа. Даже блок-схему набросать могу. Но с реализацие в коде для ардуино проблемы. Практики, кто написал не одну сотню(тысячу) строк скетчей, сбросьте кусок как реализовать вывод вышеприведенной схемы работы перемещения и выбора в меню.

P.S. Пока для меня самое понятное исполнение меню через switch/case. Но вот вопрос, оптимально ли это с точки зрения грамотного кода и нагрузки на процессор?

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

d_p пишет:

И всё же, господа, помогите практическим советом. Как решить вопрос перемещение курсора?

оптимально ли это с точки зрения грамотного кода и нагрузки на процессор?

Пересмотрел всю тему и не нашёл ни единой строчки кода от Вас.

Поэтому абсолютно непонятно: что именно "это"?

Приведите свой код, посмотрим.

d_p
Offline
Зарегистрирован: 05.10.2016

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


/*
MENU ROUTINE
*/
void basicMenu(){

  byte topItemDisplayed = 0;  // stores menu item displayed at top of LCD screen
  byte cursorPosition = 0;  // where cursor is on screen, from 0 --> totalRows. 

  // redraw = 0  - don't redraw
  // redraw = 1 - redraw cursor
  // redraw = 2 - redraw list
  byte redraw = MOVELIST;  // triggers whether menu is redrawn after cursor move.
  byte i=0; // temp variable for loops.
  byte totalMenuItems = 0;  //a while loop below will set this to the # of menu items.

// Put the menu items here. Remember, the first item will have a 'position' of 0.
  char* menuItems[]={
    "Set Timer 1", 
    "Set Timer 2", 
    "Set Probe 1 Temp",
    "Set Probe 2 Temp", 
    "Probe 1 Controller",
    "Probe 2 Controller",
    "Advanced Settings",
    "",
  };

  while (menuItems[totalMenuItems] != ""){
    totalMenuItems++;  // count how many items are in list.
  }
  totalMenuItems--;  //subtract 1 so we know total items in array.

  lcd.clear();  // clear the screen so we can paint the menu.

  boolean stillSelecting = true;  // set because user is still selecting.

  timeoutTime = millis() + menuTimeout; // set initial timeout limit. 

  do   // loop while waiting for user to select.
  {

    /*
    IF YOU WANT OTHER CODE GOING ON IN THE BACKGROUND
    WHILE WAITING FOR THE USER TO DO SOMETHING, PUT IT HERE
    */
    
    /*
    my code uses a rotary encoder for input. 
    You should obviously change the code to meet your needs.
    For a button, you could do something like this, but note that
    it does not have ANY debouncing and will scroll for as long as
    the button is being pushed. This is not a button tutorial, 
    so you should look elsewhere on how to implement that. Just 
    remember that ALL of the code between the corresponding 'case'
    and 'break' should be moved to each button push routine.
    
    
    buttonState = digitalRead(buttonPin);
    if (buttonState == HIGH) {     
      AND THEN PUT THE CORRESPONDING CODE
      FROM BELOW HERE
  } 
  */
    switch(read_encoder()) 
    {  // analyze encoder response. Default is 0.


    case 1:  // ENCODER ROTATED UP. EQUIVALENT OF 'UP' BUTTON PUSHED

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      //  if cursor is at top and menu is NOT at top
      //  move menu up one.
      if(cursorPosition == 0 && topItemDisplayed > 0)  //  Cursor is at top of LCD, and there are higher menu items still to be displayed.
      {
        topItemDisplayed--;  // move top menu item displayed up one. 
        redraw = MOVELIST;  // redraw the entire menu
      }

      // if cursor not at top, move it up one.
      if(cursorPosition>0)
      {
        cursorPosition--;  // move cursor up one.
        redraw = MOVECURSOR;  // redraw just cursor.
      }
      break;

    case 2:    // ENCODER ROTATED UP. EQUIVALENT OF 'DOWN' BUTTON PUSHED

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      // this sees if there are menu items below the bottom of the LCD screen & sees if cursor is at bottom of LCD 
      if((topItemDisplayed + (totalRows-1)) < totalMenuItems && cursorPosition == (totalRows-1))
      {
        topItemDisplayed++;  // move menu down one
        redraw = MOVELIST;  // redraw entire menu
      }
      if(cursorPosition<(totalRows-1))  // cursor is not at bottom of LCD, so move it down one.
      {
        cursorPosition++;  // move cursor down one
        redraw = MOVECURSOR;  // redraw just cursor.
      }
      break;

    case 4:  // ENCODER BUTTON PUSHED FOR SHORT PERIOD & RELEASED.
             // EQUIVALENT TO 'SELECT' OR 'OKAY' BEING PUSHED 

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      switch(topItemDisplayed + cursorPosition) // adding these values together = where on menuItems cursor is.
      {
      //  put code to be run when specific item is selected in place of the Serial.print filler.
      // the Serial.print code can be removed, but DO NOT change the case & break structure. 
      // (Obviously, you should have as many case instances as you do menu items.)
      case 0:  // menu item 1 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        // there is no menuSubMenu1() function. BUT, to have nested menus,
        // copy this function(i.e. all of basicMenu) into a new function named 
        // whatever you want for your sub menu items and repeat.
//        menuSubMenu1();
        break;

      case 1:  // menu item 2 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 2:  // menu item 3 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 3:  // menu item 4 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 4:  // menu item 5 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 5:  // menu item 6 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;


      case 6:  // menu item 7 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;
        
        // add as many "case #:" as items you have. You could put 
        //  line separators in menuList and leave out the 
        //  corresponding case, which would mean that nothing
        // would be triggered when user selected the line separator.  
      }
      break;
      
    case 8:  // encoder button was pushed for long time. This corresponds to "Back" or "Cancel" being pushed.
      stillSelecting = false;
      Serial.println("Button held for a long time");
      break;

    }

    switch(redraw){  //  checks if menu should be redrawn at all.
    case MOVECURSOR:  // Only the cursor needs to be moved.
      redraw = false;  // reset flag.
      if (cursorPosition > totalMenuItems) // keeps cursor from moving beyond menu items.
        cursorPosition = totalMenuItems;
      for(i = 0; i < (totalRows); i++){  // loop through all of the lines on the LCD
        lcd.setCursor(0,i);
        lcd.print(" ");                      // and erase the previously displayed cursor
        lcd.setCursor((totalCols-1), i);
        lcd.print(" ");
      }
      lcd.setCursor(0,cursorPosition);      // go to LCD line where new cursor should be & display it.
      lcd.print(">");
      lcd.setCursor((totalCols-1), cursorPosition);
      lcd.print("<");
      break;  // MOVECURSOR break.

    case MOVELIST:  // the entire menu needs to be redrawn
      redraw=MOVECURSOR;  // redraw cursor after clearing LCD and printing menu.
      lcd.clear(); // clear screen so it can be repainted.
      if(totalMenuItems>((totalRows-1))){  // if there are more menu items than LCD rows, then cycle through menu items.
        for (i = 0; i < (totalRows); i++){
          lcd.setCursor(1,i);
          lcd.print(menuItems[topItemDisplayed + i]);
        }
      }
      else{  // if menu has less items than LCD rows, display all available menu items.
        for (i = 0; i < totalMenuItems+1; i++){
          lcd.setCursor(1,i);
          lcd.print(menuItems[topItemDisplayed + i]);
        }
      }
      break;  // MOVELIST break
    }

    if (timeoutTime<millis()){  // user hasn't done anything in awhile
      stillSelecting = false;  // tell loop to bail out.
      /*
      in my main code, I had a function that
       displayed a default screen on the LCD, so
       I would put that function here, and it would
       bail out to the default screen.
       defaultScreen();
       */
    }
  } 


  while (stillSelecting == true);  //
}

 

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

d_p пишет:

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

Ну, если можете - редактируйте. А не можете - закажите кому-нибудь в разделе "Ищу исполнителя". Я готов помочь тому, кто пишет сам, но не готов писать за кого-то.

d_p
Offline
Зарегистрирован: 05.10.2016

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

d_p пишет:

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

Ну, если можете - редактируйте. А не можете - закажите кому-нибудь в разделе "Ищу исполнителя". Я готов помочь тому, кто пишет сам, но не готов писать за кого-то.

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

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

Я не понял, какое из моих слов Вам непонятно. Если бы Вы писали свой собственный код, я бы Вам помог. Если нет - обратитесь в "Ищу исполнителя".

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

Я готов помогать и много помогаю тем, кто делает сам. И никак по-другому.

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

Вот так и рассказывай, как первокласнику брать интеграл. d_p, вы не готовы к написанию меню. Да как пользователь может вы знаете как делать меню, а вот саму механику нет.  Энкодер меняйте на три клавиши (left,right,select) и все. А вот up и down это включить и отключить подсветку. Да и забейте про курсор. У вас не так много экрана и количество клавиш , что бы городить еще и это.

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

d_p пишет:

Как решить вопрос перемещение курсора?

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

алё! гараж, курсор - это выделенное красным прямоугольником, печатается lcd.cursor();

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016
void viev(int n) { //  в зависимости от n в нужном месте будет *
  lcd.setCursor(0, 0) ;
  lcd.print("MENU>SUBMENU1");
  lcd.setCursor(0, 1) ;
  if (n == 1) lcd.print("*");
  else lcd.print(" ");
  lcd.print("ON");
  if (n == 2) lcd.print("*");
  else lcd.print(" ");
  lcd.print("OFF");
  if (n == 3) lcd.print("*");
  else lcd.print(" ");
  lcd.print("AUTO");
}

Ну не хотят люди головой думать. Все нашарика хочется. Даже если и думать не надо.

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

qwone пишет:

Ну не хотят люди головой думать. Все нашарика хочется. Даже если и думать не надо.

так он звёздочку курсором обзывает, а ты и курсор и звёздочку заюзал...

d_p
Offline
Зарегистрирован: 05.10.2016

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

qwone пишет:

Ну не хотят люди головой думать. Все нашарика хочется. Даже если и думать не надо.

так он звёздочку курсором обзывает, а ты и курсор и звёздочку заюзал...

Он то что нужно использовал: lcd.setCursor() позиция курсора (невидимого) для вывода на дисплей. Мне не нужен lcd.Cursor(), вместо него использую звёздочку.

qwone спасибо

Logik
Offline
Зарегистрирован: 05.08.2014

qwone пишет:

void viev(int n) { //  в зависимости от n в нужном месте будет *
  lcd.setCursor(0, 0) ;
  lcd.print("MENU>SUBMENU1");
  lcd.setCursor(0, 1) ;
  if (n == 1) lcd.print("*");
  else lcd.print(" ");
  lcd.print("ON");
  if (n == 2) lcd.print("*");
  else lcd.print(" ");
  lcd.print("OFF");
  if (n == 3) lcd.print("*");
  else lcd.print(" ");
  lcd.print("AUTO");
}

Ну не хотят люди головой думать. Все нашарика хочется. Даже если и думать не надо.

Похоже. Только строки - в массив, допустим ArrStr, и тогда  заменятся строкт кода 5-13 на for(byte i=0;i<LenArrStr;i++) { if(n==i) lcd.print("*"); else lcd.ptint(" ");lcd.print(ArrStr[i]);} Если  ArrStr и LenArrStr (это количество строк в ArrStr) передавать как параметры, то функция становится универсальной для любого подменю, правда нужно еще придумать что делать с заглавием.

junior_developer
Offline
Зарегистрирован: 27.11.2017

Помогите пожалуйста понять, что означает этот кусочек кода:
 

   uint8_t keyboard() { // получение кода кода только что нажатой клавиши
      key_old = key; 
      key = keys(); 
      if (!key_old&&key ) return key;
      return 0;
      }

Предполагаю, что это защита от дребезга кнопок? Опрос клавиатуры выполняется несколько раз. Вижу, что до этого в коде есть такая функция

  void Controls_setup(){
      key = keys(); 
      } 

однако в ней нету return key; 
Как тогда она может передать значение?
и ещё мне совсем непонятно, что означает

if (!key_old&&key ) return key;

Здесь сравниваются логические значения? FALSE и TRUE? Буквально, если  key_old равно FALSE и key равно TRUE, тогда вернуть значение key? Верно?

 

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

#7  где вы там увидели true и false ?

junior_developer
Offline
Зарегистрирован: 27.11.2017

Я не правильно написал - правильно наверно эту строчку

if (!key_old&&key ) return key;

понимать так: если значение key_old равно нулю, а значение key не равно нулю, то вернуть это значение! Верно?

И кстати дальше там идет строка return 0;
 

      if (!key_old&&key ) return key;
      return 0;

А зачем возвращать из этой функции ноль? Если например условие истинно и функция возвращает значение key, оно тут же перезаписывается нулем? Или я неправильно понял?

 

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

Вот опять вы ничего не поняли. Там есть 5(пять !!) кнопок. А значит 6(шесть!!!) состояний, интересующих программиста. когда нажата одна из кнопок и когда не нажата ни одна из них. Опять же интересует переход от не нажата ни одна к нажатию одной из них. Тогда появляется значение 1,2,3,4,5 - а иначе 0.   ПС: сейчас бы я написал программу меню совершенно иначе. :)

if (/*если раньше было не нажато ни одной*/!key_old&&/*а сейчас нажато*/key ) return /*то вернуть код этой кнопки */key;

 

junior_developer
Offline
Зарегистрирован: 27.11.2017

Я наконец-то понял! Спасибо!

Ещё есть вопрос по вот этому кучочку кода

  static uint32_t MILLIS ;
  MILLIS = millis() ; 

//#5 1000 миллисекунд устройство
  static uint32_t future5 = 0 ;
  if (MILLIS>=future5) {
  future5 = MILLIS + 1000 ;
  loop5();
  MILLIS = millis() ;    
  }

можно ли его заменить на такой
 

if(MILLIS - future5>=1000){
   loop5();      
  future5 = MILLIS;        
 }
  MILLIS = millis() ; 

Подскажите пожалуйста, кто разбирается. Заранее спасибо!

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

Это мой старый стиль. Такчто код поновее был бы таким 

/**/
//#5 устройство
void setup5() {
}
void loop5() {
}
//--------main---------------------
void setup() {
  //#5
  setup5();

}
void loop() {
  unsigned long mill = millis() ;
  //#5 1000 миллисекунд устройство
  static uint32_t past5 = 0 ;
  if (mill - past5 >= 1000) {
    past5 = mill;
    loop5();
  }
}
/*Скетч использует 524 байт (1%) памяти устройства. Всего доступно 30720 байт.
  Глобальные переменные используют 13 байт (0%) динамической памяти, оставляя 2035 байт для локальных переменных. Максимум: 2048 байт.
*/

ПС: Ну а теперешний он больше такой.#1

junior_developer
Offline
Зарегистрирован: 27.11.2017

Новый код выглядит намного лучше!
Но вот заметил, что в старом варианте Вашего кода переменная, в которую передается значение из millis(); объявлена со словом static

static uint32_t

а в новом, без него! То есть unsigned long ? Перед ней слово  static писать не нужно?

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

В новом это глобальная переменая и объявляется в начале. Так как она нужна внутри классов. А в старом это локальная, но постояная, а не вновь создаваемая при каждом проходе. это про переменную со значением millis().

ПС:

/*  свободные 0,1,2,3,11,12,13,A1,A2,A3,A4,A5,A6,A7

  кнопки -> A0 (button sellect)
  S1 =Right   800-1023
  S2 =Up        0-199
  S3 =Down    200-399
  S4 =Left    400-599
  S5 = Select 600-799
  нет нажатия 800-1023
  экран  -> D4 (DB4)
       -> D5 (DB5)
       -> D6 (DB6)
       -> D7 (DB7)
       -> D8 (RS)
       -> D9 (Enable)
       -> D10 (LCD Backlight Control)
*/
uint32_t mill ;
//------------------------------
//#5 устройство
class Cl_device {
  protected:
    uint32_t past = 0 ;
  public:
    Cl_device() {};
    /*иницирование*/
    void init() {}
    /*работа*/
    void run() {
      if (mill - past >= 1000) {/*1 раз в сек*/
        past = mill ;
        /*код работы*/
      }
    }
};
//#8 дисплей
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
char* scrn0[] = {"Main  "};
char* scrn1[] = {" ..   ", "Load  ", "Save  ", "Regime", "Sub1  ", "Sub2  ", "var1.1", "var1.2", "var1.3", ""};
char* scrn2[] = {" ..   ", "var2.1", "var2.2", "var2.3", ""};
char* scrn3[] = {" ..   ", "var3.1", "var3.2", "var3.3", ""};
char* scrn4[] = {" ..   ", "Regime1", "Regime2", "Regime3", "Regime4", ""};
uint8_t N_scrn = 0; // номер экрана для показа
uint8_t N_ln = 0; // номер строки для показа

void Display_setup() {
  lcd.begin(16, 2);
}
void scrn0_viev() {
  lcd.print(scrn0[0]);
}
void scrn1_viev() {
  lcd.print(scrn1[N_ln]);
  lcd.setCursor(0, 1);
  lcd.print(scrn1[N_ln + 1]);
}
void scrn2_viev() {
  lcd.print(scrn2[N_ln]);
  lcd.setCursor(0, 1);
  lcd.print(scrn2[N_ln + 1]);
}
void scrn3_viev() {
  lcd.print(scrn3[N_ln]);
  lcd.setCursor(0, 1);
  lcd.print(scrn3[N_ln + 1]);
}
void scrn4_viev() {
  lcd.print(scrn4[N_ln]);
  lcd.setCursor(0, 1);
  lcd.print(scrn4[N_ln + 1]);
}
void Display() {
  lcd.clear();
  switch (N_scrn) {
    case 0:
      scrn0_viev();
      break;
    case 1:
      scrn1_viev();
      break;
    case 2:
      scrn2_viev();
      break;
    case 3:
      scrn3_viev();
      break;
    case 4:
      scrn4_viev();
      break;
  }
}
//#9
int const keyboard_pin = 0; // A0 - нога к которой присоединена клавиатура
uint16_t key_data; // значение уровня сигнала на ноге клавиатуры
uint8_t key, key_old; // значение последнего и прошлого состояние на ноге в коде кнопки

void Controls_setup() {
  key = keys();
}

uint8_t keys() { // первичный опрос
  key_data = analogRead (keyboard_pin);
  if (key_data < 60)       return 1;
  else if (key_data < 200) return 2;
  else if (key_data < 400) return 3;
  else if (key_data < 600) return 4;
  else if (key_data < 800) return 5;
  else return 0  ;
}

uint8_t keyboard() { // получение кода кода только что нажатой клавиши
  key_old = key;
  key = keys();
  if (!key_old && key ) return key;
  return 0;
}

uint8_t Controls() {  // анализ нажатых клавиш и выработка действия
  switch (keyboard()) {
    case 1: N_scrn++ ;       // Right
      N_ln = 0;
      break;
    case 2: N_ln-- ;         // Up
      break;
    case 3: N_ln++ ;         // Down
      break;
    case 4: N_scrn--;         //Left
      N_ln = 0;
      break;
    case 5: ;          //Select
      break;
  }
}
//---------------------------
Cl_device device;
// --------- main -------------
void setup() {
  device.init();
  //#8 вывод на дисплей
  Display_setup();
  //#9 опрос кнопок и выдача команд на выполнение
  Controls_setup();
}
void loop() {
  mill = millis() ;
  //#5 1000 миллисекунд устройство
  device.run();
  //#8  1000 миллисекунд. Вывод на дисплей
  static uint32_t past8 = 0 ;
  if (mill - past8 >= 1000) {
    past8 = mill;
    Display();
  }
  //#9  200 миллисекунд. Опрос кнопок и выдача команд на выполнение
  static uint32_t past9 = 0 ;
  if (mill - past9 >= 200) {
    past9 = mill;
    Controls();
  }
}
/*Скетч использует 2556 байт (8%) памяти устройства. Всего доступно 30720 байт.
  Глобальные переменные используют 259 байт (12%) динамической памяти, оставляя 1789 байт для локальных переменных. Максимум: 2048 байт.
*/

 

trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

Я бы сделал первой строкой (её номер 104)
if (key_data >1000 ) return 0;
чтобы зазря не бегать по всем ифам
если чаще всего результат - это
ненажатая кнопка

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

Бесполезно Пуха программированью учить. Он в классе вон использует глобальную переменную модуля. Которая, кроме как в классе, больше нигде использоваться не должна. Самаучка, чо уж тут...

junior_developer
Offline
Зарегистрирован: 27.11.2017

И где же эта глобальная переменная? Покажите конкретно строку!

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

обращение 

029     void run() {
030       if (mill - past >= 1000) {/*1 раз в сек*/
031         past = mill ;
032         /*код работы*/
033       }
034     }
 

нельзя так делать. все должно быть скрыто в классе. 

junior_developer
Offline
Зарегистрирован: 27.11.2017

да. действительно это глобальная переменная!

А как тогда правильно? Создать в классе ещё переменную (приватную) и присваивать ей значение глобальной?

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

Я завожу в классе переменную lasttime, инициализирую в конструктре нулем. При обращении к классу читается текущий millis. Некоторые датчики часто читать нельзя, поэтому заводится еще одна переменная, delta. Если при обращении к классу прошло времени меньше дельты, отдается последнее значение, если больше - датчик реально читается. В первый раз, ри обращении к классу, так как lasttime==0 установлен в конструкторе, датчик реально  читается.

Буду дома - выкладу код для примера.

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

Это дело вкуса. А на вкус и цвет все фломатреры разные. Вот скетч по qwone-эталону 

/**/
unsigned long mill;// переменная для millis()
typedef void (*pDo)() ;// тип -функция обработчик
//------Cl_Led----------------------
// класс светодиод
class Cl_Led {
  protected:
    const byte pin;
    bool led;
    unsigned long past, time;
    byte state; //0 выкл/ 1 вкл / 2 мигать
    /*установить в состояние*/
    void stand(byte state_) {
      past = mill;
      state = state_;
      switch (state) {
        case 0: // выкл
          digitalWrite(pin, led = LOW);
          break;
        case 1: // вкл
          digitalWrite(pin, led = HIGH);
          break;
        case 2:// мигать
          digitalWrite(pin, led = !led);
          break;
        case 3:// короткое выключение
          digitalWrite(pin, led = LOW);
          break;
        case 4:// короткое включение
          digitalWrite(pin, led = HIGH);
          break;
      }
    }
  public:
    /*конструктор*/
    Cl_Led(byte pin_): pin(pin_) {}
    /*инициализация-вставить в setup()*/
    void init() {
      pinMode(pin, OUTPUT);
      OFF();
    }
    /*работа-вставить в loop()*/
    void run() {
      if (state == 2 && mill - past >= time)stand(2);
      if (state == 3 && mill - past >= time)stand(1);
      if (state == 4 && mill - past >= time)stand(0);
    }
    /*включить*/
    void ON() {
      stand(1);
    }
    /*коротко включить*/
    void ON(unsigned long time_) {
      time = time_;
      stand(4);
    }
    /*выключить*/
    void OFF() {
      stand(0);
    }
    /*коротко выключить*/
    void OFF(unsigned long time_) {
      time = time_;
      stand(3);
    }
    /*мигать*/
    void blink(unsigned long time_ = 200) {
      time = time_;
      stand(2);
    }
};
//------Cl_Btn----------------------
// класс кнопка
class Cl_Btn {
  protected:
    const byte pin;
    pDo Do;//обработчик
    bool bounce = 0;
    bool btn = 1, oldBtn;
    unsigned long past;
  public:
    /*конструктор*/
    Cl_Btn(byte pin_, pDo Do_): pin(pin_), Do(Do_) {}
    /*инициализация-вставить в setup()*/
    void init() {
      pinMode(pin, INPUT_PULLUP);
    }
    /*работа-вставить в 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;
        if (!btn && oldBtn) Do();
      }
    }
};
//-----компоновка----------------------
Cl_Led Led(/*пин*/13);
void DoBtn1() {
  Led.ON(200);
  Serial.println("DoBtn1");
}
void DoBtn2() {
  Led.OFF(200);
  Serial.println("DoBtn2");
}
void DoBtn3() {
  Led.blink();
  Serial.println("DoBtn3");
}
Cl_Btn Btn1(/*пин*/2,/*обработчик*/DoBtn1);
Cl_Btn Btn2(/*пин*/3,/*обработчик*/DoBtn2);
Cl_Btn Btn3(/*пин*/4,/*обработчик*/DoBtn3);
//-----main----------------------
void setup() {
  Serial.begin(9600);
  Led.init();
  Btn1.init();
  Btn2.init();
  Btn3.init();
}
void loop() {
  mill = millis();
  Led.run();
  Btn1.run();
  Btn2.run();
  Btn3.run();
}

/*Скетч использует 2858 байт (9%) памяти устройства. Всего доступно 30720 байт.
  Глобальные переменные используют 251 байт (12%) динамической памяти, оставляя 1797 байт для локальных переменных. Максимум: 2048 байт.
*/
002 unsigned long mill;// переменная для millis()

и дальше

128 void loop() {
129   mill = millis();
130   Led.run();
131   Btn1.run();
132   Btn2.run();
133   Btn3.run();

Зачем переменная mill. Для того что бы millis() не дергать часто. Раз millis() - глобальная функция, то и переменная обслуживающая ее глобальна. И не надо городить посредников в представителе каждого класса.

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

qwone пишет:

Зачем переменная mill. Для того что бы millis() не дергать часто. Раз millis() - глобальная функция, то и переменная обслуживающая ее глобальна. И не надо городить посредников в представителе каждого класса.

А что, millis() изнашивается? 

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

sadman41 пишет:
А что, millis() изнашивается?
Процессор "изнашивается". Время для выполнения millis() больше чем извлечение из переменной. И если 10 раз выполнить millis(), то на столько времени "изностися" процессор.

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

Представляю, что творит с процессором loop(). А ISR(TIMER0_OVF_vect) и вовсе надо за геноцид привлечь.

Хотя, я же понимаю, что вы шутите. Действительно - кому нужно протухшее значение millis() в какой-то там глобальной переменной.

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

Cкетч на тему LCD Key Shield .#11  Ну и теперешняя версия 

/*LCD Key Shield
*/
unsigned long mill;// переменная для millis()
typedef void (*pDo)() ;// тип -функция обработчик
#include <LiquidCrystal.h>
//--------------------Cl_analog--------------------------------
class Cl_analog {
    const byte pin;
    int  data_next, data, data_old;
    bool bounce = 0; // антидребезговый флаг
    uint32_t past = 0 ;
    pDo Do_Right, Do_Up, Do_Down, Do_Left, Do_Select;
  public:
    /*конструктор*/
    Cl_analog(byte _pin, pDo DoR, pDo DoU, pDo DoD, pDo DoL,  pDo DoS)
      : pin(_pin), Do_Right(DoR), Do_Up(DoU), Do_Down(DoD), Do_Left(DoL), Do_Select(DoS) {}
    /*инициализация-вставить в setup()*/
    void init() {
      data = analogRead (pin);
    }
    /*работа-вставить в loop()*/
    void run() {
      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 < 110) Do_Right();
          else if (data < 200) Do_Up();
          else if (data < 400) Do_Down();
          else if (data < 600) Do_Left();
          else if (data < 800) Do_Select() ;
        }
      }
    }
};
//---------------Компоновка----------------------------------------------
LiquidCrystal lcd(/*RS*/8,/*Enable*/9,/*DB4*/4,/*DB5*/5,/*DB6*/6,/*DB7*/7);
void lcd_init() {
  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_analog Keys(/*пин*/A0,/*обработчик вправо*/Do_Right,/*обработчик вверх*/Do_Up,/*обработчик вниз*/Do_Down,/*обработчик влево*/Do_Left,/*обработчик селект*/Do_Select);
//----------------- Main() -----------------------
void setup() {
  lcd_init();
  Keys.init();
}
void loop() {
  mill = millis();
  Keys.run();
}
/*Скетч использует 2280 байт (7%) памяти устройства. Всего доступно 30720 байт.
  Глобальные переменные используют 135 байт (6%) динамической памяти, оставляя 1913 байт для локальных переменных. Максимум: 2048 байт.
*/

 

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

qwone пишет:

Это дело вкуса. 

Использование глобальной переменной в классе - дело не вкуса, а непрофессионального проектирования программы. Отвыкайте от этого. DetSimen дело говорит.

junior_developer
Offline
Зарегистрирован: 27.11.2017

DetSimen пишет:

Я завожу в классе переменную lasttime, инициализирую в конструктре нулем. При обращении к классу читается текущий millis. Некоторые датчики часто читать нельзя, поэтому заводится еще одна переменная, delta. Если при обращении к классу прошло времени меньше дельты, отдается последнее значение, если больше - датчик реально читается. В первый раз, ри обращении к классу, так как lasttime==0 установлен в конструкторе, датчик реально  читается.

Буду дома - выкладу код для примера.

Да, если Вам не сложно, приведите пожалуйста пример кода!

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

для этого еще пратрезветь надо. 

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

DetSimen пишет:

для этого еще пратрезветь надо. 

Ох, осторожнее коллега! Как бы не нарваться. Посмотрите п. 9 вот здесь! Хотя мне там больше всего нравится налог - п.7. Святые сёстры знали толк в правильных налогах :)))))

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

батогами мня уже били. 

junior_developer
Offline
Зарегистрирован: 27.11.2017

DetSimen пишет:
Буду дома - выкладу код для примера.

Посмотрите пожалуйста, правильно ли  я написал?


 const int sensor 3; // сенсор подключен к этому выводу

 int ReadSensor() { // функция чтения сенсора  через заданный интервал времени
 static uint32_t lasttime = 0; // переменная для запоминания времени
 static uint32_t millis = 0;  // для считывания времени из функции
const uint32_t delta =; 1000; // константа для разницы значений (одна секунда)
   millis = millis(); // считывание значения из функции "таймера"
   if (millis - lasttime >= delta) { // если прошло достаточно времени...
   val = analogRead(sensor);       // прочесть датчик
   lasttime = millis;             // передать значение для следующего сравнения
   }
 return val;                     // вернуть значение, сохраненное в переменной
 }
sadman41
Онлайн
Зарегистрирован: 19.10.2016

Это не будет скомпилированно. 

static uint32_t millis - не обязательно должен быть static, да и вообще может быть исключен.

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

junior_developer пишет:

Посмотрите пожалуйста, правильно ли  я написал?

Простите, за личный вопрос, но какой Вы конфессии, что Ваша религия не позволяет Вам запустить компилятор и посмотреть как он это скомпилирует?

junior_developer
Offline
Зарегистрирован: 27.11.2017

Да действительно в коде была целая куча ошибок! Однако я все их исправил и он уже успешнно компилируется! Вот что получилось  в итоге:

const int sensor = 3; // сенсор подключен к этому выводу

int ReadSensor() { // функция чтения сенсора  через заданный интервал времени
static uint32_t lasttime = 0; // переменная для запоминания времени
static uint32_t mill = 0;  // для считывания времени из функции
const uint32_t delta = 1000; // константа для разницы значений (одна секунда)
int val;
   mill = millis(); // считывание значения из функции "таймера"
   if (mill - lasttime >= delta) { // если прошло достаточно времени...
   val = analogRead(sensor);       // прочесть датчик
   lasttime = mill;             // передать значение для следующего сравнения
   }
 return val;                     // вернуть значение, сохраненное в переменной
 }

void setup() {
  pinMode(sensor, INPUT); 
}
void loop() {
 ReadSensor();
}

Теперь ведь правильно? Единственный вопрос: как сделать так, чтобы датчик читался ещё в начале запуска, а не только через заданный промежуток времени! Подскажите пожалуйста, что нужно изменить?

DIYMan
DIYMan аватар
Онлайн
Зарегистрирован: 23.11.2015

.

if (!lasttime || mill - lasttime >= delta)

 

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

/**/
const int sensor =/*пин*/ A0; // сенсор подключен к этому выводу
int ReadSensor() {
  static int val = analogRead(sensor); //<-читается в начале
  static uint32_t lasttime = 0;
  const uint32_t delta = 1000;
  uint32_t mill = millis();
  if (mill - lasttime >= delta) val = analogRead(sensor);
  return val;
}
//---main()------------------------
void setup() {
  Serial.begin(9600);

}
void loop() {
  delay (200);
  Serial.println(ReadSensor());
}
/*Скетч использует 1968 байт (6%) памяти устройства. Всего доступно 32256 байт.
  Глобальные переменные используют 196 байт (9%) динамической памяти, оставляя 1852 байт для локальных переменных. Максимум: 2048 байт.
*/

 

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

junior_developer пишет:

целая куча ошибок! Однако я все их исправил 

Вы Закон Брукса знаете?

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

junior_developer пишет:

Теперь ведь правильно? 

И опять откуда-то с глобального уровня в функцию падает int sensor. Функция вам нужна для реентерабельности или для красоты?

А поделитесь вы с другом функцией - откуда он узнает, почему компилятор ругается на необъявленную переменную sensor?

junior_developer
Offline
Зарегистрирован: 27.11.2017

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

junior_developer пишет:

целая куча ошибок! Однако я все их исправил 

Вы Закон Брукса знаете?

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

sadman41 пишет:

А поделитесь вы с другом функцией - откуда он узнает, почему компилятор ругается на необъявленную переменную sensor?

Да! Вы абсолютно правы! я как-то над этим не подумал.

 

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

Вы не о том. Причём тут название книги? 

Закон Брукса имеет прямое отношение к Вашему заявлению

junior_developer пишет:

целая куча ошибок! Однако я все их исправил 

Он формулируется так: "Всякая найденная в программе последняя ошибка, на самом деле является предпоследней"

Запомните на всю оставшуюся жизнь.

Эту фразу приписывают многим, но Кнут говорил, что оригинально она принадлежит Бруксу.

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

Херня все это . Идеальная программа это программа которая кормит всех, и даже тех кто ищет ошибки в программе. И как только из программы удалят последнюю ошибку, то эту программу отправят на свалку и начнут писать новую идеальную программу ;).

junior_developer
Offline
Зарегистрирован: 27.11.2017

DIYMan пишет:
if (!lasttime || mill - lasttime >= delta)
Расшифруйте пожалуйста, что это значит?
Две черточки это  логическое ИЛИ? 
 то есть  !lasttime ИЛИ  mill - lasttime >= delta
что означает !lasttime с этим знаком "!"? Это эквивалентно  lasttime == 0;
Верно?

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

junior_developer пишет:

DIYMan пишет:
if (!lasttime || mill - lasttime >= delta)
Расшифруйте пожалуйста, что это значит?
Две черточки это  логическое ИЛИ? 
 то есть  !lasttime ИЛИ  mill - lasttime >= delta
что означает !lasttime с этим знаком "!"? Это эквивалентно  lasttime == 0;
Верно?

uint32_t lasttime = 0;
Serial.println(lasttime);
lasttime = !lasttime;
Serial.println(lasttime);
lasttime = 12345;
Serial.println(lasttime);
lasttime = !lasttime;
Serial.println(lasttime);