Ir al contenido

Lopaka Basics 04: datos en vivo ​

Una pantalla ILI9341 de 320×240 mostrando un tablero Living room con un punto rojo LIVE, Temperature 36.3 °C, Humidity 67%, una barra azul de humedad y el texto Updated every 0.5 s

Dos pantallas. La misma placa, el mismo sensor, los mismos números. Pero una de ellas parpadea cada vez que un número cambia. Ralentízalo y puedes ver por qué: toda la imagen se borra y después se pinta de vuelta, pieza a pieza.

En este episodio descubrirás qué hace loop(), por qué parpadea una pantalla en color cuando un OLED no lo hace, y las tres reglas que lo detienen. Después convertirás los números de un diseño de Lopaka en variables, los alimentarás desde un sensor de temperatura y humedad y lo ejecutarás en un ESP32 simulado sin un solo parpadeo.

Lo que vas a crear: un monitor de habitación en color de 320×240 con números de temperatura y humedad en vivo y una barra de humedad, actualizado dos veces por segundo. Lo que necesitas: un navegador. Ejecutamos el código en un ESP32 simulado con una pantalla ILI9341 y un sensor DHT22; una placa real es opcional.

setup() y loop() ​

Cada sketch de Arduino tiene dos partes:

  • setup() se ejecuta una vez, al encender.
  • loop() se ejecuta después, una y otra vez, mientras la placa tenga energía.

Los datos en vivo viven en loop(): lee el sensor, convierte los números en texto, dibújalos y repite. Cada vuelta dibuja la pantalla otra vez.

setup() se ejecuta una vez, al encender; loop() es un anillo con tres estaciones, leer el sensor, crear texto y dibujar, y el contador de vueltas muestra 4

El OLED dibuja primero en memoria ​

El OLED de los primeros episodios nunca parpadeó. Su biblioteca, U8g2, dibuja primero en memoria: un búfer de fotogramas de un kilobyte para 128 × 64 píxeles, como viste en el episodio 1. Solo la imagen terminada va a la pantalla. El modo por páginas hace lo mismo en ocho tiras de 128 bytes, una tras otra.

La biblioteca de gráficos dibuja en un búfer de fotogramas de 1.024 bytes en RAM, 128 × 64 píxeles dividido entre 8, y el OLED recibe solo píxeles terminados

Un fotograma en color necesitaría 150 KB ​

Una pantalla en color de 320×240 guarda dos bytes por píxel, así que un fotograma ocupa 320 × 240 × 2 = 153.600 bytes, unos 150 KB. Eso es casi la mitad de la RAM que un ESP32 da a tu sketch. Así que TFT_eSPI pinta directo en la pantalla, como Adafruit GFX en el episodio 3.

Memoria para un fotograma: el fotograma OLED es una barra diminuta de 1 KB, el fotograma TFT en color es una barra larga, 320 × 240 × 2 bytes = 150 KB por fotograma

Ves cada paso ​

Cuando la biblioteca pinta directo en la pantalla, ves cada paso del dibujo. Si el sketch limpia la pantalla al inicio de cada vuelta, la pantalla se pone negra primero y la imagen vuelve pieza a pieza.

Cámara lenta de la pantalla que parpadea: la pantalla se borra a negro y el tablero se pinta de vuelta pieza a pieza, hasta ahora solo el título, los contornos de las tarjetas, los números 36.3 y 48%

En nuestra prueba (ESP32 en el simulador, SPI a 27 MHz), limpiar la pantalla de 320×240 llevó 48 milisegundos y dibujar el tablero otros 68. Casi 50 milisegundos de negro en cada actualización basta para que tu ojo lo note.

Sin búfer, directo a la pantalla: la pantalla está a medio repintar, la línea de tiempo de abajo muestra el borrado y el dibujo

Tres reglas contra el parpadeo ​

  1. No limpies la pantalla dentro de loop(). Nada de fillScreen() en cada vuelta.
  2. Dale a cada número en vivo un color de fondo. Sin él, los dígitos nuevos se dibujan sobre los viejos y se amontonan en un desastre. Con él, cada dígito se dibuja en una caja del color de fondo que borra lo que había debajo.
  3. Mantén otras formas fuera de debajo de un número en vivo. Una tarjeta rellena bajo el número se pintaría sobre él en cada vuelta y el número parpadearía. Una tarjeta con contorno no toca los píxeles tras los dígitos.

Lopaka se encarga de las dos primeras.

Las tres reglas: no limpies la pantalla dentro de loop(), fillScreen tachado; un fondo tras cada número en vivo, una caja negra borra los dígitos viejos; nada más bajo un número en vivo, una tarjeta naranja rellena está mal y una con contorno está bien

Paso 1: diseña el monitor de habitación ​

Un proyecto TFT_eSPI nuevo ​

Haz clic en + New Project y elige la plataforma TFT_eSPI, M5, Lovyan. TFT_eSPI es una de las bibliotecas de gráficos más comunes para pantallas en color en el ESP32. Pon el Title en «Room Monitor» y el Screen Size en 320×240, mantén el Background Color negro y haz clic en Save.

El diálogo Create New Project con Platform TFT_eSPI, M5, Lovyan, el título Room Monitor, Screen Size 320×240 y Background Color #000000

Dibuja el tablero ​

Dibuja la pantalla igual que la vez anterior: un título, dos tarjetas con contorno, dos etiquetas, dos números grandes, un símbolo de grado, una barra para la humedad, un pie y una marca roja LIVE. Estos son los valores que usamos:

LayerToolValues in the inspectorFontColour
Living roomtextX 12, Y 30FreeSansBold12pt7b#FFFFFF
temperature cardrectangleX 12, Y 46, W 142, H 120, R 10#FF8200
humidity cardrectangleX 166, Y 46, W 142, H 120, R 10#4C8DFF
TemperaturetextX 24, Y 72FreeSans9pt7b#FF8200
HumiditytextX 178, Y 72FreeSans9pt7b#4C8DFF
23.5textX 24, Y 140FreeSansBold24pt7b#FFFFFF
48%textX 178, Y 140FreeSansBold24pt7b#FFFFFF
CtextX 132, Y 122FreeSansBold12pt7b#FFFFFF
degree signellipseX 124, Y 104, RX 3, RY 3#FFFFFF
barrectangle, FilledX 12, Y 182, W 142, H 18#4C8DFF
empty part of the barrectangle, FilledX 154, Y 182, W 154, H 18#1F2937
Updated every 0.5 stextX 12, Y 228FreeSans9pt7b#6B7280
LIVE dotellipse, FilledX 260, Y 16, RX 4, RY 4#EF4444
LIVEtextX 272, Y 27FreeSans9pt7b#EF4444

El primer texto en un proyecto TFT_eSPI empieza con la fuente pequeña Adafruit 5x7 y cada texto posterior empieza con la última fuente que elegiste.

Las tarjetas son contornos, siguiendo la regla 3. La barra son dos rectángulos uno al lado del otro, la parte llena y la parte vacía, para que ningún píxel de la barra se pinte dos veces. Si dibujaras toda la barra oscura y la parte azul encima, la parte azul parpadearía en cada vuelta.

La Y de un texto ​

Mira el número grande de temperatura en el código:

cpp
tft.drawString("23.5", 24, 104);

El inspector dice Y 140, el código dice 104. Como en U8g2, la Y del inspector es la línea base, la línea sobre la que se apoyan los dígitos. El drawString de TFT_eSPI toma en su lugar la parte superior del texto, así que Lopaka convierte el número por ti. Los colores también se convierten: #FF8200 se convierte en 0xFC00 en RGB565, el formato de color de dos bytes del episodio 3.

Ahora mismo estos números son texto simple. Nunca cambiarán.

Paso 2: las capas se convierten en variables ​

Nombra la capa ​

Para hacer que un número esté en vivo, conviértelo en una variable. Primero, nombra la capa: haz doble clic en string 6, el texto 23.5, en el panel Capas y renómbrala a temp.

Haz clic en el botón de variable ​

Selecciona la capa y haz clic en el botón variable en el extremo derecho de su campo de texto en el inspector (su tooltip dice Add variable). El botón se vuelve naranja y dos cosas cambian en el código:

cpp
const char* temp_text = "23.5";
cpp
// temp
tft.drawString(temp_text, 24, 104);

Lopaka declara una variable al inicio del código, con el nombre de la capa y el texto como su valor inicial. La llamada de dibujo ahora usa la variable en lugar del texto fijo. Tu sketch puede poner cualquier texto en temp_text y la próxima llamada a la función de dibujo lo mostrará.

La capa temp seleccionada con el botón de variable junto a su campo de texto activado; el código empieza con const char* temp_text = "23.5"

Humedad y la barra ​

Haz lo mismo para la humedad y dale a la barra tamaños variables:

  1. Renombra string 7, el texto 48%, a hum y haz clic en el botón de variable junto a su texto: hum_text.
  2. Renombra la barra llena a hum_bar y haz clic en el botón de variable junto a W: hum_bar_w.
  3. Renombra la parte vacía de la barra a hum_rest y haz clic en los botones de variable junto a X y W: hum_rest_x y hum_rest_w.

La barra obtiene un ancho variable y su parte vacía una posición y un ancho variables, para que el sketch siga pintando cada píxel de la barra una vez, sea cual sea la humedad.

cpp
int hum_bar_w = 142;
int hum_rest_w = 154;
int hum_rest_x = 154;
const char* hum_text = "48%";
const char* temp_text = "23.5";
cpp
// hum_bar
tft.fillRect(12, 182, hum_bar_w, 18, 0x4C7F);
// hum_rest
tft.fillRect(hum_rest_x, 182, hum_rest_w, 18, 0x1946);

Un texto se convierte en un const char*, un número como un ancho se convierte en un int. El nombre de la capa y la propiedad forman el nombre de la variable: hum_bar + w.

La capa hum_rest seleccionada con los botones de variable activados para X y W; el código empieza con int hum_bar_w = 142, int hum_rest_w = 154, int hum_rest_x = 154 y las dos variables de texto

Un fondo tras cada número ​

Selecciona temp y pon su color Back en #000000, negro como la pantalla. Haz lo mismo para hum. El código ahora pone dos colores para el texto, los dígitos y la caja tras ellos:

cpp
// temp
tft.setTextColor(0xFFFF, 0x0);
tft.setFreeFont(&FreeSansBold24pt7b);
tft.drawString(temp_text, 24, 104);
// hum
tft.drawString(hum_text, 178, 104);

0xFFFF es blanco, 0x0 es negro. El número de humedad usa los mismos colores, así que Lopaka no repite la llamada. Esa es la regla 2.

La capa temp con Back en #000000 en el inspector; el código muestra tft.setTextColor(0xFFFF, 0x0) antes de drawString(temp_text, 24, 104)

Desactiva Clear/Fill display ​

Ahora la regla 1. En la lista de interruptores junto al código, encuentra Clear/Fill display. En un proyecto TFT_eSPI nuevo está activado y la función de dibujo empieza llenando toda la pantalla:

cpp
void drawScreen_1(void) {
    tft.fillScreen(0x0);

Esa sola línea causa el parpadeo: la función se ejecuta en cada vuelta de loop(), así que la pantalla se pone negra en cada vuelta. Desactiva Clear/Fill display y la línea desaparece.

El panel de código con Clear/Fill display activado: la función de dibujo empieza con tft.fillScreen(0x0); Include fonts está desactivado

Desactiva Include fonts ​

Un interruptor más: desactiva Include fonts. TFT_eSPI ya trae estas FreeFonts consigo, así que el sketch no necesita archivos de fuente.

Paso 4: tu propio loop ​

Escribe alrededor del bloque generado ​

El resto son unas líneas tuyas. Puedes escribir todo el sketch aquí mismo en el panel de código. Lopaka mantiene tu código alrededor de su bloque generado, entre // [BEGIN lopaka generated] y // [END lopaka generated], y permanece cuando cambias el diseño o recargas la página.

Haz clic sobre la línea BEGIN y escribe los includes y los objetos:

cpp
#include <TFT_eSPI.h>
#include <DHTesp.h>

TFT_eSPI tft;
DHTesp dht;
char t[8], h[8];

Después, bajo la línea END, añade setup() y loop():

cpp
void setup() {
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  tft.setTextPadding(94);
  dht.setup(27, DHTesp::DHT22);
}

void loop() {
  TempAndHumidity d = dht.getTempAndHumidity();
  snprintf(t, sizeof t, "%.1f", d.temperature);
  snprintf(h, sizeof h, "%.0f%%", d.humidity);
  temp_text = t;
  hum_text = h;
  hum_bar_w = d.humidity * 2.96;
  hum_rest_x = 12 + hum_bar_w;
  hum_rest_w = 296 - hum_bar_w;
  drawScreen_1();
  delay(500);
}

El panel de código tras el marcador END lopaka generated: void setup() con tft.begin, setRotation, fillScreen, setTextPadding(94) y dht.setup, después void loop() leyendo el sensor; Include fonts y Clear/Fill display están desactivados

Qué hace setup() ​

setup() arranca la pantalla, la gira a horizontal con setRotation(1) y la pinta de negro una vez, después arranca el sensor en el pin 27. El único fillScreen aquí está bien: se ejecuta al encender, no en cada vuelta.

setTextPadding(94) le da a cada número en vivo una caja fija de 94 píxeles de ancho. TFT_eSPI llena el fondo del texto solo con el ancho del texto nuevo. Cuando «23.5» cambia a un valor más estrecho como «9.8», el extremo derecho de los dígitos viejos quedaría en la pantalla. 94 píxeles es el ancho de un valor de dos dígitos como «48%» en esta fuente, y la caja se detiene antes del símbolo de grado.

Qué hace loop() ​

loop() lee temperatura y humedad, las escribe en las variables de texto, convierte la humedad en el ancho de la barra y llama a la función de dibujo. Después espera medio segundo y vuelve:

  • snprintf convierte un número en texto: "%.1f" mantiene un decimal, así que 23,46 se convierte en «23.5», y "%.0f%%" da «48%». %% imprime el símbolo de porcentaje.
  • temp_text = t; apunta la variable a ese texto. Los búferes t y h se declaran fuera de loop(), así que el texto sigue ahí cuando la función de dibujo lo lee.
  • La barra mide 296 píxeles al 100%, de x = 12 a 308, así que un uno por ciento son 2,96 píxeles. La parte vacía empieza donde termina la parte llena y ocupa el resto.
  • delay(500) da dos actualizaciones por segundo.

Paso 5: ejecútalo ​

En el simulador ​

Haz clic en Copy sobre el código. Copia todo el panel, tu código incluido. En el simulador del episodio 1:

  1. Crea un proyecto ESP32 nuevo.
  2. Añade una pantalla ILI9341 y conéctala: VCC y LED a 3V3, GND a GND, CS a 15, RST a 2, D/C a 0, MOSI a 13, SCK a 14, MISO a 12.
  3. Añade un sensor DHT22: VCC a 3V3, GND a GND, SDA a 27.
  4. En el Library Manager, añade TFT_eSPI y DHT sensor library for ESPx (la biblioteca con DHTesp.h).
  5. Pega el código en sketch.ino y pulsa Play.

TFT_eSPI lee sus pines de un archivo de ajustes en la carpeta de la biblioteca. El simulador usa los ajustes predeterminados de la biblioteca: un ILI9341 en estos pines de ESP32.

Haz clic en el DHT22 y arrastra sus sliders. Los números y la barra siguen a los sliders sin un solo parpadeo.

El simulador ejecutando el sketch: los sliders del DHT22 están en 18,2 °C y 31,0%, y el ILI9341 junto al ESP32 muestra Temperature 18.2 °C y Humidity 31% con una barra azul corta

El código completo del botón Copy, con el bloque de Lopaka en el centro:

cpp
#include <TFT_eSPI.h>
#include <DHTesp.h>

TFT_eSPI tft;
DHTesp dht;
char t[8], h[8];

// [BEGIN lopaka generated]
int hum_bar_w = 142;
int hum_rest_w = 154;
int hum_rest_x = 154;
const char* hum_text = "48%";
const char* temp_text = "23.5";
void drawScreen_1(void) {
    // string 1
    tft.setTextColor(0xFFFF);
    tft.setTextSize(1);
    tft.setFreeFont(&FreeSansBold12pt7b);
    tft.drawString("Living room", 12, 12);
    // rect 2
    tft.drawRoundRect(12, 46, 142, 120, 10, 0xFC00);
    // rect 3
    tft.drawRoundRect(166, 46, 142, 120, 10, 0x4C7F);
    // string 4
    tft.setTextColor(0xFC00);
    tft.setFreeFont(&FreeSans9pt7b);
    tft.drawString("Temperature", 24, 58);
    // string 5
    tft.setTextColor(0x4C7F);
    tft.drawString("Humidity", 178, 58);
    // temp
    tft.setTextColor(0xFFFF, 0x0);
    tft.setFreeFont(&FreeSansBold24pt7b);
    tft.drawString(temp_text, 24, 104);
    // hum
    tft.drawString(hum_text, 178, 104);
    // string 8
    tft.setTextColor(0xFFFF);
    tft.setFreeFont(&FreeSansBold12pt7b);
    tft.drawString("C", 132, 104);
    // ellipse 9
    tft.drawEllipse(127, 107, 3, 3, 0xFFFF);
    // hum_bar
    tft.fillRect(12, 182, hum_bar_w, 18, 0x4C7F);
    // hum_rest
    tft.fillRect(hum_rest_x, 182, hum_rest_w, 18, 0x1946);
    // string 12
    tft.setTextColor(0x6B90);
    tft.setFreeFont(&FreeSans9pt7b);
    tft.drawString("Updated every 0.5 s", 12, 214);
    // ellipse 13
    tft.fillEllipse(264, 20, 4, 4, 0xEA28);
    // string 14
    tft.setTextColor(0xEA28);
    tft.drawString("LIVE", 272, 13);

}
// [END lopaka generated]

void setup() {
  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  tft.setTextPadding(94);
  dht.setup(27, DHTesp::DHT22);
}

void loop() {
  TempAndHumidity d = dht.getTempAndHumidity();
  snprintf(t, sizeof t, "%.1f", d.temperature);
  snprintf(h, sizeof h, "%.0f%%", d.humidity);
  temp_text = t;
  hum_text = h;
  hum_bar_w = d.humidity * 2.96;
  hum_rest_x = 12 + hum_bar_w;
  hum_rest_w = 296 - hum_bar_w;
  drawScreen_1();
  delay(500);
}

Compilado para un ESP32, el sketch usa el 24% del espacio de programa y el 6% de la RAM.

El mismo sketch con Clear/Fill display ​

La pantalla dividida de abajo ejecuta el mismo sketch dos veces: a la izquierda con Clear/Fill display activado, a la derecha el nuestro. La única diferencia es la línea tft.fillScreen(0x0); al inicio de drawScreen_1(). La pantalla izquierda está capturada a mitad de un redibujo, con solo el primer dígito de vuelta. A la derecha, solo cambian los dígitos.

Dos pantallas simuladas una al lado de la otra: con Clear/Fill display activado, la pantalla izquierda está a mitad de redibujo con solo el dígito 3 visible; con Clear/Fill display desactivado, la pantalla derecha muestra 36.3 °C y 48%

En una placa real ​

Instala TFT_eSPI y DHT sensor library for ESPx desde el Library Manager en el IDE de Arduino, cablea la pantalla y el sensor igual y sube el código. Cosas que comprobar en hardware real:

  • Ajustes de TFT_eSPI. La biblioteca toma el controlador de pantalla y los pines de User_Setup.h en su propia carpeta. El predeterminado es un ILI9341 con los pines de arriba. Para otra pantalla u otros pines, cambia ese archivo, no el sketch.
  • Pines de arranque. En un ESP32 real, GPIO 0 y GPIO 12 también deciden cómo arranca la placa. Si la placa no arranca con la pantalla conectada, mueve D/C y MISO a otros pines en User_Setup.h.
  • El sensor. Un DHT22 da una lectura nueva como máximo cada dos segundos. La biblioteca devuelve la última lectura entre medias, así que la pantalla sigue redibujando dos veces por segundo. Si los números muestran nan, el sensor no respondió: comprueba su cableado.

Resumen ​

  1. loop() redibuja tu pantalla para siempre: lee, crea texto, dibuja, repite.
  2. Nombra una capa, obtén una variable: el botón de variable convierte un texto o un tamaño en una variable con el nombre de la capa y tu loop la alimenta.
  3. Sin borrado más un fondo significa sin parpadeo: desactiva Clear/Fill display y dale a cada número en vivo un color Back.

Próximo episodio: From Browser to Board. Sacamos un diseño de la pantalla y lo llevamos a una placa real, y repasamos cinco razones por las que una pantalla nueva se queda en negro. Lee el episodio 5 o míralo en YouTube.

¿Listo para probar? Abre Lopaka y haz que tu propia pantalla cobre vida.