Lopaka Basics 02: texto, fontes e ícones

Um relógio, o clima e uma imagem da lua, tudo em uma tela do tamanho de um selo postal. Texto, fontes, ícones e imagens tornam uma telinha útil, e em um microcontrolador cada um deles custa memória.
Neste episódio, você vai descobrir o que é uma fonte em um microcontrolador, onde ela fica armazenada e quanto espaço ela ocupa. Depois você vai montar um pequeno painel matinal no Lopaka com duas telas, uma barra de status com texto invertido e sua própria imagem transformada em pontos, e executar ele em um Arduino UNO simulado.
O que você vai construir: um painel OLED de 128×64 com duas telas. Morning mostra o horário, a data, ícones de Wi-Fi e bateria e uma barra de clima. Night mostra uma imagem da lua e o horário. Do que você precisa: um navegador e o projeto do simulador do episódio 1. Uma placa real é opcional.
Uma fonte é um conjunto de imagens
Uma imagem por caractere
Um microcontrolador não sabe o que são letras. Para mostrar "07:45", a biblioteca gráfica precisa de uma imagenzinha para cada caractere: uma para 0, uma para 7, uma para : e assim por diante. Uma fonte em um microcontrolador é um conjunto de imagenzinhas, uma para cada caractere que ela consegue desenhar.
Texto maior, imagens maiores
Cada imagem é uma grade de pixels. Uma fonte maior significa grades maiores: o mesmo dígito 2 tem 5×9 pixels em uma fonte pequena e 12×18 em uma grande. Cada uma dessas imagens precisa ser armazenada no chip, então uma fonte maior ocupa mais memória.

Dois tipos de memória
A flash é a estante, a RAM é a mesa
Um microcontrolador tem dois tipos de memória:
- Flash guarda seu programa, suas fontes e suas imagens. Ela funciona como uma estante: tudo de que o programa pode precisar fica guardado ali, e continua ali quando a energia acaba.
- RAM é a mesinha onde o trabalho acontece: os valores com os quais o programa está trabalhando agora.
Em um Arduino UNO, a estante guarda 32 KB e a mesa, só 2 KB.
Quanto custa uma fonte
Uma fonte pequena ocupa cerca de um kilobyte de flash, uma grande de dois a quatro. Medimos isso compilando um sketch para um UNO com um texto em uma única fonte e comparando com um sketch sem texto: 4x6 adicionou 1,1 KB, profont22 2,1 KB, profont29 2,5 KB e timR24 3,7 KB.

Cada fonte é armazenada uma vez, por mais textos que a usem. A regra prática: duas ou três fontes por tela. Para ver quais fontes o seu design usa, procure as linhas setFont no painel de código do Lopaka: cada nome de fonte diferente ali é outra fonte na estante.
Etapa 1: um novo projeto
O mesmo começo do episódio 1: clique em + New Project, escolha a plataforma U8g2, mantenha o tamanho da tela em 128×64 e dê um título ao projeto, "Morning Dashboard". Clique em Salvar.
Etapa 2: texto
O horário em uma fonte grande
Escolha a ferramenta de texto (Aa), clique na tela de desenho e selecione a nova camada string 1. No inspetor, substitua o texto por 07:45, abra a lista Font e escolha profont29, uma fonte fácil de ler do outro lado da sala.
Clique no botão de alinhamento centralizar horizontalmente no topo do inspetor e defina Y como 40. O código agora mostra:
u8g2.setFont(u8g2_font_profont29_tr);
u8g2.drawStr(24, 40, "07:45");
Os dois números podem ser conferidos à mão. Cada caractere da profont29 tem 16 pixels de largura, então "07:45" tem 5 × 16 = 80 pixels de largura, e (128 − 80) / 2 = 24 coloca ele no meio. O 40 é a linha de base, a linha sobre a qual os dígitos ficam, como no episódio 1: os dígitos preenchem as linhas acima dela.
A data em uma fonte pequena
Adicione outro texto, FRI 24 OCT. Um texto novo começa na última fonte que você usou, então mude ela para a pequena 5x8. Centralize ele horizontalmente e defina Y como 11. Dez caracteres de 5 pixels fazem 50 pixels, o que dá drawStr(39, 11, "FRI 24 OCT").
Sua própria fonte
Quer um visual diferente? O primeiro item da lista Font, Import font..., recebe um arquivo TrueType (.ttf; .otf, .woff e .bdf também funcionam), e o Lopaka transforma ele em pixels na altura que você escolher. No vídeo, importamos Press Start 2P, uma fonte pixel gratuita, e definimos Font Height como 8. A pré-visualização mostra a fonte inteira: 95 imagenzinhas, cada caractere imprimível, do espaço ao ~.

Clique em Import, e a fonte fica pronta para usar. O texto selecionado muda para ela, e o código ganha uma linha extra, #include "Press_Start_2P_8.h". Esse cabeçalho guarda os dados da fonte. Um botão com o mesmo nome aparece sob o código: baixe o arquivo e coloque ele ao lado do seu sketch, ou o sketch não vai compilar.
O painel mantém as fontes integradas. Se você testou a importação no horário, volte ele para profont29.
Etapa 3: ícones
Pesquise e adicione
O Lopaka tem mais de dois mil ícones integrados. Abra a guia Imagens à esquerda, digite no campo de busca e clique em um ícone para adicionar ele à tela. Adicione três:
wifi_25(busque "wifi"),battery_30(busque "battery"),weather_sun(busque "sun").
Os três estão no conjunto Gaai.
Coloque cada ícone no lugar dele
Arraste os ícones para o lugar na tela de desenho: Wi-Fi no canto superior esquerdo, a bateria no canto superior direito, o sol no canto inferior esquerdo.
Um ícone tem 32 bytes
Um ícone também é uma imagenzinha. O sol tem 15 pixels de largura e 16 de altura, e em uma tela de uma cor cada pixel é um bit: aceso ou apagado. Oito pixels cabem em um byte. Uma linha de 15 pixels precisa de dois bytes, e o último bit fica sem uso, então o ícone inteiro tem 2 bytes × 16 linhas = 32 bytes.
![]()
No código, o ícone vira esta lista de números. A biblioteca lê eles e desenha os pixels:
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 recebe a posição, a largura e a altura da imagem e o array. Neste formato, chamado XBM, o primeiro pixel de uma linha é o bit mais baixo do byte. É por isso que a linha destacada se lê 00010000 da esquerda para a direita, mas é armazenada como 0x08.
Os outros dois ícones têm 19×16 e 24×16 pixels. Os dois precisam de três bytes por linha, 48 bytes cada.
Etapa 4: texto invertido
Uma barra branca com texto escuro
Para uma barra de status, desenhe um retângulo preenchido e coloque texto por cima:
- Escolha a ferramenta retângulo, arraste um retângulo na tela de desenho e defina X
0, Y46, W128, H18. Ative Filled. A barra agora cobre as 18 linhas inferiores da tela, as linhas de 46 a 63. - O retângulo novo é a camada superior e esconde o sol. Pressione Ctrl + ↓ (Cmd + ↓ em um Mac) para mover ele uma camada para baixo, sob
weather_sun. - Selecione
weather_sune ative Inverted (XOR). - Adicione um texto
Sunny 23C(com dois espaços), escolha a fonte6x10, defina X28e Y59e ative Inverted (XOR).
O sol e o texto atravessam a barra branca em preto.

XOR: onde as formas se sobrepõem, os pixels invertem
Inverted usa um truque chamado XOR. Onde uma forma nova se sobrepõe a algo já desenhado, os pixels invertem: um pixel aceso sob a forma apaga, um apagado acende. Em uma tela de uma cor, é assim que você obtém texto escuro em uma barra clara.

No código, isso é uma linha antes do 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) muda o U8g2 para o modo XOR, e o modo continua ativo até o código mudar ele de novo. É por isso que o texto depois do sol não precisa de uma segunda chamada. A ordem das camadas importa aqui: a barra precisa ser desenhada primeiro, para o sol e o texto terem algo para inverter.
Etapa 5: sua própria imagem
Envie uma imagem
Você também pode trazer suas próprias imagens. Na guia Imagens, clique em Upload e escolha um arquivo de imagem. O Lopaka abre uma caixa de diálogo de importação que converte ela para o display. Usamos uma imagem fotográfica da lua.
Defina Dimensions como W 48 e H 48 e ative Invert palette: a lua é clara em um fundo escuro, e isso torna a lua os pixels acesos no OLED.
O dithering transforma cinza em pontos
Esta tela não tem cinza. Cada pixel é preto ou branco. A conversão mais simples corta na metade do brilho: pixels mais brilhantes acendem, mais escuros apagam. Na lua, isso mantém só uma mancha branca, e todo o sombreamento se perde.
Dithering resolve isso. Ele mistura pontos pretos e brancos, mais densos onde a imagem é mais brilhante, então de longe seu olho vê tons de cinza.

Escolha um estilo na lista Dithering: Floyd-Steinberg, Atkinson, Bayer 8x8 ou No dithering. Mantivemos Floyd-Steinberg. Clique em Import.

Em 48 por 48 pixels, a imagem ocupa 48 / 8 × 48 = 288 bytes.
Uma segunda tela para a noite
Vamos dar à lua uma tela só dela. Um projeto pode ter quantas telas você precisar.
- Clique duas vezes no nome da tela acima da tela de desenho e renomeie
Screen 1paraMorning. - Clique em Adicionar tela no painel Camadas e renomeie a nova tela para
Night.
Coloque a lua na tela Night em X 6, Y 8 e adicione dois textos:
Good nightem6x10em X62, Y26,22:30emprofont22em X62, Y50.

Cada tela vira sua própria função no código: drawMorning() e drawNight(), com o nome das telas. O painel de código tem uma guia para cada tela. Seu sketch decide qual mostrar, e quando.
Etapa 6: PROGMEM
Uma configuração que vale conhecer: Declare as PROGMEM, na lista de interruptores ao lado do código. Ela vem ativada por padrão.

Em uma placa AVR como o UNO, um array constante é copiado para a RAM quando o programa começa, a menos que seja marcado para a flash. U8X8_PROGMEM mantém os dados da imagem na flash, na estante, para não lotar a mesinha da RAM, e drawXBMP (com o P) lê eles de lá. Com o interruptor desligado, o Lopaka escreve drawXBM e os arrays sem U8X8_PROGMEM.
A diferença para este painel: as quatro imagens ocupam 416 bytes. Compilado para um UNO, o sketch usa 78% da RAM com PROGMEM e 98% sem ele. Sem ele, sobram só 32 bytes para tudo o que o programa faz enquanto executa, e o sketch pode travar ou se comportar de forma estranha.

Etapa 7: execute ele
O código
Clique em Copiar na guia Morning, cole ele no seu sketch e depois faça o mesmo na guia Night. O Copiar pega o código da guia aberta. Este é o código que o Lopaka gera para as duas telas:
// [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};
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 nas duas funções, mas é a mesma fonte e é armazenada na flash uma vez. O design inteiro usa quatro fontes: profont29, 5x8, 6x10 e profont22.
O sketch
Comece pelo sketch do episódio passado e mude loop() para ele mostrar cada tela por três 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() conta os milissegundos desde que a placa começou. Dividido por 3000, ele cresce um a cada três segundos, e % 2 transforma isso em 0, 1, 0, 1. Em 0, o sketch desenha Morning; em 1, Night. Como no episódio 1, as duas funções desenham entre clearBuffer() e sendBuffer().
Execute ele no simulador
Abra o projeto do simulador do episódio 1: um Arduino UNO, um OLED SSD1306 ligado a GND, 5V, A5 (SCL) e A4 (SDA) e a biblioteca U8g2. Substitua o sketch pelo de cima, cole o código do Lopaka no lugar dele e pressione Play. O display alterna entre as duas telas a cada três segundos.

Conferimos as duas telas pixel por pixel: a tela de desenho do Lopaka, a tela desenhada pela biblioteca U8g2 e o display simulado são idênticos.
Quanta memória tudo isso ocupa
Tudo isso, com quatro fontes, três ícones e uma imagem, cabe em menos da metade da flash do UNO. O 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.A maior parte dos 1.600 bytes de RAM é o buffer de quadro de 1.024 bytes do episódio 1. Para este sketch, isso não é problema.
Recapitulação
- Fontes e ícones são imagens: cada caractere de uma fonte e cada ícone é uma pequena grade de pixels, um bit por pixel.
- Imagens custam memória: uma fonte ocupa de um a quatro kilobytes de flash, então use duas ou três por tela e mantenha as imagens na flash com PROGMEM.
- O dithering transforma cinza em pontos: escolha um estilo de dithering quando importar uma foto, e a tela de uma cor mostra tons.
Próximo episódio: Going Colour. O mesmo tipo de interface em um display TFT colorido, e o que RGB565 significa. Leia o episódio 3 ou assista no YouTube.
Pronto para testar? Abra o Lopaka e monte seu próprio painel.