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

d_p
Offline
Зарегистрирован: 05.10.2016
Господа, прошу помощи в реализации меню для Ардуино УНО+ЖК дисплей 16x2+аналоговые кнопки вверх, вниз, влево, вправо, выбор. Только начал осваивать ардуино (считайте зелёный новичек), и встал вопрос о практической реализации меню. Микроконтроллер нужен для управления аквариумумом. Ниже на схеме условно показана структура работы:
 
В качестве курсора использовал символ "*" (звёздочка), при нажатии "Выбор" переход к подменю выделенного пункта. Дальше подменю разветвляется на еще 2 позиции, звездочка перемещается горизонтально (выбираем тип осещения - "накаливания" или "светодиоды")
 
 
По моим прикидкам все меню и подменю должны быть в многомерном массиве, и экран прорисоввывается в зависимости от нахождения в конкретном меню (то есть ячейки массива, которая изменяется при навигации). Остаётся неясным как реализовать горизонтальное перемещение курсора "*" и заставить по нажатию "Выбор" изменять переменную, отвечающую за включение и отключение реле управления светом. То есть в позиции курсора на ВКЛ и нажатии "выбор" замыкаются контакты на реле, зажигающей светодиоды. При нажатии "вправо" курсор перемещается на "ВЫКЛ" и при нажатии "выбор" реле размыкается. При перемещении еще вправо (и нажатии кнопки "выбор" активируется режим "АВТО" и реле работает согласно настройкам из следующего подменю "НАСТР", в котором указывается период освещения. При настройки периода курсор перемещается по часам, минутам пуска и по часам, минутам отключения. Кнопками "вверх", "вниз" меняются данные переменные и при нажатии "выбор" сохраняются в EEPROM. Как это реализовать буду думать позже, видел подобные примеры, но пока прошу подсказать с реализацией меню и изменении переменных через него. Наверняка многоуровневое меню требуется не мне одному, и сделать свою поделку с изменением настроек и переменных без ПК хочется многим.
 
 
 
vvadim
Offline
Зарегистрирован: 23.05.2012

вы только начали осваивать дуню и сразу изобретать велик начали

есть 100500 готовых решений в гугле и на ютубе

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

d_p пишет:

Наверняка многоуровневое меню требуется не мне одному

Это точно! И тем таких здесь навалом и готовых решений - девать некуда.

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

d_p пишет:

По моим прикидкам все меню и подменю должны быть в многомерном массиве, 
 

Можно, конечно и так. Отлаживать будет очень трудно, но если уж отладите - спецом станете.

А так вообще, для меню больше подходят списки, чем массивы.

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

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

ПС. Тщательней проектируйте, на рисунке не все строки влазят в 20 символов. Экранчик у вас жутко слабый, может взять чего поприличней, пока не позно? Мой - http://arduino.ru/forum/obshchii/avtomatizatsiya-akvariuma#comment-222580

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

Спасибо за развернутый ответ. Шилд 1602 с аналоговыми кнопками уже давно валяется, заказывать что то посерьёзнее не вижу смысла. Все пункты будут на англ. и с сокращениями в 16-ти символьной строке всё умещается. Это не проект, это набросок для форума, причем очень условный) Полная структура меню в экселе готова, ищу варианты её реализации в коде и чтобы не сильно отнимало и так малые ресурсы UNO. По сути, это программируемые реле времени с функцией ручного управления.

Можно ссылку с примером меню на списках?

nik182
Онлайн
Зарегистрирован: 04.05.2015

В своё время я использовал микроменю
http://www.avrfreaks.net/projects/micromenu?skey=micromenu
Есть обсуждение здесь
http://www.forum.easyelectronics.ru/viewtopic.php?f=56&t=20416
Собралось легко. Работает замечательно как раз на шилде с аналоговыми кнопками.
Вызовы всех функций через callback.
http://arduino.ru/forum/programmirovanie/arduino-menyu-lsd-arduino-menu-lcd
А начинал разработку отсюда
http://easyelectronics.ru/organizaciya-drevovidnogo-menyu.html
Можно просто библиотеку с примерами
https://github.com/jonblack/arduino-menusystem/tree/master/examples/lcd_nav

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

vvadim пишет:

вы только начали осваивать дуню и сразу изобретать велик начали

Если бы велик. Так нет сразу вертолет да и притом военный бронированый.

Так что бы ТС прикинул сложность . Вот код. Но он не дописан.

/*  свободные 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)
*/

//#5 устройство
  void setup5(){
  }
  void loop5(){
  }
//#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;
         }
     }
// --------- main -------------
void setup() {
//#5
  setup5();
//#8 вывод на дисплей
  Display_setup();
//#9 опрос кнопок и выдача команд на выполнение
  Controls_setup();
}

void loop() {
  static uint32_t MILLIS ;
  MILLIS = millis() ; 

//#5 1000 миллисекунд устройство
  static uint32_t future5 = 0 ;
  if (MILLIS>=future5) {
  future5 = MILLIS + 1000 ;
  loop5();
  MILLIS = millis() ;    
  }
//#8  1000 миллисекунд. Вывод на дисплей
  static uint32_t future8 = 0 ;
  if (MILLIS>=future8) {
  future8 = MILLIS + 1000 ;
  Display();
  MILLIS = millis() ;    
  }
//#9  200 миллисекунд. Опрос кнопок и выдача команд на выполнение
  static uint32_t future9 = 0 ;
  if (MILLIS>=future9) {
  future9 = MILLIS + 200 ;
  Controls();  
  }
}

 

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

А это примерная картинка 

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

Та ладно там - вертолет. Посмотрел в своем проекте модуль меню, ну 190 строк. Тож разве много. Сам проект около тысячи строк, а бывают и на несколько тысяч с десятками модулей, и компилируется в 28кБ. Так это что - звезда смерти!? ))

Пример всего кода не выложу, там специфично очень, 3 главных меню и управление енкодером. А вот как описываю пункты меню может и интересно будет.


//***********************************************
//****  Главное меню
//***********************************************
char* MenuVert[]={"Atr123","str2","str3","str4"};

//***********************************************
//****  Подменю для каждого пункта меню
//***********************************************
char* SubMenu1[]={"S11","S12","S13"};
char* SubMenu2[]={"At21","At22","AAA23","DD24"};
char* SubMenu3[]={"zz31","zxc32"};
char* SubMenuExit=STR_EXIT;

const byte SubMenusLen[SIZE_MAIN_MENU]={
  SIZE_MENU(SubMenu1),
  SIZE_MENU(SubMenu2),
  SIZE_MENU(SubMenu3)};
  
char** SubMenus[SIZE_MAIN_MENU]={SubMenu1,SubMenu2,SubMenu3};

#define SIZE_MAIN_MENU  (sizeof(MenuVert)/sizeof(MenuVert[0]))

 

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

Logik Во-во.Я и говорю вертолет. Вертолет по весу вроде маленький, но специфики хоть отбавляй. Толи дело плот. Весу много, но на деле это просто вязанка. И специфики там очень мало.  А вот написать меню и без специфики сложновато будет.

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

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

ptr
Offline
Зарегистрирован: 28.05.2016

Logik пишет:

А вот как описываю пункты меню может и интересно будет.

После пива не возьмусь писать код, но я всю жизнь подобные вещи делал через указатели на функции. То есть, каждое меню есть массив структур, состоящих из отображаемой строки, указателя на функцию, выполняющую этот пункт меню и указателя-параметр функции. В качестве параметра, функции передется указатель на меню из которого она вызвана и порядковый номер элемента меню в нем и указатель-параметр (вычисляемый через номер элемента меню). Данный указатель-параметр используем как указатель на подменю для подменю или по своему усмотрению в иных случаях. Кроме массива меню должно содержать указатель на родительское меню или NULL (опционально и на пункт меню родительского меню). Чтобы обратно можно было подниматься тоже.

 

ptr
Offline
Зарегистрирован: 28.05.2016

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

А так вообще, для меню больше подходят списки, чем массивы.

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

 

ptr
Offline
Зарегистрирован: 28.05.2016

Logik пишет:

ИМХО - даже сложное меню это средний уровень программирования, хороший джуниор с ним справится за 3 бутылки пива и пару часов. 

Ну сложное меню я за пару часов не сделаю, хоть и не юниор. Имеется в виду, действительно сложное, как в древней С библиотеке CXL. То есть с enable/disable/hide пунктов, настраиваемыми пользователем горячими клавишами, отображением текущих статусов пунктов меню (вкл/выкл/текущее значение), динамической группировкой (в зависимости от того, какие пункты скрыты), полями ввода прямо в строках меню и т.п.

 

ptr
Offline
Зарегистрирован: 28.05.2016

ptr пишет:

Ну сложное меню я за пару часов не сделаю, хоть и не юниор.

Имелось в виду на МК без готового фреймворка. На том же QT и за полчаса управлюсь )

 

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

Опять не забываем, надо еще учитывать пользователя устройства. А то иногда делают такое меню, что без толстого мануала для пользователя нужную функцию не найти. Да и экран LCD 1602 и кнопок 3(вариант 1 энкодер)

ПС: А то иногда получается, что надо раз 12 нажать на одну кнопку, а потом 10 на другую , дальше обойти устройсто по кругу два раза, и вот устройство в нужном режиме. :)

ptr
Offline
Зарегистрирован: 28.05.2016

qwone пишет:

кнопок 3(вариант 1 энкодер)

А чем такой вариант не угодил?
https://ru.aliexpress.com/item/Hot-Selling-New-Infrared-IR-Wireless-Remo...

или такой:

https://ru.aliexpress.com/item/New-4-4-Matrix-Array-Matrix-Keyboard-16-K...

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

Ну да , еще плазму вместо экрана. Если много кнопок, то 1 кнопка - одна функция. Там меню не к чему.Даже экрана не надо. Все покажет результат.

ptr
Offline
Зарегистрирован: 28.05.2016

qwone пишет:

Ну да , еще плазму вместо экрана.

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

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

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

ptr
Offline
Зарегистрирован: 28.05.2016

qwone пишет:

 Если много кнопок, то 1 кнопка - одна функция. Там меню не к чему.Даже экрана не надо. Все покажет результат.

Хмм... Мои наблюдения показали, что у средней домохозяйки на запоминание четырех кнопок с часто используемыми функциями STB (телепрограмма, пауза/продолжение, текущее время с календарем, настройка записи по расписанию) уходит не меньше недели. Боюсь, что уже 8 кнопок потребуют не меньше месяца. А запомнить 12, как на пульте, может оказаться вообще не выполнимой задачей.

 

ptr
Offline
Зарегистрирован: 28.05.2016

qwone пишет:

А про многоуровневое меню лучше забыть.

Меню да, а вот подсказки на индикаторе все равно потребуются.

 

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

qwone пишет:

vvadim пишет:

вы только начали осваивать дуню и сразу изобретать велик начали

Если бы велик. Так нет сразу вертолет да и притом военный бронированый.

Так что бы ТС прикинул сложность . Вот код. Но он не дописан.

/*  свободные 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)
*/

//#5 устройство
  void setup5(){
  }
  void loop5(){
  }
//#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;
         }
     }
// --------- main -------------
void setup() {
//#5
  setup5();
//#8 вывод на дисплей
  Display_setup();
//#9 опрос кнопок и выдача команд на выполнение
  Controls_setup();
}

void loop() {
  static uint32_t MILLIS ;
  MILLIS = millis() ; 

//#5 1000 миллисекунд устройство
  static uint32_t future5 = 0 ;
  if (MILLIS>=future5) {
  future5 = MILLIS + 1000 ;
  loop5();
  MILLIS = millis() ;    
  }
//#8  1000 миллисекунд. Вывод на дисплей
  static uint32_t future8 = 0 ;
  if (MILLIS>=future8) {
  future8 = MILLIS + 1000 ;
  Display();
  MILLIS = millis() ;    
  }
//#9  200 миллисекунд. Опрос кнопок и выдача команд на выполнение
  static uint32_t future9 = 0 ;
  if (MILLIS>=future9) {
  future9 = MILLIS + 200 ;
  Controls();  
  }
}

 

Спасибо за код. 

Кто нибудь сможет подсказать как сделать курсор перемещающийся по пунктам (курсор "*" из первого поста). Придется перерисовывать весь экран чтобы изменить позицию? Или можно заменить символ "*" на "пробел в старой позиции и заменить "пробел" на "*" в новой?

На экране будет так:

MENU>SUBMENU1

*ON   OFF     AUTO

При нажатии кнопки "вправо" получаем:

MENU>SUBMENU1

  ON   *OFF     AUTO

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

Ну да скорее всего так и будет. Для обновления положения курсора надо перерисовывать экран. Но есть хорошая новость. Так как человек видит и осознает медленно, то обновлять экран можно раз в 0,5-1 сек. Что в масштабах процессора вечность. Да и LCD 1602 это всего 32 знакоместа. Но то надо делать быстро. То есть переменые должны быть уже готовы к выводу, даже если это спец знаки и рисунки. А то некоторые уникумы выводят часть экрана, а потом довычисляют и выводят другую половину. Вот и происходит визуально как бы всплывание. И да курсор можно делать мигающим. Это когда один раз вывести " " пробел, другой "*". Можно в зависимости от режима выводить "." или " -". Но когда разрабатываешь меню прежде всего надо учитывать не только удобство программиста, но и пользователя. 

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

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

qwone пишет:

Ну да скорее всего так и будет. Для обновления положения курсора надо перерисовывать экран. Но есть хорошая новость. Так как человек видит и осознает медленно, то обновлять экран можно раз в 0,5-1 сек. Что в масштабах процессора вечность. Да и LCD 1602 это всего 32 знакоместа. Но то надо делать быстро. То есть переменые должны быть уже готовы к выводу, даже если это спец знаки и рисунки. А то некоторые уникумы выводят часть экрана, а потом довычисляют и выводят другую половину. Вот и происходит визуально как бы всплывание. И да курсор можно делать мигающим. Это когда один раз вывести " " пробел, другой "*". Можно в зависимости от режима выводить "." или " -". Но когда разрабатываешь меню прежде всего надо учитывать не только удобство программиста, но и пользователя. 

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

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

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

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

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

И что я там нашел бы. Или вы как Поняковский "Шура, Поезжайте в Киев"

http://www.youtube.com/watch?v=smfylqeFHgY

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

qwone пишет:

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

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

И что я там нашел бы. Или вы как Поняковский "Шура, Поезжайте в Киев"

http://www.youtube.com/watch?v=smfylqeFHgY

что-то, кроме print

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

Нет, я просто понял что вы меня послали, но очень тонко. Радует, что не заставили смотреть .exe файл.

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

qwone пишет:

смотреть .exe файл.

какой .exe файл, если .hex файл?

ты точно про дуино бредил?

 

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

qwone пишет:

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

А в чем сакральный смысл непрерывной перерисовки экрана? Ну вывели экран и ждем пока кто чего нажмет. Если лазит в пределах текущего меню - ну перерисовали два пункта - тот который был выделен до перемещения и тот который стал после. Если совсем влом думать - перерисовали поверху не вытерая старое. Но обновлять его раз в 0,5-1сек зачем?

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

Одна из причин. Вывод на экран можно организовать отдельным потоком, который не думает, что где подтереть и написать по верху,было ли изменение или нет. Обновилось раз в 0,5-1сек и порядок. Опять же таким образом проще менять разные типы экранов , не меняя программу. Что так и делается в Windows. Теряя в малом выигрываем в большом. Когда перешли на графические интерфейсы, то же часть программистов выло против этого.

ptr
Offline
Зарегистрирован: 28.05.2016

qwone пишет:

 Вывод на экран можно организовать отдельным потоком, который не думает, что где подтереть и написать по верху,было ли изменение или нет. Обновилось раз в 0,5-1сек и порядок. Опять же таким образом проще менять разные типы экранов , не меняя программу. Что так и делается в Windows.

В Windows так не делают. Не знаю ни одного DE или WM где так делалось. Обмен между прикладной программой и DE идет на уровне сообщений. Аналогично и обмен между DE и WM. А значит, перерисовываться будет только тот кусок экрана, для которого это понадобилось по двум причинам:

1. Само приложение пожелало что-то изменить в своем окне

2. То, что было ранее закрыто другим окном стало открыто и DE сам сообщил программе о необходимости перерисовки указанной зоны окна.

В рамках же GUI фреймворка, формирующего окно приложения, опять то же самое. Перерисовывается только измененный или новый элемент управления.

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

 

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

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

ptr
Offline
Зарегистрирован: 28.05.2016

qwone пишет:

ptr.Ну пока выходит так. Ну да возможно изврат.

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

Цитата:

Это мое мнение, и пока я не вижу альтернативы.

Альтернатива - через сообщения. Как в Windows. Просто строить на МК полноценную цепочку WM - DE - GUI framework смысла нет. Проще, как лет 20 назад, напрямую WM - framework. Если будет простейшая реализация X-Windows, то да, прикладная программа не будет завязана на конкретный дисплей.

Но это даже для символьных дисплеев не меньше недели работы. 8 часов проектирование, 24 программирования, 8 отладки и тестирования.

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

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

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

qwone пишет:

Одна из причин. Вывод на экран можно организовать отдельным потоком, который не думает, что где подтереть и написать по верху,было ли изменение или нет. Обновилось раз в 0,5-1сек и порядок. Опять же таким образом проще менять разные типы экранов , не меняя программу.

хочу тебя огорчить в, отличии от твоей реальности потокого коня в вакууме, в настоящей реальности присутсвуют экраны отличающиеся от формата 16Х2.

как будет выглядеть код твоей программы, которая чего-то там не думает, на экраны не 16Х2 ?

 

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

qwone пишет:

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

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

1. это мой подход, т.к. я три года тому написал себе первый велосипед меню, где меню представляло из себя нумерованый набор экранов, листаемое энкодером.

2. про корону учителя - даже комментиовать не желаю...

ptr
Offline
Зарегистрирован: 28.05.2016

qwone пишет:

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

Вы заблуждаетесь. На прикладном уровне обработчик событий получает и при нажатии иконки, и при выборе пункта меню, одно и то же сообщение. Вне зависимости от того, выбрана была эта иконка или пункт меню с клавиатуры или мышью. Разница между мышью и клавиатурой есть на уровне DE и GUI framework.

Само собой, прикладной уровень может запросить и непосредственное получение сообщений с клавиатуры (скан коды) и с мыши. Живой пример - компьютерные игры, не использующие DE и имеющие свой framework, обычно 3D. Но это уже исключение, а не правило.

 

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

ptr пишет:

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

 

 

И для МК - изврат.

ptr пишет:

 
Альтернатива - через сообщения. Как в Windows. Просто строить на МК полноценную цепочку WM - DE - GUI framework смысла нет.
 

 
 
 
Смысл есть, он просто неочевиден и не проявляется в небольших приложениях. 
А в сложных приложениях появляется такая проблема. На примере меню смотрим. Допустим выбрали из меню пункт типа "опросить датчики и отправить на сервер". Что имеем без сообщений, императивным подходом, вызвали в лупе проверку кнопок и обнаружили нажата "ОК", вызвали систему меню и передали ей код клавиши, она определила что надо делать "опросить датчики и отправить на сервер" и вызвала нужную функцию, та полезла к датчикам, получила нужное сохранила локально в лог и отправила на сервер. Получается очень долго и за счет цепочки вызовов большая нагрузка на стек. Из за длительности исполнения к следущей проверки кнопок прийдем не скоро, пропуски нажатия кнопок и др. неприятности заставляющие думать о многопоточности и других глупостях.
А можно с сообщениями: принимаем - один цикл лупа это обработка одного сообщения, ну а добавляем сколько хотим лиш бы в очередь влезло. В лупе обнаружили нажатие  "ОК" - сунули в очередь сообщение, на это или следующем проходе лупа нашли это сообщение и вызвали систему меню она в ответ вносит в очередь сообщение "опросить датчики", на следующем лупе погнал опрос и при завершении вносит в очередь допустим 2 сообщения сохранить в логе и отправить на датчик. Плюсы подхода - луп остался быстрым, стек короткий. Есть и минус - данные с датчиков надо дето хранить пока их логируют и на сервер отправляют, но это другая тема.

ptr пишет:

 
Но это даже для символьных дисплеев не меньше недели работы. 8 часов проектирование, 24 программирования, 8 отладки и тестирования.
 

 
 
 
Завышено. Императивный подход от сообщений отличается самой системой сообщений. А это банальная очередь с функционалом добавить сообщение и выбрать сообщение самое старое.
Там работы и на час не будет. А вот мое. Лежит в ашнике реализации меню на графическом 320*240, там вобще не только меню а GUI. Сообщения названы командами, т.к. наличие параметров не предполагал. Просто не байтовая очередь а структуры будут.
/*************************************************************************************************************
Процессор команд. Не является частью GUI, но через него поступают команды от обектов GUI в основную программу.
/*************************************************************************************************************


/* Обявление данных для процессора команд */
#define COMMAND_ARRAY_SIZE 8
#define COMMAND_NULL 0
#define DECLARE_COMMAND_PROCESS byte CommandStack[COMMAND_ARRAY_SIZE];
/* Цикл выполнения команд */
#define INIT_COMMAND_PROCESS {for(byte i_tmp_=0;i_tmp_<sizeof(CommandStack);i_tmp_++)CommandStack[i_tmp_]=COMMAND_NULL;}
#define BEGIN_COMMAND CommandExec_: switch (CommandStack[0]){
#define END_COMMAND }{memcpy(&CommandStack[0],&CommandStack[1],sizeof(CommandStack)-1);CommandStack[sizeof(CommandStack)-1]=COMMAND_NULL;}

/* Инициируем команду */
#define SET_COMMAND(a) {for(byte i_tmp_=0;i_tmp_<sizeof(CommandStack);i_tmp_++) if(CommandStack[i_tmp_]==COMMAND_NULL){CommandStack[i_tmp_]=a;break;}}
#define GET_COMMAND CommandStack[0]

extern DECLARE_COMMAND_PROCESS

void SetCommand(byte cmd);

Ну и пример

    
/**********************************************************************************************/
    /*                                      Обработка команд                                      */
    /**********************************************************************************************/
    
BEGIN_COMMAND
    case CMD_INIT:
      SetCommand(CMD_GET_REAL_DATATIME);  //устанавливаем текущее время
      SetCommand(CMD_GET_TIMER_CHECK);    //определяем состояния всех таймеров и находим переключившмеся устройства и их новые состояния
      SetCommand(CMD_OUT_DEV_CHANGE);     //
      SetCommand(CMD_OUT_DEV_SPEED_INIT); // Инициируем вывод всех каналов на их текущме уровни быстро но не мгновенно
        break;
    case CMD_SECOND_INC:
      if(DateTime.sec>=59)
      {
         DateTime.sec=0;
         SetCommand(CMD_MINIT_INC);
      }
...END_COMMAND

 

 

Но теперь я понял чего qwone считает меню сложным и "вертолетом". С его подходом - так и есть!

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

 

ptr
Offline
Зарегистрирован: 28.05.2016

Logik пишет:

ptr пишет:

 
Но это даже для символьных дисплеев не меньше недели работы. 8 часов проектирование, 24 программирования, 8 отладки и тестирования.
 

Завышено.

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

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

 

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

Та сделано оно давно. Он их скока..

enum
{
  EMPTY =0,
  PANEL ,   // может содержать несколько других обектов
  BUTTON,   // просто кнопка с надписью
  BOX,      // область ввода
  LIST,     // многострочная область вывода с прокруткой
  CHECK,    // флажек
  STATICTEXT,
  TEXT,
  NO_BORDER_PANEL,
  V_MENU,
  USER_BOX,
  USER_TEXT, //вызывается callback для вывода текста или возвращения указателя на строку
};

И меню там среди них.

Все через сообщения, с тачпадом, с перекрытиями (кстати совсем не оправдалось, экран мелкий и каждое окно всеравно во весь экран, разве что виртуальную клавиатуру цифровую в пол экрана) c перерисовкой и т.д.  Про конкуренцию контролов... э ну тут специфика, их хочется держать в PROGMEM, по крайней мере основную часть каждого контрола, выделяя немного ОЗУ, в нем переменные описывающие динамические св-ва ну и Z-последовательность. В общем оно есть но большая специфика из-за статичности контролов.  

В работе уже выкладывал http://arduino.ru/forum/obshchii/avtomatizatsiya-akvariuma#comment-222580

А весь код -ну наверно могу, но не за спасибо.

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

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

 

ptr
Offline
Зарегистрирован: 28.05.2016

Logik пишет:

#define END_COMMAND }{memcpy(&CommandStack[0],&CommandStack[1],sizeof(CommandStack)-1);CommandStack[sizeof(CommandStack)-1]=COMMAND_NULL;}

Строго говоря, это ошибка. Для перекрывающихся областей памяти следует использовать memmove()

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

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

ptr пишет:

Строго говоря, это ошибка. Для перекрывающихся областей памяти следует использовать memmove()

Я то знаю как стандартная memcpy работает, и то что в данном варианте она сработает верно не сомниваюсь, а при иных вариантах перекрытия могут быть проблемы с memcpy, это да. Про историю когда её доработали недавно я в курсе. 

ptr пишет:

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

Ну вот ))))

Logik пишет:

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

 

 

ptr
Offline
Зарегистрирован: 28.05.2016

Logik пишет:

ptr пишет:

Строго говоря, это ошибка. Для перекрывающихся областей памяти следует использовать memmove()

Я то знаю как стандартная memcpy работает, и то что в данном варианте она сработает верно не сомниваюсь

Видимо не знаете. В стандарте явно оговорено: "If the objects overlap, the behavior is undefined."
Аналогично и для GCC/Linux: "The memory areas must not overlap.  Use memmove(3) if the memory areas do overlap."

А то, что Вам повезло и эта реализация функции memcpy при данном выравнивании параметров отработала так, как Вы хотели, еще ни о чем не говорит. Она имеет права копировать не с начала, а с конца. Не с первого байта, а первой граница слова (например, 2 байта). Она может вообще часть скопировать программно, а часть - через DMA.

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

Цитата:

Ну вот ))))

Прошу прощения. Больше не буду )

 

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

ptr пишет:

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

 

Вы наверно пропустили эту битву титанов. ))) http://avva.livejournal.com/2323823.html

Я слишкомм мелок, чтоб занимать там какую-то стороно ))) У меня просто оно работает.

Что работает последние 40 лет, лучше не трогать )). Стандарт де-факто называется.

ptr
Offline
Зарегистрирован: 28.05.2016

Logik пишет:

ptr пишет:

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

 

Вы наверно пропустили эту битву титанов. ))) http://avva.livejournal.com/2323823.html

Я слишкомм мелок, чтоб занимать там какую-то стороно ))) У меня просто оно работает.

Что работает последние 40 лет, лучше не трогать )). Стандарт де-факто называется.

Как это пропустил? Явно же сказано, если хотите быстро, то пользуйтесь memcpy(), но учтите, что перекрытие она не отследит. И заметьте, тех, кто наплевал на то, что оговорено в стандарте - единицы!

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

Скажем так, еще в 90-х, будучи руководителем отдела разработки, стучал лично по голове тех своих сотрудников, которые использовали memcpy() там, где нужно использовать memmove().

Если Вам действительно не существенно быстродействие, никто не запрещает макрос себе написать:

#define memcpy(a,b,c) memmove(a,b,c)

Зато будете уверены, что Ваш код всегда будет работать.

На данный момент, для AVR используется memcpy.h из generic ветки glibc. И кто Вам даст гарантии, что завтра это не изменится? Будете тогда в "битве титанов" участвовать? )

 

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

Не. не буду. Хотя конечно прикольно бы было запостить "Я и Торвальд считаем" ;) 

ptr пишет:

 то пользуйтесь memcpy(), но учтите, что перекрытие она не отследит. 

 

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

ptr пишет:

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

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

ptr пишет:

Если Вам действительно не существенно быстродействие

Существенно. Оставим встороне вопрос что в данном примере разницу и под микроскопом не заметна. 

 

ptr пишет:

 

Зато будете уверены, что Ваш код всегда будет работать.

На данный момент, для AVR используется memcpy.h из generic ветки glibc. И кто Вам даст гарантии, что завтра это не изменится? 

 

"Моя плякала"))) Откуда столько оптимизма при наличии опыта?  Легкое подозрение что код вероятно работает в рамках ТЗ возникает при передче QA, после их положительного тестирования код скорей соответствует ТЗ чем нет, хороше если нашли ошибки - значить действительно тестировали. После передачи заказчику при отсутствии замечаний вероятность успеха не изменяется, т.к. скорей всего его просто не используют. При наличии замечаний можна принять что код ограничено рабочий, с учетом этих замечаний и невыявленых проблем, и только спустя месяцы-годы годы можна утверждать что наличествующие в коде ошибки не влияют на используемый функционал, заметим, не на соответствие ТЗ и ТО. Таковы реалии. Слов "Ваш код всегда будет работать" и "кто Вам даст гарантии" в ИТ нету, это из профессионального наречия маркетологов. В ИТ обычно "Работает - не трогай" и "не тестировано - значить не работает".  Я знаю что при желании во всех стандартах (один TCP/IP чего с его флагами чего стоит) можно наковырять массу "моментов" с помощю которых поламать все оставаясь формально в рамках стандарта. Нормальный, понятно на это не пойдет, но "в семе не без урода", желание ускорится на "нольцелыххрендесятых" ради маркетинга приводит к тому что увереным и "всегда" - не получится. Но на это и других причин валом.

Что конкретно буду делать если  gcc последует дурному примеру. Ясно дело - макросы повписываю. Трудозатраты минимальные. Главное чтоб при появлении новых версия четко и громко заявлялось о изменениях. 

Кстати последователей федоровской инициативы не нашлось больше, а 5 лет прошл.

ПС. Мы сильно ушли от темы ТС.

 

ptr
Offline
Зарегистрирован: 28.05.2016

Logik пишет:

Вы уверены что это все про Ардуино?
А я что ли сюда Linux приплел? )))
 
Цитата:
"Моя плякала"))) Откуда столько оптимизма при наличии опыта?

Как раз наоборот, все строго по законам Мерфи. Если что может случиться - оно случиться обязательно. Хотя "Мерфи был оптимистом"...  )

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

 

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

А я к Вам и не пойду )))

ptr пишет:

Потому я никогда не пишу код в рассчете на то, что что-то не произойдет и оно и так проскочит. 

 

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

 

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

Ой блин.Ну и договорились. Абсолютного надежного кода не существует.И нет гарантии что этот код не заглючит после какого-то обновления. Так что работа программиста это скорее всего написание заплаток на ранее рабочую программу. Опять же не надо делать из себя царя, того возьму, а того нет. Чаще всего есть заказ, есть деньги  приходится искать помошников, если самостоятельно не получится писать, а заказчику надо сдавать хотя бы демо код. Вы думаете что если сюда с заказом придете и к вам очередь постоется. Ага счаз. Даже за деньги. Людой код всегда надо проверять в железе. А при дистанционом не у всех получится собрать даже макет.