Pular para o conteúdo

Lopaka Basics 01: sua primeira tela ​

Um módulo OLED SSD1306 mostrando um HELLO! emoldurado com a legenda my first screen

Termostatos, relógios inteligentes, impressoras 3D, estações meteorológicas: quase todo gadget tem uma telinha, e alguém precisou desenhar ela. Lopaka Basics é uma série em dez partes para quem nunca mexeu com um microcontrolador e nunca abriu o Lopaka.

Cada episódio segue o mesmo caminho: desenhe, pegue o código, veja na tela. Neste primeiro, você vai ver o que acontece dentro de um gadget quando ele mostra uma imagem, depois vai criar uma telinha no Lopaka e executar o código gerado em um Arduino simulado.

O que você vai construir: uma tela OLED de 128×64 com uma moldura, um "HELLO!" grande, uma legenda, uma linha divisória e um pequeno ponto de status. Do que você precisa: um navegador. Uma placa real é opcional; executamos o código em um simulador online gratuito.

O que há dentro de um gadget ​

Um computador minúsculo que executa um programa ​

A maioria dos dispositivos pequenos tem uma placa com microcontrolador dentro: um Arduino UNO, um ESP32, um Raspberry Pi Pico ou algo parecido. Um microcontrolador é um computador minúsculo. Diferente do seu notebook, ele executa um programa, o seu firmware. Em um sketch do Arduino, a função loop() desse programa se repete enquanto a placa tiver energia.

Seu programa desenha tudo o que aparece na tela. A placa não oferece área de trabalho, janelas nem botões prontos para você começar.

Um Arduino UNO ligado a um display OLED de 128×64 com quatro fios: GND, VCC, SCL e SDA

A tela é uma grade de pixels ​

O display que usamos nos primeiros episódios é um dos mais comuns na eletrônica por hobby: um pequeno OLED monocromático, com 128 pixels de largura e 64 de altura. O módulo comum é uma placa de 0,96 polegada com o controlador SSD1306. Isso dá 8.192 pixels, e cada um está aceso ou apagado. Sem tons de cinza, tudo nele, incluindo o texto, fica nítido e quadrado.

A versão I2C do módulo se conecta à placa com quatro fios: dois de energia (GND e VCC) e dois de dados (SCL e SDA).

A grade de 128 por 64 pixels do display OLED: 8.192 pixels, cada um aceso ou apagado

Cada pixel tem um endereço ​

Para acender um pixel específico, seu programa precisa dizer qual é. Cada pixel tem um endereço feito de dois números:

  • x conta a partir da borda esquerda,
  • y conta para baixo a partir da borda superior.

Então (0, 0) é o canto superior esquerdo. Dois detalhes confundem a maioria dos iniciantes:

  1. O Y cresce para baixo, o contrário da matemática da escola. Um pixel em (40, 20) está 40 pixels à direita e 20 pixels abaixo do canto.
  2. A contagem começa no zero. Em um display de 128×64, a última coluna é 127 e a última linha é 63.

Coordenadas de pixel em um display de 128×64: x vai de 0 a 127 para a direita, y vai de 0 a 63 para baixo, com os pontos (0, 0) e (40, 20) marcados

Uma biblioteca gráfica transforma comandos em pixels ​

Você não acende os 8.192 pixels sozinho. Seu programa chama uma biblioteca gráfica e conversa com ela em formas: desenhe uma moldura aqui, escreva este texto ali. A biblioteca calcula quais pixels isso significa e envia eles para o display.

Para displays monocromáticos pequenos, uma das bibliotecas mais populares é a U8g2. Duas linhas bastam para desenhar o começo da nossa tela:

cpp
u8g2.drawFrame(2, 2, 124, 60);      // a frame: x, y, width, height
u8g2.drawStr(28, 32, "HELLO!");     // text at x = 28, y = 32

A biblioteca gráfica desenha uma moldura e a palavra HELLO! a partir de duas linhas de código U8g2

O jeito antigo: adivinhe, compile, envie, olhe ​

Você escreve cada número desse código à mão. Você adivinha onde o texto deve começar, compila o sketch, envia para a placa e olha a tela. O texto está três pixels longe demais para a direita? Mude um número e recomece: compile, envie, olhe.

O jeito antigo de programar uma tela à mão: um ciclo de adivinhar os números, compilar, enviar e olhar a tela, já na rodada 8

Cada rodada custa uma compilação e um envio, muitas vezes dezenas de segundos, e você precisa de uma placa conectada para ver qualquer coisa. Até uma tela simples como a nossa pode levar dezenas de rodadas até tudo se alinhar.

O Lopaka remove esse ciclo. Ele é um editor que roda no seu navegador. Você desenha a tela, e o Lopaka escreve o código para a sua biblioteca gráfica enquanto você desenha.

Abra lopaka.app e entre com e-mail, Google ou GitHub. O Lopaka mantém seus projetos na nuvem, então você pode abrir eles de qualquer computador.

A página inicial do Lopaka: My Projects com o botão New Project no topo e a Projects Gallery da comunidade abaixo

Etapa 1: crie um novo projeto ​

Clique em + New Project. A primeira e mais importante escolha é a Platform, a biblioteca gráfica que o seu firmware vai usar. Isso decide que tipo de código o Lopaka vai gerar, então escolha a biblioteca que você vai instalar na placa.

Para cada biblioteca, o Lopaka escreve o código do jeito que aquela biblioteca espera.

Para um OLED pequeno em preto e branco, escolha U8g2. A descrição dela diz que é "the default choice for monochrome OLED and LCD panels such as SSD1306, SH1106 and ST7565, where every pixel is simply on or off", e o código pode ser exportado como C++ para Arduino ou C para ESP-IDF.

Depois preencha o restante da caixa de diálogo:

  • Title: "My First Screen".
  • Screen Size: 128×64. Para U8g2, ele já está selecionado; a lista tem dezenas de outros tamanhos. O interruptor Custom permite digitar qualquer tamanho, e Rounded é para displays redondos.

Clique em Salvar.

A caixa de diálogo Create New Project com Platform definida como U8g2, o título My First Screen e o tamanho de tela 128×64

Etapa 2: um passeio rápido pelo editor ​

Este é o editor. Tudo de que você precisa para este episódio cabe em uma tela:

O editor do Lopaka com suas partes contornadas em laranja: projeto, biblioteca e tamanho no topo, ferramentas de desenho, camadas, tela de desenho, propriedades, código gerado e opções de código

  • Barra superior. O nome do projeto, seguido de dois selos: a biblioteca (u8g2) e o tamanho da tela (128×64). O ícone de engrenagem ao lado deles abre as configurações do projeto. À direita ficam Fork e Share.
  • Ferramentas de desenho, acima da tela de desenho: desfazer e refazer, a ferramenta de seleção, depois o pincel, o texto (Aa), o retângulo, a elipse (o ícone de círculo), a linha, o triângulo e o polígono.
  • A tela de desenho no meio é a sua tela em 128×64, com réguas nas bordas. Quando você seleciona uma forma, as réguas marcam as bordas exatas dela em pixels.
  • Camadas à esquerda. Cada forma que você desenha vira uma camada aqui, agrupada sob a tela dela. Adicionar tela cria outra tela no mesmo projeto; as guias Imagens e Animações abaixo guardam seus bitmaps e animações.
  • Propriedades à direita, também chamado de inspetor: a posição, o tamanho e o estilo exatos do que estiver selecionado.
  • Código gerado na parte inferior. Ele atualiza ao vivo enquanto você desenha. Ao lado dele ficam o interruptor Syntax (C++ para Arduino ou C para ESP-IDF para U8g2) e os interruptores que controlam o que entra no código.

Etapa 3: desenhe a tela ​

Uma moldura e sua primeira linha de código ​

Escolha a ferramenta retângulo e arraste pela tela de desenho. Uma moldura aparece na tela e, no mesmo momento, uma nova linha aparece no código:

cpp
u8g2.drawFrame(2, 2, 124, 60);

Esses quatro números são x, y, largura e altura: os mesmos endereços de pixel do começo do artigo. Pule o arrastar preciso e digite os valores exatos no inspetor: X 2, Y 2, W 124, H 60. As réguas acompanham seus números e marcam as bordas da forma em 2 e 126 na horizontal, 2 e 62 na vertical. As réguas contam as linhas entre os pixels, então a borda direita em 126 significa que a última coluna acesa é 125.

Um retângulo selecionado na tela de desenho com X 2, Y 2, W 124 e H 60 no inspetor e a linha drawFrame correspondente destacada no código

Uma moldura que começa em x = 2 e tem 124 pixels de largura cobre as colunas de 2 a 125. Isso deixa duas colunas vazias de cada lado da tela de 128 pixels e a mesma margem de dois pixels em cima e embaixo. À mão, você faria essa conta de cabeça antes de cada compilação.

As outras propriedades do retângulo também mudam o código:

  • R arredonda os cantos, e a chamada vira drawRFrame.
  • Filled transforma o contorno em um bloco sólido, drawBox.
  • Inverted (XOR) inverte os pixels sob a forma em vez de acender eles, o que é útil para itens de menu destacados.

Texto e fontes ​

Escolha a ferramenta de texto (Aa) e clique na tela de desenho. Uma nova camada com a palavra Text aparece; selecione ela, se ainda não estiver selecionada, e digite HELLO! no campo de texto do inspetor. O primeiro texto de um projeto usa a fonte padrão, 4x6, que é minúscula: cada caractere tem quatro pixels de largura. Cada texto que você adiciona depois começa na última fonte que você escolheu.

Abra a lista Font. O Lopaka oferece 42 fontes U8g2 integradas, de um microtexto de 4×6 pixels a tipos grandes para display. O primeiro item da lista, Import font..., adiciona a sua própria. Escolha profont22.

A lista de fontes aberta no inspetor, de 4x6 a profont29, com Import font no topo

Uma linha setFont aparece no código antes do texto, para a biblioteca saber com qual fonte desenhar ele:

cpp
u8g2.setFont(u8g2_font_profont22_tr);
u8g2.drawStr(56, 35, "HELLO!");

Alinhe e veja os números se moverem ​

Agora posicione o texto com os botões de alinhamento no topo do inspetor: os dois botões centrais colocam o texto no meio da tela, horizontal e verticalmente. Ou arraste ele para qualquer lugar.

Observe o código enquanto você faz isso. Cada movimento atualiza os números na hora, e centralizar define X como 28. Você vê o resultado enquanto muda, sem compilar e sem enviar.

HELLO! centralizado na tela com os botões de alinhamento; o código agora mostra drawStr(28, 39, "HELLO!")

Olhe as réguas nesta captura: elas marcam a caixa ao redor das letras de 25 a 39, e o código diz drawStr(28, 39, ...). No U8g2, o y de um texto é a linha de base dele, a linha sobre a qual as letras ficam, e as letras preenchem as linhas acima dela. Um texto que você posiciona à mão em y = 0 acaba acima da tela, um erro clássico de iniciante. O inspetor do Lopaka usa a mesma linha de base para o Y, então o número no campo vai direto para o código, e a tela de desenho mostra onde as letras vão parar.

No design final, movemos "HELLO!" um pouco mais para cima, para y = 32, para abrir espaço para a linha e a legenda abaixo dele.

Legenda, linha e um ponto de status ​

Termine a tela com mais três camadas:

  • Uma legenda. Outra camada de texto, my first screen. Ela começa em profont22, a última fonte que você usou, então mude ela para a menor 6x10, centralize horizontalmente e defina Y como 52: drawStr(19, 52, "my first screen"). Como este texto usa uma fonte diferente, o Lopaka adiciona outro setFont antes dele. Se dois textos seguidos usam a mesma fonte, ele não repete a chamada.
  • Uma linha sob "HELLO!": drawLine(24, 39, 103, 39). Para linhas, os quatro números são o ponto inicial e o ponto final.
  • Um pontinho no canto superior direito, feito com a ferramenta elipse, com Filled ativado.

No inspetor, o ponto tem X 117, Y 10, RX 2 e RY 2: o canto superior esquerdo da caixa ao redor dele e seus dois raios. A função de elipse do U8g2 recebe o centro, então o código diz drawFilledEllipse(119, 12, 2, 2). Cada biblioteca tem convenções como esta. O Lopaka converte elas para você, e você trabalha com o que vê na tela de desenho.

Agora olhe o painel Camadas: rect 1, string 2, string 3, line 4, ellipse 5. Cada forma é uma camada, e cada camada é uma linha de código, com o nome dela como um comentário acima dela.

Etapa 4: pegue o código ​

Quando a tela parecer certa, clique em Copiar acima do código. Você recebe uma função pronta, com o nome da tela:

cpp
// [BEGIN lopaka generated]
void drawScreen_1(void) {
    u8g2.setFontMode(1);
    u8g2.setBitmapMode(1);
    // rect 1
    u8g2.drawFrame(2, 2, 124, 60);
    // string 2
    u8g2.setFont(u8g2_font_profont22_tr);
    u8g2.drawStr(28, 32, "HELLO!");
    // string 3
    u8g2.setFont(u8g2_font_6x10_tr);
    u8g2.drawStr(19, 52, "my first screen");
    // line 4
    u8g2.drawLine(24, 39, 103, 39);
    // ellipse 5
    u8g2.drawFilledEllipse(119, 12, 2, 2);
}
// [END lopaka generated]

O painel de código gerado com o botão Copiar e os interruptores para PROGMEM, a função contenedora, variáveis, fontes, imagens, funções auxiliares de animação, comentários e limpar o display

Quatro detalhes neste código:

  • As fontes já estão definidas. Cada texto recebe a fonte que você escolheu no editor, com o nome de fonte U8g2 certo. Em u8g2_font_profont22_tr, o t significa um fundo transparente e o r significa o conjunto de caracteres reduzido: as letras latinas básicas, os dígitos e a pontuação, o que mantém a fonte pequena na memória.
  • setFontMode(1) e setBitmapMode(1) deixam texto e imagens transparentes, para uma letra desenhada sobre uma linha não apagar ela com o fundo dela.
  • Os marcadores BEGIN e END mostram qual parte do seu sketch veio do Lopaka. Quando você mudar o design, substitua tudo entre eles.
  • Os interruptores ao lado do código controlam o que entra nele: envolver o desenho em uma função, declarar imagens como PROGMEM, incluir fontes e dados de imagem, comentários com nomes de camadas e muito mais. Os padrões servem para este episódio.

Etapa 5: execute ele ​

Onde a função vai: o buffer de quadro ​

Seu sketch precisa chamar a função copiada. Um sketch mínimo do Arduino para o nosso display:

cpp
#include <U8g2lib.h>
#include <Wire.h>

// 128x64 SSD1306 OLED on I2C, full frame buffer
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();
  drawScreen_1();
  u8g2.sendBuffer();
}

O nome longo do objeto do display se lê da esquerda para a direita: o controlador SSD1306, um painel 128X64, um módulo genérico (NONAME), um buffer de quadro Full completo, conectado por I2C de HardWare. U8G2_R0 significa sem rotação, e U8X8_PIN_NONE diz que o módulo não tem nenhum pino de reset ligado.

Em loop(), chame sua função entre clearBuffer() e sendBuffer():

  1. clearBuffer() apaga a imagem.
  2. drawScreen_1() desenha tudo na memória, e não no display.
  3. sendBuffer() envia a imagem pronta para o display de uma vez.

Essa imagem na memória se chama buffer de quadro. O display recebe só quadros completos, então você nunca vê uma tela desenhada pela metade. O preço é memória: 128 × 64 pixels a um bit cada dá 1.024 bytes, metade dos 2 KB de RAM que um Arduino UNO tem. Nosso sketch inteiro ocupa 12.218 bytes de flash (37%) e 1.578 bytes de RAM (77%) em um UNO, e a maior parte dessa RAM é o buffer de quadro. Para um sketch tão pequeno, isso não é problema. Em um ESP32, o mesmo sketch usa 7% da RAM. Voltaremos à memória, e a desenhar com menos dela, mais adiante na série.

O sketch com o buffer de quadro explicado: clearBuffer apaga a imagem, drawScreen_1 desenha na memória, sendBuffer envia para o display; 1.024 bytes são metade da RAM de um Arduino UNO

Sem placa ainda? Execute em um simulador ​

Você pode ver sua tela funcionando sem nenhum hardware. O Wokwi é um simulador online gratuito que compila e executa código Arduino real:

  1. Crie um novo projeto Arduino UNO.
  2. Adicione um display OLED SSD1306 e conecte quatro fios: GND ao GND, VCC ao 5V, SCL ao A5 e SDA ao A4.
  3. Adicione a biblioteca U8g2 na guia Library Manager.
  4. Cole o sketch acima em sketch.ino, com o código do Lopaka no lugar dele.
  5. Pressione Play.

O display mostra o que você desenhou, executando como código Arduino real. Colamos o código do Lopaka sem mudar um caractere, e o display simulado correspondeu à tela de desenho do Lopaka pixel por pixel.

O simulador executando o sketch: o OLED simulado conectado a um Arduino UNO mostra HELLO! e my first screen

Em uma placa real ​

Como é código real, o mesmo sketch roda em uma placa real também. Ligue o display do mesmo jeito, instale a U8g2 pelo Library Manager na Arduino IDE e envie. Duas coisas para conferir no hardware real:

  • O controlador. A maioria dos módulos de 0,96 polegada usa o SSD1306. Muitos módulos de 1,3 polegada parecem iguais, mas usam o SH1106; para esses, mude SSD1306 para SH1106 no construtor: U8G2_SH1106_128X64_NONAME_F_HW_I2C.
  • Energia. A maioria desses módulos aceita 3,3 V e 5 V, mas algumas placas avulsas aceitam só 3,3 V. Confira o anúncio do seu módulo.

Um episódio posterior da série passa por esta etapa passo a passo, incluindo o que conferir quando a tela fica preta.

Recapitulação ​

O fluxo continua o mesmo para cada tela que você fizer:

  1. Escolha sua biblioteca: a plataforma que você escolhe ao criar um projeto decide qual código você recebe. Para um OLED monocromático, comece com U8g2.
  2. Desenhe sua tela: cada forma é uma camada, cada camada é uma linha de código, e o inspetor mostra os números exatos.
  3. Copie o código: chame a função entre clearBuffer() e sendBuffer() e execute ela no simulador ou em uma placa real.

Próximo episódio: Text, Fonts & Icons. Construímos uma miniestação meteorológica e descobrimos por que cada fonte custa memória. Leia o episódio 2 ou assista no YouTube.

Pronto para testar? Abra o Lopaka e desenhe sua primeira tela.