21 de junho de 2021

Do bit Ao Byte

Embarcados, Linux e programação

Display OLED com mesmo endereço na RPi Pico

display oled ssd1306

Primeira dica: Leia o artigo todo, está repleto de dicas importantes que podem ser aplicadas a outras placas que não a Pico. Agora, suponhamos que um determinado dispositivo tenha um endereço atrelado e seja necessário adicionar mais um. Se não tem como mudar o endereço, o que fazer? – Respondo, mas aposto que muitos já deduziram antes mesmo de chegar ao fim da sentença. Podemos fazer isso de vários modos, mas veremos primeiramente nesse artigo o modo mais simples de colocar não um display OLED, mas 3 com o mesmo endereço na Raspberry Pi Pico.

I2C na Raspberry Pi Pico

Useo o expansor de IO PCF8574 como pretexto para falar sobre I2C, porque queria referenciar detalhes importantes. Por isso, para ver o pinout e a tabela de pinos dos dois canais I2C da RPi Pico, leia o artigo “Aumentar GPIO na RPi Pico“.

A RPi Pico tem 2 canais I2C, mas temos 3 dispositivos com o mesmo endereço. O que fazer? – Simples, inclusive poderiam ser mais displays, bastando que a partir do terceiro se use SoftI2C. Vamos começar com o básico pra coisa funcionar, em seguida descrevo alguns comandos. E repare que usando MicroPython, praticamente não se escreve código – e já que citei “economia de código”, sugiro o artigo sobre “Regex no Micropython“.

Instalar o módulo SSD1306 para display OLED no MicroPython

O display não é suportado por padrão e do mesmo modo que um Arduino, podemos adicionar os recursos externos necessários; invés de uma biblioteca, um módulo. Para tal, clique em Tools > Manage Packages na IDE Thonny. No vídeo demonstro o processo, inclusive mostro como criar um virtualenv em Linux para quem não usa Windows.

Antes de instalar, certifique-se de que está com a RPi Pico conectada e com o shell REPL disponível. Na hora de pesquisar, a versão do módulo ssd1306 deve começar com o prefixo micropython. Feita a instalação, devemos ter algo semelhante a isso:

display oled na Thonny IDE

Agora o código necessário para mostrar mensagens diferentes nos 3 displays:

from machine import Pin
from machine import I2C
from machine import SoftI2C
from ssd1306 import SSD1306_I2C

sda1    = Pin(0)
scl1    = Pin(1)

sda2    = Pin(2)
scl2    = Pin(3)

sda3    = Pin(15)
scl3    = Pin(14)

dsp1_w  = 128
dsp1_h  = 64

dsp2_w  = 128
dsp2_h  = 64

dsp3_w  = 128
dsp3_h  = 32

i2c1    = I2C(0,sda=sda1,scl=scl1,freq=400000)
i2c2    = I2C(1,sda=sda2,scl=scl2,freq=400000)
i2c3    = SoftI2C(scl3,sda3,freq=400000,timeout=255)

oled1   = SSD1306_I2C(dsp1_w, dsp1_h, i2c1)
oled2   = SSD1306_I2C(dsp2_w, dsp2_h, i2c2)
oled3   = SSD1306_I2C(dsp3_w, dsp3_h, i2c3)

oled1.text("OLED 1",0,0)
oled1.show()

oled2.text("OLED 2",0,0)
oled2.show()

oled3.text("OLED 3",0,0)
oled3.show()



Para saber os GPIOs padrão para os respectivos barramentos, mais uma vez, refira-se ao artigo sobre como aumentar o GPIO, apenas para o pinout e tabela de correspondência.

Repare que no SoftI2C o SCL precede o SDA, diferentemente do I2C por hardware. A implementação foi feita assim, não tem o que reclamar. Com esse código já teremos o resultado da imagem de destaque. Agora vamos conferir os demais recursos oferecidos.

Display OLED SSD1306 com MicroPython

Agora vamos começar a implementar mais do que texto. A partir daqui veremos como fazer:

  • lower third (ok, está em cima, mas é só reposicionar)
  • símbolo de anotação
  • progressbar
  • exibição de imagem

Todos esses recursos são simples de implementar e o código estará disponível em seguida da explicação, tenha 1 minuto de paciência, apenas para olhar o código e saber identificar o recurso que lhe interessa.

.pixel(x, y, P/B)

Essa função é precedida pelo objeto. Se você criou o objeto oled1, então deve executar oled1.pixel(10,10,1), por exemplo. Se fizer desse jeito, um pixel será aceso na posição x=10 e y=10. Se usar 0 como terceiro parâmetro, o pixel será preto. O pixel preto pode ser usado para apagar um pixel aceso na respectiva posição ou para destacar um pixel sobre um fundo de pixel acesos (por exemplo, todos os pixels acessos, exceto x,y).

No exemplo do código estou fazendo uma linha animada com intervalo de 30ms entre cada pixel. Mas se quiser uma linha sem animação, nem precisa fazer um loop, basta usar a função a seguir.

.hline(x, y, w, P/B)

Para traçar uma linha horizontal, passamos a posição X, Y e seguidamente o comprimento dessa linha. No código estou fazendo uma linha de “sombra” de 50px.

.vline(x, y, w, P/B)

Exatamente os mesmos parâmetros da linha horizontal, mas agora vertical. No código estou utilizando essa linha para fechar o texto OLED 1 como se fosse um lower third.

.line(x1, y1, x2, y2, P/B)

São mais parâmetros, mas é fácil. Com a função .line podemos traçar uma linha em qualquer direção. Os parâmetros são x,y 1, referentes ao ponto de início e x,y 2, referentes ao ponto final. O último parâmetro é sempre relacionado ao pixel (Preto ou Branco). No código ele está como marcador de nota no canto inferior direito do display.

.rect(x, y, w, h, P/B)

Para traçar um retângulo é tão fácil quanto traçar uma linha. Aqui os parâmetros são o ponto inicial (x,y), largura e altura do retângulo, a partir do ponto inicial. No código estou usando o retângulo para traçar o progressbar.

Como exibir imagem no display OLED?

Para essa tarefa você precisará ter o Python3 instalado. Se for Linux, já estará tudo lá, mas é fácil instalar no Windows também.

Preparar a imagem a exibir

Só podemos exibir imagens monocromáticas e para fazer a imagem é fácil. E esse é o primeiro passo. Você pode baixar uma imagem de silhueta que não seja vigorosa em detalhes (afinal, há uma limitação de pixels na telinha). O redimensionamento pode ser feito no Gimp, que é um programa de edição de imagem gratuito. – inclusive, a imagem pode ser criada do zero a partir dele. Foi o que fiz, usando a ferramenta brush com o pincel de desenho. No vídeo demonstro a criação de uma imagem, não se preocupe se não entender algum detalhe.

Preparando imagem para display oled no Gimp

A imagem deve obrigatoriamente ser exportada como jpg. Clique em File e Export, então dê um nome para a imagem e substitua a extensão por jpg.

Sua imagem está pronta para a conversão. Agora é a hora que o Python entra em ação. Salve um arquivo com o conteúdo a seguir. Aqui eu chamei de img2pico.py. Não é nada de especial, praticamente um padrão para quem manipula imagem com PIL. A única diferença é que usei o argparser para receber parâmetros por linha de comando. Explico logo após o código:

from io import BytesIO
from PIL import Image
import argparse
import os
import sys

#se nao requerido, pode ter um valor padrao com default=X
args = argparse.ArgumentParser()

args.add_argument("-i", "--imagem", required=True, help="Caminho da imagem")
args.add_argument("-l", "--largura", type=int, required=True, help="largura pretendida")
args.add_argument("-a", "--altura", type=int, required=True, help="altura pretendida")

argPar = vars(args.parse_args())


src_image = str(argPar["imagem"])
x = argPar["largura"]
y = argPar["altura"]

im = Image.open(src_image).convert('1')
im_resize = im.resize((x,y))
buf = BytesIO()
im_resize.save(buf, 'ppm')
byte_im = buf.getvalue()
temp = len(str(x) + ' ' + str(y)) + 4
print(byte_im[temp::])
print()
print("Copie todo o conteudo acima para uma variavel")

Como usar o argparse

Repare que com poucas linhas podemos receber parâmetros de uma forma flexível, sem a necessidade de colocar em uma ordem qualquer. Podemos passar primeiro a largura da imagem, a imagem e a altura da imagem nessa ordem. Se não lembrar como usa, ainda tem um help:

script python para converter imagem para exibir no display oled

Se quiser evitar a mensagem de erro, basta chamar o programa com –help ou -h:

help do conversor de imagem para display oled

O código relacionado ao argparse é muito simples.

import argparse

args = argparse.ArgumentParser()

args.add_argument("-i", "--imagem", required=True, help="Caminho da imagem")
args.add_argument("-l", "--largura", type=int, required=True, help="largura pretendida")
args.add_argument("-a", "--altura", type=int, required=True, help="altura pretendida")

argPar = vars(args.parse_args())

src_image = str(argPar["imagem"])
x = argPar["largura"]
y = argPar["altura"]

Ao passar os parâmetros corretamente, um bytearray será impresso no terminal. Copie o conteúdo na íntegra. Invés de salvar direto no código principal, criei um arquivo chamado image.py, no qual pretendo adicionar as imagens. Na hora de importar para o código principal, basta importar a imagem específica que deseja usar. Essa imagem de exemplo eu chamei de bird. Para incluí-la no código fiz assim:

from image import bird

Código completo (na RPi Pico)

Para gerar a imagem, devemos importar também o módulo framebuf. Nas últimas 3 linhas do programa temos tudo o que é necessário para exibir a imagem!

fb = framebuf.FrameBuffer(bird,64,64, framebuf.MONO_HLSB)
oled1.blit(fb,32,0)
oled1.show()

Fácil ou não? E o código completo desse exemplo ficou assim:

from machine import Pin
from machine import I2C
from machine import SoftI2C
from ssd1306 import SSD1306_I2C
from time import sleep_ms as delay
from image import bird
import framebuf

sda1    = Pin(0)
scl1    = Pin(1)

sda2    = Pin(2)
scl2    = Pin(3)

sda3    = Pin(15)
scl3    = Pin(14)

dsp1_w  = 128
dsp1_h  = 64

dsp2_w  = 128
dsp2_h  = 64

dsp3_w  = 128
dsp3_h  = 32

i2c1    = I2C(0,sda=sda1,scl=scl1,freq=400000)
i2c2    = I2C(1,sda=sda2,scl=scl2,freq=400000)
i2c3    = SoftI2C(scl3,sda3,freq=400000,timeout=255)

oled1   = SSD1306_I2C(dsp1_w, dsp1_h, i2c1)
oled2   = SSD1306_I2C(dsp2_w, dsp2_h, i2c2)
oled3   = SSD1306_I2C(dsp3_w, dsp3_h, i2c3)

oled1.text("OLED 1",0,0)
oled1.show()

oled2.text("OLED 2",0,0)
oled2.show()

oled3.text("OLED 3",0,0)
oled3.show()

delay(2000)

#O U T R A S    F U N C O E S
def progressBar(pI,percentNow):
    #apagar o texto de porcentagem
    oled1.fill_rect(0,38,128,12,0) # x, y, w, h, ON/OFF
    
    #preencher progressbar (total de 2px por passo, por isso range de 0 até 64)
    oled1.fill_rect(pI,24,pI+1,10,1)
       
    perc = percentNow
    oled1.text(perc,50,38)
    oled1.show()
    delay(30)

def percent(value,maximum):
    p = value*100
    p = int(p/maximum)
    return str(p) + "%"

def clearScreen():
    oled1.fill(0)
    oled1.show()
    
for i in range(50):
    # pixel 
    oled1.pixel(i,10,1) #x, y, ON/OFF
    oled1.show()
    delay(30)

# linha horizontal
oled1.hline(0,12,50,1) #x, y, w, ON/OFF
oled1.show()
delay(500)

# linha vertical
oled1.vline(50,0,12,1) #x, y, w, ON/OFF
oled1.show()
delay(500)

# linha diagonal
for i in range(1,11):
    oled1.line(117+i,64,128,54+i,1) # x1, y1, x2, y2, ON/OFF
    oled1.show()
    delay(100)
    
oled1.rect(0,24,128,10,1) # x, y, w, h, ON/OFF
oled1.show()

oled1.text("Loading...",30,26)
oled1.show()

delay(1000)

for i in range(65):
    p = percent(i,64)
    progressBar(i,p)
    
delay(2000)
clearScreen()
delay(200)

fb = framebuf.FrameBuffer(bird,64,64, framebuf.MONO_HLSB)
oled1.blit(fb,32,0)
oled1.show()

    

Vídeo explanativo – “Display OLED na Pico”

Nada mal, hum? Vimos bem mais do que usar 3 displays OLED com o mesmo endereço nesse artigo. Espero que esteja satisfeito com o artigo e espero também que o vídeo explanativo sane quaisquer dúvidas remanescentes a respeito. O vídeo deve sair em 1 dia a partir desse artigo, não deixe de assistir e, se não é inscrito, inscreva-se em nosso canal no Youtube! Vou chamar o vídeo de “Display OLED na Pico”, mas para você que leu o artigo, sabe que tem muito mais!