/*
Como hacer MENU de OPCIONES con pantalla LCD y pulsadores en arduino

Basado en un trabajo de Electroall

Entrada en su blog:
https://www.electroallweb.com/index.php/2024/07/12/como-hacer-menu-de-opciones-con-pantalla-lcd-y-pulsadores-en-arduino/

Vídeo en YouTube:
https://www.youtube.com/watch?v=DFC6QnWaPEg&t=1209

Se ha omitido la parte del accionamiento de los motores por no haber los elementos necesarios en Wokwi.
*/

//librerias
#include <EEPROM.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);

//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
//PINES DE ENTRADA DE LOS PULSADORES         █
const int in_A = 2;     //ADD                █
const int in_B = 4;     //LESS               █
const int swPin = 3;    //OK                 █
bool btnpress = false;  //memoria boton OK   █
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
//PINES DE SALIDA PARA CONTROLAR LOS MOTORES                                   █
#define pwm_motor1 9   //Salidas PWM para el motor 1                           █
#define pwm_motor2 10  //Salidas PWM para el motor 2                           █
#define der_m1 5       // señal para que el motor 1 gire hacia la derecha      █
#define izq_m1 6       // señal para que el motor 1 gire hacia la izquierda    █
#define der_m2 7       // señal para que el motor 2 gire hacia la derecha      █
#define izq_m2 8       // señal para que el motor 2 gire hacia la ziquierda    █ \
                       /////                                                   █
//MEMORIAS PARA LA VELOCIDAD DEL MOTOR                                         █
int vel_moto1 = 0;    // variable para la velocidad del motor1                 █
int vel_moto2 = 0;    // variable para la velocidad del motor2                 █
int direc_moto1 = 1;  //Direccion EEPROM para almacenar la variable vel_moto1  █
int direc_moto2 = 2;  //Direccion EEPROM para almacenar la variable vel_moto2  █
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█

//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
//---------------------------------------------------MENUS--------------------------------------------------------------------------------------------------
String menu1[] = { "ON // OFF", "ADJ VELOCIDAD", "INVER GIRO", "Atras" };  //Palabras del menu principal
int sizemenu1 = sizeof(menu1) / sizeof(menu1[0]);                          // Aqui vamos a obtener el número de elementos ocupados en la matriz. en este caso 3

String menu2[] = { "Motor1", "Motor2", "Atras" };
int sizemenu2 = sizeof(menu2) / sizeof(menu2[0]);

String menu3[] = { "ON", "OFF", "Atras" };
int sizemenu3 = sizeof(menu3) / sizeof(menu3[0]);

String menu4[] = { "Izquierda", "Derecha", "Atras" };
int sizemenu4 = sizeof(menu4) / sizeof(menu4[0]);

String linea1, linea2;  //lines o filas del lcd 16x2
int level_menu = 0;     // para ubicarnos en un menú { "ON // OFF", "ADJ VELOCIDAD", "INVER GIRO", "Atras" } { "Motor1", "Motor2", "Atras" };
int level2_menu = 0;    // para ubicarnos en el subnemu { "ON", "OFF", "Atras" } { "Izquierda", "Derecha", "Atras" }

int contador = 0;  // CONTADOR PARA EL DESPLAZAMIENTO ENTRE LOS MENUS

byte flecha[] = { B10000, B11000, B11100, B11110, B11100, B11000, B10000, B00000 };  //Creamos un array de 8 posiciones para la flecha del menu
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄

void setup() {

  pinMode(in_A, INPUT);
  pinMode(in_B, INPUT);
  pinMode(swPin, INPUT);

  pinMode(pwm_motor1, OUTPUT);
  pinMode(pwm_motor2, OUTPUT);
  pinMode(der_m1, OUTPUT);
  pinMode(izq_m1, OUTPUT);
  pinMode(der_m2, OUTPUT);
  pinMode(izq_m2, OUTPUT);

  lcd.init();
  lcd.backlight();
  lcd.createChar(0, flecha);  //Caracter personalizado

  vel_moto1 = EEPROM.read(direc_moto1);  // Obtiene el dato de EEPROM y lo envía a la variable vel_moto1
  vel_moto2 = EEPROM.read(direc_moto2);

  analogWrite(pwm_motor1, map(vel_moto1, 0, 9, 0, 255));  //Enviamos el dato leido de EEPROM al MOTOR 1
  analogWrite(pwm_motor2, map(vel_moto2, 0, 9, 0, 255));
}

void loop() {

  selectOption();  //Funcion para detectar cuando se Presionar el pusador de OK

  //███████████████████████████████████ PANTALLA PRINCIPAL ██████████████████████████████████████████
  if (level_menu == 0) {
    lcd.setCursor(0, 0);  //Detalles mas importantes del motor1 // linea0
    lcd.print("Motor1: ");
    if (vel_moto1) lcd.print("ON, ");
    else lcd.print("OFF, ");
    lcd.print(vel_moto1);

    lcd.setCursor(0, 1);  //Detalles mas importantes del motor2  // linea1
    lcd.print("Motor2: ");
    if (vel_moto2) lcd.print("ON, ");
    else lcd.print("OFF, ");
    lcd.print(vel_moto2);

    if (btnpress) {  // si se presiona el pulsador de OK nos llevará al menu principal
      level_menu = 1;
      fn_menu(contador, menu1, sizemenu1);
      btnpress = false;
    }
  }  //███████████████████████████████████████████████████████████████████████████████████████████████

  //███████████████████████████████████ MENU 1 PRINCIPAL ██████████████████████████████████████████
  if (level_menu == 1) {  //Esta variable corresponde al nivel principal del menu. level_menu = 1.

    if (fnSwitch(sizemenu1)) {              //Esta funcion muestra en el LCD el menu en el que estamos
      fn_menu(contador, menu1, sizemenu1);  //Esta funcion muestra la posicion dentro de ese menu segun el valor de la variable contador
    }

    if (btnpress) {  //Verificamos si el boton del encoder fue oprimido. btnpress == true
      // >ON // OFF
      if (contador == 0) {
        contador = 0;                         //Seleccionamos la posicion donde quedara la flecha de seleccion. On, Off, Atras
        fn_menu(contador, menu2, sizemenu2);  //Vamos a la funcion que nos muestra las opciones para el Led 1
        level_menu = 2;                       //Esta variable corresponde al nivel secundario del menu. level_menu = 1.
      }

      // > ADJ VELOCIDAD
      if (contador == 1) {
        contador = 0;
        fn_menu(contador, menu2, sizemenu2);
        level_menu = 3;
      }

      //> INVERSION DE GIRO
      if (contador == 2) {
        contador = 0;
        fn_menu(contador, menu2, sizemenu2);
        level_menu = 4;
      }
      //>Atras
      if (contador == 3) {
        contador = 0;
        level_menu = 0;
      }
      btnpress = false;  //  Nos aseguramos que esta variable de retorno de la funcion selectOption() vuelva a su estado inicial
    }
  }  //██████████████████████████████████████████████████████████████████████████████████████████████████████████████

  //███████████████████████████████████ MENU2: { "Motor1", "Motor2", "Atras" } ██████████████████████████████████████████
  if (level_menu == 2) {

    if (level2_menu == 0) {
      if (fnSwitch(sizemenu2)) {  //Nos desplazamos con el encoder sleccionando las diferentes opciones
        fn_menu(contador, menu2, sizemenu2);
      }
      if (btnpress) {  //Verificamos si el boton del encoder fue oprimido. btnpress == true

        if (contador == 0) {  // motor1
          contador = 0;
          fn_menu(contador, menu3, sizemenu3);
          level2_menu = 1;
        }

        if (contador == 1) {  // motor2
          contador = 0;
          fn_menu(contador, menu3, sizemenu3);
          level2_menu = 2;
        }

        if (contador == 2) {  //atras
          contador = 0;
          fn_menu(contador, menu1, sizemenu1);
          level_menu = 1;
        }

        btnpress = false;
      }
    }

    //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ MENU 3: { "ON", "OFF", "Atras" } ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
    if (level2_menu == 1) {
      if (fnSwitch(sizemenu3)) {  //Nos desplazamos con el encoder sleccionando las diferentes opciones
        fn_menu(contador, menu3, sizemenu3);
      }
      if (btnpress) {
        if (contador == 0) {
          digitalWrite(pwm_motor1, HIGH);  //Encedemos motor 1
          vel_moto1 = 9;                   //Maximo velocidad
          EEPROM.write(direc_moto1, vel_moto1);
        }
        if (contador == 1) {
          digitalWrite(pwm_motor1, LOW);  //Apagamos motor 1
          vel_moto1 = 0;                  //minima velocidad
          EEPROM.write(direc_moto1, vel_moto1);
        }
        if (contador == 2) {  //atras
          contador = 0;
          fn_menu(contador, menu2, sizemenu2);
          level2_menu = 0;
        }
        btnpress = false;
      }
    }
    if (level2_menu == 2) {
      if (fnSwitch(sizemenu3)) {  //Nos desplazamos con el encoder sleccionando las diferentes opciones
        fn_menu(contador, menu3, sizemenu3);
      }
      if (btnpress) {
        if (contador == 0) {
          digitalWrite(pwm_motor2, HIGH);  // ON MOTOR 2
          vel_moto2 = 9;                   // velocidad maxima
          EEPROM.write(direc_moto2, vel_moto2);
        }
        if (contador == 1) {
          digitalWrite(pwm_motor2, LOW);  //OFF MOTOR2
          vel_moto2 = 0;                  //Velocidad minima
          EEPROM.write(direc_moto2, vel_moto2);
        }
        if (contador == 2) {  //atras
          contador = 0;
          fn_menu(contador, menu2, sizemenu2);
          level2_menu = 0;
        }
        btnpress = false;
      }
    }  //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
  }    //██████████████████████████████████████████████████████████████████████████████████████████████████████████████

  //███████████████████████████████████ MENU3: velocidad motor1, velocidad motor2 ██████████████████████████████████████████
  if (level_menu == 3) {

    if (fnSwitch(sizemenu2)) {  // MENU2: { "Motor1", "Motor2", "Atras" }
      fn_menu(contador, menu2, sizemenu2);
    }

    if (btnpress) {

      if (contador == 0) {

        contador = vel_moto1;
        lcd.clear();

        do {

          fn_contador_brillo();
          vel_moto1 = contador;

          lcd.setCursor(0, 0);
          lcd.print("velocidad motor1");
          analogWrite(pwm_motor1, map(vel_moto1, 0, 9, 0, 255));
          lcd.setCursor(0, 1);
          lcd.print(vel_moto1);

        } while (digitalRead(swPin) == LOW);  //Instrucciones despues del do se repetirán mientras esta condición sea cierta
        delay(500);
        EEPROM.write(direc_moto1, vel_moto1);  // Se agrega a EEPROM con la dirección primero y valor de la variable después
        contador = 0;
        fn_menu(contador, menu2, sizemenu2);
      }

      if (contador == 1) {

        contador = vel_moto2;
        lcd.clear();

        do {

          fn_contador_brillo();
          vel_moto2 = contador;

          lcd.setCursor(0, 0);
          lcd.print("velocidad motor2");
          analogWrite(pwm_motor2, map(vel_moto2, 0, 9, 0, 255));
          lcd.setCursor(0, 1);
          lcd.print(vel_moto2);

        } while (digitalRead(swPin) == LOW);  //Instrucciones despues del do se repetirán mientras esta condición sea cierta
        delay(500);
        EEPROM.write(direc_moto2, vel_moto2);
        contador = 1;
        fn_menu(contador, menu2, sizemenu2);
      }

      if (contador == 2) {                    //ATRAS
        contador = 1;                         //Posicion del menu donde se genero el llamado 0 --> 5
        fn_menu(contador, menu1, sizemenu1);  //Retorna al Menu Principal a la posicion del menu donde se genero el llamado
        level_menu = 1;
      }

      btnpress = false;
    }
  }  //██████████████████████████████████████████████████████████████████████████████████████████████████████████████

  //███████████████████████████████████ MENU2: { "Motor1", "Motor2", "Atras" } ██████████████████████████████████████████
  if (level_menu == 4) {

    if (level2_menu == 0) {
      if (fnSwitch(sizemenu2)) {
        fn_menu(contador, menu2, sizemenu2);
      }
      if (btnpress) {

        if (contador == 0) {  // MOTOR 1
          contador = 0;
          fn_menu(contador, menu4, sizemenu4);
          level2_menu = 1;
        }

        if (contador == 1) {  // MOTOR 2
          contador = 0;
          fn_menu(contador, menu4, sizemenu4);
          level2_menu = 2;
        }

        if (contador == 2) {  //ATRAS
          contador = 0;
          fn_menu(contador, menu1, sizemenu1);
          level_menu = 1;
        }

        btnpress = false;
      }
    }

    //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ MENU 3.1: { "IZQUIERDA", "DERECHA", "Atras" } ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
    if (level2_menu == 1) {  // IZQUIERDA, DERECHA, ATRAS: MOTOR 2
      if (fnSwitch(sizemenu4)) {
        fn_menu(contador, menu4, sizemenu4);
      }
      if (btnpress) {
        if (contador == 0) {  //IZQUIERDA
          digitalWrite(der_m1, 1);
          digitalWrite(izq_m1, 0);
        }
        if (contador == 1) {  //DERECHA
          digitalWrite(der_m1, 0);
          digitalWrite(izq_m1, 1);
        }
        if (contador == 2) {  //ATRAS
          contador = 0;
          fn_menu(contador, menu2, sizemenu2);
          level2_menu = 0;
        }
        btnpress = false;
      }
    }
    if (level2_menu == 2) {  // IZQUIERDA, DERECHA, ATRAS: MOTOR 2
      if (fnSwitch(sizemenu4)) {
        fn_menu(contador, menu4, sizemenu4);
      }
      if (btnpress) {
        if (contador == 0) {  // IZQUIERDA
          digitalWrite(der_m2, 1);
          digitalWrite(izq_m2, 0);
        }
        if (contador == 1) {  //DERECHA
          digitalWrite(der_m2, 0);
          digitalWrite(izq_m2, 1);
        }
        if (contador == 2) {  //ATRAS
          contador = 1;
          fn_menu(contador, menu2, sizemenu2);
          level2_menu = 0;
        }
        btnpress = false;
      }
    }  //▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
  }    //██████████████████████████████████████████████████████████████████████████████████████████████████████████████
}

void selectOption() {  //funcion para leer el pulsador de ok
  if (digitalRead(swPin) == HIGH) {
    delay(500);
    btnpress = true;
  }
}

void fn_contador_brillo() {

  if (digitalRead(in_A) == HIGH) {
    contador++;
    delay(250);
  } else if (digitalRead(in_B) == HIGH) {
    contador--;
    delay(250);
  }

  if (contador > 9) {
    contador = 9;
  }
  if (contador < 0) {
    contador = 0;
  }
}

void fn_menu(int pos, String menus[], byte sizemenu) {  // funcion de desplazamiento de los menús
  lcd.clear();
  linea1 = "";
  linea2 = "";

  if ((pos % 2) == 0) {

    lcd.setCursor(0, 0);
    lcd.write(byte(0));
    linea1 = menus[pos];

    if (pos + 1 != sizemenu) {
      linea2 = menus[pos + 1];
    }

  } else {
    linea1 = menus[pos - 1];
    lcd.setCursor(0, 1);
    lcd.write(byte(0));
    linea2 = menus[pos];
  }

  lcd.setCursor(1, 0);
  lcd.print(linea1);

  lcd.setCursor(1, 1);
  lcd.print(linea2);
}

bool fnSwitch(byte sizemenu) {  //Lee las señales de los pulsadores, exepto el de ok
  bool retorno = false;
  if (digitalRead(in_A) == 1 || digitalRead(in_B) == 1) {
    if (digitalRead(in_A) == HIGH) {
      contador++;
      delay(250);
    } else if (digitalRead(in_B) == HIGH) {
      contador--;
      delay(250);
    }

    if (contador <= 0) {
      contador = 0;
    }

    if (contador >= sizemenu - 1) {
      contador = sizemenu - 1;
    }

    retorno = true;
  }
  return retorno;
}