En este tutorial construimos un menú con un total de 4 botones y 1 pantalla LCD para que tu microcontrolador Arduino, ESP8266 o ESP32 controle un programa en ejecución.
El artículo se divide en dos partes:
- El cableado del microcontrolador, los botones y la pantalla LCD para crear un menú.
- Crear el script del programa para controlar variables predefinidas como una temperatura.
Cómo crear un menú con botones para un microcontrolador
Se me ocurrió la idea de un menú cuando planifiqué un nuevo proyecto de controlador de temperatura para mi ahumador. Pensé que sería genial no solo mostrar la temperatura actual de múltiples sondas de temperatura en la pantalla LCD, sino también darme una descripción general de la temperatura objetivo seleccionada para la carne. Esto es importante porque los diferentes tipos de carne tienen diferentes temperaturas objetivo.
También puede transferir mi ejemplo al área de la casa inteligente. Por ejemplo, también puede tener un menú para mostrar la temperatura y la humedad de diferentes habitaciones en su hogar, con cada pantalla en la pantalla mostrando estadísticas de una habitación. Y si la humedad excede un umbral preestablecido, que se puede configurar con el menú, obtienes una alarma en tu teléfono.
En este tutorial, usamos un menú con 4 botones:
La siguiente tabla describe la asignación lógica y física de cada uno de los cuatro pulsadores del panel frontal:
| Pulsador / Dirección | Color Identificador | Pin Arduino (PULLUP) | Función en la Máquina de Estados |
|---|---|---|---|
| Izquierda (Left) | Azul | D5 | Navega hacia la pantalla o parámetro anterior (rotación cíclica) |
| Derecha (Right) | Amarillo | D8 | Avanza a la pantalla o parámetro siguiente (rotación cíclica) |
| Arriba (Up) | Verde | D7 | Incrementa el valor del parámetro activo en pantalla (+) |
| Abajo (Down) | Rojo | D6 | Decrementa el valor del parámetro activo en pantalla (-) |
La siguiente imagen muestra los botones en una maqueta para este tutorial. En un proyecto real, soldaría los botones en una placa de circuito con forma de plus.

- Peligro de pines en alta impedancia (entradas flotantes): Si conectas un pulsador a un pin GPIO sin resistencia de pull-up o pull-down, el pin queda flotante y actúa como antena recogiendo ruido electromagnético ambiental. Esto provoca falsos disparos y saltos erráticos en el menú. Activa siempre
pinMode(pin, INPUT_PULLUP)para fijar el nivel en reposo a 5 V o 3,3 V. - Rebote mecánico de contactos (Contact Bounce): Las láminas metálicas de un pulsador rebotan físicamente entre 2 y 15 ms al ser presionadas. Sin un retardo de debouncing por software o un filtro RC físico, el microcontrolador detectará decenas de pulsaciones instantáneas, saltando varios menús de golpe.
- Tensión máxima en entradas de ESP32 y ESP8266 (3,3 V): Mientras que Arduino Uno opera a 5 V, los pines GPIO del ESP32 y ESP8266 no toleran 5 V. Si alimentas la pantalla LCD a 5 V, asegúrate de que los pulsadores cierren hacia masa (GND) con pull-up referenciado a 3,3 V para no destruir las compuertas de entrada del SoC.
Cableado entre botones y microcontrolador para crear un menú
A continuación se detallan las conexiones hardware estandarizadas entre los cuatro pulsadores de navegación, la pantalla LCD con interfaz I2C (controlador PCF8574) y las principales plataformas de microcontroladores (Arduino Uno/Nano, ESP8266 y ESP32):
| Componente / Entrada | Arduino Uno / Nano | ESP8266 (NodeMCU) | ESP32 (DevKit v1) | Modo y Función en el Menú |
|---|---|---|---|---|
| LCD I2C SDA | Pin A4 | D2 (GPIO 4) | GPIO 21 | Línea de datos serie bidireccional I2C |
| LCD I2C SCL | Pin A5 | D1 (GPIO 5) | GPIO 22 | Línea de reloj de sincronismo I2C |
| LCD VCC / GND | 5V / GND | VIN (5V) / GND | 5V (VIN) / GND | Alimentación lógica y masa común |
| Pulsador Izquierda (Azul) | Pin D5 | D5 (GPIO 14) | GPIO 18 | INPUT_PULLUP (Menú / Pantalla anterior) |
| Pulsador Derecha (Amarillo) | Pin D8 | D6 (GPIO 12) | GPIO 19 | INPUT_PULLUP (Menú / Pantalla siguiente) |
| Pulsador Arriba (Verde) | Pin D7 | D7 (GPIO 13) | GPIO 23 | INPUT_PULLUP (Incrementar parámetro +) |
| Pulsador Abajo (Rojo) | Pin D6 | D3 (GPIO 0) | GPIO 5 | INPUT_PULLUP (Decrementar parámetro -) |
Para este proyecto estoy usando diferentes placas Arduino. Pero puede usar cualquier microcontrolador que desee, como ESP8266 o ESP32. El siguiente bosquejo fritzing muestra la conexión entre las placas Arduino, los 4 botones y la pantalla LCD.
Estoy usando una pantalla LCD de 20 × 4 con comunicación I2C. Si también está interesado en las pantallas LCD en general, le recomiendo que consulte tutorial sobre pantallas LCD 16×2 e I2C.
- Arduino Nano
- Arduino Pro Mini
- Arduino Uno
- Arduino Mega
Arduino Nano

Para obtener más información sobre Arduino Nano, visite el tutorial de Arduino Nano.
Arduino Pro Mini

Arduino Uno

Para obtener más información sobre Arduino Uno, visite el tutorial de Arduino Uno.
Arduino Mega

Para obtener más información sobre Arduino Mega, visite el tutorial de Arduino Mega.
La pantalla LCD está conectada a través de la interfaz I2C a las placas Arduino para la comunicación y transmisión de datos entre el microcontrolador y la pantalla LCD. Usamos el pin de 5V para la potencia de la pantalla.
Los cuatro botones diferentes están conectados a un pin digital en la placa Arduino. En mi caso, estoy usando los pines de E / S digitales 5 a 8 para la conexión. Si está utilizando otros pines digitales, asegúrese de cambiar también el código del programa.
No utilizo resistencias para la conexión a los botones. En su lugar, utilizo la resistencia pull-up interna de la placa Arduino. La función del pull-up interno es la siguiente: Si no se presiona el botón, la resistencia de pull-up interna se conecta a 5V. Por lo tanto, Arduino reporta ALTO. Sin embargo, si presionamos el botón, el pin Arduino se tira a tierra y señala BAJO. Necesitamos recordar esto más adelante en la parte de programación de este tutorial.
Script de programa para crear un menú multipantalla
Ahora vamos a sumergirnos en el programa Arduino. Este script es un poco más complicado porque la parte difícil no es la conexión entre la pantalla, los botones y el microcontrolador, sino el script para crear la lógica del menú. Pero que no cunda el pánico, repasamos el guión paso a paso y describo cada pequeño detalle.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Inicialización de pantalla LCD 20x4 I2C en dirección 0x27
LiquidCrystal_I2C lcd(0x27, 20, 4);
// Asignación de pines para los 4 pulsadores
const int pinBtnIzquierda = 5;
const int pinBtnAbajo = 6;
const int pinBtnArriba = 7;
const int pinBtnDerecha = 8;
// Parámetros y matriz de pantallas del menú
const int TOTAL_PANTALLAS = 4;
int pantallaActual = 0;
// Matriz de nombres de parámetros y unidades físicas
String menuItems[TOTAL_PANTALLAS][2] = {
{"Temp. Objetivo", "°C"},
{"Temporizador", "Min"},
{"Alarma Humedad", "% RH"},
{"Potencia PWM", "%"}
};
// Valores editables de cada parámetro
int valoresParametros[TOTAL_PANTALLAS] = {25, 10, 60, 100};
// Bandera de actualización para evitar parpadeo (flicker) en el LCD
bool requiereActualizacion = true;
// Prototipos de funciones
void dibujarPantalla();
void setup() {
Serial.begin(115200);
// Inicialización de la pantalla LCD
lcd.init();
lcd.backlight();
// Configuración de pulsadores con resistencia pull-up interna
pinMode(pinBtnIzquierda, INPUT_PULLUP);
pinMode(pinBtnAbajo, INPUT_PULLUP);
pinMode(pinBtnArriba, INPUT_PULLUP);
pinMode(pinBtnDerecha, INPUT_PULLUP);
// Mensaje de bienvenida
lcd.clear();
lcd.setCursor(2, 1);
lcd.print("SISTEMA DE CONTROL");
lcd.setCursor(4, 2);
lcd.print("MENU INICIADO");
delay(1500);
lcd.clear();
dibujarPantalla();
}
void loop() {
// Lectura activa en BAJO (conmutación a GND)
bool btnIzq = (digitalRead(pinBtnIzquierda) == LOW);
bool btnDer = (digitalRead(pinBtnDerecha) == LOW);
bool btnArr = (digitalRead(pinBtnArriba) == LOW);
bool btnAba = (digitalRead(pinBtnAbajo) == LOW);
// Navegación horizontal entre pantallas
if (btnIzq) {
if (pantallaActual == 0) {
pantallaActual = TOTAL_PANTALLAS - 1; // Rotación cíclica
} else {
pantallaActual--;
}
requiereActualizacion = true;
delay(200); // Antirrebote básico por software
}
else if (btnDer) {
if (pantallaActual == TOTAL_PANTALLAS - 1) {
pantallaActual = 0; // Rotación cíclica
} else {
pantallaActual++;
}
requiereActualizacion = true;
delay(200);
}
// Modificación vertical de valores
else if (btnArr) {
valoresParametros[pantallaActual]++;
requiereActualizacion = true;
delay(150);
}
else if (btnAba) {
valoresParametros[pantallaActual]--;
requiereActualizacion = true;
delay(150);
}
// Redibujado condicional solo si ha cambiado el estado
if (requiereActualizacion) {
dibujarPantalla();
requiereActualizacion = false;
}
}
void dibujarPantalla() {
lcd.clear();
// Pantalla 3: Resumen consolidado de todos los valores
if (pantallaActual == 3) {
for (int i = 0; i < TOTAL_PANTALLAS; i++) {
lcd.setCursor(0, i);
lcd.print(menuItems[i][0]);
lcd.print(":");
lcd.setCursor(14, i);
lcd.print(valoresParametros[i]);
lcd.print(menuItems[i][1]);
}
}
// Pantallas 0 a 2: Detalle de edición individual
else {
lcd.setCursor(0, 0);
lcd.print("--- PANEL CONTROL ---");
lcd.setCursor(0, 1);
lcd.print("Ajuste de:");
lcd.setCursor(0, 2);
lcd.print(menuItems[pantallaActual][0]);
lcd.setCursor(0, 3);
lcd.print("> Valor: ");
lcd.print(valoresParametros[pantallaActual]);
lcd.print(" ");
lcd.print(menuItems[pantallaActual][1]);
}
}
Primero, importamos la biblioteca necesaria para la comunicación LCD I2C y creamos un objeto LiquidCrystal_I2C. El objeto tiene un total de tres variables:
- Dirección hexadecimal I2C: 0x27
En mi caso, la pantalla LCD del menú I2C está conectada a la dirección hexadecimal 0x27. Esta es la dirección predeterminada para esta pantalla. O puede probar el script con la misma dirección, puede encontrar la dirección I2C Hex con un escáner de direcciones I2C Hex. Puede encontrar el script y la descripción del escáner de direcciones hexadecimales I2C en este artículo. - Dígitos seguidos: 20 y número de líneas: 4
En mi caso, estoy usando una pantalla LCD de 20 × 4. Si estás usando una pantalla de 16 × 2, debes cambiar el código porque estamos usando más de 2 líneas de la pantalla.
Si no sabe cómo instalar la biblioteca LiquidCrystal_I2C, encontrará un tutorial paso a paso en este artículo.
Ahora definimos un total de 4 variables diferentes con el número de pin para saber en qué señal los diferentes botones envían su estado al Arduino. Por ejemplo, el botón de abajo está conectado al pin 6.
Las variables buttonState almacenan el estado actual del botón. Al comienzo del script, los botones tienen el estado 0. Si se presiona un botón, obtiene el estado 1. Con este estado, sabemos más adelante en el script qué botón se ha presionado y puede desencadenar la reacción.
Una vez que nos preocupamos por los botones, llegamos a las pantallas. La primera variable define el número de pantallas que queremos mostrar. En este caso, queremos crear 4 pantallas. La segunda variable define la pantalla que se muestra al inicio del programa. Definimos la pantalla 0. Y aquí está el primer obstáculo, porque ahora estamos creando una matriz bidimensional. En cada una de las 4 pantallas queremos registrar 2 variables. Las 2 variables son un valor y una unidad y pueden ser temperatura y kelvin. La variable de pantalla actual es 0 porque accedemos al primer elemento de una matriz en Arduino con 0 y no 1.
La siguiente tabla muestra la matriz bidimensional.
La matriz bidimensional de parámetros almacena el nombre del campo, la unidad física asociada y el valor numérico editable para cada una de las pantallas configurables:
| Índice de Pantalla (ID) | Nombre del Parámetro | Valor Inicial por Defecto | Unidad de Medida | Aplicación Práctica |
|---|---|---|---|---|
0 |
Temperatura Objetivo | 0 | °C | Consigna de termostato o ahumador |
1 |
Tiempo de Cocción / Temporizador | 10 | Min | Conteo regresivo de proceso |
2 |
Humedad de Alarma | 50 | % RH | Umbral de activación de relé o extractor |
3 |
Resumen Global / Estado General | 100 | % PWM | Monitorización consolidada en 4 líneas |
La última variable que declaramos es una matriz que almacena el valor actual de la pantalla. Como tenemos 4 pantallas en total, almacenamos 4 valores que se pueden aumentar o disminuir.
Una vez definidas todas las variables nos encargamos de la función de configuración que solo funcionará una vez cuando se inicie el microcontrolador. Primero establecemos la velocidad en baudios en 9600, lo que significa que podemos ver los valores en el monitor en serie con una velocidad de datos de 9600 bits por segundo. Por tanto, la velocidad de transmisión definida en la función de configuración y en el monitor serie debe ser síncrona.
Después de configurar la velocidad en baudios, inicializamos la pantalla LCD y encendemos la luz de fondo.
Luego inicializamos los 4 botones con sus pines y activamos la resistencia pull-up interna para que no necesitemos una resistencia extra para nuestro circuito.
Para la función de configuración, queremos escribir un texto breve en las pantallas. Primero limpiamos la pantalla complete y coloque el cursor en la segunda línea del número 4. Aquí imprimimos la primera parte del texto. Luego colocamos el cursor en la siguiente línea y como la segunda parte de la prueba es más larga, colocamos el cursor en el número de 3 dígitos e imprimimos la segunda parte del texto. El texto se muestra durante 5 segundos y después de este tiempo se borra la pantalla.
Ahora entramos en la función de ciclo completo que, por supuesto, se procesa una y otra vez. Las primeras 4 líneas de la función de bucle se utilizan para leer el estado actual de los botones. Si se presiona un botón, la variable buttonState es BAJA y reaccionamos a continuación con la reacción de acuerdo con el botón presionado y luego ejecutamos la función printScreen que se explicará en la siguiente sección de este tutorial de menú.
Primero discutimos las declaraciones buttonStateLeft y buttonStateRight. Ingresamos el primero si se presiona el botón y despejamos la pantalla para mostrar la nueva pantalla sin la información anterior. Para el botón izquierdo, decrementamos el puntero para mostrar la pantalla anterior; si el puntero ya se encuentra en la primera pantalla (currentScreen == 0), pasamos a la última pantalla para lograr una navegación cíclica continua. Para el botón derecho, queremos mostrar la siguiente pantalla. Es por eso que estamos aumentando la variable currentScreen. Si llegamos a la última pantalla, también mostramos la primera pantalla para obtener la funcionalidad de un bucle sin fin en las pantallas.
Ahora queremos discutir qué sucede si presionamos el botón hacia arriba o hacia abajo. Primero limpiamos la pantalla, luego aumentamos o disminuimos el valor de la pantalla actual. La matriz correspondiente que definimos anteriormente almacena los valores de los cambios. Por lo tanto, para cada pantalla podemos cambiar el valor.
La función printScreen tiene muchas líneas pero es bastante simple y directa. Primero comprobamos si estamos en la última pantalla número 3. Si es así, queremos mostrar cada número y cada unidad fila por fila. Por lo tanto, colocamos el cursor en la primera línea e imprimimos el nombre del número, el número y la unidad. Hacemos esto línea por línea cambiando el cursor.
Si no estamos en la última pantalla, queremos mostrar el título “MENÚ TOTORIAL” en la primera línea y usar las dos últimas líneas para mostrar el nombre del número, número y unidad.
Sé que estos tutoriales pueden ser un poco secos, pero en el siguiente video puedes ver el menú de botones en acción. Me ves pasar por todas las pantallas y cambiar los valores. Siempre que se cambia un valor, también se almacena y no se sobrescribe a partir de los valores iniciales.
En este tutorial, te mostré cómo crear un menú básico con 4 botones, una pantalla LCD y un Arduino Nano. Por supuesto, estos son los básicos, pero a partir de ahí puedes crear tu propio menú y soluciones aún más complejas. Si tiene problemas para crear su menú o tiene preguntas generales sobre este tutorial, no dude en utilizar la sección de comentarios a continuación para hacer sus preguntas. Les respondo lo antes posible.
Diagnóstico del Rebote Mecánico en Pulsadores con Osciloscopio
El fallo más desconcertante al implementar un menú con pulsadores mecánicos en microcontroladores es el rebote de contactos (contact bounce). Cuando el usuario presiona un botón físico, las láminas metálicas internas chocan y se separan microscópicamente varias decenas de veces antes de asentarse en conducción continua. Durante un intervalo crítico de entre 2 y 15 ms, la línea de entrada digital conmuta de forma caótica entre ALTO (5 V o 3,3 V) y BAJO (0 V), provocando que el procesador ejecute el bucle de interrupción o lectura múltiples veces e incremente los valores de forma incontrolada.
Para analizar y erradicar este fenómeno en el laboratorio, se conecta la sonda del osciloscopio en paralelo con el pulsador:
- Configuración de Adquisición y Disparo: Ajusta la base de tiempos a
1 ms/divo2 ms/diven acoplamiento DC. Configura el sistema de disparo en Flanco de Bajada (Falling Edge) con modo Disparo Único (Single Shot) y sitúa el nivel de umbral a1,5 V(para sistemas de 3,3 V o 5 V). Al presionar el botón una única vez, la pantalla congelará con total nitidez el tren caótico de pulsos espurios de rebote. - Diseño y Verificación del Filtro Pasabajo RC Hardware: Si deseas descargar al microcontrolador de retardos bloqueantes en el código, coloca un condensador cerámico de C = 100 nF en paralelo directo con los bornes del pulsador junto a una resistencia de pull-up de R = 10 kΩ. La constante de tiempo resultante τ = R × C = 10.000 × 100 × 10-9 = 1 ms absorbe la energía del rebote mecánico y transforma el tren de conmutaciones en una curva de descarga exponencial suave y monótona.
- Conformación de Onda con Disparador Schmitt Trigger (74HC14): Para garantizar que la rampa lenta del filtro RC no induzca oscilaciones parásitas al cruzar la región de indeterminación lógica del microcontrolador, se intercala una compuerta inversora Schmitt Trigger (74HC14 o 74LVC14). La histéresis interna genera un flanco de transición digital vertical de apenas unos nanosegundos libre de cualquier oscilación.
En la siguiente tabla se comparan cuatro osciloscopios de laboratorio especialmente idóneos para la captura de transitorios, disparo único y depuración de buses digitales en microcontroladores:
| Modelo | Ancho de Banda | Canales | Muestreo | Memoria | Idoneidad para Robótica y Menús Digitales |
|---|---|---|---|---|---|
| Rigol DS1054Z | 50 MHz (ampliable a 100 MHz) | 4 analógicos | 1 GSa/s | 24 Mpts | Excelente: 4 canales para auditar simultáneamente los 4 pulsadores del menú en disparo único. |
| Siglent SDS1104X-E | 100 MHz | 4 analógicos | 1 GSa/s | 14 Mpts | Muy alta: tecnología SPO con decodificación I2C/SPI integrada de serie para monitorizar el bus LCD. |
| Hantek DSO2D10 | 100 MHz | 2 analógicos + Generador | 1 GSa/s | 8 Mpts | Económico para laboratorio de banco básico con generador AWG para inyectar señales de prueba. |
| FNIRSI 1014D | 100 MHz (nominal) | 2 analógicos | 1 GSa/s | 240 kpts | Uso aficionado básico; buffer limitado para captura profunda de secuencias de rebote prolongadas. |
Si estás equipando tu puesto de electrónica para proyectos con microcontroladores y depuración de señales digitales, consulta nuestra guía especializada sobre osciloscopios para Arduino y ESP32, o explora las comparativas de osciloscopios digitales de sobremesa y osciloscopios USB para PC.
Preguntas Frecuentes sobre Menús y Pantallas en Arduino y ESP32
¿Por qué parpadea el texto de mi menú en la pantalla LCD 16×2 o 20×4?
El parpadeo molesto (flickering) se origina cuando se ejecuta lcd.clear() de forma incondicional en cada iteración del bucle loop(). El comando clear() tarda aproximadamente 2 ms en completarse en el controlador HD44780/PCF8574, dejando la matriz de cristal líquido en blanco momentáneamente. La solución consiste en utilizar una bandera booleana (ej. bool requiereActualizacion = true;) y redibujar el contenido del LCD exclusivamente cuando el usuario pulsa un botón o se detecta una variación real en los datos.
¿Cómo puedo evitar que los valores configurados se borren al reiniciar el microcontrolador?
Para que los valores ajustados por el usuario mediante el menú se conserven tras un apagado o corte de energía, deben almacenarse en memoria no volátil. En Arduino Uno y Nano puedes utilizar la librería nativa EEPROM.h (funciones EEPROM.put() y EEPROM.get()). En ESP32 y ESP8266 se recomienda emplear la librería Preferences.h o el sistema de almacenamiento no volátil NVS, que gestiona automáticamente el desgaste de celdas flash.
¿Cómo conectar 4 o más botones utilizando un único pin analógico en Arduino?
Si tienes escasez de pines GPIO en tu placa, puedes interconectar los cuatro pulsadores mediante una red resistiva en escalera R-2R o un divisor de tensión múltiple conectado a una entrada analógica (ej. pin A0). Al presionar cada botón, se conecta una resistencia de distinto valor a masa, entregando tensiones escalonadas (ej. 1,0 V, 2,0 V, 3,0 V, 4,0 V) que se discriminan fácilmente con analogRead(A0) mediante ventanas de comparación por umbral.
Hola, ¿Cómo hacer para que en la pantalla aparezcas únicamente «MENU TUTORIAL», y (Value_1 0 Unit_1) pase a la otra pantalla y también cómo podría agregar un submenu en algunas de ellas?.
Saludos…
Para hacer que en la pantalla aparezca únicamente «MENU TUTORIAL», una opción podría ser crear una variable booleana que indique si se está mostrando el menú principal o un submenú. Al inicio, se configuraría esta variable como verdadera para indicar que se está mostrando el menú principal.
Luego, en el código que se encarga de mostrar la pantalla, se podría hacer uso de esta variable para determinar qué información se muestra. Por ejemplo:
if (mostrandoMenuPrincipal) {// Mostrar "MENU TUTORIAL" en la pantalla
} else {
// Mostrar el contenido del submenú
}
Para pasar a la otra pantalla, se podría hacer uso de una función que cambie el valor de la variable booleana y actualice la información que se muestra en la pantalla. Por ejemplo:
void mostrarSubmenu() {mostrandoMenuPrincipal = false;
// Actualizar la información de la pantalla para mostrar el submenú
}
Para agregar un submenú en algunas de las pantallas, se podría hacer uso de la misma técnica descrita anteriormente. Simplemente se necesitaría crear una nueva variable booleana que indique si se está mostrando un submenú en particular y modificar el código que muestra la pantalla de acuerdo a ese valor.
Es importante tener en cuenta que la forma en que se implemente esto dependerá del microcontrolador que estés utilizando y de la librería gráfica que estés empleando. Por lo tanto, es posible que sea necesario adaptar esta solución a las características específicas de tu proyecto.
Buenas tardes amino no sé nada de Arduino me gustaría que me alludaras con una ide para bajar y subir las pwm y que se me muestre en la pantalla tengo una LCD 16×2 con i2c
Para controlar las señales PWM y mostrar la información en una pantalla LCD con controlador I2C, puedes seguir los siguientes pasos:
Espero que esta información te sea útil. Si tienes alguna duda o necesitas más detalles, no dudes en preguntar.