Hay una creencia instalada de que una interfaz táctil decente exige una pantalla cara y meses de pelear con código gráfico. No es así: una placa ESP32-S3 con pantalla redonda de 1,28 pulgadas cuesta menos que un juego de destornilladores de precisión, y con LVGL más SquareLine Studio la interfaz se diseña arrastrando elementos, sin escribir una sola línea de código de dibujo.

En este tutorial vas a montar una aplicación táctil de cuatro pantallas sobre ese hardware: dos pantallas de presentación, una que muestra en vivo el valor de un potenciómetro y una cuarta con un botón que enciende y apaga un LED real. De paso vas a resolver los dos problemas que hacen abandonar este camino a la mayoría: los colores que salen invertidos después de cada exportación, y las lecturas analógicas que bailan solas. Y vas a armar una plantilla de proyecto que reutilizas para siempre.

Pantalla redonda táctil ESP32-S3 ejecutando una interfaz LVGL para control y monitoreo de sensores

Qué hace cada pieza

Antes de instalar nada, conviene tener claro quién es quién, porque son tres cosas distintas que la gente suele confundir:

  • LVGL es la librería gráfica que corre dentro del ESP32-S3. Dibuja botones, etiquetas, barras y maneja los eventos táctiles. Es la única de las tres que termina compilada dentro de tu firmware.
  • SquareLine Studio es una aplicación de escritorio donde diseñas la interfaz visualmente. No corre en el microcontrolador: su salida son archivos .c y .h que exportas y compilas junto a tu sketch.
  • El driver de la pantalla (TFT_eSPI, LovyanGFX o el que traiga tu placa) es lo que convierte lo que LVGL dibujó en memoria en píxeles sobre el panel SPI.

La mayoría de los problemas de "compila pero la pantalla queda negra" vienen de la tercera capa, no de las dos primeras.

Preparar el entorno

Pantalla redonda SPI TFT de 1,28 pulgadas con ESP32-S3 y panel táctil

Abre el gestor de librerías del Arduino IDE, busca LVGL e instala la versión 8.3.11. El número importa más de lo que parece: entre la serie 8.3 y la 9.x cambiaron nombres de funciones y la estructura de configuración, así que el código generado por SquareLine Studio para una versión no compila en la otra. Si más adelante ves errores de compilación con nombres de función que "deberían existir", lo primero que tienes que revisar es la versión de LVGL instalada.

El archivo lv_conf.h, sin pagar por él

LVGL no arranca sin un archivo lv_conf.h, que es donde se define la profundidad de color, qué widgets se compilan, qué fuentes se incluyen y cuánta memoria reserva la librería.

Archivo de configuración lv_conf.h usado para configurar LVGL en un proyecto ESP32-S3

El tutorial original ofrece ese archivo ya preparado a través de su Patreon. No lo necesitas: la propia librería LVGL trae la plantilla oficial y generarla toma un minuto.

  1. Entra a la carpeta Arduino/libraries/lvgl/.
  2. Busca el archivo lv_conf_template.h.
  3. Cópialo un nivel hacia arriba, a Arduino/libraries/, y renómbralo como lv_conf.h.
  4. Ábrelo. En la línea 15 aproximadamente vas a ver #if 0. Cámbialo a #if 1 — esa línea es la que mantiene desactivado el archivo entero mientras es solo una plantilla.
  5. Busca LV_COLOR_DEPTH y déjalo en 16, que es lo que usan estos paneles SPI.

Copiando el archivo lv_conf.h a la carpeta de librerías de Arduino

La ubicación es estricta: lv_conf.h va al lado de la carpeta lvgl, no dentro. Si lo dejas adentro, LVGL no lo encuentra y el compilador reclama que falta el archivo de configuración.

La plantilla reutilizable

Esta es la parte que ahorra una hora en cada proyecto futuro. La idea es fijar una estructura de carpetas una sola vez y no volver a armarla nunca más.

Organización de los archivos de proyecto de SquareLine Studio y de la interfaz generada

Dentro de la carpeta del sketch de Arduino se crean dos carpetas adicionales: una guarda el proyecto nativo de SquareLine Studio, y la otra los archivos de interfaz que SquareLine genera al exportar. Los archivos generados se copian después al mismo nivel del .ino, de modo que al abrir el sketch el IDE los carga todos automáticamente.

Archivos de interfaz generados por SquareLine Studio listos para usar en un proyecto ESP32-S3

El fabricante de la placa suele incluir sus propios ejemplos de LVGL, pero esos ejemplos están atados a su hardware. Esta estructura sirve para cualquier proyecto LVGL, y cerca del 95 % del código de la plantilla queda idéntico de un proyecto a otro: lo único que cambia son los archivos de interfaz.

El bug de color que aparece después de cada exportación

Al abrir ui.c vas a encontrar la definición LV_COLOR_16_SWAP. En las exportaciones para este tipo de panel sale en 1, y hay que ponerla en 0.

Vale la pena entender qué hace, porque el valor correcto no es siempre 0 y copiarlo a ciegas te va a morder en el próximo proyecto.

En profundidad de color de 16 bits, cada píxel es RGB565: cinco bits de rojo, seis de verde, cinco de azul, empaquetados en dos bytes. El ESP32 es little endian, así que al escribir un entero de 16 bits en memoria el byte bajo queda primero. Pero el bus SPI transmite bytes en el orden en que están, y el controlador del panel espera recibir primero el byte alto. LV_COLOR_16_SWAP es justamente el interruptor que intercambia esos dos bytes antes de enviarlos.

Cuando el valor no coincide con lo que hace tu driver, los canales rojo y azul quedan cruzados: los rojos se ven azules, los blancos salen con tinte y toda la interfaz parece rota aunque el código y el diseño estén perfectos.

La regla práctica: si tu driver ya hace el intercambio por su cuenta. TFT_eSPI lo hace cuando usas pushColors() con el flag de swap activado. entonces LVGL no debe hacerlo también, y va en 0. Si el driver envía los bytes tal cual, va en 1. Dos intercambios se cancelan entre sí, igual que ninguno. Prueba un valor, mira la pantalla, y si los rojos salen azules cambia al otro. Con eso resuelves el problema en cualquier panel, no solo en este.

Como SquareLine Studio regenera ui.c en cada exportación, esta corrección se pierde cada vez. Anótalo como paso fijo de tu flujo: exportar, abrir ui.c, revisar LV_COLOR_16_SWAP, compilar.

Configuración de la placa en el Arduino IDE

Estos ajustes se hacen una sola vez y sirven para todos los ejemplos siguientes. Una configuración equivocada acá es la causa habitual de los sketches que suben bien pero no muestran nada.

  • Tools > Board > ESP32 → ESP32S3 Dev Module
  • Tools > Port → el puerto correspondiente
  • USB CDC On Boot → Enabled
  • Tools > Flash Size → 16MB
  • Tools > Partition Scheme → 16M Flash (3MB APP / 9.9MB FATFS)
  • Tools > PSRAM → OPI PSRAM

La PSRAM no es opcional: LVGL necesita un buffer de dibujo que, a 240×240 píxeles y 16 bits, no cabe cómodo en la RAM interna junto con el resto del programa. Si dejas la PSRAM desactivada, el sketch compila pero se reinicia al asignar el buffer.

Importar el proyecto en SquareLine Studio

Pantalla de inicio de SquareLine Studio 1.5.0 para diseñar interfaces LVGL

Con SquareLine Studio 1.5.0 abierto, usa Import Project, navega hasta la carpeta del proyecto y abre el archivo correspondiente.

Ajustes del proyecto en SquareLine Studio configurados para una pantalla redonda ESP32-S3

En el panel de la derecha están los ajustes del proyecto. Para este panel:

  • Resolución: 240 × 240
  • Rotación: 0°, sin desplazamiento en X ni en Y
  • Forma: Circle
  • Profundidad de color: 16-bit swap
  • Versión de LVGL: 8.3.11
  • Tema: Dark
  • Multilenguaje: desactivado

Propiedades de placa configuradas en SquareLine Studio para un proyecto LVGL con ESP32-S3

Desde el menú File > Project Settings se revisan las propiedades del proyecto y de la placa. La opción Circle en la forma de la pantalla no es cosmética: le dice al editor dónde están los bordes reales del panel, y sin ella vas a diseñar elementos en las esquinas que en el hardware quedan fuera del área visible.

Ajustes de forma de pantalla redonda en SquareLine Studio para un táctil ESP32-S3

En la sección File Export defines el Project Export Root (donde se guardan los archivos nativos de SquareLine) y el UI Files Export Path (donde van los .c y .h generados). En LVGL Include Path escribe simplemente lvgl.h.

Rutas de exportación configuradas en SquareLine Studio con la ruta de inclusión de LVGL

Más abajo marca Flat Export, que exporta todos los archivos a una sola carpeta en vez de en subdirectorios, y cierra con Apply Changes.

Botón Apply Changes en los ajustes de proyecto de SquareLine Studio

Primer ejemplo: dos pantallas y nada más

Diseño de una interfaz LVGL de varias pantallas en SquareLine Studio para ESP32-S3

El primer proyecto es deliberadamente pobre: dos pantallas, cada una con un texto, y deslizamiento entre ambas. La razón es de método. En este montaje hay cinco capas que pueden fallar. el driver del panel, el controlador táctil, LVGL, la plantilla y los ajustes de placa. y si tu primer intento es una interfaz compleja que no funciona, no tienes forma de saber cuál de las cinco se rompió.

Empieza con lo mínimo, confirma que dibuja y que el táctil responde, y recién ahí construye encima.

El flujo de exportación queda así, y es el mismo para siempre:

  1. Export > Export UI Files en SquareLine Studio.
  2. Copiar los archivos generados a la carpeta de interfaz del proyecto Arduino.
  3. Abrir ui.c y revisar LV_COLOR_16_SWAP.
  4. Compilar y subir con los ajustes de placa de más arriba.

Datos de sensor que no tiemblen

Lecturas en tiempo real de un potenciómetro mostradas en una interfaz táctil LVGL

Conecta un potenciómetro a una entrada analógica, muestra el valor en pantalla y vas a ver el número saltando solo, sin que nadie toque nada. No está fallando el potenciómetro ni la placa: así se comporta cualquier ADC frente al ruido eléctrico, las variaciones de la fuente y la interferencia de los componentes vecinos.

En un ejemplo de escritorio eso da lo mismo. En un panel de control que dispara una alarma, mueve un motor o toma una decisión, un valor inestable no es feo: es incorrecto, y hace que el sistema actúe cuando no corresponde.

La solución se arma en tres capas de software y una de hardware.

Capa 1. Sobremuestreo

C++
#define ADC_SAMPLES 32

long sum = 0;
for (int i = 0; i < ADC_SAMPLES; i++) {
  sum += analogRead(ADC_INPUT_1);
}
float sample = sum / (float)ADC_SAMPLES;

En vez de confiar en una sola lectura, se toman 32 y se promedian. El ruido aleatorio se cancela solo al promediar: la desviación estándar del promedio baja con la raíz cuadrada del número de muestras, así que 32 lecturas reducen el ruido a la sexta parte aproximadamente. Cuadruplicar las muestras solo lo mejora al doble, por eso subir de 32 a 128 rinde poco y cuesta tiempo de CPU que LVGL necesita para refrescar.

Capa 2. Media móvil exponencial

C++
#define EMA_ALPHA 0.9f

if (!ema_initialized) {
  ema_value = sample;
  ema_initialized = true;
} else {
  ema_value = EMA_ALPHA * sample + (1.0f - EMA_ALPHA) * ema_value;
}

Un filtro pasabajos de una línea: cada valor nuevo se mezcla con el histórico acumulado.

Acá hay un detalle que confunde a casi todo el mundo, y vale la pena fijarlo antes de que te muerda. El alpha es el peso de la muestra nueva, no el del histórico. Con EMA_ALPHA en 0.9f, el 90 % del valor mostrado viene de la lectura recién tomada y solo el 10 % del pasado: es un filtrado muy suave, que responde rápido pero alisa poco.

Si quieres más estabilidad tienes que bajar ese número, no subirlo. Con 0.2f el valor se vuelve notoriamente más pegajoso; con 0.05f queda muy estable pero se nota un retardo al girar el potenciómetro. El rango útil para una lectura que una persona mira en pantalla suele estar entre 0.1f y 0.3f.

Capa 3. Histéresis

C++
const int threshold = 10;

if (abs(rounded_val - displayed_val) >= threshold) {
  displayed_val = rounded_val;
  lv_label_set_text_fmt(ui_lblPotValue, "%d", displayed_val);
}

La banda muerta resuelve un problema distinto de los dos anteriores: no es ruido de medición, es ruido visual. Aunque el valor filtrado esté razonablemente estable, si oscila entre 511 y 512 el número en pantalla parpadea y se lee mal. Con la histéresis, la etiqueta solo se actualiza cuando el cambio supera un umbral que vale la pena mostrar.

Un beneficio lateral que no es menor: cada lv_label_set_text_fmt() obliga a LVGL a redibujar esa zona de la pantalla. Actualizar solo cuando corresponde libera bastante tiempo de CPU.

Capa 4. El condensador

Agregando un condensador al potenciómetro para reducir el ruido de la señal analógica

Un condensador electrolítico conectado entre el cursor del potenciómetro y GND filtra en el origen: absorbe los picos de tensión antes de que lleguen al pin del ESP32.

Junto con la resistencia del potenciómetro forma un filtro RC, y la frecuencia de corte sale de f = 1 / (2π · R · C). Con un potenciómetro de 10 kΩ, la resistencia vista desde el cursor es como máximo 2,5 kΩ (el peor caso, con el cursor al medio). Un condensador de 10 µF te deja el corte en unos 6 Hz: suficiente para matar el ruido de red y el conmutado de la fuente, y lo bastante rápido para que el movimiento de la mano se siga viendo fluido.

Si pones un valor mucho mayor, el filtro es mejor pero el potenciómetro empieza a sentirse "con retardo". Si el ruido que ves es de alta frecuencia, un cerámico de 100 nF en paralelo lo complementa bien.

Filtrar solo por software funciona. Filtrar solo por hardware funciona. Hacer las dos cosas es lo que se hace en un producto de verdad.

Pantalla redonda ESP32-S3 mostrando lecturas de sensor estables mediante una interfaz LVGL

Proyecto final: monitoreo y control en cuatro pantallas

El proyecto completo junta todo: el sensor analógico se lee en GPIO17 y se muestra en una pantalla, mientras que un LED conectado a GPIO18 se controla desde otra, todo por tacto.

Interfaz LVGL de varias pantallas diseñada en SquareLine Studio para la pantalla redonda del ESP32-S3

En SquareLine Studio se agrega una cuarta pantalla con un botón asociado a una función de callback. en el proyecto original se llama BtnFunLED— que conmuta el estado del pin y actualiza la etiqueta a ON u OFF.

Una corrección al montaje del original que conviene hacer: pon una resistencia de 220 Ω en serie con el LED. El tutorial lo conecta directo al GPIO, y aunque el LED encienda, sin limitar la corriente estás exigiéndole al pin más de lo que debe entregar y acortando la vida de ambos. Son treinta pesos de componente.

El LED es un sustituto didáctico. En una aplicación real ese mismo GPIO maneja la entrada de un módulo relé, la compuerta de un MOSFET que mueve un motor, o un solenoide. El código del botón no cambia: lo único que cambia es qué hay al otro lado del pin.

Cuando algo no funciona

Casi todos los problemas de un primer proyecto LVGL caen en esta tabla. La columna de la izquierda es lo que ves; la de la derecha, dónde mirar antes de empezar a cambiar código al azar.

Síntoma Dónde está el problema
lv_conf.h: No such file or directory El archivo quedó dentro de la carpeta lvgl en vez de al lado, o sigue con #if 0 en la cabecera.
Compila y sube, pero la pantalla queda negra Revisa primero la PSRAM en OPI y el esquema de particiones. Si están bien, el driver del panel no está inicializando: prueba el ejemplo del fabricante para confirmar que el hardware responde.
Los rojos se ven azules, los blancos con tinte LV_COLOR_16_SWAP en ui.c tiene el valor contrario al que necesita tu driver. Cambia entre 0 y 1. Se pierde en cada exportación.
Errores de compilación con funciones lv_* que "deberían existir" Versión de LVGL distinta de la que usaste en SquareLine Studio. Ambas deben ser 8.3.11.
El táctil responde desplazado respecto a donde tocas Rotación u offsets mal configurados en el proyecto de SquareLine, o el controlador táctil necesita calibración. Verifica que la rotación sea 0 en ambos lados.
Se reinicia solo al arrancar Falta PSRAM o el buffer de dibujo pedido es demasiado grande. Un buffer de un décimo de la pantalla alcanza de sobra.
Elementos que diseñaste en las esquinas no se ven Falta marcar Circle en la forma de la pantalla: el editor te dejó ubicar widgets fuera del área visible del panel redondo.
La interfaz se mueve a saltos Algo está bloqueando el bucle. Revisa que no haya delay() largos ni lecturas de sensor pesadas entre llamadas a lv_timer_handler().

Variantes y mejoras

Reemplaza el potenciómetro por un sensor real. Toda la cadena de filtrado que armaste funciona igual con un sensor de humedad de suelo, un divisor con LDR o un sensor de corriente ACS712. El único ajuste es el mapeo del valor crudo del ADC a la unidad que quieras mostrar.

Agrega WiFi y convierte la pantalla en un panel remoto. El ESP32-S3 tiene WiFi sin usar en este proyecto. Con un cliente MQTT, la misma interfaz puede mostrar datos que llegan de otros nodos de la casa en vez de su propio sensor, y el botón de la cuarta pantalla puede publicar un comando en lugar de mover un GPIO local. La interfaz no cambia ni un píxel.

Guarda los ajustes entre reinicios. Hoy el estado del LED se pierde al cortar la alimentación. Con la librería Preferences del ESP32. tres líneas. el valor queda en la memoria NVS y la pantalla arranca mostrando el último estado, que es lo que espera cualquiera que use el aparato.

Un gráfico en vez de un número. LVGL incluye el widget lv_chart, que SquareLine Studio expone directamente en el editor. Cambiar la etiqueta del valor por un gráfico de línea con los últimos 60 segundos de lectura convierte el proyecto de una demostración en algo que sirve para diagnosticar.

Personalización para Chile

Estos son los componentes que necesitas, con los nombres que usa el catálogo de MechatronicStore:

  • Placa ESP32-S3 con pantalla redonda 1.28 pulgadas SPI. es el corazón del proyecto. Si no encuentras exactamente el modelo redondo, cualquier placa ESP32-S3 con TFT SPI integrado sirve: lo único que cambia son la resolución y la forma en los ajustes de SquareLine Studio, y el resto del flujo es idéntico. Eso sí, tiene que ser ESP32-S3 con PSRAM: un ESP32 clásico sin PSRAM no tiene memoria para el buffer de LVGL.
  • Potenciómetro 10kΩ. lineal, de eje. Es la entrada analógica del ejemplo.
  • LED 5mm. de cualquier color, para la demostración de salida.
  • Resistencia 220Ω. para limitar la corriente del LED. El tutorial original la omite; agrégala.
  • Condensador electrolítico 10uF. el filtro de hardware del potenciómetro.
  • Protoboard 830 puntos. para el montaje sin soldar.
  • Jumpers macho hembra 20cm. para llegar desde la placa a la protoboard.
  • Cable USB-C a USB-A. para programar y alimentar.

El stock y los precios rotan, así que revisa la disponibilidad actual de cada ítem en el catálogo antes de armar tu compra. SquareLine Studio tiene una versión gratuita con límite de widgets que alcanza de sobra para este proyecto, y LVGL es software libre.

Fuera de la placa con pantalla, todo lo demás son componentes de cajón que probablemente ya tengas: el costo real del proyecto está casi completo en el módulo ESP32-S3.

Recursos

Versión chilena inspirada en el tutorial de Electronic Clinic, con el archivo de configuración lv_conf.h obtenido desde la plantilla oficial de LVGL en lugar de una descarga de pago, y con la explicación del intercambio de bytes RGB565 que el original no incluye.