Простое МЕНЮ для символьного дисплея 2

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Этот пример являеться развитием примера меню которое я выкладывал тут. Т.к. в том коде было много недостатков, в часности отсутствовала защита от дребезга кнопок, мерцал экран и другие мелочи. Т.к. на форуме много кто из начинающих пытаеться его использовать например тут  по причине наглядности. Я его переработал и откоментировал. Структура не изменилась.

//Пример простого меню для LCD 16x2 V2
//yul-i-an. 03.2016
//В меню используется 4 экрана
//за номер отображаемого экрана отвечает переменная m
#include <LiquidCrystal.h> //Библиотека LCD
// инициализация LCD
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
int m=0; //переменная для экранов меню
int p1=0; // переменная для примера 1
int p2=0; // -//- 2
int p3=0; // -//- 3
long PSTimer = 0;//таймер для обновления экрана
#define nextPin 6 //кнопка меню на 6 входе
#define prevPin 7 //кнопка возврата
#define upPin 9 //увеличение значения отображаемого параметра
#define downPin 10 //уменьшение значения
byte f_key=0;//флаг для фиксации нажатия кнопки
#define ledPin 13 //Светодиод

void setup() {
  //Настройка входов
  pinMode(nextPin, INPUT);
  pinMode(prevPin, INPUT);
  pinMode(upPin, INPUT);
  pinMode(downPin, INPUT);
  //Настройка выходов
  pinMode(ledPin, OUTPUT);
  //Настройка дисплея
  lcd.begin(16, 2);//Установка количества столбцов и строк дисплея
  // Вывод приветствия
  lcd.print("DEMO MENU! V2");
  delay (3000);
}

void loop() {
  key();//опрос кнопок
  if (PSTimer>millis()){//защита от переполнения счетчика
    PSTimer = 0;
  }

  if (millis()-PSTimer>=50){//вывод экрана каждые 50мсек (20к/с)
    PSTimer=millis();
    PreentScreen(m);//выводим экран
  }
}//end LOOP

void PreentScreen(int s){//Вывод меню
  lcd.clear();//отчистка экрана
  //Описание экранов меню
  switch (s){
  case 0:
    lcd.setCursor(0, 0);
    lcd.print("Main Menu");
    lcd.setCursor(0, 1);
    lcd.print("P1=");
    lcd.print(p1);
    lcd.print(" P2=");
    lcd.print(p2);
    lcd.print(" LED=");
    lcd.print(p3);
    break;

  case 1:
    lcd.setCursor(0, 0);
    lcd.print("Parametr-1");
    lcd.setCursor(0, 1);
    lcd.print("P1 = ");
    lcd.print(p1);
    break;

  case 2:
    lcd.setCursor(0, 0);
    lcd.print("Parametr-2");
    lcd.setCursor(0, 1);
    lcd.print("P2 = ");
    lcd.print(p2);
    break;

  case 3:
    lcd.setCursor(0, 0);
    lcd.print("LED Control");
    lcd.setCursor(0, 1);
    lcd.print("LED = ");
    lcd.print(p3);
    break;
  }
}

//-------------ОПРОС КНОПОК------------
void key(){
  //Обработка нажатия кнопки NEXT
  if (digitalRead(nextPin)== HIGH && f_key==false)
  {
    action(0);
    f_key=true;//устанавливаем флаг
  }
  //Обработка нажатия кнопки PREV
  if (digitalRead(prevPin)== HIGH && f_key==false)
  {
    action(1);
    f_key=true;//устанавливаем флаг
  }
  //Обработка нажатия UP
  if (digitalRead(upPin)== HIGH && f_key==false)//если находимся на экране с переменной р1
  {
    action(2);
    f_key=true;//устанавливаем флаг
  }
  //Обработка нажатия DN
  if (digitalRead(downPin)== HIGH && f_key==false)//если находимся на экране с переменной р1
  {
    action(3);
    f_key=true;//устанавливаем флаг
  }
  //проверка на отпускание кнопки
  if (digitalRead(nextPin)==0 && digitalRead(prevPin)==0 && digitalRead(upPin)==0 && digitalRead(downPin)==0 && f_key==true){
    f_key=false;//сброс флага
  }
}

//--------РЕАКЦИЯ НА КНОПКИ-------------------
void action(int x){
  switch (x){
  case 0://next
    m=var(m, 0, 3, 1, 1, 1);//увеличиваем переменную уровня меню
    break;
  case 1://prev
    m=var(m, 0, 3, 1, 0, 1);//уменьшаем переменную уровня меню
    break;
  case 2://up
    if (m==1){//если нажали UP когда экран с переменной р1
      p1= var(p1, 0, 10, 1, 1, 1);//то при нажатии кнопки + увеличиваем переменную р1 на единицу
    }
    if (m==2){//если нажали UP когда экран с переменной р2
      p2= var(p2, 0, 10, 1, 1, 1);
    }
    if (m==3){//если нажали UP когда экран с переменной р3
      p3= var(p3, 0, 1, 1, 1, 1);
      digitalWrite(ledPin, p3);
    }
    break;
  case 3://down
    if (m==1){//если нажали DOWN когда экран с переменной р1
      p1= var(p1, 0, 10, 1, 0, 1);//то при нажатии кнопки + увеличиваем переменную р1 на единицу
    }
    if (m==2){//если нажали DOWN когда экран с переменной р2
      p2= var(p2, 0, 10, 1, 0, 1);
    }
    if (m==3){//если нажали DOWN когда экран с переменной р3
      p3= var(p3, 0, 1, 1, 0, 1);
      digitalWrite(ledPin, p3);
    }  
    break;
  }
}

//--------------функция изменения переменной-------------------
//х=var(переменная,минимум,максимум,шаг,направление,циклически);
//переменная - переменная для изменения (int)
//минимум, максимум - пределы переменной
//шаг - шаг изменения переменной
//направление - увеличить или уменьшить
//циклически - если 0 то от мин до макс,
//если 1 то при достижение мах переходим к минимуму и наоборот
int var(int v, int mn, int mx, int stp, boolean pm, boolean c){
  switch (pm){//pm=1 увеличение pm=0 уменьшение
  case 0:
    v -= stp;//уменьшаем на шаг
    break;
  case 1:
    v += stp;//увеличиваем на шаг
    break;
  }
  switch (c){//c-1 циклически с-0 до пределов
  case 1://
    if (v<mn){
      v=mx;
    }
    if (v>mx){
      v=mn;
    }
    break;
  case 0:
    if (v<mn){
      v=mn;
    }
    if (v>mx){
      v=mx;
    }
    break;
  }
  return v;
}
//http://allduino.forum2x2.ru/t12-topic#17
//http://arduwir.netdo.ru/article/6264
Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Есть масса замечаний к коду, а значит есть еще куда развиваться.

1.Строка 37 очевидно от непонимания.

2.Антидребезга не увидел. Опрос кнопок оставляет желать лучшего.

3.Нет выхода из подменю без сохранения переменной.

4.Переменные разумнее организовать в виде структуры {Название, значение, max, min, шаг, цикличность}

5.В чем смысл вывода на экран каждые 50 мС? Индикация разве динамическая? Вывод нужно делать по событию.

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Andy пишет:

Есть масса замечаний к коду, а значит есть еще куда развиваться.

1.Строка 37 очевидно от непонимания.

2.Антидребезга не увидел. Опрос кнопок оставляет желать лучшего.

3.Нет выхода из подменю без сохранения переменной.

4.Переменные разумнее организовать в виде структуры {Название, значение, max, min, шаг, цикличность}

5.В чем смысл вывода на экран каждые 50 мС? Индикация разве динамическая? Вывод нужно делать по событию.

1. Строка 37 - служит для того чтобы при переполнении millis() условие в 41 строке выполнилось (если устройство расчитано на непрерывнуюработу - более 50 дней).

2. Антидребезг

//-------------ОПРОС КНОПОК------------
void key(){
  //Обработка нажатия кнопки NEXT
  if (digitalRead(nextPin)== HIGH && f_key==false)
  {
    action(0);
    f_key=true;//устанавливаем флаг
  }
  //Обработка нажатия кнопки PREV
  if (digitalRead(prevPin)== HIGH && f_key==false)
  {
    action(1);
    f_key=true;//устанавливаем флаг
  }
  //Обработка нажатия UP
  if (digitalRead(upPin)== HIGH && f_key==false)//если находимся на экране с переменной р1
  {
    action(2);
    f_key=true;//устанавливаем флаг
  }
  //Обработка нажатия DN
  if (digitalRead(downPin)== HIGH && f_key==false)//если находимся на экране с переменной р1
  {
    action(3);
    f_key=true;//устанавливаем флаг
  }
  //проверка на отпускание кнопки
  if (digitalRead(nextPin)==0 && digitalRead(prevPin)==0 && digitalRead(upPin)==0 && digitalRead(downPin)==0 && f_key==true){
    f_key=false;//сброс флага
  }
}

Я думаю что пока будет выполнятся код от 16 строки при нажатии кнопки UP до 27 строки где проверяется отпущина кнопка или нет положение контактов устаканиться. (если можете предложите лучьший варийант опроса кнопок).

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

4. Это дело сугубо личное, хоть объектом опишите.

5. Так проще, чем отслеживать изменение каждой переменной и при её изменении перирисовывать экран.

Спасибо за критику.

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

yul-i-an пишет:
1. Строка 37 - служит для того чтобы при переполнении millis() условие в 41 строке выполнилось (если устройство расчитано на непрерывнуюработу - более 50 дней).

Это все от непонимания беззнаковой арифметики.

yul-i-an пишет:
2. Антидребезг

Я думаю что пока будет выполнятся код от 16 строки при нажатии кнопки UP до 27 строки где проверяется отпущина кнопка или нет положение контактов устаканиться. (если можете предложите лучьший варийант опроса кнопок).

Дребезг носит миллисекундный характер, за 1 мС контроллер выполняет 16 000 инструкций, в строках 16-27 явно меньше. Лучший вариант опрашивать дважды с интервалом 5-100 мС и нажатие и отпускание.

yul-i-an пишет:
3. Если переключаться между экранами не меняя переменных, то они и не изменяться.
А если изменить значение, то шансов отказаться от изменения уже нет.

yul-i-an пишет:
4. Это дело сугубо личное, хоть объектом опишите.
Это если пишешь только для себя, тогда да, личное. Правда тогда нет смысла выкладывать код на всеобщее обозрение. Надо учиться структурировать код, переменные. Тогда меньше шансов допустить ошибку, код становится проще и понятней.

yul-i-an пишет:
5. Так проще, чем отслеживать изменение каждой переменной и при её изменении перирисовывать экран.
Какие проблемы при изменении переменной взводить флаг перерисовки экрана, и один раз перерисовывать сбрасывая флаг?

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Добавил автовозврат к основному экрану через 5 сек бездействия, обновление экрана 4 раза/сек. (достаточно) и поправил код.

//Пример простого меню для LCD 16x2
//В меню используется 4 экрана
//за отображаемый экран отвечает переменная m
#include <LiquidCrystal.h> //Библиотека LCD
// инициализация LCD
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
//массив надписей которые выводяться не на одном экране для экономии памяти
char* myLabel[]={//повторяющиеся надписи
  "Parametr","P1=","P2=","LED-"};
int m=0; //переменная для экранов меню
int p1=0; // переменная для примера 1
int p2=0; // -//- 2
boolean p3=0; // -//- 3
unsigned long PSTimer = 0;//таймер для обновления экрана
unsigned long KPTimer = 0;//таймер для опроса кнопок
unsigned long MSRTimer = 0;//таймер для автовозврата к MainScreen
#define MSRecovery 5000 //задержка автовозврата к MainScreen 3сек
#define nextPin 8 //кнопка меню на 8 входе
#define prevPin 11 //кнопка возврата
#define upPin 9 //увеличение значения отображаемого параметра
#define downPin 10 //уменьшение значения
boolean f_key=0;//флаг для фиксации нажатия кнопки
#define bounce 30 //задержка антидребезга
#define ledPin 13 //Светодиод

void setup() {
  //Настройка входов
  pinMode(nextPin, INPUT);
  pinMode(prevPin, INPUT);
  pinMode(upPin, INPUT);
  pinMode(downPin, INPUT);
  //Настройка выходов
  pinMode(ledPin, OUTPUT);
  //Настройка дисплея
  lcd.begin(16, 2);
  // Вывод приветствия
  lcd.print("DEMO MENU V2");
  delay (2000);
}

void loop() {
  //------Опрос кнопок-----------------------
  if (millis()>KPTimer){//опрос кнопок
    KPTimer=millis()+bounce;
    switch (f_key){
    case 0:
      key_dn();//проверка нажатия кнопки
      break;
    case 1:
      key_up();//проверка отпускания кнопки
      break;
    }
  }
  //-----Автовозврат к основному экрану---------------
  if (millis()-MSRTimer>=MSRecovery){  //Если счетчик достиг интервала
    MSRTimer=millis();
    m=0; //то отобразить главный экран                                        
  }
  //-----------Обновление экрана--------------
  if (millis()-PSTimer>250){//вывод экрана каждые 250мсек 2р/сек
    PSTimer=millis();
    PreentScreen(m);//обновление экрана
  }
}//end LOOP

//---------вывод лейблов в указанном месте экрана-------------
void PrintLabel(char* t, byte x, byte y){//char* t - лейбл, x-столбец, y-строка
  lcd.setCursor(x, y);
  lcd.print(t);
}

//--------Вывод экранов----------------------
void PreentScreen(int s){//Вывод меню
  lcd.clear();//отчистка экрана
  //Описание экранов меню
  switch (s){
  case 0://Экран выводимый по умолчанию (не пункт меню)
    PrintLabel("Main Screen",0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    PrintLabel(myLabel[2],5,1);
    lcd.print(p2);
    PrintLabel(myLabel[3],10,1);
    lcd.print(p3);
    break;

  case 1://экран изменения переменной 1
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    break;

  case 2://экран изменения переменной 2
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[2],0,1);
    lcd.print(p2);
    break;

  case 3://экран изменения переменной 3
    PrintLabel("LED Control",0,0);
    PrintLabel(myLabel[3],0,1);
    lcd.print(p3);
    break;
  }
}

//-------------ОПРОС КНОПОК------------
void key_dn(){
  //Обработка нажатия кнопки NEXT
  if (digitalRead(nextPin)== HIGH)
  {
    action(0);
    f_key=true;//устанавливаем флаг
  }
  //Обработка нажатия кнопки PREV
  if (digitalRead(prevPin)== HIGH)
  {
    action(1);
    f_key=true;//устанавливаем флаг
  }
  //Обработка нажатия UP
  if (digitalRead(upPin)== HIGH)//если находимся на экране с переменной р1
  {
    action(2);
    f_key=true;//устанавливаем флаг
  }
  //Обработка нажатия DN
  if (digitalRead(downPin)== HIGH)//если находимся на экране с переменной р1
  {
    action(3);
    f_key=true;//устанавливаем флаг
  }
  if (f_key==true){
    MSRTimer=millis();//сброс таймера автовозврата к главному экрану   
  }
}

void key_up(){
  if (digitalRead(nextPin)==0 && digitalRead(prevPin)==0 && digitalRead(upPin)==0 && digitalRead(downPin)==0){
    f_key=false;//сброс флага
  }
}

//--------РЕАКЦИЯ НА КНОПКИ-------------------
void action(byte x){
  switch (x){
  case 0://next
    m=var(m, 0, 3, 1, 1, 1);//увеличиваем переменную уровня меню
    break;
  case 1://prev
    m=var(m, 0, 3, 1, 0, 1);//уменьшаем переменную уровня меню
    break;
  case 2://up
    if (m==1){//если нажали UP когда экран с переменной р1
      p1= var(p1, 0, 10, 1, 1, 1);//то при нажатии кнопки + увеличиваем переменную р1 на единицу
    }
    if (m==2){//если нажали UP когда экран с переменной р2
      p2= var(p2, 0, 10, 1, 1, 1);
    }
    if (m==3){//если нажали UP когда экран с переменной р3
      p3= var(p3, 0, 1, 1, 1, 1);
      digitalWrite(ledPin, p3);
    }
    break;
  case 3://down
    if (m==1){//если нажали DOWN когда экран с переменной р1
      p1= var(p1, 0, 10, 1, 0, 1);//то при нажатии кнопки + увеличиваем переменную р1 на единицу
    }
    if (m==2){//если нажали DOWN когда экран с переменной р2
      p2= var(p2, 0, 10, 1, 0, 1);
    }
    if (m==3){//если нажали DOWN когда экран с переменной р3
      p3= var(p3, 0, 1, 1, 0, 1);
      digitalWrite(ledPin, p3);
    }  
    break;
  }
}

//--------------функция изменения переменной-------------------
//х=var(переменная,минимум,максимум,шаг,направление,циклически);
//переменная - переменная для изменения (int)
//минимум, максимум - пределы переменной
//шаг - шаг изменения переменной
//направление - увеличить или уменьшить
//циклически - если 0 то от мин до макс,
//если 1 то при достижение мах переходим к минимуму и наоборот
int var(int v, int mn, int mx, int stp, boolean pm, boolean c){
  switch (pm){//pm=1 увеличение pm=0 уменьшение
  case 0:
    v -= stp;//уменьшаем на шаг
    break;
  case 1:
    v += stp;//увеличиваем на шаг
    break;
  }
  switch (c){//c-1 циклически с-0 до пределов
  case 1://
    if (v<mn){
      v=mx;
    }
    if (v>mx){
      v=mn;
    }
    break;
  case 0:
    if (v<mn){
      v=mn;
    }
    if (v>mx){
      v=mx;
    }
    break;
  }
  return v;
}
//Спасибо сюда yasobe.ru/na/buter#form_submit
//allduino.forum2x2.ru/t12-topic#17
//arduwir.netdo.ru/article/6264

демо видео

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Изменил код. Что-то на подобие конечного автомата (меню описано таблицей переходов)

//Пример простого меню для LCD
//В меню используется 4 экрана
//за отображаемый экран отвечает переменная m
#include <LiquidCrystal.h> //Библиотека LCD
// инициализация LCD
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
//массив надписей которые выводяться не на одном экране для экономии памяти
char* myLabel[]={//повторяющиеся надписи
  "Parametr","P1=","P2=","LED-"};
int m=0; //переменная для экранов меню
int p1=0; // переменная для примера 1
int p2=0; // -//- 2
boolean p3=0; // -//- 3
unsigned long PSTimer = 0;//таймер для обновления экрана
unsigned long KPTimer = 0;//таймер для опроса кнопок
unsigned long MSRTimer = 0;//таймер для автовозврата к MainScreen
#define MSRecovery 5000 //задержка автовозврата к MainScreen 5сек
#define nextPin 8 //кнопка "SET" на 8 входе
#define upPin 9 //кнопка "+"
#define downPin 10 //кнопка "-"
boolean f_key=0;//флаг для фиксации нажатия кнопки
#define bounce 30 //задержка антидребезга
#define ledPin 13 //Светодиод

//  |состояния|события     | на пересечении действие
//  |    |    | 0  | 1 | 2 |
//  |    V    |next| + | - |
//  |  0   m=0|m++ | 0 | 0 |
//  |  1   m=1|m++ |p1+|p1-|
//  |  2   m=2|m++ |p2+|p2-|
//  |  3   m=3|m++ |p3+|p3-|

//действия
#define m_up 1// цифра - адрес команды в функции run(state,event)
#define p1_up 3
#define p1_dn 4
#define p2_up 5
#define p2_dn 6
#define led_on 7
#define led_off 8
int automat[4][3] = {         
  {    m_up, 0, 0                        }
  ,{   m_up, p1_up, p1_dn                  }
  ,{   m_up, p2_up, p2_dn                        }
  ,{   m_up, led_on, led_off                    } 
};

void setup() {
  //Настройка входов
  pinMode(nextPin, INPUT);
  pinMode(upPin, INPUT);
  pinMode(downPin, INPUT);
  //Настройка выходов
  pinMode(ledPin, OUTPUT);
  //Настройка дисплея
  lcd.begin(16, 2);
  // Вывод приветствия
  lcd.print("DEMO MENU V2");
  delay (2000);
}

void loop() {
  //------Опрос кнопок-----------------------
  if (millis()>KPTimer){//опрос кнопок
    KPTimer=millis()+bounce;
    switch (f_key){
    case 0:
      key_dn();//проверка нажатия кнопки
      if (f_key==true){
        MSRTimer=millis();//сброс таймера автовозврата к главному экрану   
      }
      break;
    case 1:
      key_up();//проверка отпускания кнопки
      break;
    }
    digitalWrite(ledPin,p3);
  }  
  //-----Автовозврат к основному экрану---------------
  if (millis()-MSRTimer>=MSRecovery){  //Если счетчик достиг интервала
    MSRTimer=millis();
    m=0; //то отобразить главный экран                                        
  }
  //-----------Обновление экрана--------------
  if (millis()-PSTimer>250){//вывод экрана каждые 250мсек 4р/сек
    PSTimer=millis();
    PreentScreen(m);//обновление экрана
  }
}//end LOOP

//---------вывод лейблов в указанном месте экрана-------------
void PrintLabel(char* t, byte x, byte y){//char* t - лейбл, x-столбец, y-строка
  lcd.setCursor(x, y);
  lcd.print(t);
}

//--------Вывод экранов----------------------
void PreentScreen(int s){//Вывод меню
  lcd.clear();//отчистка экрана
  //Описание экранов меню
  switch (s){
  case 0://Экран выводимый по умолчанию (не пункт меню)
    PrintLabel("Main Screen",0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    PrintLabel(myLabel[2],5,1);
    lcd.print(p2);
    PrintLabel(myLabel[3],10,1);
    lcd.print(p3);
    break;

  case 1://экран изменения переменной 1
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    break;

  case 2://экран изменения переменной 2
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[2],0,1);
    lcd.print(p2);
    break;

  case 3://экран изменения переменной 3
    PrintLabel("LED Control",0,0);
    PrintLabel(myLabel[3],0,1);
    lcd.print(p3);
    break;
  }
}

//-------------ОПРОС КНОПОК------------
void key_dn(){
  //Обработка нажатия кнопки NEXT
  if (digitalRead(nextPin)== HIGH)
  {
    //передаем в run - действие из таблицы на пересечении [m][0]
    run(automat[m][0]);//[m]-текущее состояние [0]-условный код кнопки NEXT
    f_key=true;//устанавливаем флаг
  }
  //Обработка нажатия UP
  if (digitalRead(upPin)== HIGH)//если находимся на экране с переменной р1
  {
    run(automat[m][1]);
    f_key=true;//устанавливаем флаг
  }
  //Обработка нажатия DN
  if (digitalRead(downPin)== HIGH)//если находимся на экране с переменной р1
  {
    run(automat[m][2]);
    f_key=true;//устанавливаем флаг
  }
}


void key_up(){
  if (digitalRead(nextPin)==0 && digitalRead(upPin)==0 && digitalRead(downPin)==0){
    f_key=false;//сброс флага
  }
}

//--------------функция изменения переменной-------------------
//х=var(переменная,минимум,максимум,шаг,циклически);
int var(int v, int mn, int mx, int stp, boolean c){
  v+=stp;
  switch (c){//c-1 циклически с-0 до пределов
  case 1://
    if (v<mn){
      v=mx;
    }
    if (v>mx){
      v=mn;
    }
    break;
  case 0:
    if (v<mn){
      v=mn;
    }
    if (v>mx){
      v=mx;
    }
    break;
  }
  return v;
}

void run(int f){
  switch (f){
  case 0:
    break;
  case 1:
    m=var(m, 0, 3, 1, 1);//увеличиваем переменную уровня меню
    break;
  case 3:
    p1= var(p1, 0, 10, 1, 1);//то при нажатии кнопки + увеличиваем переменную р1 на единицу
    break;
  case 4:
    p1= var(p1, 0, 10, -1, 1);//то при нажатии кнопки - уменьшаем переменную р1 на единицу
    break;
  case 5:
    p2= var(p2, 0, 10, 1, 1);
    break;
  case 6:
    p2= var(p2, 0, 10, -1, 1);
    break;
  case 7:
    p3= var(p3, 0, 1, 1, 0);
    break;
  case 8:
    p3= var(p3, 0, 1, -1, 0);
    break;
  }
}
//Спасибо сюда yasobe.ru/na/buter#form_submit
//allduino.forum2x2.ru/t12-topic#17
//arduwir.netdo.ru/article/6264

 

Baks
Baks аватар
Offline
Зарегистрирован: 11.01.2016

интересно  завтра попробую залить ваше меню и в коде поразбираюсь, библиотекой для меню не пробовали работать?

Baks
Baks аватар
Offline
Зарегистрирован: 11.01.2016
(//-----------Обновление экрана--------------
085   if (millis()-PSTimer>250){//вывод экрана каждые 250мсек 4р/сек )

 

для чего вы постоянно выводите на дисплей информацию ? у вас экран не мигает?

обновление экрана нужно делать только по событию- по нажатию кнопки а не в цыкле каждые 250мсек и возвращать на главное меню 1 раз и больше не трогать его если кнопка кнопка не будет снова нажата

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Добавил в код ОЧЕРЕДЬ СОБЫТИЙ. События добавляются в очередь и по меревыполнения очередь продвигается.

//Пример простого меню для LCD c очередью событий
//длина очереди в переменной eventLenght
//В меню используется 4 экрана
//за отображаемый экран отвечает переменная m
#include <LiquidCrystal.h> //Библиотека LCD
// инициализация LCD
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
//массив надписей которые выводяться не на одном экране для экономии памяти
char* myLabel[]={//повторяющиеся надписи
  "Parametr","P1=","P2=","LED-"};
int m=0; //переменная для экранов меню
int p1=0; // переменная для примера 1
int p2=0; // -//- 2
boolean p3=0; // -//- 3
unsigned long PSTimer = 0;//таймер для обновления экрана
unsigned long KPTimer = 0;//таймер для опроса кнопок
unsigned long MSRTimer = 0;//таймер для автовозврата к MainScreen
#define MSRecovery 5000 //задержка автовозврата к MainScreen 5сек
#define nextPin 8 //кнопка "SET" на 8 входе
#define upPin 9 //кнопка "+"
#define downPin 10 //кнопка "-"
boolean f_key=0;//флаг для фиксации нажатия кнопки
#define bounce 30 //задержка антидребезга
#define ledPin 13 //Светодиод

#define eventLenght 10//длина очереди событий
byte eventList[eventLenght+1];//инициализация очереди событий + 1 пустое

//действия
#define m_up 1// цифра - адрес команды в функции run(state,event)
#define p1_up 3
#define p1_dn 4
#define p2_up 5
#define p2_dn 6
#define led_on 7
#define led_off 8
//  |состояния|   события    | на пересечении действие
//  |    v    |0| 1  | 2 | 3 |
//  |         |N|next| + | - | N-нет событий
//  |---------+-+----+---+---| 0-нет действий
//  |  0   m=0|0|m++ | 0 | 0 |
//  |  1   m=1|0|m++ |p1+|p1-|
//  |  2   m=2|0|m++ |p2+|p2-|
//  |  3   m=3|0|m++ |p3+|p3-|

byte automat[4][4] = {         
  {    
    0, m_up, 0, 0      }
  ,{   
    0, m_up, p1_up, p1_dn      }
  ,{   
    0, m_up, p2_up, p2_dn      }
  ,{   
    0, m_up, led_on, led_off      } 
};

void setup() {

  //Настройка входов
  pinMode(nextPin, INPUT);
  pinMode(upPin, INPUT);
  pinMode(downPin, INPUT);
  //Настройка выходов
  pinMode(ledPin, OUTPUT);
  //Настройка дисплея
  lcd.begin(16, 2);
  // Вывод приветствия
  lcd.print("DEMO MENU V2.2");
  //заполнение очереди - 0-ми с индикацией на LCD
  for (int i=0; i <= eventLenght; i++){
    eventList[i]=0;
    lcd.setCursor(i, 1);
    lcd.print(">");
    lcd.setCursor(14, 1);
    lcd.print(i);   
    delay (200);
  }
}

void loop() {
  //  byte event=0;//переменная для хранения действий
  if (getEvent()==0){//если нет действия
    pushEvent();//сдвигаем очередь
  }
  else//иначе
  {
    run(automat[m][getEvent()]);//выполнение действия из таблицы переходов
    pushEvent();//сдвигаем очередь
  }
  //------Опрос кнопок-----------------------
  if (millis()>KPTimer){//опрос кнопок
    KPTimer=millis()+bounce;
    switch (f_key){
    case 0:
      key_dn();//получаем события кнопок
      if (f_key==true){
        MSRTimer=millis();//сброс таймера автовозврата к главному экрану   
      }
      break;
    case 1:
      key_up();//проверка отпускания кнопки
      break;
    }
    digitalWrite(ledPin,p3);
  }  
  //-----Автовозврат к основному экрану---------------
  if (millis()-MSRTimer>=MSRecovery){  //Если счетчик достиг интервала
    MSRTimer=millis();
    m=0;//то отобразить главный экран                                        
  }
  //-----------Обновление экрана--------------
  if (millis()-PSTimer>250){//вывод экрана каждые 250мсек 4р/сек
    PSTimer=millis();
    PreentScreen(m);//обновление экрана
  }
}//end LOOP

//--------------функция изменения переменной-------------------
//х=var(переменная,минимум,максимум,шаг,циклически);
int var(int v, int mn, int mx, int stp, boolean c){
  v+=stp;
  switch (c){//c-1 циклически с-0 до пределов
  case 1://
    if (v<mn){
      v=mx;
    }
    if (v>mx){
      v=mn;
    }
    break;
  case 0:
    if (v<mn){
      v=mn;
    }
    if (v>mx){
      v=mx;
    }
    break;
  }
  return v;
}

//-------------ОПРОС КНОПОК------------
void key_dn(){
  //Обработка нажатия кнопки NEXT
  if (digitalRead(nextPin)== HIGH)
  {
    f_key=true;//устанавливаем флаг
    cludEvent(1);//сохраняем в очередь код события
  }
  //Обработка нажатия UP
  if (digitalRead(upPin)== HIGH)//если находимся на экране с переменной р1
  {
    f_key=true;//устанавливаем флаг
    cludEvent(2);
  }
  //Обработка нажатия DN
  if (digitalRead(downPin)== HIGH)//если находимся на экране с переменной р1
  {
    f_key=true;//устанавливаем флаг
    cludEvent(3);
  }
}
//проверка отпускания кнопки
void key_up(){
  if (digitalRead(nextPin)==0 && digitalRead(upPin)==0 && digitalRead(downPin)==0){
    f_key=false;//сброс флага
  }
}

//функции очереди
//взять задание
byte getEvent(){
  return eventList[0];
}
//сдвинуть очередь
void pushEvent(){
  for (int i=0; i <= eventLenght-1; i++){
    eventList[i]=eventList[i+1];
  }
}
//положить задание
void cludEvent(byte e){
  eventList[eventLenght-1]=e;
}

//выполнить действие согласно таблице переходов
void run(int f){
  switch (f){
  case 0:
    break;
  case 1:
    m=var(m, 0, 3, 1, 1);//увеличиваем переменную уровня меню
    break;
  case 3:
    p1= var(p1, 0, 10, 1, 1);//то при нажатии кнопки + увеличиваем переменную р1 на единицу
    break;
  case 4:
    p1= var(p1, 0, 10, -1, 1);//то при нажатии кнопки - уменьшаем переменную р1 на единицу
    break;
  case 5:
    p2= var(p2, 0, 10, 1, 1);
    break;
  case 6:
    p2= var(p2, 0, 10, -1, 1);
    break;
  case 7:
    p3= var(p3, 0, 1, 1, 0);
    break;
  case 8:
    p3= var(p3, 0, 1, -1, 0);
    break;
  }
}

//---------вывод лейблов в указанном месте экрана-------------
void PrintLabel(char* t, byte x, byte y){//char* t - лейбл, x-столбец, y-строка
  lcd.setCursor(x, y);
  lcd.print(t);
}

//--------Вывод экранов----------------------
void PreentScreen(int s){
  lcd.clear();//отчистка экрана
  switch (s){
  case 0://Экран выводимый по умолчанию (не пункт меню)
    PrintLabel("Main Screen",0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    PrintLabel(myLabel[2],5,1);
    lcd.print(p2);
    PrintLabel(myLabel[3],10,1);
    lcd.print(p3);
    break;

  case 1://экран изменения переменной 1
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    break;

  case 2://экран изменения переменной 2
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[2],0,1);
    lcd.print(p2);
    break;

  case 3://экран изменения переменной 3
    PrintLabel("LED Control",0,0);
    PrintLabel(myLabel[3],0,1);
    lcd.print(p3);
    break;
  }
}

//Спасибо сюда yasobe.ru/na/buter#form_submit
//или сюда money.yandex.ru/to/41001603404940
//allduino.forum2x2.ru/t12-topic#17
//arduwir.netdo.ru/article/6264


Фаил Proteus`a

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

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

//Пример меню для LCD c очередью событий и таблицей переходов
//длина очереди в переменной eventLenght
//В меню используется 4 экрана
//за отображаемый экран отвечает переменная m
#include <LiquidCrystal.h> //Библиотека LCD
// инициализация LCD
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
//массив надписей которые выводяться не на одном экране для экономии памяти
char* myLabel[]={//повторяющиеся надписи
  "Parametr","P1=","P2=","LED-"};
int m=0; //переменная для экранов меню
int p1=0; // переменная для примера 1
int p2=0; // -//- 2
boolean p3=0; // -//- 3
unsigned long PSTimer = 0;//таймер для обновления экрана
unsigned long KPTimer = 0;//таймер для опроса кнопок
unsigned long MSRTimer = 0;//таймер для автовозврата к MainScreen
#define MSRecovery 5000 //задержка автовозврата к MainScreen 5сек
#define nextPin 8 //кнопка "SET" на 8 входе
#define upPin 9 //кнопка "+"
#define downPin 10 //кнопка "-"
boolean f_key=0;//флаг для фиксации нажатия кнопки
#define bounce 30 //задержка антидребезга
#define ledPin 13 //Светодиод
#define eventLenght 10//длина очереди событий
byte eventList[eventLenght+1];//инициализация очереди событий + 1 пустое

//  |состояния|         события          | на пересечении действие
//  |---------+-+----+---+---+---+---+---|
//  |    v    |0| 1  | 2 | 3 | 4 | 5 | 6 | -расшифровка событий-
//  |         | |SET | + | - |   |   |   | 0-нет действий
//  |---------+-+----+---+---+---+---+---| 1-кнопка SET
//  |  0   m=0|0|m++ | 0 | 0 |PS | 0 |gk | 2-кнопка +
//  |  1   m=1|0|m++ |p1+|p1-|PS |m0 |gk | 3-кнопка -
//  |  2   m=2|0|m++ |p2+|p2-|PS |m0 |gk | 4-таймер обновления экрана
//  |  3   m=3|0|m++ |p3+|p3-|PS |m0 |gk | 5-таймер возврата к основному экрану
//  |---------+-+----+---+---+---+---+---| 6-таймер опроса кнопок

//коды действий
#define m_up 1// цифра - адрес команды в функции run(event)
#define p1_up 3
#define p1_dn 4
#define p2_up 5
#define p2_dn 6
#define led_on 7
#define led_off 8
#define PS 9
#define m0 10
#define gk 11

byte automat[4][7] = {         
  {    
    0, m_up, 0, 0, PS, 0, gk               }
  ,{   
    0, m_up, p1_up, p1_dn, PS, m0, gk                }
  ,{   
    0, m_up, p2_up, p2_dn, PS, m0, gk                }
  ,{   
    0, m_up, led_on, led_off, PS, m0, gk                } 
};

void setup() {

  //Настройка входов
  pinMode(nextPin, INPUT);
  pinMode(upPin, INPUT);
  pinMode(downPin, INPUT);
  //Настройка выходов
  pinMode(ledPin, OUTPUT);
  //Настройка дисплея
  lcd.begin(16, 2);
  // Вывод приветствия
  lcd.print("DEMO MENU V3");
  //заполнение очереди - 0-ми с индикацией на LCD
  for (int i=0; i <= eventLenght; i++){
    eventList[i]=0;
    lcd.setCursor(i, 1);
    lcd.print(">");
    lcd.setCursor(14, 1);
    lcd.print(i);   
    delay (50);
  }
}

void loop() {
  run(automat[m][getEvent()]);//выполнение действия из таблицы переходов
  pushEvent();//сдвигаем очередь

  //------Опрос кнопок-----------------------
  if (millis()>KPTimer){//если пришло время
    cludEvent(6);//опрос кнопок - сохраняем в очередь код события
  }  
  digitalWrite(ledPin,p3);
  //-----Автовозврат к основному экрану---------------
  if (millis()-MSRTimer>=MSRecovery && m !=0 ){  //если пришло время и экран не основной
    MSRTimer=millis();
    cludEvent(5);//то отобразить главный экран - сохраняем в очередь код события                                    
  }
  //getEventLenght();//выводит очередь заданий на экран
  //-----------Обновление экрана--------------
  if (millis()-PSTimer>250){//вывод экрана каждые 250мсек 4р/сек
    PSTimer=millis();
    cludEvent(4);//обновление экрана - сохраняем в очередь код события
  }
}//end LOOP


//положить задание
void cludEvent(byte e){
  if(eventList[0] !=0){//чтобы не потерять задание в очереди
    run(automat[m][getEvent()]);//выполнение действия из таблицы переходов
  }
  pushEvent();//сдвигаем очередь
  eventList[eventLenght-1]=e;//положить задание в очередь
}

//взять задание
byte getEvent(){
  return eventList[0];
}
//сдвинуть очередь
void pushEvent(){
  for (int i=0; i <= eventLenght-1; i++){
    eventList[i]=eventList[i+1];
  }
}

//количество заданий в очереди
byte getEventLenght(){
  byte el=0;
  for (int i=0; i <= eventLenght-1; i++){
    //для отладки
    lcd.setCursor(5+i, 0);//выводит на дисплей очередь
    lcd.print(eventList[i]);
    if(eventList[i] != 0){//подсчет заданий в очереди
      el++;
    } 
  }
  return el;//возвращает кол-во заданий в очереди
}

//-------------ОПРОС КНОПОК------------
void getKey(){
  KPTimer=millis()+bounce;//сброс таймера
  switch (f_key){
  case 0:
    key_dn();//получаем события кнопок
    if (f_key==true){//если кнопка была нажата
      MSRTimer=millis();//сброс таймера автовозврата к главному экрану   
    }
    break;
  case 1:
    key_up();//проверка отпускания кнопки
    break;
  }
}

//--------------ПРОВВЕРКА НАЖАТИЯ------------
void key_dn(){
  //Обработка нажатия кнопки NEXT
  if (digitalRead(nextPin)== HIGH)
  {
    f_key=true;//устанавливаем флаг
    cludEvent(1);//сохраняем в очередь код события
  }
  //Обработка нажатия UP
  if (digitalRead(upPin)== HIGH)//если находимся на экране с переменной р1
  {
    f_key=true;//устанавливаем флаг
    cludEvent(2);//сохраняем в очередь код события
  }
  //Обработка нажатия DN
  if (digitalRead(downPin)== HIGH)//если находимся на экране с переменной р1
  {
    f_key=true;//устанавливаем флаг
    cludEvent(3);//сохраняем в очередь код события
  }
}
//проверка отпускания кнопки
void key_up(){
  if (digitalRead(nextPin)==0 && digitalRead(upPin)==0 && digitalRead(downPin)==0){
    f_key=false;//сброс флага
  }
}

//--------------функция изменения переменной-------------------
//х=var(переменная,минимум,максимум,шаг,циклически);
int var(int v, int mn, int mx, int stp, boolean c){
  v+=stp;
  switch (c){//c-1 циклически с-0 до пределов
  case 1://
    if (v<mn){
      v=mx;
    }
    if (v>mx){
      v=mn;
    }
    break;
  case 0:
    if (v<mn){
      v=mn;
    }
    if (v>mx){
      v=mx;
    }
    break;
  }
  return v;
}

//--------Вывод экранов----------------------
void PreentScreen(int s){
  lcd.clear();//отчистка экрана
  switch (s){
  case 0://Экран выводимый по умолчанию (не пункт меню)
    PrintLabel("Main",0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    PrintLabel(myLabel[2],5,1);
    lcd.print(p2);
    PrintLabel(myLabel[3],10,1);
    lcd.print(p3);
    break;

  case 1://экран изменения переменной 1
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    break;

  case 2://экран изменения переменной 2
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[2],0,1);
    lcd.print(p2);
    break;

  case 3://экран изменения переменной 3
    PrintLabel("Ctrl",0,0);
    PrintLabel(myLabel[3],0,1);
    lcd.print(p3);
    break;
  }
}

//---------вывод лейблов в указанном месте экрана-------------
void PrintLabel(char* t, byte x, byte y){//char* t - лейбл, x-столбец, y-строка
  lcd.setCursor(x, y);
  lcd.print(t);
}

//выполнить действие согласно таблице переходов
void run(int f){
  switch (f){
  case 0:
    break;
  case 1:
    m=var(m, 0, 3, 1, 1);//увеличиваем переменную уровня меню
    break;
  case 3:
    p1= var(p1, 0, 10, 1, 1);//то при нажатии кнопки + увеличиваем переменную р1 на единицу
    break;
  case 4:
    p1= var(p1, 0, 10, -1, 1);//то при нажатии кнопки - уменьшаем переменную р1 на единицу
    break;
  case 5:
    p2= var(p2, 0, 10, 1, 1);
    break;
  case 6:
    p2= var(p2, 0, 10, -1, 1);
    break;
  case 7:
    p3= var(p3, 0, 1, 1, 0);
    break;
  case 8:
    p3= var(p3, 0, 1, -1, 0);
    break;
  case 9:
    PreentScreen(m);//обновление экрана
    break;
  case 10:
    m=0;
    break;
  case 11:
    getKey();
    break;
  }
}

//Спасибо сюда yasobe.ru/na/buter#form_submit
//или сюда money.yandex.ru/to/41001603404940

если раскоментировать 99 строку то на дисплей будет выводиться очередь заданий (использовалось для отладки). Если данная возможность не нужна то эту строку и функцию getEventLenght() можно удалить.

 

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Добавил приоритеты событий.

//Пример меню для LCD c очередью событий и таблицей переходов
//длина очереди в переменной eventLenght
//В меню используется 4 экрана
//за отображаемый экран отвечает переменная m
#include <LiquidCrystal.h> //Библиотека LCD
// инициализация LCD
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
//массив надписей которые выводяться не на одном экране для экономии памяти
char* myLabel[]={//повторяющиеся надписи
  "Parametr","P1=","P2=","LED-"};
int m=0; //переменная для экранов меню
int p1=0; // переменная для примера 1
int p2=0; // -//- 2
boolean p3=0; // -//- 3
unsigned long PSTimer = 0;//таймер для обновления экрана
unsigned long KPTimer = 0;//таймер для опроса кнопок
unsigned long MSRTimer = 0;//таймер для автовозврата к MainScreen
#define MSRecovery 5000 //задержка автовозврата к MainScreen 5сек
#define nextPin 8 //кнопка "SET" на 8 входе
#define upPin 9 //кнопка "+"
#define downPin 10 //кнопка "-"
boolean f_key=0;//флаг для фиксации нажатия кнопки
#define bounce 30 //задержка антидребезга
#define ledPin 13 //Светодиод
#define eventLenght 10//длина очереди событий
byte eventList[eventLenght+1];//инициализация очереди событий + 1 пустое

//  |состояния|         события          | на пересечении действие
//  |---------+-+----+---+---+---+---+---|
//  |    v    |0| 1  | 2 | 3 | 4 | 5 | 6 | -расшифровка событий-
//  |         | |SET | + | - |   |   |   | 0-нет действий
//  |---------+-+----+---+---+---+---+---| 1-кнопка SET
//  |  0   m=0|0|m++ | 0 | 0 |PS | 0 |gk | 2-кнопка +
//  |  1   m=1|0|m++ |p1+|p1-|PS |m0 |gk | 3-кнопка -
//  |  2   m=2|0|m++ |p2+|p2-|PS |m0 |gk | 4-таймер обновления экрана
//  |  3   m=3|0|m++ |p3+|p3-|PS |m0 |gk | 5-таймер возврата к основному экрану
//  |---------+-+----+---+---+---+---+---| 6-таймер опроса кнопок

//коды действий
#define m_up 1// цифра - адрес команды в функции run(event)
#define p1_up 3
#define p1_dn 4
#define p2_up 5
#define p2_dn 6
#define led_on 7
#define led_off 8
#define PS 9
#define m0 10
#define gk 11

byte automat[4][7] = {         
  {    
    0, m_up, 0, 0, PS, 0, gk               }
  ,{   
    0, m_up, p1_up, p1_dn, PS, m0, gk                }
  ,{   
    0, m_up, p2_up, p2_dn, PS, m0, gk                }
  ,{   
    0, m_up, led_on, led_off, PS, m0, gk                } 
};

void setup() {

  //Настройка входов
  pinMode(nextPin, INPUT);
  pinMode(upPin, INPUT);
  pinMode(downPin, INPUT);
  //Настройка выходов
  pinMode(ledPin, OUTPUT);
  //Настройка дисплея
  lcd.begin(16, 2);
  // Вывод приветствия
  lcd.print("DEMO MENU V3");
  //заполнение очереди - 0-ми с индикацией на LCD
  for (int i=0; i <= eventLenght; i++){
    eventList[i]=0;
    lcd.setCursor(i, 1);
    lcd.print(">");
    lcd.setCursor(14, 1);
    lcd.print(i);   
    delay (50);
  }
}

void loop() {
  run(automat[m][getEvent()]);//выполнение действия из таблицы переходов
  pushEvent();//сдвигаем очередь

  //------Опрос кнопок-----------------------
  if (millis()>KPTimer){//если пришло время
    cludEvent(6,1);//опрос кнопок - сохраняем в очередь код события
  }  
  digitalWrite(ledPin,p3);
  //-----Автовозврат к основному экрану---------------
  if (millis()-MSRTimer>=MSRecovery && m !=0 ){  //если пришло время и экран не основной
    MSRTimer=millis();
    cludEvent(5,2);//то отобразить главный экран - сохраняем в очередь код события                                    
  }
  getEventLenght();//выводит очередь заданий на экран
  //-----------Обновление экрана--------------
  if (millis()-PSTimer>250){//вывод экрана каждые 250мсек 4р/сек
    PSTimer=millis();
    cludEvent(4,0);//обновление экрана - сохраняем в очередь код события
  }
}//end LOOP

//--------------функция изменения переменной-------------------
//х=var(переменная,минимум,максимум,шаг,циклически);
int var(int v, int mn, int mx, int stp, boolean c){
  v+=stp;
  switch (c){//c-1 циклически с-0 до пределов
  case 1://
    if (v<mn){
      v=mx;
    }
    if (v>mx){
      v=mn;
    }
    break;
  case 0:
    if (v<mn){
      v=mn;
    }
    if (v>mx){
      v=mx;
    }
    break;
  }
  return v;
}

//-------------ОПРОС КНОПОК------------
void getKey(){
  KPTimer=millis()+bounce;//сброс таймера
  switch (f_key){
  case 0:
    key_dn();//получаем события кнопок
    if (f_key==true){//если кнопка была нажата
      MSRTimer=millis();//сброс таймера автовозврата к главному экрану   
    }
    break;
  case 1:
    key_up();//проверка отпускания кнопки
    break;
  }
}

//--------------ПРОВВЕРКА НАЖАТИЯ------------
void key_dn(){
  //Обработка нажатия кнопки SET
  if (digitalRead(nextPin)== HIGH)
  {
    f_key=true;//устанавливаем флаг
    cludEvent(1,2);//сохраняем в очередь код события
  }
  //Обработка нажатия UP
  if (digitalRead(upPin)== HIGH)//если находимся на экране с переменной р1
  {
    f_key=true;//устанавливаем флаг
    cludEvent(2,2);//сохраняем в очередь код события
  }
  //Обработка нажатия DN
  if (digitalRead(downPin)== HIGH)//если находимся на экране с переменной р1
  {
    f_key=true;//устанавливаем флаг
    cludEvent(3,2);//сохраняем в очередь код события
  }
}
//проверка отпускания кнопки
void key_up(){
  if (digitalRead(nextPin)==0 && digitalRead(upPin)==0 && digitalRead(downPin)==0){
    f_key=false;//сброс флага
  }
}

//---------ФУНКЦИИ ОЧЕРЕДИ СОБЫТИЙ-------------
//положить задание в очередь (код, приоритет)
//0-незамедлительно, 1-высокий, 2-в порядке очереди
void cludEvent(byte e, boolean p){
  switch (p){
  case 0://незамедлительный
    run(automat[m][e]);
    break;
  case 1://высокий
    for (int i=0; i <= eventLenght-1; i++){
      if (eventList[i]==0){//ищем первую пустую ячейку очереди
        eventList[i]=e;//пишем в нее задание
        break;
      }
      run(automat[m][e]);//если пустой нет то выполняем
    }
    break;
  case 2://в порядке очереди
    if(eventList[0] !=0){//чтобы не потерять задание в очереди
      run(automat[m][getEvent()]);//выполнение действия из таблицы переходов
    }
    pushEvent();//сдвигаем очередь
    eventList[eventLenght-1]=e;//положить задание в очередь
    break;
  }

}

//взять задание
byte getEvent(){
  return eventList[0];
}
//сдвинуть очередь
void pushEvent(){
  for (int i=0; i <= eventLenght-1; i++){
    eventList[i]=eventList[i+1];
  }
}

//количество заданий в очереди
byte getEventLenght(){
  byte el=0;
  for (int i=0; i <= eventLenght-1; i++){
    //для отладки
    lcd.setCursor(5+i, 0);//выводит на дисплей очередь
    lcd.print(eventList[i]);
    if(eventList[i] != 0){//подсчет заданий в очереди
      el++;
    } 
  }
  return el;//возвращает кол-во заданий в очереди
}

//выполнить действие согласно таблице переходов
void run(int f){
  switch (f){
  case 0:
    break;
  case 1:
    m=var(m, 0, 3, 1, 1);//увеличиваем переменную уровня меню
    break;
  case 3:
    p1= var(p1, 0, 10, 1, 1);//то при нажатии кнопки + увеличиваем переменную р1 на единицу
    break;
  case 4:
    p1= var(p1, 0, 10, -1, 1);//то при нажатии кнопки - уменьшаем переменную р1 на единицу
    break;
  case 5:
    p2= var(p2, 0, 10, 1, 1);
    break;
  case 6:
    p2= var(p2, 0, 10, -1, 1);
    break;
  case 7:
    p3= var(p3, 0, 1, 1, 0);
    break;
  case 8:
    p3= var(p3, 0, 1, -1, 0);
    break;
  case 9:
    PreentScreen(m);//обновление экрана
    break;
  case 10:
    m=0;
    break;
  case 11:
    getKey();
    break;
  }
}

//--------Вывод экранов----------------------
void PreentScreen(int s){
  lcd.clear();//отчистка экрана
  switch (s){
  case 0://Экран выводимый по умолчанию (не пункт меню)
    PrintLabel("Main",0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    PrintLabel(myLabel[2],5,1);
    lcd.print(p2);
    PrintLabel(myLabel[3],10,1);
    lcd.print(p3);
    break;

  case 1://экран изменения переменной 1
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    break;

  case 2://экран изменения переменной 2
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[2],0,1);
    lcd.print(p2);
    break;

  case 3://экран изменения переменной 3
    PrintLabel("Ctrl",0,0);
    PrintLabel(myLabel[3],0,1);
    lcd.print(p3);
    break;
  }
}

//---------вывод лейблов в указанном месте экрана-------------
void PrintLabel(char* t, byte x, byte y){//char* t - лейбл, x-столбец, y-строка
  lcd.setCursor(x, y);
  lcd.print(t);
}

//Спасибо сюда yasobe.ru/na/buter#form_submit
//или сюда money.yandex.ru/to/41001603404940

 

Baks
Baks аватар
Offline
Зарегистрирован: 11.01.2016
yul-i-an 
о вы для кого свой код пишите? 
Вы сами его хоть пробовали запустить на дисплей? и Вам нравится нагружать лишней работой ардуино и смотреть на постоянно моргающий дисплей?
сам тоже меню делаю могу выложить свои наброски для меню с библиотекой
вот посмотрите
#include <MyMenu.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,16,2);  //инициализируем 2х строчный дисплей
#define NUM_ITEMS 17 //длина элементов массива включают в себя заголовки подменю

int pinButton = A0 ; 
int varDell = 123; 


MItm items[NUM_ITEMS] = {
  MItm("GENERAL MENU",0,0), //основной заголовок всегда 0,0
   MItm("Date Time",11), //1 Индекс подменю целевой 1 is target submenu index  
    MItm("New Date Time",12),
  // MItm("Other",13),
   //  MItm("Banana",14),
   MItm("Pear",1),
   MItm(" <<BACK",0), 
  MItm("SUB MENU 1",1,0), //это подменю с индексом 1 (0 предыдущий индекс меню)
  //  MItm("Banana",6),
  MItm("Pear",2),
  MItm(" <<BACK",0), 
  MItm("SUB MENU 2",2,0),
 // MItm("GENERAL MENU 4",0),
 // MItm("Banana",8),
  MItm("Pear",3),
  MItm(" <<BACK",0), 
 // MItm("Pear",80),
  MItm("SUB MENU 3",3,0), // заметить, что в 3-индекс предыд теперь не нулевой
   MItm("VAR1="+ varDell,4),
    MItm("VAR2=",5),
	 MItm("VAR3=",6),
	  MItm("VAR4=",7),
  MItm(" <<BACK",0) 
};

Menu menu(items,NUM_ITEMS,&lcd,menuCallback);

void setup() {
Serial.begin(9600); 
  
  //initialize lcd
 lcd.init();      // инициализация LCD
 lcd.backlight();    // включаем подсветку
  
  //show main menu on screen
  menu.goMain();
}

void loop() {
 int i =	getPressedButton();
  if(3 == i){
    menu.goBack();
    delay(100); 
  }
  if(6 == i){
    menu.goDown();
    delay(100);  
  }
  if(4 == i){
    menu.goUp();
    delay(100); 
  }
  if(7 == i){
    menu.goNext();
    delay(100); 
  }  

delay(100);
}

void menuCallback(int idx){
  //сделать что-то в соответствии с индексом выбранного элемента
  	  /////
   if(idx == 4){ //selected "shark" atoi
 	int tt = nevValMenu();
        lcd.clear(); 
	lcd.setCursor(0,0);	
   lcd.print(tt)   ;  
   delay(2000); 
   //lcd.print("RET"+ nevValMenu());  	
  }
  /////
  if(idx < 5){
    lcd.clear();
    lcd.setCursor(2,0);
    lcd.print("NO MEAT n FISH!"); 
    delay(1000);
	 if(idx == 1){ Serial.println("id=1");}
	 if(idx == 2){ Serial.println("id=2");}
	 if(idx == 0){ Serial.println("id=0");}
 
  }else if(idx == 5){ //selected "shark"
    int i=16;
    while(i >= 0){
      lcd.clear();
      lcd.setCursor(0,2);
      //lcd.print(arrMenuChar[i]);
      lcd.setCursor(i,1);
      lcd.print("/l");
      delay(300);
      i--;
    }
  }
  
  else{
    lcd.clear();
    lcd.setCursor(2,2);
    lcd.print(String(idx)+" pressed");
    delay(1000);
  }
  menu.goLast(); //return to last viewed menu
}
int nevValMenu(){ // выведем значение на 2 строку 12+ клетку 
     int i=1;
	  int z = -1;
	int y=0;
	lcd.setCursor(11,1) ;
	lcd.print(i);
    while(y != 2){//выход
	 y = getPressedButton();
	  if (y == 4)// вверх
         {i++;
		lcd.setCursor(11,1) ; 
		 lcd.print(i); }
	  if (y == 6)// низ
	 {i--;
	 if (i<0){i=0;}
	 lcd.setCursor(11,1) ;
	 lcd.print(i);}
	  if (y == 3)// лево
	 {}
	  if (y == 7) //право
	 {}
	  if (y == 5) //сохранить
	 { z = i; }
      delay(100);
      if(i==9){lcd.setCursor(12,1);
	  lcd.print(' ');}  
      if(i==99){lcd.setCursor(13,1);
	  lcd.print(' ');}
      if(i==999){lcd.setCursor(14,1);
	  lcd.print(' ');}	
      if(i==9999){lcd.setCursor(15,1);
	  lcd.print(' ');}	  
    }
return z	;
}
//процедура определения нажатой кнопки
int getPressedButton(){
  int val = analogRead(pinButton);
if (val>100){
if (val < 472){Serial.println("val=1"); return 1;  }
if (val < 515){Serial.println("val=2"); return 2;  }  
if (val < 567){Serial.println("val=3"); return 3; }   
if (val < 630){Serial.println("val=4"); return 4; }  
if (val < 710){Serial.println("val=5"); return 5; } 
if (val < 810){Serial.println("val=6"); return 6; } 
if (val < 1025){Serial.println("val=7"); return 7; } 
   while(analogRead(pinButton)>100) ;
}
  else return 0;
}

 вот библиотека https://cloud.mail.ru/public/KEXw/rAXWQj99V

кстати если не против в вашей теме задам вопрос знающим людям, как в этой библиотеке выводить пункт меню со значением както так VAR1= 220

к примеру пробовал так 

 MItm("VAR1="+ varDell,4),

но выдает пустую строку, и переменную не могу соединить со строкой

 

 

 

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

У меня хоть delay`ев нет)

Кому не интересно проходите мимо. В своем коде я пытаюсь объяснить просто (для тех кто совсем не понимает) саму концепцию построения меню, а то некоторые на форуме спрашивают как войти в меню или выйти из меню. Ну и за одно подход к написанию кода не в лоб.

Baks
Baks аватар
Offline
Зарегистрирован: 11.01.2016

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

и да в дальнейшем буду проходить мимо......

Удачи!

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Переписал код под использование LCDKeyPad Shield. Теперь экран обновляется только при необходимости (НЕ МЕРЦАЕТ), для этого был введен флаг который указывает на необходимость перерисовки экрана.

Код максимально откоментирован. Проверен в железе, всё работает.

//Пример меню для LCDKeyPad Shield c очередью событий и таблицей переходов
//длина очереди в переменной eventLenght
//В меню используется 4 экрана
//за отображаемый экран отвечает переменная m
#include <LiquidCrystal.h> //Библиотека LCD
// инициализация LCD
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);//для LCDKeyPad
//LiquidCrystal lcd(7, 6, 5, 4, 3, 2);//для Proteus
//массив надписей которые выводяться не на одном экране для экономии памяти
char* myLabel[]={//повторяющиеся надписи
  "Parametr","P1=","P2=","LED-"};
int m=0; //переменная для экранов меню
boolean sc=1;//флаг обнавления экрана
int p1=0; // переменная для примера 1
int p2=0; // -//- 2
boolean p3=0; // -//- 3
unsigned long PSTimer = 0;//таймер для обновления экрана
unsigned long KPTimer = 0;//таймер для опроса кнопок
unsigned long MSRTimer = 0;//таймер для автовозврата к MainScreen
#define MSRecovery 5000 //задержка автовозврата к MainScreen 5сек
//аналоговые кнопки LCDKeyPad
#define k_set 638 //SET
#define k_up 98 //UP
#define k_dn 253 //DN
#define k_wdh 10 //ширина чувствительности на аналоговую кнопку +-10
boolean f_key=0;//флаг для фиксации нажатия кнопки
#define ledPin 13 //Светодиод
#define eventLenght 10//длина очереди событий
byte eventList[eventLenght+1];//инициализация очереди событий + 1 пустое

//  |состояния|         события          | на пересечении действие
//  |---------+-+----+---+---+---+---+---|
//  |    v    |0| 1  | 2 | 3 | 4 | 5 | 6 | -расшифровка событий-
//  |         | |SET | + | - |   |   |   | 0-нет действий
//  |---------+-+----+---+---+---+---+---| 1-кнопка SET
//  |  0   m=0|0|m++ | 0 | 0 |PS | 0 |gk | 2-кнопка +
//  |  1   m=1|0|m++ |p1+|p1-|PS |m0 |gk | 3-кнопка -
//  |  2   m=2|0|m++ |p2+|p2-|PS |m0 |gk | 4-таймер обновления экрана
//  |  3   m=3|0|m++ |p3+|p3-|PS |m0 |gk | 5-таймер возврата к основному экрану
//  |---------+-+----+---+---+---+---+---| 6-таймер опроса кнопок

//коды действий
#define m_up 1// цифра - адрес команды в функции run(event)
#define p1_up 3
#define p1_dn 4
#define p2_up 5
#define p2_dn 6
#define led_on 7
#define led_off 8
#define PS 9
#define m0 10
#define gk 11

byte automat[4][7] = {         
  {    
    0, m_up, 0, 0, PS, 0, gk                       }
  ,{   
    0, m_up, p1_up, p1_dn, PS, m0, gk                        }
  ,{   
    0, m_up, p2_up, p2_dn, PS, m0, gk                        }
  ,{   
    0, m_up, led_on, led_off, PS, m0, gk                        } 
};

void setup() {
  //Настройка выходов
  pinMode(ledPin, OUTPUT);
  //Настройка дисплея
  lcd.begin(16, 2);
  // Вывод приветствия
  lcd.print("DEMO MENU V3");
  //заполнение очереди - 0-ми с индикацией на LCD
  for (int i=0; i <= eventLenght; i++){
    eventList[i]=0;
    lcd.setCursor(i, 1);
    lcd.print(">");
    lcd.setCursor(14, 1);
    lcd.print(i);   
    delay (100);
  }
}

void loop() {
  run(automat[m][getEvent()]);//выполнение действия из таблицы переходов
  pushEvent();//сдвигаем очередь
  //------Опрос кнопок-----------------------
  if (millis()-KPTimer>50){//если пришло время
    cludEvent(6,1);//опрос кнопок - сохраняем в очередь код события
    KPTimer=millis();//сброс таймера
  }  
  digitalWrite(ledPin,p3);//управление светодиодом
  //-----Автовозврат к основному экрану---------------

  if (millis() - MSRTimer > MSRecovery && m>0){  //если пришло время и экран не основной
    sc=1;//флаг необходимости обновить экран
    cludEvent(5,2);//то отобразить главный экран - сохраняем в очередь код события 
  }
  cludEvent(4,0);//обновление экрана - сохраняем в очередь код события
}//end LOOP

//--------------функция изменения переменной-------------------
//х=var(переменная,минимум,максимум,шаг,циклически);
int var(int v, int mn, int mx, int stp, boolean c){
  sc=1;//обновить экран
  v+=stp;
  switch (c){//c-1 циклически с-0 до пределов
  case 1://
    if (v<mn){
      v=mx;
    }
    if (v>mx){
      v=mn;
    }
    break;
  case 0:
    if (v<mn){
      v=mn;
    }
    if (v>mx){
      v=mx;
    }
    break;
  }
  return v;
}

//-------------ОПРОС АНАЛОГОВЫХ КНОПОК------------
void getKey(){
  int x = analogRead (0);
  switch (f_key){
  case 0:
    if (x > 60 && x < 800){
      f_key=1;//влаг нажатия кнопки
      MSRTimer=millis();//сброс таймера автовозврата к главному экрану 
  }
    //Обработка нажатия UP
    if (a_key(x,k_up)) {//UP   98         
      cludEvent(2,2);//сохраняем в очередь код события
    }
    //Обработка нажатия DN
    else if (a_key(x,k_dn)){//DN    253
      cludEvent(3,2);//сохраняем в очередь код события
    }
    //Обработка нажатия кнопки SET
    else if (a_key(x,k_set)) {//SET 638
      cludEvent(1,2);//сохраняем в очередь код события
    }
    break;
  case 1:
    if (x>800){//если кнопка отпущена
      f_key=0;//сбрасываем флаг
    }
    break;
  }
}
//проверка входа кнопки в диапазон
boolean a_key(int x, int key){
  if (x > (key-k_wdh) && x < key+k_wdh) {
    return 1;//входит
  }
  else
  {
    return 0;//не входит
  }
}

//---------ФУНКЦИИ ОЧЕРЕДИ СОБЫТИЙ-------------
//положить задание в очередь (код, приоритет)
//0-незамедлительно, 1-высокий, 2-в порядке очереди
void cludEvent(byte e, boolean p){
  switch (p){
  case 0://незамедлительный
    run(automat[m][e]);
    break;
  case 1://высокий
    for (int i=0; i <= eventLenght-1; i++){
      if (eventList[i]==0){//ищем первую пустую ячейку очереди
        eventList[i]=e;//пишем в нее задание
        break;
      }
      run(automat[m][e]);//если пустой нет то выполняем
    }
    break;
  case 2://в порядке очереди
    if(eventList[0] !=0){//чтобы не потерять задание в очереди
      run(automat[m][getEvent()]);//выполнение действия из таблицы переходов
    }
    pushEvent();//сдвигаем очередь
    eventList[eventLenght-1]=e;//положить задание в очередь
    break;
  }
}

//взять задание
byte getEvent(){
  return eventList[0];
}
//сдвинуть очередь
void pushEvent(){
  for (int i=0; i <= eventLenght-1; i++){
    eventList[i]=eventList[i+1];
  }
}

//количество заданий в очереди
byte getEventLenght(){
  byte el=0;
  for (int i=0; i <= eventLenght-1; i++){
    //для отладки
    lcd.setCursor(5+i, 0);//выводит на дисплей очередь
    lcd.print(eventList[i]);
    if(eventList[i] != 0){//подсчет заданий в очереди
      el++;
    } 
  }
  return el;//возвращает кол-во заданий в очереди
}

//выполнить действие согласно таблице переходов
void run(int f){
  switch (f){
  case 0:
    break;
  case m_up:
    m=var(m, 0, 3, 1, 1);//увеличиваем переменную уровня меню
    break;
  case p1_up:
    p1= var(p1, 0, 10, 1, 1);//то при нажатии кнопки + увеличиваем переменную р1 на единицу
    break;
  case p1_dn:
    p1= var(p1, 0, 10, -1, 1);//то при нажатии кнопки - уменьшаем переменную р1 на единицу
    break;
  case p2_up:
    p2= var(p2, 0, 10, 1, 1);
    break;
  case p2_dn:
    p2= var(p2, 0, 10, -1, 1);
    break;
  case led_on:
    p3= var(p3, 0, 1, 1, 0);
    break;
  case led_off:
    p3= var(p3, 0, 1, -1, 0);
    break;
  case PS:
    if (sc==1){//если нужно обновить  
      PreentScreen(m);//обновление экрана
      sc=0;//сбрасываем флаг
    }
    break;
  case m0:
    m=0;
    break;
  case gk:
    getKey();
    break;
  }
}

//--------Вывод экранов----------------------
void PreentScreen(int s){
  lcd.clear();//отчистка экрана
  switch (s){
  case 0://Экран выводимый по умолчанию (не пункт меню)
    PrintLabel("Main",0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    PrintLabel(myLabel[2],5,1);
    lcd.print(p2);
    PrintLabel(myLabel[3],10,1);
    lcd.print(p3);
    break;

  case 1://экран изменения переменной 1
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    break;

  case 2://экран изменения переменной 2
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[2],0,1);
    lcd.print(p2);
    break;

  case 3://экран изменения переменной 3
    PrintLabel("Ctrl",0,0);
    PrintLabel(myLabel[3],0,1);
    lcd.print(p3);
    break;
  }
}

//---------вывод лейблов в указанном месте экрана-------------
void PrintLabel(char* t, byte x, byte y){//char* t - лейбл, x-столбец, y-строка
  lcd.setCursor(x, y);
  lcd.print(t);
}

//yasobe.ru/na/buter#form_submit
//money.yandex.ru/to/41001603404940

 

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012
trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

Я с этим шилдом такую функцию использую:

#define KEYBOARDPIN   A0	      // Keypad 

#define BUTTONNONE    0
#define BUTTONRIGHT   1
#define BUTTONUP      2
#define BUTTONDOWN    3
#define BUTTONLEFT    4
#define BUTTONSELECT  5

int ReadADCButtons()
{
  int adc_key_in = analogRead(KEYBOARDPIN);      // read the value from the sensor
  if (adc_key_in > 1000) return BUTTONNONE;    // We make this the 1st option for speed reasons since it will be the most likely result
  // Serial.println (adc_key_in);
  if (adc_key_in < 50)   return BUTTONRIGHT;   //   0 My
  if (adc_key_in < 220)  return BUTTONUP;      // 130 My
  if (adc_key_in < 380)  return BUTTONDOWN;    // 307 My
  if (adc_key_in < 555)  return BUTTONLEFT;    // 480 My
  if (adc_key_in < 900)  return BUTTONSELECT;  // 720 My
  return BUTTONNONE;  // when all others fail, return this...
}

 

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

trembo пишет:

Я с этим шилдом такую функцию использую:

#define KEYBOARDPIN   A0	      // Keypad 

#define BUTTONNONE    0
#define BUTTONRIGHT   1
#define BUTTONUP      2
#define BUTTONDOWN    3
#define BUTTONLEFT    4
#define BUTTONSELECT  5

int ReadADCButtons()
{
  int adc_key_in = analogRead(KEYBOARDPIN);      // read the value from the sensor
  if (adc_key_in > 1000) return BUTTONNONE;    // We make this the 1st option for speed reasons since it will be the most likely result
  // Serial.println (adc_key_in);
  if (adc_key_in < 50)   return BUTTONRIGHT;   //   0 My
  if (adc_key_in < 220)  return BUTTONUP;      // 130 My
  if (adc_key_in < 380)  return BUTTONDOWN;    // 307 My
  if (adc_key_in < 555)  return BUTTONLEFT;    // 480 My
  if (adc_key_in < 900)  return BUTTONSELECT;  // 720 My
  return BUTTONNONE;  // when all others fail, return this...
}

 

Можно и так.

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Для большей наглядности вместо управления светодиодом на 13 выходе реализовал регулировку яркости LCD.

//Пример меню для LCDKeyPad Shield c очередью событий и таблицей переходов
//длина очереди в переменной eventLenght
//В меню используется 4 экрана
//за отображаемый экран отвечает переменная m
#include <LiquidCrystal.h> //Библиотека LCD
// инициализация LCD
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);//для LCDKeyPad
//LiquidCrystal lcd(7, 6, 5, 4, 3, 2);//для Proteus
//массив надписей которые выводяться не на одном экране для экономии памяти
char* myLabel[]={//повторяющиеся надписи
  "Parametr","P1-","P2-","BL-","BackLight"};
int m=0; //переменная для экранов меню
boolean sc=1;//флаг обнавления экрана
int p1=0; // переменная для примера 1
int p2=0; // -//- 2
boolean p3=50; //начальная яркость подсветки
unsigned long PSTimer = 0;//таймер для обновления экрана
unsigned long KPTimer = 0;//таймер для опроса кнопок
unsigned long MSRTimer = 0;//таймер для автовозврата к MainScreen
#define MSRecovery 5000 //задержка автовозврата к MainScreen 5сек
//аналоговые кнопки LCDKeyPad
#define k_set 638 //SET
#define k_up 98 //UP
#define k_dn 253 //DN
#define k_wdh 10 //ширина чувствительности на аналоговую кнопку +-10
boolean f_key=0;//флаг для фиксации нажатия кнопки
#define eventLenght 10//длина очереди событий
byte eventList[eventLenght+1];//инициализация очереди событий + 1 пустое

//  |состояния|         события          | на пересечении действие
//  |---------+-+----+---+---+---+---+---|
//  |    v    |0| 1  | 2 | 3 | 4 | 5 | 6 | -расшифровка событий-
//  |         | |SET | + | - |   |   |   | 0-нет действий
//  |---------+-+----+---+---+---+---+---| 1-кнопка SET
//  |  0   m=0|0|m++ | 0 | 0 |PS | 0 |gk | 2-кнопка +
//  |  1   m=1|0|m++ |p1+|p1-|PS |m0 |gk | 3-кнопка -
//  |  2   m=2|0|m++ |p2+|p2-|PS |m0 |gk | 4-таймер обновления экрана
//  |  3   m=3|0|m++ |p3+|p3-|PS |m0 |gk | 5-таймер возврата к основному экрану
//  |---------+-+----+---+---+---+---+---| 6-таймер опроса кнопок

//коды действий
#define m_up 1// цифра - адрес команды в функции run(event)
#define p1_up 3
#define p1_dn 4
#define p2_up 5
#define p2_dn 6
#define led_up 7
#define led_dn 8
#define PS 9
#define m0 10
#define gk 11

byte automat[4][7] = {         
  {    
    0, m_up, 0, 0, PS, 0, gk                       }
  ,{   
    0, m_up, p1_up, p1_dn, PS, m0, gk                        }
  ,{   
    0, m_up, p2_up, p2_dn, PS, m0, gk                        }
  ,{   
    0, m_up, led_up, led_dn, PS, m0, gk                        } 
};

void setup() {
  //Настройка дисплея
  lcd.begin(16, 2);
  // Вывод приветствия
  lcd.print("DEMO MENU V3");
  //заполнение очереди - 0-ми с индикацией на LCD
  for (int i=0; i <= eventLenght; i++){
    eventList[i]=0;
    lcd.setCursor(i, 1);
    lcd.print(">");
    lcd.setCursor(14, 1);
    lcd.print(i);   
    delay (100);
  }
}

void loop() {
  run(automat[m][getEvent()]);//выполнение действия из таблицы переходов
  pushEvent();//сдвигаем очередь
  //------Опрос кнопок-----------------------
  if (millis()-KPTimer>30){//если пришло время
    cludEvent(6,1);//опрос кнопок - сохраняем в очередь код события
    KPTimer=millis();//сброс таймера
  }  
  analogWrite(10,p3);
  //digitalWrite(ledPin,p3);//управление светодиодом
  //-----Автовозврат к основному экрану---------------

  if (millis() - MSRTimer > MSRecovery && m>0){  //если пришло время и экран не основной
    sc=1;//флаг необходимости обновить экран
    cludEvent(5,2);//то отобразить главный экран - сохраняем в очередь код события 
  }
  cludEvent(4,0);//обновление экрана - сохраняем в очередь код события
}//end LOOP

//--------------функция изменения переменной-------------------
//х=var(переменная,минимум,максимум,шаг,циклически);
int var(int v, int mn, int mx, int stp, boolean c){
  sc=1;//обновить экран
  v+=stp;
  switch (c){//c-1 циклически с-0 до пределов
  case 1://
    if (v<mn){
      v=mx;
    }
    if (v>mx){
      v=mn;
    }
    break;
  case 0:
    if (v<mn){
      v=mn;
    }
    if (v>mx){
      v=mx;
    }
    break;
  }
  return v;
}

//-------------ОПРОС АНАЛОГОВЫХ КНОПОК------------
void getKey(){
  int x = analogRead (0);
  switch (f_key){
  case 0:
    if (x > 60 && x < 800){
      f_key=1;//влаг нажатия кнопки
      MSRTimer=millis();//сброс таймера автовозврата к главному экрану 
  }
    //Обработка нажатия UP
    if (a_key(x,k_up)) {//UP   98         
      cludEvent(2,2);//сохраняем в очередь код события
    }
    //Обработка нажатия DN
    else if (a_key(x,k_dn)){//DN    253
      cludEvent(3,2);//сохраняем в очередь код события
    }
    //Обработка нажатия кнопки SET
    else if (a_key(x,k_set)) {//SET 638
      cludEvent(1,2);//сохраняем в очередь код события
    }
    break;
  case 1:
    if (x>800){//если кнопка отпущена
      f_key=0;//сбрасываем флаг
    }
    break;
  }
}
//проверка входа кнопки в диапазон
boolean a_key(int x, int key){
  if (x > (key-k_wdh) && x < key+k_wdh) {
    return 1;//входит
  }
  else
  {
    return 0;//не входит
  }
}

//---------ФУНКЦИИ ОЧЕРЕДИ СОБЫТИЙ-------------
//положить задание в очередь (код, приоритет)
//0-незамедлительно, 1-высокий, 2-в порядке очереди
void cludEvent(byte e, boolean p){
  switch (p){
  case 0://незамедлительный
    run(automat[m][e]);
    break;
  case 1://высокий
    for (int i=0; i <= eventLenght-1; i++){
      if (eventList[i]==0){//ищем первую пустую ячейку очереди
        eventList[i]=e;//пишем в нее задание
        break;
      }
      run(automat[m][e]);//если пустой нет то выполняем
    }
    break;
  case 2://в порядке очереди
    if(eventList[0] !=0){//чтобы не потерять задание в очереди
      run(automat[m][getEvent()]);//выполнение действия из таблицы переходов
    }
    pushEvent();//сдвигаем очередь
    eventList[eventLenght-1]=e;//положить задание в очередь
    break;
  }
}

//взять задание
byte getEvent(){
  return eventList[0];
}
//сдвинуть очередь
void pushEvent(){
  for (int i=0; i <= eventLenght-1; i++){
    eventList[i]=eventList[i+1];
  }
}

//количество заданий в очереди
byte getEventLenght(){
  byte el=0;
  for (int i=0; i <= eventLenght-1; i++){
    //для отладки
    lcd.setCursor(5+i, 0);//выводит на дисплей очередь
    lcd.print(eventList[i]);
    if(eventList[i] != 0){//подсчет заданий в очереди
      el++;
    } 
  }
  return el;//возвращает кол-во заданий в очереди
}

//выполнить действие согласно таблице переходов
void run(int f){
  switch (f){
  case 0:
    break;
  case m_up:
    m=var(m, 0, 3, 1, 1);//увеличиваем переменную уровня меню
    break;
  case p1_up:
    p1= var(p1, 0, 10, 1, 1);//то при нажатии кнопки + увеличиваем переменную р1 на единицу
    break;
  case p1_dn:
    p1= var(p1, 0, 10, -1, 1);//то при нажатии кнопки - уменьшаем переменную р1 на единицу
    break;
  case p2_up:
    p2= var(p2, 0, 10, 1, 1);
    break;
  case p2_dn:
    p2= var(p2, 0, 10, -1, 1);
    break;
  case led_up:
    p3= var(p3, 20, 255, 5, 0);
    break;
  case led_dn:
    p3= var(p3, 20, 255, -5, 0);
    break;
  case PS:
    if (sc==1){//если нужно обновить  
      PreentScreen(m);//обновление экрана
      sc=0;//сбрасываем флаг
    }
    break;
  case m0:
    m=0;
    break;
  case gk:
    getKey();
    break;
  }
}

//--------Вывод экранов----------------------
void PreentScreen(int s){
  lcd.clear();//отчистка экрана
  switch (s){
  case 0://Экран выводимый по умолчанию (не пункт меню)
    PrintLabel("Main",0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    PrintLabel(myLabel[2],5,1);
    lcd.print(p2);
    PrintLabel(myLabel[3],10,1);
    lcd.print(p3);
    break;

  case 1://экран изменения переменной 1
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[1],0,1);
    lcd.print(p1);
    break;

  case 2://экран изменения переменной 2
    PrintLabel(myLabel[0],0,0);
    PrintLabel(myLabel[2],0,1);
    lcd.print(p2);
    break;

  case 3://экран изменения переменной 3
    PrintLabel(myLabel[4],0,0);
    PrintLabel(myLabel[3],0,1);
    lcd.print(p3);
    break;
  }
}

//---------вывод лейблов в указанном месте экрана-------------
void PrintLabel(char* t, byte x, byte y){//char* t - лейбл, x-столбец, y-строка
  lcd.setCursor(x, y);
  lcd.print(t);
}

//Спасибо сюда yasobe.ru/na/buter#form_submit
//или сюда money.yandex.ru/to/41001603404940


 

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

yul-i-an пишет:

У меня хоть delay`ев нет)

И что? Это заслуга? Зато, у Вас if'ы есть.

yul-i-an пишет:

В своем коде я пытаюсь объяснить просто (для тех кто совсем не понимает) саму концепцию построения меню, 

Может Вам сначала научиться писать программы, а потом кому-то что-то объяснять? Заодно и концепцию меню получше самому понять? Вы совсем недалеко ушли от тех, "кто совсем не понимает", а лезете кого-то учить, а сами, простите, с переполнением миллис боретесь. Извините, конечно, не хотел обидеть.

Сан-Саныч
Offline
Зарегистрирован: 16.05.2016

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

yul-i-an
yul-i-an аватар
Offline
Зарегистрирован: 10.12.2012

Сан-Саныч пишет:

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

Ссылка на тему очереди. А тут более грамотный подход.

den_r777
Offline
Зарегистрирован: 13.03.2017

А можно для каждого из дисплеев сделать свой изменяемый

т.е. m это 4 наших дисплея, а в каждом из m находится такая же "m"