Pular para o conteúdo

Lopaka Basics 04: dados ao vivo ​

Um display ILI9341 de 320×240 mostrando um painel Living room com um ponto LIVE vermelho, Temperature 36.3 °C, Humidity 67%, uma barra de umidade azul e a legenda Updated every 0.5 s

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.

setup() executa uma vez, ao ligar; loop() é um anel com três estações, ler o sensor, transformar em texto e desenhar, e o contador de voltas mostra 4

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.

A biblioteca gráfica desenha em um buffer de quadro de 1.024 bytes na RAM, 128 × 64 pixels divididos por 8, e o OLED recebe só pixels prontos

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.

Memória para um quadro: o quadro do OLED é uma barrinha de 1 KB, o quadro do TFT colorido é uma barra longa, 320 × 240 × 2 bytes = 150 KB por quadro

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.

Câmera lenta da tela piscando: o display é apagado para preto e o painel é pintado de volta pedaço por pedaço, até agora só o título, os contornos dos cartões, os números 36.3 e 48%

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.

Sem buffer, direto no display: a tela está repintada pela metade, a linha do tempo abaixo mostra a limpeza e o desenho

Três regras contra piscar ​

  1. Não limpe a tela dentro de loop(). Nada de fillScreen() em cada volta.
  2. 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.
  3. 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.

As três regras: não limpe a tela dentro de loop(), fillScreen riscado; um fundo atrás de cada número ao vivo, uma caixa preta apaga os dígitos antigos; nada mais sob um número ao vivo, um cartão laranja preenchido está errado e um contornado está certo

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.

A caixa de diálogo Create New Project com Platform TFT_eSPI, M5, Lovyan, o título Room Monitor, Screen Size 320×240 e Background Color #000000

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:

CamadaFerramentaValores no inspetorFonteCor
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

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:

cpp
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:

cpp
const char* temp_text = "23.5";
cpp
// 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.

A camada temp selecionada com o botão de variável ao lado do campo de texto dela ativado; o código começa com const char* temp_text = "23.5"

Umidade e a barra ​

Faça o mesmo para a umidade e dê tamanhos variáveis à barra:

  1. Renomeie string 7, o texto 48%, para hum e clique no botão de variável ao lado do texto dela: hum_text.
  2. Renomeie a barra preenchida para hum_bar e clique no botão de variável ao lado de W: hum_bar_w.
  3. Renomeie a parte vazia da barra para hum_rest e clique nos botões de variável ao lado de X e W: hum_rest_x e hum_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.

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);

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.

A camada hum_rest selecionada com os botões de variável ativados para X e W; o código começa com int hum_bar_w = 142, int hum_rest_w = 154, int hum_rest_x = 154 e as duas variáveis de texto

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:

cpp
// 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.

A camada temp com Back definido como #000000 no inspetor; o código mostra tft.setTextColor(0xFFFF, 0x0) antes de drawString(temp_text, 24, 104)

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:

cpp
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.

O painel de código com Clear/Fill display ativado: a função de desenho começa com tft.fillScreen(0x0); Include fonts está desativado

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:

cpp
#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():

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);
}

O painel de código depois do marcador END lopaka generated: void setup() com tft.begin, setRotation, fillScreen, setTextPadding(94) e dht.setup, depois void loop() lendo o sensor; Include fonts e Clear/Fill display estão desativados

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:

  • snprintf transforma 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 buffers t e h são declarados fora de loop(), 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:

  1. Crie um novo projeto ESP32.
  2. 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.
  3. Adicione um sensor DHT22: VCC ao 3V3, GND ao GND, SDA ao 27.
  4. No Library Manager, adicione TFT_eSPI e DHT sensor library for ESPx (a biblioteca com DHTesp.h).
  5. Cole o código em sketch.ino e 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 simulador executando o sketch: os controles do DHT22 estão definidos como 18,2 °C e 31,0%, e o ILI9341 ao lado do ESP32 mostra Temperature 18.2 °C e Humidity 31% com uma barra azul curta

O código completo do botão Copiar, com o bloco do Lopaka no meio:

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 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.

Dois displays simulados lado a lado: com Clear/Fill display ativado, a tela da esquerda é capturada no meio do redesenho com só o dígito 3 visível; com Clear/Fill display desativado, a tela da direita mostra 36,3 °C e 48%

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.h na 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 ​

  1. loop() redesenha sua tela para sempre: leia, transforme em texto, desenhe, repita.
  2. 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.
  3. 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.