Pular para o conteúdo

Displays

I2C na ESP32: OLED SSD1306 e LCD 16×2 com MicroPython

O barramento I2C na ESP32 — endereços, scan, pull-ups e erros ENODEV — e como usar o OLED SSD1306 e o LCD 16×2 com PCF8574 em MicroPython, sem sobras na tela.

Por Prof. Rodrigo Faustino · Atualizado em · 12 min de leitura

Um projeto com sensor fica muito mais interessante quando mostra o resultado numa tela, longe do computador. Os dois displays mais comuns em laboratório — o OLED SSD1306 de 128 × 64 pontos e o LCD 16×2 com o módulo I2C — conversam com a ESP32 pelo mesmo caminho: o I2C, um barramento de dois fios que vários dispositivos dividem ao mesmo tempo.

Este artigo começa pelo barramento, que é onde moram quase todos os problemas ("o display não mostra nada"), e depois estuda cada display: o OLED, que é desenhado ponto a ponto numa cópia da tela na memória, e o LCD, que mostra caracteres prontos e tem as suas próprias manias — 5 V, contraste e números que "sobram" na tela.

O barramento I2C

O I2C (Inter-Integrated Circuit, lê-se "i-dois-cê") usa duas linhas:

  • SDA (serial data): por onde os bytes passam, nos dois sentidos;
  • SCL (serial clock): o relógio. A ESP32 marca o ritmo, e cada pulso transporta um bit.

Além delas, cada módulo precisa de alimentação e do GND comum com a placa. Numa montagem típica, o OLED e o LCD ficam pendurados nos mesmos dois fios: SDA no GPIO21 e SCL no GPIO22.

Endereços

Se todos escutam os mesmos fios, como a ESP32 fala com um só? Cada módulo tem um endereço de 7 bits, e toda conversa começa com ele. Quem não é chamado fica quieto. Os endereços dos dois displays deste artigo são:

  • OLED SSD1306: 0x3C (60 em decimal); alguns módulos vêm com 0x3D (61);
  • LCD 16×2 com o PCF8574: 0x27 (39); alguns vêm com 0x3F (63).

O 0x indica um número em hexadecimal, o jeito como os manuais escrevem endereços. Em Python, 0x3C e 60 são o mesmo número; hex(60) escreve '0x3c'.

Um aviso sobre manuais: o endereço tem 7 bits, mas no fio ele viaja junto com um oitavo bit que diz se a conversa é de escrita ou de leitura. Alguns manuais escrevem o endereço já "deslocado" com esse bit — 0x78 em vez de 0x3C, 0x4E em vez de 0x27. O MicroPython sempre usa a forma de 7 bits, a mesma que o scan() devolve. Se um manual mostra um endereço que parece o dobro do esperado, divida por dois.

Ninguém empurra o 1: os pull-ups

No I2C, nem a ESP32 nem os módulos colocam a linha em 1. Todos só sabem puxar para 0 ou soltar (saídas de dreno aberto). É isso que permite vários aparelhos dividirem o fio sem curto-circuito: se um puxa e os outros soltam, ninguém briga. Quando todos soltam, quem leva a linha para 1 são os resistores de pull-up, do SDA e do SCL para a alimentação.

Os módulos de OLED e LCD costumam trazer esses resistores na plaquinha (os do simulador trazem). O I2C(0) e o I2C(1) da ESP32 ainda ligam o pull-up interno do pino — que é fraco, da ordem de dezenas de kΩ, mas ajuda. O SoftI2C, o I2C feito por software em qualquer par de pinos, não liga nada. Sem pull-up nenhum, o simulador mostra o aviso i2c-sem-pullup: sem eles, a linha SDA fica em 0, todo endereço parece responder e ninguém entende nada.

Diagrama do barramento I2C: a ESP32 com SDA no GPIO21 e SCL no GPIO22, duas linhas horizontais com resistores de pull-up para o 3,3 V, e dois módulos pendurados nas mesmas linhas: o OLED no endereço 0x3C e o LCD no endereço 0x27.
Dois fios, vários módulos: SDA e SCL são compartilhados, os pull-ups levam as linhas para 1 e cada módulo responde só ao seu endereço.
FastTeacherPara professores

Da ementa ao semestre planejado

  • Plano de ensino e calendário montados sozinhos
  • Diário de aulas pronto para colar no SIGA
  • Slides e listas de exercícios com gabarito
Conhecer o FastTeacher

Versão Community grátis · Windows

O primeiro teste: scan()

Antes de escrever qualquer código de display, o primeiro teste com um módulo I2C novo é perguntar quem está no barramento. O scan() chama todos os endereços válidos (de 0x08 a 0x77), um por um, e devolve a lista dos que responderam:

from machine import I2C, Pin

i2c = I2C(0, scl=Pin(22), sda=Pin(21))

enderecos = i2c.scan()
print('responderam', len(enderecos), 'módulos:', enderecos)
for e in enderecos:
    print(e, 'em hexadecimal é', hex(e))

O resultado neste circuito é [39, 60]: o LCD e o OLED. Se um módulo não aparece no scan(), não adianta escrever o resto do programa — o problema é de fio, alimentação ou endereço.

I2C(0), I2C(1) e SoftI2C

A ESP32 tem dois controladores de I2C em hardware: I2C(0) e I2C(1) (I2C(2) dá ValueError). Os pinos são escolhidos ao criar o objeto; sem escolher, o I2C(0) usa SCL no 18 e SDA no 19, e o I2C(1), SCL no 25 e SDA no 26. A frequência padrão é 400 kHz, o "modo rápido" do I2C.

Como acontece com Pin, o I2C(0) devolve sempre o mesmo objeto: criar de novo só muda o que for passado. Já o SoftI2C(scl, sda) cria um objeto novo a cada chamada e funciona em quaisquer pinos de saída. Ele existe para quando os dois controladores estão ocupados ou os pinos não ajudam, e é mais lento e mais exigente com os pull-ups: sem eles, o SCL não sobe, o SoftI2C espera o tempo-limite e dá OSError: [Errno 116] ETIMEDOUT.

Quando dá errado

Os erros do I2C seguem padrões, e conhecê-los economiza horas:

  • ninguém respondeu no endereço: OSError: [Errno 19] ENODEV. Confira o endereço (0x3C × 0x3D, 0x27 × 0x3F), a alimentação e os fios;
  • SDA e SCL trocados: o scan() volta vazio. O simulador identifica o caso e mostra o aviso i2c-trocado;
  • dois módulos com o mesmo endereço no mesmo barramento: aviso i2c-endereco-repetido. Troque o endereço de um deles (quando o módulo deixa) ou use o outro controlador;
  • pinos 34 a 39: são só de entrada e não servem para SDA nem SCL.

Quanto tempo leva uma conversa

A 400 kHz, cada bit dura 2,5 µs, e cada byte ocupa 9 bits no fio: os 8 do dado e um de confirmação (o ACK, com que o receptor diz "recebi"). Mandar 1 KB leva, então, cerca de 23 ms. Parece pouco, mas é tempo em que o programa está parado — e vai importar no OLED.

OLED SSD1306: desenhe na memória, depois mostre

O OLED tem 128 × 64 pontos que acendem sozinhos (não há luz de fundo) e um chip controlador, o SSD1306. Para usá-lo, o MicroPython precisa de um driver, e aqui está a primeira surpresa: o firmware da ESP32 não traz o driver do SSD1306. Os módulos que vêm congelados no firmware são poucos (dht, neopixel, onewire, ds18x20, umqtt e mais alguns); o ssd1306.py é um arquivo Python comum da biblioteca oficial micropython-lib, que fica ao lado do seu main.py.

No PyPlaca, o botão "Abrir no simulador" do exemplo abaixo coloca o ssd1306.py no projeto. Abra a aba dele e leia: são menos de 200 linhas que mandam comandos com i2c.writeto(), nada de mágico. Na placa real, o arquivo também precisa estar na ESP32, e a exportação da IDE já o inclui. Sem o arquivo, import ssd1306 dá ImportError: no module named 'ssd1306'.

from machine import I2C, Pin
import ssd1306
import time

i2c = I2C(0, scl=Pin(22), sda=Pin(21))
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

oled.fill(0)                      # apaga tudo (só na memória)
oled.text('Ola, ESP32!', 0, 0)    # texto com o canto em x = 0, y = 0
oled.text('PyPlaca', 0, 16)
oled.rect(0, 32, 128, 24, 1)      # moldura: x, y, largura, altura, cor
oled.hline(8, 44, 112, 1)         # linha deitada dentro da moldura
oled.show()                       # agora sim: manda tudo para a tela

while True:                       # mantém a tela ligada no simulador
    time.sleep(1)
Captura de tela do PyPlaca com a lição do OLED: o código que desenha texto, moldura e linha no editor e, no circuito, o display OLED mostrando o desenho.
A lição do OLED no simulador: o desenho só aparece na tela depois do oled.show().

O frame buffer

O driver guarda uma cópia da tela na memória da ESP32: um bytearray de 128 × 64 ÷ 8 = 1024 bytes, um bit por ponto. Esse é o frame buffer. Todos os métodos de desenho — fill, pixel, text, hline, vline, line, rect, fill_rect, ellipse — mexem só nessa cópia, sem tocar no I2C. Eles vêm do módulo framebuf, que faz parte do firmware e que o simulador traduz linha a linha do código C do MicroPython.

É o show() que envia a cópia inteira para o display, de uma vez. E isso leva tempo: os 1024 bytes, mais o endereço e um byte de controle, passam pelo I2C em cerca de 23 ms a 400 kHz. Daí a regra de ouro do OLED: desenhe tudo primeiro e chame show() uma vez por quadro. Chamar show() depois de cada texto deixa o programa lento e faz a tela aparecer pela metade.

Coordenadas e texto

O ponto (0, 0) é o canto de cima à esquerda. O x cresce para a direita, até 127; o y cresce para baixo, até 63. O text() usa uma fonte de 8 × 8 pontos: cabem 16 letras por linha e 8 linhas de texto, em y = 0, 8, 16 e assim por diante até 56.

A tela do OLED de 128 por 64 pontos com a origem no canto superior esquerdo, o eixo x crescendo para a direita até 127 e o eixo y para baixo até 63, as oito linhas de texto de 8 pontos de altura e a conta de 1024 bytes do frame buffer.
As coordenadas do OLED: (0, 0) no canto de cima à esquerda, letras de 8 × 8 pontos — 16 por linha, 8 linhas — e 1024 bytes de memória para a tela inteira.

Dois cuidados com o texto:

  • a fonte não tem acentos. Cada caractere acima do ASCII vira um quadradinho xadrez. Escreva Ola, não Olá;
  • o text() não apaga o fundo: ele só acende os pontos da letra. Escrever 2 em cima de um 3 deixa os dois misturados. Para trocar um texto, apague antes — fill(0) limpa a tela toda, fill_rect(x, y, largura, 8, 0) limpa só um pedaço.

Outros métodos úteis do driver: invert(1) troca aceso por apagado na tela inteira, contrast(x) muda o brilho e poweroff()/poweron() desligam e ligam o painel.

Um mostrador que se atualiza

Juntando a regra do show() com a do fill_rect, um contador que se atualiza fica assim:

from machine import I2C, Pin
import ssd1306
import time

i2c = I2C(0, scl=Pin(22), sda=Pin(21))
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

oled.fill(0)
oled.text('Contagem', 0, 0)
for n in range(10, -1, -1):
    oled.fill_rect(0, 24, 128, 8, 0)   # apaga só a linha do número
    oled.text('%2d' % n, 56, 24)
    oled.show()                        # um envio por quadro
    time.sleep_ms(500)

while True:
    time.sleep(1)

O título é desenhado uma vez só; a cada volta, apenas a faixa do número é apagada e redesenhada.

LCD 16×2: caracteres prontos

O LCD 16×2 mostra 2 linhas de 16 caracteres. Diferente do OLED, ele não desenha pontos soltos: cada posição mostra um caractere pronto, tirado de uma tabela gravada no chip do LCD, o HD44780. Cada caractere é uma grade de 5 × 8 pontos, e dá para criar até 8 caracteres próprios (um sino, um coração, uma seta).

Sozinho, o HD44780 é ligado em paralelo, com três sinais de controle (RS, RW e E) e quatro ou oito fios de dados, além da alimentação. A plaquinha presa atrás do LCD, com o chip PCF8574, faz a ponte: recebe os comandos pelo I2C e mexe nos pinos do LCD. Por isso bastam quatro fios: GND, VCC, SDA e SCL.

O driver também não vem no firmware e é dividido em dois arquivos:

  • lcd_api.py: os comandos do HD44780 (limpar, posicionar, escrever), sem saber como o LCD está ligado;
  • i2c_lcd.py: como mandar esses comandos pelo PCF8574. Ele importa o lcd_api, então os dois precisam estar no projeto (e na placa).
from machine import I2C, Pin
from i2c_lcd import I2cLcd
import time

i2c = I2C(0, scl=Pin(22), sda=Pin(21))
lcd = I2cLcd(i2c, 0x27, 2, 16)    # endereço, linhas, colunas

lcd.putstr('Ola, LCD!')
lcd.move_to(0, 1)                 # coluna 0, linha 1 (a de baixo)
lcd.putstr('16x2 no I2C')

while True:                       # mantém a tela ligada no simulador
    time.sleep(1)

Os métodos mais usados:

  • putstr('texto') escreve a partir do cursor ('\n' pula para a outra linha);
  • move_to(coluna, linha) posiciona o cursor — repare: primeiro a coluna, de 0 a 15, depois a linha, 0 ou 1;
  • clear() apaga tudo e volta o cursor para o começo;
  • backlight_on()/backlight_off() ligam e desligam a luz de fundo;
  • show_cursor()/hide_cursor() mostram ou escondem o cursor.

Por que o LCD vai no VIN

O vidro do LCD precisa de 5 V para mostrar os caracteres; o VCC dele vai no VIN da placa. A parte digital funciona com menos: ligado no 3V3, o LCD responde no scan(), mas a tela fica vazia. O simulador mostra o aviso lcd-sem-5v nesse caso. É um dos "o programa está certo e não aparece nada" mais comuns da bancada.

O contraste

Atrás do módulo há um trimpot de contraste. Fora do ponto, a tela parece vazia mesmo com tudo certo. Quando o scan() acha o 0x27 e a tela não mostra nada, este é o primeiro lugar para olhar. No simulador, o trimpot fica no painel da peça: arraste até a ponta e veja o texto sumir, com o programa continuando igual.

Sem acentos aqui também

A tabela de caracteres do HD44780 tem letras sem acento, números e sinais. Letras acentuadas viram outro símbolo. Escreva Ola, Temp, Umid. Um caractere útil da tabela é o 223, o símbolo de grau: lcd.putstr(chr(223)).

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre

Números que sobram na tela

Um LCD não apaga sozinho: cada caractere fica lá até alguém escrever outro na mesma posição. Rode uma contagem regressiva de 12 a 0 e olhe a passagem do 10 para o 9: aparece 90. O 10 ocupou duas posições; o 9 só escreveu por cima da primeira, e o 0 antigo continuou lá. O mesmo acontece com o OLED se você não apagar o fundo.

Três jeitos de resolver, do mais bruto ao mais elegante:

  1. limpar tudo com clear() e escrever de novo. Funciona, mas apaga também o que não mudou e a tela pode piscar;
  2. escrever espaços depois do número, para cobrir o que sobrou: putstr(str(n) + ' ');
  3. formatar com largura fixa, para o número ocupar sempre o mesmo espaço.
from machine import I2C, Pin
from i2c_lcd import I2cLcd
import time

i2c = I2C(0, scl=Pin(22), sda=Pin(21))
lcd = I2cLcd(i2c, 0x27, 2, 16)
lcd.putstr('Contagem:')

for n in range(12, -1, -1):
    lcd.move_to(0, 1)
    lcd.putstr('%3d' % n)      # sempre 3 posições, alinhado à direita
    time.sleep_ms(400)

while True:
    time.sleep(1)

A formatação com % é a ferramenta dos painéis: '%3d' % 7 dá ' 7' (três posições, alinhado à direita), '%-3d' % 7 dá '7 ' (alinhado à esquerda) e '%5.1f' % 9.5 dá ' 9.5' (cinco posições com uma casa decimal). Com ela, cada informação ganha o seu pedaço da tela, e cada atualização reescreve o pedaço inteiro.

OLED ou LCD?

Os dois resolvem o problema de mostrar números, mas têm vocações diferentes:

OLED SSD1306LCD 16×2 com PCF8574
O que mostrapontos livres: texto, linhas, gráficos32 caracteres prontos
Endereço I2C0x3C (ou 0x3D)0x27 (ou 0x3F)
Alimentação3,3 V5 V (no VIN)
Driverssd1306.pylcd_api.py + i2c_lcd.py
Atualizardesenhar na memória e show()escrever na posição do cursor
Acentosnãonão

O OLED é a escolha para gráficos, ícones e telas mais ricas; o LCD é imbatível em legibilidade de longe e simplicidade de uso. Como os dois falam I2C em endereços diferentes, nada impede usar os dois no mesmo barramento, como no circuito do scan().

Roteiro de diagnóstico

Quando um display I2C não mostra nada, siga esta ordem:

  1. o módulo aparece no scan()? Se não: fios, alimentação, SDA/SCL trocados, GND comum;
  2. o endereço do código é o que o scan() mostrou?
  3. o driver (ssd1306.py ou lcd_api.py + i2c_lcd.py) está no projeto?
  4. no OLED: o programa chama show()?
  5. no LCD: o VCC está no VIN (5 V)? O contraste está no ponto?
  6. no simulador: o programa continua rodando? Quando ele termina, a bancada desliga e as telas apagam — por isso os exemplos acabam num while True com sleep.

O próximo artigo, Programar sem travar, mostra como atualizar uma tela, piscar um LED e atender um botão ao mesmo tempo, sem que um sleep trave tudo. Na trilha de displays você pratica o scan(), o OLED, o LCD e ainda o anel de LEDs NeoPixel, que usa um protocolo de um fio só.

Para praticar no PyPlaca

Trilhas

Lições

Fichas de componentes

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre

FastTeacherPara professores

Da ementa ao semestre planejado

  • Plano de ensino e calendário montados sozinhos
  • Diário de aulas pronto para colar no SIGA
  • Slides e listas de exercícios com gabarito
Conhecer o FastTeacher

Versão Community grátis · Windows

FastMindAssistente de IA local

A IA que lembra da sua vida e mora no seu computador

  • Memória de longo prazo com a fonte de cada resposta
  • Funciona offline: seus dados ficam no seu PC
  • Lê PDFs, slides, fotos e gravações de reunião
Baixar grátis para Windows

Plano Essencial grátis para sempre