/*
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;
}