Lopaka Basics 03: agora em cores

A mesma ideia dos dois primeiros episódios, agora em cores: um painel de sala de estar em um display colorido, montado em poucos minutos.
Neste episódio, você vai descobrir como uma tela colorida faz as cores dela, o que RGB565 significa e por que uma única imagem em tela cheia não cabe em um Arduino UNO. Depois você vai montar o painel no Lopaka com cartões arredondados, texto colorido, ícones tingidos e uma imagenzinha colorida, e executar ele em um ESP32.
O que você vai construir: um painel colorido de 320×240 com um título e o horário, três cartões arredondados para temperatura, umidade e qualidade do ar e uma linha sobre o clima lá fora. Do que você precisa: um navegador e o simulador online do episódio 1. Uma placa real é opcional.
Como uma tela colorida funciona
Um pixel é três luzinhas
No OLED dos dois últimos episódios, um pixel está aceso ou apagado. Em um display colorido, cada pixel é feito de três luzinhas: vermelha, verde e azul. Cada uma pode brilhar com qualquer intensidade, e a uma distância normal de visualização seu olho mistura elas em uma cor.
Misture elas, e você obtém qualquer cor. Vermelho e verde juntos fazem amarelo. As três na potência máxima fazem branco. As três apagadas é preto.
![]()
RGB565: uma cor em dois bytes
Seu computador armazena uma cor em 24 bits: 8 para vermelho, 8 para verde e 8 para azul, cerca de 16,7 milhões de cores. Essa é a cor que você digita como #FF8200 em uma página web ou no Lopaka: dois dígitos hexadecimais por luz.
Chips pequenos e seus displays geralmente usam 16 bits, em um formato chamado RGB565:
- 5 bits para vermelho,
- 6 bits para verde,
- 5 bits para azul.
O verde recebe o bit extra porque nossos olhos são mais sensíveis a ele. Isso dá 65.536 cores, e cada pixel ocupa exatamente dois bytes.

Por que uma imagem não cabe em um UNO
Agora a matemática. Uma tela de 320×240 tem 320 × 240 = 76.800 pixels. A dois bytes cada, uma imagem em tela cheia ocupa 153.600 bytes, cerca de 150 KB.
Um Arduino UNO tem 32 KB de flash para o programa inteiro. Uma imagem em tela cheia é quase cinco vezes isso. Então, em telas coloridas, você desenha com formas e texto e mantém as imagens pequenas.

Etapa 1: um novo projeto colorido
Clique em + New Project e abra a lista Platform. Para este episódio, escolha AdafruitGFX Color: Adafruit GFX é uma das bibliotecas gráficas mais populares para displays coloridos. O Lopaka descreve ela como "the shared drawing layer under most Adafruit display drivers": você combina ela com o driver do seu painel exato, como Adafruit_ILI9341, as cores são empacotadas como RGB565 e o desenho vai direto para o painel.
Depois preencha o restante da caixa de diálogo:
- Title: "Color Weather".
- Screen Size: 320×240.
- Background Color:
#0F1B2D, um azul escuro.
Clique em Salvar.

O Lopaka converte cada cor para RGB565
O painel de código já tem uma linha:
tft.fillScreen(0x8C5);fillScreen pinta o display inteiro de uma cor, e 0x8C5 é o azul escuro em RGB565. O Lopaka faz a conversão para cada cor que você escolhe. À mão, funciona assim: mantenha os 5 bits superiores do vermelho, os 6 bits superiores do verde e os 5 bits superiores do azul, e escreva eles um depois do outro.
| Hex | Bits superiores | |
|---|---|---|
| red | 0x0F | 00001 |
| green | 0x1B | 000110 |
| blue | 0x2D | 00101 |
Juntos, isso é 0000100011000101, ou 0x08C5. O Lopaka escreve ele sem o zero à esquerda.
Etapa 2: três cartões arredondados
Escolha a ferramenta retângulo e arraste um cartão na tela de desenho. No inspetor, defina R como 12 para arredondar os cantos, ative Filled e digite #FF8200 em Fill color para laranja. Uma linha aparece no código:
tft.fillRoundRect(10, 56, 96, 140, 12, 0xFC00);Os números são x, y, largura, altura, raio do canto e cor. Uma forma é uma linha de código, por maior que seja: este cartão abrange 96 × 140 pixels, e a linha que desenha ele ocupa poucos bytes de flash.
Repita para os outros dois cartões: azul #2D7FF9 e verde #22B573. Os três têm 96 pixels de largura e 140 de altura, em y 56, e começam em x 10, 112 e 214. Isso deixa uma margem de 10 pixels dos dois lados da tela e 6 pixels entre os cartões: 10 + 3 × 96 + 2 × 6 + 10 = 320.

Etapa 3: texto em cores
O texto funciona como nos dois primeiros episódios, e agora ele pode ter qualquer cor. Escolha a ferramenta de texto (Aa): uma nova camada de texto aparece na tela de desenho. Digite o texto no inspetor, escolha uma fonte e defina Fill color. Um texto novo começa na cor que você usou por último, então confira a cor toda vez.
O título e o horário
Living roomem FreeSansBold12pt7b, branco#FFFFFF, no canto superior esquerdo.07:45em FreeSans12pt7b, cinza-azulado claro#9FB3C8, no canto superior direito.
tft.setTextColor(0xFFFF);
tft.setTextWrap(false);
tft.setFont(&FreeSansBold12pt7b);
tft.setCursor(12, 35);
tft.print("Living room");setTextColor define a cor para cada texto depois dele, então o Lopaka só escreve ele de novo quando a cor muda. setTextWrap(false) mantém um texto longo em uma linha, em vez de quebrar ele na borda da tela.
Com fontes FreeSans, o y de setCursor é a linha de base: a última linha de pixels das letras. No inspetor, o título tem Y 36, a linha sob aquela linha (as réguas contam as linhas entre os pixels, como no episódio 1), então o código diz 35.
Um rótulo pequeno e um número grande em cada cartão
Cada cartão recebe um rótulo pequeno na fonte clássica e um número grande em uma FreeSans em negrito:
- Os rótulos
TEMP,HUMIDeAIRusam Adafruit 5x7, a fonte clássica integrada à biblioteca, com Size definido como2. - Os números
23C,48%eOKusam FreeSansBold18pt7b.
Os seis textos são brancos. Coloque cada rótulo perto do topo do cartão dele e cada número perto da parte inferior.

A fonte clássica pode ser ampliada duas ou três vezes: Size 2 transforma cada pixel de uma letra em um bloco de 2×2. O código para o rótulo e o número:
tft.setTextSize(2);
tft.setFont();
tft.setCursor(20, 72);
tft.print("TEMP");
tft.setTextSize(1);
tft.setFont(&FreeSansBold18pt7b);
tft.setCursor(20, 179);
tft.print("23C");setFont() sem nada entre os parênteses volta para a fonte clássica. A fonte clássica conta o y de outro jeito: setCursor recebe o canto superior esquerdo do texto, e não a linha de base. As letras do rótulo têm 7 pixels de altura, 14 no tamanho 2, então o 86 do inspetor vira 86 − 14 = 72 no código. O Lopaka faz essa conversão para você. Antes do número FreeSans, ele redefine o tamanho como 1, para a fonte grande não ser dobrada.
O texto diz 23C, e não 23°C: as fontes FreeSans contêm só os caracteres ASCII básicos, e o sinal de grau não é um deles.
Fontes custam flash só quando você usa elas
FreeSans e as outras fontes da lista vêm com a biblioteca Adafruit GFX. Cada fonte é um arquivo separado, e ela ocupa flash só quando a sua tela usa ela. O Lopaka adiciona um #include para cada fonte que o design usa, aqui três:
#include "FreeSans12pt7b.h"
#include "FreeSansBold12pt7b.h"
#include "FreeSansBold18pt7b.h"Etapa 4: ícones tingidos
Abra a guia Imagens, busque um ícone e clique nele para adicionar ele à tela. Adicione três:
- um termômetro (busque "thermo"),
- uma gota (busque "drop"),
- uma folha (busque "leaf").
Volte para Camadas e coloque cada ícone sob o rótulo do cartão dele.
Ícones da biblioteca têm uma cor, então você pode tingir eles para combinar com o seu design. Selecione cada ícone e defina o Fill color dele como o azul escuro do fundo, #0F1B2D. Nos cartões claros, os ícones agora parecem recortados.
![]()
No código, um ícone de uma cor vira drawBitmap:
tft.drawBitmap(20, 104, image_streamline_weather_temperature_thermometer__weather_png_bits, 21, 21, 0x8C5);Os argumentos são a posição, o array de bits, a largura e a altura e a cor. O array armazena um bit por pixel, como no OLED do episódio 2: o termômetro tem 21×21 pixels, 3 bytes por linha, 63 bytes no total. A cor é um número separado no código, então o mesmo ícone pode ser desenhado em qualquer cor sem uma segunda cópia.
Etapa 5: uma imagem colorida
Envie ela como RGB
Imagens também podem ser totalmente coloridas. Na guia Imagens, clique em Upload e escolha uma imagem, do mesmo jeito que no episódio 2. Desta vez, mude o conversor de Monochrome para RGB.

O contador no topo mostra o custo. No tamanho completo dela, de 240×240 pixels, esta imagem ocuparia 240 × 240 × 2 = 115.200 bytes, mais de três vezes a flash inteira de um UNO.
Encolha ela para 40×40
Defina Dimensions como W 40 e H 40. Agora a imagem ocupa 40 × 40 × 2 = 3.200 bytes, cerca de três kilobytes. Esse é o ponto ideal para imagens coloridas: detalhes pequenos e coloridos. Clique em Import.
No código, a imagem vira um array de 1.600 números RGB565, um por pixel, e uma linha que desenha ela:
tft.drawRGBBitmap(12, 197, image_sun_cloud_pixels, 40, 40);Diferente de drawBitmap, esta chamada não recebe uma cor: cada pixel traz a sua própria.
A linha de baixo
Solte a imagem no canto inferior esquerdo e adicione mais uma linha de texto ao lado dela: Outside: 18C, sunny em Adafruit 5x7, Size 2, no mesmo cinza-azulado #9FB3C8 do horário.
Etapa 6: pegue o código
Um cartão, três dialetos
Cada biblioteca fala seu próprio dialeto. O mesmo cartão arredondado é drawRBox no U8g2, fillRoundRect no Adafruit GFX e um objeto estilizado no LVGL. O Lopaka escreve o da plataforma que você escolheu.

Copie e baixe os arquivos de fonte
Clique em Copiar acima do código. Sob os interruptores ao lado do código, o Lopaka lista as Fonts e Images que o design usa. Clique em cada um dos três arquivos de fonte para baixar ele e coloque os arquivos ao lado do seu sketch. A imagem e os ícones já estão dentro do código copiado como arrays, então você não precisa dos arquivos de imagem.

Se você esquecer os arquivos de fonte, o compilador para com fatal error: FreeSans12pt7b.h: No such file or directory.
O Lopaka gera este código para o painel. O array da imagem tem 1.600 números, então ele está encurtado aqui; Copiar entrega ele inteiro para você.
// [BEGIN lopaka generated]
#include "FreeSans12pt7b.h"
#include "FreeSansBold12pt7b.h"
#include "FreeSansBold18pt7b.h"
static const unsigned char PROGMEM image_drop_bits[] = {0x10,0x38,0x7c,0x7c,0xfe,0xde,0xde,0xde,0x7c,0x38};
static const unsigned char PROGMEM image_streamline_ecology_leaf_bug__ecology_png_bits[] = {0x00,0x00,0x30,0x00,0x00,0x48,0x00,0x00,0x88,0x00,0x3f,0x08,0x03,0xe0,0x08,0x0c,0x00,0x08,0x10,0xe0,0x08,0x21,0x70,0x88,0x41,0xd1,0x08,0x40,0xe2,0x08,0x80,0x0c,0x08,0x80,0x30,0x10,0x80,0xc0,0x10,0x83,0x00,0x20,0x84,0x00,0x20,0x88,0x00,0x40,0x50,0x00,0x80,0x20,0x03,0x00,0x50,0x0c,0x00,0x8f,0xf0,0x00,0x80,0x00,0x00};
static const unsigned char PROGMEM image_streamline_weather_temperature_thermometer__weather_png_bits[] = {0x03,0x00,0x00,0x04,0x80,0x00,0x04,0x80,0x00,0x04,0x87,0x80,0x04,0x80,0x00,0x04,0x80,0x00,0x04,0x80,0x00,0x04,0x87,0x80,0x04,0x80,0x00,0x04,0x80,0x00,0x07,0x80,0x00,0x07,0x87,0x80,0x07,0x80,0x00,0x07,0x80,0x00,0x0f,0xc0,0x00,0x1f,0x60,0x00,0x1f,0xa0,0x00,0x1f,0xa0,0x00,0x1f,0xe0,0x00,0x0f,0xc0,0x00,0x07,0x80,0x00};
static const uint16_t PROGMEM image_sun_cloud_pixels[] = {/* 1,600 RGB565 values, shortened here */};
void drawScreen_1(void) {
tft.fillScreen(0x8C5);
// rect 1
tft.fillRoundRect(10, 56, 96, 140, 12, 0xFC00);
// rect 2
tft.fillRoundRect(112, 56, 96, 140, 12, 0x2BFF);
// rect 3
tft.fillRoundRect(214, 56, 96, 140, 12, 0x25AE);
// string 4
tft.setTextColor(0xFFFF);
tft.setTextWrap(false);
tft.setFont(&FreeSansBold12pt7b);
tft.setCursor(12, 35);
tft.print("Living room");
// string 5
tft.setTextColor(0x9D99);
tft.setFont(&FreeSans12pt7b);
tft.setCursor(246, 35);
tft.print("07:45");
// string 6
tft.setTextColor(0xFFFF);
tft.setTextSize(2);
tft.setFont();
tft.setCursor(20, 72);
tft.print("TEMP");
// string 7
tft.setTextSize(1);
tft.setFont(&FreeSansBold18pt7b);
tft.setCursor(20, 179);
tft.print("23C");
// string 8
tft.setTextSize(2);
tft.setFont();
tft.setCursor(122, 72);
tft.print("HUMID");
// string 9
tft.setTextSize(1);
tft.setFont(&FreeSansBold18pt7b);
tft.setCursor(122, 179);
tft.print("48%");
// string 10
tft.setTextSize(2);
tft.setFont();
tft.setCursor(224, 72);
tft.print("AIR");
// string 11
tft.setTextSize(1);
tft.setFont(&FreeSansBold18pt7b);
tft.setCursor(224, 179);
tft.print("OK");
// streamline-weather-temperature-thermometer--weather.png
tft.drawBitmap(20, 104, image_streamline_weather_temperature_thermometer__weather_png_bits, 21, 21, 0x8C5);
// drop
tft.drawBitmap(122, 104, image_drop_bits, 7, 10, 0x8C5);
// streamline-ecology-leaf-bug--ecology.png
tft.drawBitmap(224, 104, image_streamline_ecology_leaf_bug__ecology_png_bits, 21, 21, 0x8C5);
// sun_cloud
tft.drawRGBBitmap(12, 197, image_sun_cloud_pixels, 40, 40);
// string 16
tft.setTextColor(0x9D99);
tft.setTextSize(2);
tft.setFont();
tft.setCursor(62, 211);
tft.print("Outside: 18C, sunny");
}
// [END lopaka generated]Etapa 7: execute ele em um ESP32
O sketch
Desta vez, executamos a tela em um ESP32, uma placa mais rápida, popular para telas coloridas; uma placa ESP32 típica tem 4 MB de flash. O display é um módulo ILI9341 de 320×240 em SPI.
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
// ILI9341 320x240 color display on SPI
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST 4
Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);
// Paste the Lopaka code here
void setup() {
tft.begin();
tft.setRotation(1); // landscape, 320x240
drawScreen_1();
}
void loop() {
}Adafruit_ILI9341 tft(...)cria o objeto do display e diz para ele quais pinos ele usa para chip select, data/command e reset. O código do Lopaka chama ele detft, então mantenha esse nome.- O ILI9341 é um painel retrato, com 240 de largura e 320 de altura.
setRotation(1)gira ele para paisagem, 320×240, o tamanho que criamos. drawScreen_1()executa uma vez, emsetup(). A tela mantém a imagem dela até algo desenhar por cima, entãoloop()fica vazio por enquanto.
Ligue ele no simulador
No simulador do episódio 1, comece um novo projeto ESP32 e adicione um display ILI9341. Conecte ele:
- VCC e LED (o backlight) ao 3V3, GND ao GND,
- CS ao 15, D/C ao 2, RST ao 4,
- MOSI ao 23, SCK ao 18, MISO ao 19.
Adicione as bibliotecas Adafruit ILI9341 e Adafruit GFX Library, cole o sketch com o código do Lopaka no lugar dele, adicione os três arquivos de fonte ao projeto e pressione Play. O painel aparece no display simulado.

Sem buffer de quadro
Lembra do buffer de quadro do episódio 1, a imagem inteira mantida na RAM antes de ir para o display? Em 320×240 em cores, ele precisaria dos mesmos 150 KB de uma imagem em tela cheia. Bibliotecas coloridas como Adafruit GFX pulam ele e desenham direto no display: cada fillRoundRect e print envia os pixels dele por SPI na hora.
Isso mantém o uso de RAM minúsculo. Compilado para o ESP32, o sketch usa 23% do espaço de programa e 7% da RAM. O mesmo sketch compilado para um Arduino UNO:
Sketch uses 25946 bytes (80%) of program storage space. Maximum is 32256 bytes.
Global variables use 317 bytes (15%) of dynamic memory, leaving 1731 bytes for local variables. Maximum is 2048 bytes.Só 15% da RAM do UNO. A flash é a parte apertada: 80%, e as três fontes e a imagem de 3.200 bytes são uma fatia grande dela.
Em uma placa real
- Tensão do sinal. O ESP32 trabalha em 3,3 V, como o ILI9341. Muitos módulos ILI9341 de baixo custo não têm conversor de nível, então em uma placa de 5 V como o UNO confira o anúncio do seu módulo antes de ligar ele.
- O controlador. Alguns módulos de 320×240 usam o controlador ST7789. Para esses, inclua
Adafruit_ST7789.h, crie o display comoAdafruit_ST7789 tft(TFT_CS, TFT_DC, TFT_RST);e inicie ele comtft.init(240, 320);em vez detft.begin();. O código do Lopaka continua o mesmo.
Etapa 8: ideias da galeria
Precisa de ideias? A Projects Gallery tem milhares de projetos públicos. Abra um, pressione Fork, e o Lopaka copia ele para os seus projetos, pronto para mudar.
Recapitulação
- Três luzes fazem cada cor: cada pixel mistura vermelho, verde e azul.
- RGB565 empacota elas em dois bytes: 5 bits de vermelho, 6 de verde, 5 de azul, e o Lopaka converte cada cor para você.
- Formas vencem imagens grandes: um cartão é uma linha de código, uma imagem em tela cheia tem 150 KB, então mantenha as imagens pequenas.
Próximo episódio: Live Data. Fazemos esses números mudarem sozinhos. Leia o episódio 4 ou assista no YouTube.
Pronto para testar? Abra o Lopaka e monte seu próprio painel colorido.