Lopaka Basics 02: texto, fuentes e íconos

Un reloj, el clima y una imagen de la luna, todo en una pantalla del tamaño de un sello postal. El texto, las fuentes, los íconos y las imágenes hacen útil una pantalla pequeña, y en un microcontrolador cada uno de ellos cuesta memoria.
En este episodio descubrirás qué es una fuente en un microcontrolador, dónde se guarda y cuánto espacio ocupa. Después crearás un pequeño tablero matutino en Lopaka con dos pantallas, una barra de estado con texto invertido y tu propia imagen convertida en puntos, y lo ejecutarás en un Arduino UNO simulado.
Lo que vas a crear: un tablero OLED de 128×64 con dos pantallas. Morning muestra la hora, la fecha, íconos de Wi-Fi y batería y una barra de clima. Night muestra una imagen de la luna y la hora. Lo que necesitas: un navegador y el proyecto de simulador del episodio 1. Una placa real es opcional.
Una fuente es un conjunto de imágenes
Una imagen por carácter
Un microcontrolador no sabe qué son las letras. Para mostrar «07:45», la biblioteca de gráficos necesita una imagen pequeña para cada carácter: una para 0, una para 7, una para : y así sucesivamente. Una fuente en un microcontrolador es un conjunto de imágenes diminutas, una para cada carácter que puede dibujar.
Texto más grande, imágenes más grandes
Cada imagen es una cuadrícula de píxeles. Una fuente más grande significa cuadrículas más grandes: el mismo dígito 2 mide 5×9 píxeles en una fuente pequeña y 12×18 en una grande. Cada una de esas imágenes tiene que guardarse en el chip, así que una fuente más grande ocupa más memoria.

Dos tipos de memoria
Flash es la estantería, RAM es el escritorio
Un microcontrolador tiene dos tipos de memoria:
- Flash guarda tu programa, fuentes e imágenes. Funciona como una estantería: todo lo que el programa podría necesitar se guarda ahí y permanece ahí cuando se corta la energía.
- RAM es el escritorio pequeño donde ocurre el trabajo: los valores con los que el programa está trabajando ahora mismo.
En un Arduino UNO la estantería guarda 32 KB y el escritorio solo 2 KB.
Cuánto cuesta una fuente
Una fuente pequeña ocupa alrededor de un kilobyte de flash, una grande de dos a cuatro. Lo medimos compilando un sketch para un UNO con un texto en una sola fuente y comparándolo con un sketch sin texto: 4x6 añadió 1,1 KB, profont22 2,1 KB, profont29 2,5 KB y timR24 3,7 KB.

Cada fuente se guarda una vez, sin importar cuántos textos la usen. La regla práctica: dos o tres fuentes por pantalla. Para ver qué fuentes usa tu diseño, busca las líneas setFont en el panel de código de Lopaka: cada nombre de fuente distinto ahí es otra fuente en la estantería.
Paso 1: un proyecto nuevo
El mismo inicio que en el episodio 1: haz clic en + New Project, elige la plataforma U8g2, mantén el tamaño de pantalla en 128×64 y dale al proyecto un título, «Morning Dashboard». Haz clic en Save.
Paso 2: texto
La hora en una fuente grande
Elige la herramienta text (Aa), haz clic en el lienzo y selecciona la capa nueva string 1. En el inspector, sustituye el texto por 07:45, abre la lista Font y elige profont29, una fuente fácil de leer desde el otro lado de la habitación.
Haz clic en el botón de alineación centre horizontally en la parte superior del inspector y pon Y en 40. El código ahora dice:
u8g2.setFont(u8g2_font_profont29_tr);
u8g2.drawStr(24, 40, "07:45");
Ambos números pueden comprobarse a mano. Cada carácter de profont29 tiene 16 píxeles de ancho, así que «07:45» mide 5 × 16 = 80 píxeles de ancho, y (128 − 80) / 2 = 24 lo pone en el centro. El 40 es la línea base, la línea sobre la que se apoyan los dígitos, como en el episodio 1: los dígitos llenan las filas sobre ella.
La fecha en una fuente pequeña
Añade otro texto, FRI 24 OCT. Un texto nuevo empieza con la última fuente que usaste, así que cámbialo a la pequeña 5x8. Céntralo horizontalmente y pon Y en 11. Diez caracteres de 5 píxeles hacen 50 píxeles, lo que da drawStr(39, 11, "FRI 24 OCT").
Tu propia fuente
¿Quieres un aspecto distinto? El primer elemento de la lista Font, Import font..., acepta un archivo TrueType (.ttf; .otf, .woff y .bdf también funcionan) y Lopaka lo convierte en píxeles con la altura que elijas. En el video importamos Press Start 2P, una fuente de píxeles gratuita, y pusimos Font Height en 8. La vista previa muestra la fuente completa: 95 imágenes diminutas, cada carácter imprimible desde el espacio hasta ~.

Haz clic en Import y la fuente está lista para usar. El texto seleccionado cambia a ella y el código recibe una línea extra, #include "Press_Start_2P_8.h". Ese encabezado guarda los datos de la fuente. Debajo del código aparece un botón con el mismo nombre: descarga el archivo y ponlo junto a tu sketch, o el sketch no compilará.
El tablero mantiene las fuentes integradas. Si probaste la importación en la hora, devuélvela a profont29.
Paso 3: íconos
Busca y añade
Lopaka tiene más de dos mil íconos integrados. Abre la pestaña Images a la izquierda, escribe en el campo de búsqueda y haz clic en un ícono para añadirlo a la pantalla. Añade tres:
wifi_25(busca «wifi»),battery_30(busca «battery»),weather_sun(busca «sun»).
Los tres están en el conjunto Gaai.
Pon cada ícono en su sitio
Arrastra los íconos a su sitio en el lienzo: el Wi-Fi en la esquina superior izquierda, la batería en la esquina superior derecha y el sol en la esquina inferior izquierda.
Un ícono son 32 bytes
Un ícono también es una imagen diminuta. El sol mide 15 píxeles de ancho y 16 de alto, y en una pantalla de un color cada píxel es un bit: encendido o apagado. Ocho píxeles caben en un byte. Una fila de 15 píxeles necesita dos bytes y el último bit queda sin usar, así que todo el ícono son 2 bytes × 16 filas = 32 bytes.
![]()
En el código, el ícono se convierte en esta lista de números. La biblioteca los lee y dibuja los píxeles:
static const unsigned char image_weather_sun_bits[] U8X8_PROGMEM = {0x80,0x00,0x84,0x10,0x08,0x08,0xc0,0x01,0x31,0x46,0x12,0x24,0x08,0x08,0x08,0x08,0x08,0x08,0x12,0x24,0x31,0x46,0xc0,0x01,0x08,0x08,0x84,0x10,0x80,0x00,0x00,0x00};u8g2.drawXBMP(5, 47, 15, 16, image_weather_sun_bits);drawXBMP toma la posición, el ancho y el alto de la imagen y el array. En este formato, llamado XBM, el primer píxel de una fila es el bit más bajo del byte. Por eso la fila resaltada se lee 00010000 de izquierda a derecha pero se guarda como 0x08.
Los otros dos íconos miden 19×16 y 24×16 píxeles. Ambos necesitan tres bytes por fila, 48 bytes cada uno.
Paso 4: texto invertido
Una barra blanca con texto oscuro
Para una barra de estado, dibuja un rectángulo relleno y pon texto encima:
- Elige la herramienta rectangle, arrastra un rectángulo en el lienzo y pon X
0, Y46, W128, H18. Activa Filled. La barra ahora cubre las 18 filas inferiores de la pantalla, las filas 46 a 63. - El rectángulo nuevo es la capa superior y oculta el sol. Pulsa Ctrl + ↓ (Cmd + ↓ en Mac) para moverlo una capa hacia abajo, bajo
weather_sun. - Selecciona
weather_suny activa Inverted (XOR). - Añade un texto
Sunny 23C(con dos espacios), elige la fuente6x10, pon X28e Y59y activa Inverted (XOR).
El sol y el texto atraviesan la barra blanca en negro.

XOR: donde las formas se solapan, los píxeles se invierten
Invertido usa un truco llamado XOR. Donde una forma nueva se solapa con algo ya dibujado, los píxeles se invierten: un píxel encendido bajo la forma se apaga, uno apagado se enciende. En una pantalla de un color, así consigues texto oscuro sobre una barra clara.

En el código esto es una línea antes del sol:
// rect 6
u8g2.drawBox(0, 46, 128, 18);
// weather_sun
u8g2.setDrawColor(2);
u8g2.drawXBMP(5, 47, 15, 16, image_weather_sun_bits);
// string 7
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(28, 59, "Sunny 23C");setDrawColor(2) pone U8g2 en modo XOR y el modo permanece activo hasta que el código lo cambia otra vez. Por eso el texto tras el sol no necesita una segunda llamada. Aquí importa el orden de las capas: la barra tiene que dibujarse primero, para que el sol y el texto tengan algo que invertir.
Paso 5: tu propia imagen
Sube una imagen
También puedes traer tus propias imágenes. En la pestaña Images, haz clic en Upload y elige un archivo de imagen. Lopaka abre un diálogo de importación que la convierte para la pantalla. Usamos una imagen de la luna con aspecto de foto.
Pon Dimensions en W 48 y H 48 y activa Invert palette: la luna es clara sobre un fondo oscuro y esto hace que la luna sean los píxeles encendidos en el OLED.
El tramado convierte el gris en puntos
Esta pantalla no tiene gris. Cada píxel es negro o blanco. La conversión más sencilla corta a mitad de brillo: los píxeles más brillantes se encienden, los más oscuros se apagan. En la luna eso deja solo una mancha blanca y todo el sombreado se pierde.
El tramado lo arregla. Mezcla puntos negros y blancos, más densos donde la imagen es más brillante, para que desde la distancia tu ojo vea tonos de gris.

Elige un estilo en la lista Dithering: Floyd-Steinberg, Atkinson, Bayer 8x8 o No dithering. Mantuvimos Floyd-Steinberg. Haz clic en Import.

A 48 por 48 píxeles la imagen ocupa 48 / 8 × 48 = 288 bytes.
Una segunda pantalla para la noche
Démosle a la luna una pantalla propia. Un proyecto puede contener tantas pantallas como necesites.
- Haz doble clic en el nombre de la pantalla sobre el lienzo y renombra
Screen 1aMorning. - Haz clic en Add Screen en el panel Capas y renombra la pantalla nueva a
Night.
Pon la luna en la pantalla Night en X 6, Y 8 y añade dos textos:
Good nighten6x10en X62, Y26,22:30enprofont22en X62, Y50.

Cada pantalla se convierte en su propia función en el código: drawMorning() y drawNight(), con el nombre de las pantallas. El panel de código tiene una pestaña para cada pantalla. Tu sketch decide cuál mostrar y cuándo.
Paso 6: PROGMEM
Un ajuste que vale la pena conocer: Declare as PROGMEM, en la lista de interruptores junto al código. Está activado por defecto.

En una placa AVR como el UNO, un array constante se copia a la RAM cuando el programa empieza, a menos que esté marcado para flash. U8X8_PROGMEM mantiene los datos de imagen en flash, en la estantería, para que no abarroten el pequeño escritorio de RAM, y drawXBMP (con la P) los lee desde ahí. Con el interruptor desactivado, Lopaka escribe drawXBM y los arrays sin U8X8_PROGMEM.
La diferencia para este tablero: las cuatro imágenes ocupan 416 bytes. Compilado para un UNO, el sketch usa el 78% de la RAM con PROGMEM y el 98% sin él. Sin él solo quedan 32 bytes para todo lo que el programa hace mientras se ejecuta, y el sketch puede fallar o comportarse de forma extraña.

Paso 7: ejecútalo
El código
Haz clic en Copy en la pestaña Morning, pégalo en tu sketch y después haz lo mismo en la pestaña Night. Copy toma el código de la pestaña abierta. Este es el código que Lopaka genera para las dos pantallas:
// [BEGIN lopaka generated]
static const unsigned char image_battery_30_bits[] U8X8_PROGMEM = {0x00,0x00,0x00,0xf0,0xff,0x7f,0x08,0x00,0x80,0x08,0x00,0xaa,0x0e,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x01,0x00,0xaa,0x0e,0x00,0xaa,0x08,0x00,0xaa,0x08,0x00,0x80,0xf0,0xff,0x7f,0x00,0x00,0x00,0x00,0x00,0x00};
static const unsigned char image_weather_sun_bits[] U8X8_PROGMEM = {0x80,0x00,0x84,0x10,0x08,0x08,0xc0,0x01,0x31,0x46,0x12,0x24,0x08,0x08,0x08,0x08,0x08,0x08,0x12,0x24,0x31,0x46,0xc0,0x01,0x08,0x08,0x84,0x10,0x80,0x00,0x00,0x00};
static const unsigned char image_wifi_25_bits[] U8X8_PROGMEM = {0x80,0x0f,0x00,0x60,0x30,0x00,0x18,0xc0,0x00,0x84,0x0f,0x01,0x62,0x30,0x02,0x11,0x40,0x04,0x0a,0x87,0x02,0xc4,0x18,0x01,0x28,0xa0,0x00,0x10,0x47,0x00,0xa0,0x2f,0x00,0xc0,0x1d,0x00,0x80,0x0a,0x00,0x00,0x07,0x00,0x00,0x02,0x00,0x00,0x00,0x00};
void drawMorning(void) {
u8g2.setFontMode(1);
u8g2.setBitmapMode(1);
// string 1
u8g2.setFont(u8g2_font_profont29_tr);
u8g2.drawStr(24, 40, "07:45");
// string 2
u8g2.setFont(u8g2_font_5x8_tr);
u8g2.drawStr(39, 11, "FRI 24 OCT");
// wifi_25
u8g2.drawXBMP(2, 0, 19, 16, image_wifi_25_bits);
// battery_30
u8g2.drawXBMP(102, 0, 24, 16, image_battery_30_bits);
// rect 6
u8g2.drawBox(0, 46, 128, 18);
// weather_sun
u8g2.setDrawColor(2);
u8g2.drawXBMP(5, 47, 15, 16, image_weather_sun_bits);
// string 7
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(28, 59, "Sunny 23C");
}
// [END lopaka generated]
// [BEGIN lopaka generated]
static const unsigned char image_moon_bits[] U8X8_PROGMEM = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xd0,0x0a,0x00,0x00,0x00,0x00,0x7f,0x57,0x00,0x00,0x00,0xc0,0xee,0xad,0x00,0x00,0x00,0xf0,0xbf,0xbb,0x05,0x00,0x00,0xd8,0xfb,0x56,0x09,0x00,0x00,0xfc,0xbf,0xbf,0x12,0x00,0x00,0xfe,0xff,0xd5,0x06,0x00,0x00,0xdf,0x6f,0xbf,0x2a,0x00,0x80,0x7f,0xbb,0xd5,0x42,0x00,0xc0,0xdf,0x7f,0xbf,0x2a,0x00,0xc0,0xff,0xfe,0xd6,0x8a,0x00,0xe0,0xff,0xdf,0xbb,0x2a,0x00,0x60,0xff,0xff,0x6e,0x95,0x00,0xf0,0xff,0xbf,0xb7,0x4a,0x00,0xd0,0xff,0xef,0xdd,0x12,0x02,0xf0,0xff,0xff,0xb7,0x56,0x00,0xf0,0xff,0xbd,0x6e,0x09,0x01,0xf8,0xbf,0xef,0x5b,0x53,0x00,0xe8,0xfd,0xff,0xd6,0x14,0x10,0x78,0xff,0xdd,0xad,0x8a,0x00,0xe8,0x6f,0x77,0x5b,0x11,0x00,0xf8,0xff,0xdf,0x56,0x45,0x04,0xd0,0x5e,0x7d,0xab,0x10,0x00,0xf8,0xfb,0xd7,0x56,0x05,0x00,0x50,0xef,0xbe,0x55,0x41,0x00,0xf0,0xbd,0x6b,0x55,0x0a,0x04,0x50,0xf7,0xd6,0xaa,0x00,0x00,0xd0,0xad,0xbd,0x0a,0x25,0x00,0xa0,0x76,0xab,0x52,0x00,0x02,0x60,0xdb,0x56,0x25,0x01,0x00,0x40,0xad,0x55,0x49,0x08,0x00,0x80,0x6a,0x55,0x15,0x82,0x00,0x80,0x5a,0x55,0x82,0x00,0x00,0x00,0xa5,0x4a,0x29,0x00,0x00,0x00,0x2a,0x29,0x00,0x00,0x00,0x00,0x48,0x92,0x02,0x20,0x00,0x00,0xa4,0x04,0x08,0x04,0x00,0x00,0x00,0x51,0x80,0x00,0x00,0x00,0x20,0x04,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x20,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
void drawNight(void) {
u8g2.setFontMode(1);
u8g2.setBitmapMode(1);
// moon
u8g2.drawXBMP(6, 8, 48, 48, image_moon_bits);
// string 9
u8g2.setFont(u8g2_font_6x10_tr);
u8g2.drawStr(62, 26, "Good night");
// string 10
u8g2.setFont(u8g2_font_profont22_tr);
u8g2.drawStr(62, 50, "22:30");
}
// [END lopaka generated]6x10 aparece en ambas funciones, pero es la misma fuente y se guarda en flash una vez. Todo el diseño usa cuatro fuentes: profont29, 5x8, 6x10 y profont22.
El sketch
Empieza desde el sketch del episodio anterior y cambia loop() para que muestre cada pantalla durante tres segundos:
#include <U8g2lib.h>
#include <Wire.h>
// 128x64 OLED display on I2C
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
// Paste the Lopaka code here
void setup() {
u8g2.begin();
}
void loop() {
u8g2.clearBuffer();
// show each screen for 3 seconds
if ((millis() / 3000) % 2 == 0) drawMorning();
else drawNight();
u8g2.sendBuffer();
}millis() cuenta los milisegundos desde que la placa arrancó. Dividido entre 3000, crece en uno cada tres segundos, y % 2 lo convierte en 0, 1, 0, 1. En 0 el sketch dibuja Morning, en 1 Night. Como en el episodio 1, ambas funciones dibujan entre clearBuffer() y sendBuffer().
Ejecútalo en el simulador
Abre el proyecto de simulador del episodio 1: un Arduino UNO, un OLED SSD1306 cableado a GND, 5V, A5 (SCL) y A4 (SDA), y la biblioteca U8g2. Sustituye el sketch por el de arriba, pega el código de Lopaka en su sitio y pulsa Play. La pantalla cambia entre las dos pantallas cada tres segundos.

Comprobamos ambas pantallas píxel a píxel: el lienzo de Lopaka, la pantalla dibujada por la biblioteca U8g2 y la pantalla simulada son idénticos.
Cuánta memoria ocupa todo
Todo, con cuatro fuentes, tres íconos y una imagen, cabe en menos de la mitad de la flash del UNO. El compilador informa:
Sketch uses 15394 bytes (47%) of program storage space. Maximum is 32256 bytes.
Global variables use 1600 bytes (78%) of dynamic memory, leaving 448 bytes for local variables. Maximum is 2048 bytes.La mayor parte de los 1.600 bytes de RAM es el búfer de fotogramas de 1.024 bytes del episodio 1. Para este sketch está bien.
Resumen
- Las fuentes y los íconos son imágenes: cada carácter de una fuente y cada ícono es una pequeña cuadrícula de píxeles, un bit por píxel.
- Las imágenes cuestan memoria: una fuente ocupa de uno a cuatro kilobytes de flash, así que usa dos o tres por pantalla y mantén las imágenes en flash con PROGMEM.
- El tramado convierte el gris en puntos: elige un estilo de tramado al importar una foto y la pantalla de un color mostrará tonos.
Próximo episodio: Going Colour. El mismo tipo de interfaz en una pantalla TFT en color, y qué significa RGB565. Lee el episodio 3 o míralo en YouTube.
¿Listo para probar? Abre Lopaka y crea tu propio tablero.