Lopaka Basics 04: dados ao vivo

Duas telas. A mesma placa, o mesmo sensor, os mesmos números. Mas uma delas pisca toda vez que um número muda. Desacelere, e você consegue ver por quê: a imagem inteira é apagada e depois pintada de volta, pedaço por pedaço.
Neste episódio, você vai descobrir o que loop() faz, por que um display colorido pisca quando um OLED não pisca, e as três regras que param isso. Depois você vai transformar os números de um design do Lopaka em variáveis, alimentar eles com um sensor de temperatura e umidade e executar em um ESP32 simulado sem piscar nenhuma vez.
O que você vai construir: um monitor de ambiente colorido de 320×240 com números de temperatura e umidade ao vivo e uma barra de umidade, atualizado duas vezes por segundo. Do que você precisa: um navegador. Executamos o código em um ESP32 simulado com um display ILI9341 e um sensor DHT22; uma placa real é opcional.
setup() e loop()
Todo sketch do Arduino tem duas partes:
setup()executa uma vez, ao ligar.loop()executa depois dela, de novo e de novo, enquanto a placa tiver energia.
Dados ao vivo moram em loop(): leia o sensor, transforme os números em texto, desenhe eles e repita. Cada volta desenha a tela de novo.

Por que uma tela colorida pisca
O OLED desenha na memória primeiro
O OLED dos primeiros episódios nunca piscou. A biblioteca dele, U8g2, desenha na memória primeiro: um buffer de quadro de um kilobyte para 128 × 64 pixels, como você viu no episódio 1. Só a imagem pronta vai para o display. O modo por páginas faz o mesmo em oito faixas de 128 bytes, uma depois da outra.

Um quadro colorido precisaria de 150 KB
Uma tela colorida de 320×240 armazena dois bytes por pixel, então um quadro ocupa 320 × 240 × 2 = 153.600 bytes, cerca de 150 KB. Isso é quase metade da RAM que um ESP32 dá ao seu sketch. Então o TFT_eSPI pinta direto no display, como o Adafruit GFX no episódio 3.

Você vê cada etapa
Quando a biblioteca pinta direto no display, você vê cada etapa do desenho. Se o sketch limpa a tela no começo de cada volta, o display fica preto primeiro, e a imagem volta pedaço por pedaço.

No nosso teste (ESP32 no simulador, SPI a 27 MHz), limpar a tela de 320×240 levava 48 milissegundos e desenhar o painel, mais 68. Quase 50 milissegundos de preto em cada atualização é o suficiente para seu olho perceber.

Três regras contra piscar
- Não limpe a tela dentro de
loop(). Nada defillScreen()em cada volta. - Dê a cada número ao vivo uma cor de fundo. Sem ela, os dígitos novos são desenhados por cima dos antigos e se acumulam em uma bagunça. Com ela, cada dígito é desenhado em uma caixa da cor de fundo que apaga o que estava sob ele.
- Mantenha outras formas fora de sob um número ao vivo. Um cartão preenchido sob o número seria pintado por cima dele em cada volta, e o número piscaria. Um cartão contornado não toca os pixels atrás dos dígitos.
O Lopaka cuida das duas primeiras.

Etapa 1: crie o monitor de ambiente
Um novo projeto TFT_eSPI
Clique em + New Project e escolha a plataforma TFT_eSPI, M5, Lovyan. TFT_eSPI é uma das bibliotecas gráficas mais comuns para displays coloridos no ESP32. Defina o Title como "Room Monitor" e o Screen Size como 320×240, mantenha a Background Color preta e clique em Salvar.

Desenhe o painel
Desenhe a tela do mesmo jeito que da última vez: um título, dois cartões contornados, dois rótulos, dois números grandes, um sinal de grau, uma barra para umidade, uma legenda e uma marca LIVE vermelha. Estes são os valores que usamos:
| Camada | Ferramenta | Valores no inspetor | Fonte | Cor |
|---|---|---|---|---|
Living room | text | X 12, Y 30 | FreeSansBold12pt7b | #FFFFFF |
| temperature card | rectangle | X 12, Y 46, W 142, H 120, R 10 | #FF8200 | |
| humidity card | rectangle | X 166, Y 46, W 142, H 120, R 10 | #4C8DFF | |
Temperature | text | X 24, Y 72 | FreeSans9pt7b | #FF8200 |
Humidity | text | X 178, Y 72 | FreeSans9pt7b | #4C8DFF |
23.5 | text | X 24, Y 140 | FreeSansBold24pt7b | #FFFFFF |
48% | text | X 178, Y 140 | FreeSansBold24pt7b | #FFFFFF |
C | text | X 132, Y 122 | FreeSansBold12pt7b | #FFFFFF |
| degree sign | ellipse | X 124, Y 104, RX 3, RY 3 | #FFFFFF | |
| bar | rectangle, Filled | X 12, Y 182, W 142, H 18 | #4C8DFF | |
| empty part of the bar | rectangle, Filled | X 154, Y 182, W 154, H 18 | #1F2937 | |
Updated every 0.5 s | text | X 12, Y 228 | FreeSans9pt7b | #6B7280 |
| LIVE dot | ellipse, Filled | X 260, Y 16, RX 4, RY 4 | #EF4444 | |
LIVE | text | X 272, Y 27 | FreeSans9pt7b | #EF4444 |
O primeiro texto em um projeto TFT_eSPI começa na fonte pequena Adafruit 5x7, e cada texto depois dele começa na última fonte que você escolheu.
Os cartões são contornos, seguindo a regra 3. A barra são dois retângulos lado a lado, a parte preenchida e a parte vazia, então nenhum pixel da barra é pintado duas vezes. Se você desenhasse a barra escura inteira e a parte azul por cima dela, a parte azul piscaria em cada volta.
O Y de um texto
Olhe o número grande da temperatura no código:
tft.drawString("23.5", 24, 104);O inspetor diz Y 140, o código diz 104. Como no U8g2, o Y do inspetor é a linha de base, a linha sobre a qual os dígitos ficam. O drawString do TFT_eSPI recebe o topo do texto, então o Lopaka converte o número para você. As cores também são convertidas: #FF8200 vira 0xFC00 em RGB565, o formato de cor de dois bytes do episódio 3.
Agora mesmo, esses números são texto simples. Eles nunca vão mudar.
Etapa 2: camadas viram variáveis
Nomeie a camada
Para tornar um número ao vivo, transforme ele em uma variável. Primeiro, nomeie a camada: clique duas vezes em string 6, o texto 23.5, no painel Camadas e renomeie ela para temp.
Clique no botão de variável
Selecione a camada e clique no botão de variável na extremidade direita do campo de texto dela no inspetor (a dica dele diz Add variable). O botão fica laranja, e duas coisas mudam no código:
const char* temp_text = "23.5";// temp
tft.drawString(temp_text, 24, 104);O Lopaka declara uma variável no topo do código, com o nome da camada, com o texto como valor inicial dela. A chamada de desenho agora usa a variável em vez do texto fixo. Seu sketch pode colocar qualquer texto em temp_text, e a próxima chamada da função de desenho mostra ele.

Umidade e a barra
Faça o mesmo para a umidade e dê tamanhos variáveis à barra:
- Renomeie
string 7, o texto48%, parahume clique no botão de variável ao lado do texto dela:hum_text. - Renomeie a barra preenchida para
hum_bare clique no botão de variável ao lado de W:hum_bar_w. - Renomeie a parte vazia da barra para
hum_reste clique nos botões de variável ao lado de X e W:hum_rest_xehum_rest_w.
A barra recebe uma largura variável, e a parte vazia dela, uma posição e largura variáveis, então o sketch continua pintando cada pixel da barra uma vez, qualquer que seja a umidade.
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";// hum_bar
tft.fillRect(12, 182, hum_bar_w, 18, 0x4C7F);
// hum_rest
tft.fillRect(hum_rest_x, 182, hum_rest_w, 18, 0x1946);Um texto vira um const char*, um número como uma largura vira um int. O nome da camada e a propriedade fazem o nome da variável: hum_bar + w.

Etapa 3: nada de piscar
Um fundo atrás de cada número
Selecione temp e defina a cor Back dela como #000000, preta como a tela. Faça o mesmo para hum. O código agora define duas cores para o texto, os dígitos e a caixa atrás deles:
// temp
tft.setTextColor(0xFFFF, 0x0);
tft.setFreeFont(&FreeSansBold24pt7b);
tft.drawString(temp_text, 24, 104);
// hum
tft.drawString(hum_text, 178, 104);0xFFFF é branco, 0x0 é preto. O número da umidade usa as mesmas cores, então o Lopaka não repete a chamada. Essa é a regra 2.

Desative Clear/Fill display
Agora a regra 1. Na lista de interruptores ao lado do código, encontre Clear/Fill display. Em um projeto TFT_eSPI novo, ele está ativado, e a função de desenho começa preenchendo a tela inteira:
void drawScreen_1(void) {
tft.fillScreen(0x0);Essa única linha causa o piscar: a função executa em cada volta de loop(), então a tela fica preta em cada volta. Desative Clear/Fill display, e a linha some.

Desative Include fonts
Mais um interruptor: desative Include fonts. O TFT_eSPI já traz essas FreeFonts com ele, então o sketch não precisa de arquivos de fonte.
Etapa 4: seu próprio loop
Escreva ao redor do bloco gerado
O resto são algumas linhas suas. Você pode escrever o sketch inteiro aqui no painel de código. O Lopaka mantém seu código ao redor do bloco gerado dele, entre // [BEGIN lopaka generated] e // [END lopaka generated], e ele continua quando você muda o design ou recarrega a página.
Clique acima da linha BEGIN e digite os includes e os objetos:
#include <TFT_eSPI.h>
#include <DHTesp.h>
TFT_eSPI tft;
DHTesp dht;
char t[8], h[8];Depois, abaixo da linha END, adicione setup() e loop():
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);
}
O que setup() faz
setup() inicia o display, gira ele para paisagem com setRotation(1) e pinta ele de preto uma vez, depois inicia o sensor no pino 27. O único fillScreen aqui não é problema: ele executa ao ligar, e não em cada volta.
setTextPadding(94) dá a cada número ao vivo uma caixa fixa de 94 pixels de largura. O TFT_eSPI preenche o fundo do texto só com a largura do texto novo. Quando "23.5" muda para um valor mais estreito como "9.8", a extremidade direita dos dígitos antigos continuaria na tela. 94 pixels é a largura de um valor de dois dígitos como "48%" nesta fonte, e a caixa para antes do sinal de grau.
O que loop() faz
loop() lê temperatura e umidade, escreve elas nas variáveis de texto, transforma a umidade na largura da barra e chama a função de desenho. Depois ele espera meio segundo e recomeça:
snprintftransforma um número em texto:"%.1f"mantém uma casa decimal, então 23,46 vira "23.5", e"%.0f%%"dá "48%".%%imprime o sinal de porcentagem.temp_text = t;aponta a variável para aquele texto. Os bufferstehsão declarados fora deloop(), então o texto ainda está lá quando a função de desenho lê ele.- A barra tem 296 pixels em 100%, de x = 12 a 308, então um por cento é 2,96 pixels. A parte vazia começa onde a parte preenchida termina e ocupa o resto.
delay(500)dá duas atualizações por segundo.
Etapa 5: execute ele
No simulador
Clique em Copiar acima do código. Ele copia o painel inteiro, com o seu código incluído. No simulador do episódio 1:
- Crie um novo projeto ESP32.
- Adicione um display ILI9341 e conecte ele: VCC e LED ao 3V3, GND ao GND, CS ao 15, RST ao 2, D/C ao 0, MOSI ao 13, SCK ao 14, MISO ao 12.
- Adicione um sensor DHT22: VCC ao 3V3, GND ao GND, SDA ao 27.
- No Library Manager, adicione TFT_eSPI e DHT sensor library for ESPx (a biblioteca com
DHTesp.h). - Cole o código em
sketch.inoe pressione Play.
O TFT_eSPI lê os pinos dele de um arquivo de configurações na pasta da biblioteca. O simulador usa as configurações padrão da biblioteca: um ILI9341 nesses pinos do ESP32.
Clique no DHT22 e arraste os controles deslizantes dele. Os números e a barra acompanham os controles sem piscar nenhuma vez.

O código completo do botão Copiar, com o bloco do Lopaka no meio:
#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 um ESP32, o sketch usa 24% do espaço de programa e 6% da RAM.
O mesmo sketch com Clear/Fill display
A tela dividida abaixo executa o mesmo sketch duas vezes: à esquerda com Clear/Fill display ativado, à direita o nosso. A única diferença é a linha tft.fillScreen(0x0); no começo de drawScreen_1(). A tela da esquerda é capturada no meio de um redesenho, com só o primeiro dígito de volta. À direita, só os dígitos mudam.

Em uma placa real
Instale TFT_eSPI e DHT sensor library for ESPx pelo Library Manager na Arduino IDE, ligue o display e o sensor do mesmo jeito e envie. Coisas para conferir no hardware real:
- Configurações do TFT_eSPI. A biblioteca pega o driver do display e os pinos de
User_Setup.hna pasta dela. O padrão é um ILI9341 com os pinos acima. Para outro display ou outros pinos, mude aquele arquivo, e não o sketch. - Pinos de boot. Em um ESP32 real, GPIO 0 e GPIO 12 também decidem como a placa inicia. Se a placa não iniciar com o display conectado, mova D/C e MISO para outros pinos em
User_Setup.h. - O sensor. Um DHT22 dá uma nova leitura no máximo a cada dois segundos. A biblioteca retorna a última leitura no meio tempo, então a tela ainda redesenha duas vezes por segundo. Se os números mostrarem
nan, o sensor não respondeu: confira a fiação dele.
Recapitulação
loop()redesenha sua tela para sempre: leia, transforme em texto, desenhe, repita.- Nomeie uma camada, receba uma variável: o botão de variável transforma um texto ou um tamanho em uma variável com o nome da camada, e seu loop alimenta ela.
- Sem limpar mais um fundo significa sem piscar: desative Clear/Fill display e dê a cada número ao vivo uma cor Back.
Próximo episódio: From Browser to Board. Tiramos um design da tela e colocamos em uma placa real, e passamos por cinco motivos para um display novo ficar preto. Leia o episódio 5 ou assista no YouTube.
Pronto para testar? Abra o Lopaka e torne sua própria tela ao vivo.