Manual
do
Maker
.
com
Tem diversas opções para expandir os GPIO na Raspberry Pi Pico ou em qualquer outra MCU; ou outras Raspberry. Para quem acompanha o blog, sabe que escrevo com frequência sobre o meu expansor de IO preferido: PCF8574, do qual também tenho meu artigo preferido: "Dominando PCF8574 com Arduino". Mas aqui vamos usar a RPi Pico com o firmware MicroPython, então esse artigo será o meu preferido sobre a RPi Pico, pois além de utilizarmos a PCF8574 para expandir o GPIO na Raspberry Pi Pico, ainda vamos usar o I2C!
Para expandirmos o GPIO na Raspberry Pi Pico com PCF8574 precisaremos configurar o barramento I2C. É tão simples quanto no Arduino, mas o código final é muito menor. Confira.
Usar Python já é por si só muito mais fácil do que programar em C/C++ para MCUs, mas usar MicroPython chega a ser covardia de tão fácil que fica. Primeiro, vamos dar uma conferida no pinout da placa em relação ao barramento I2C.
Nessa seção do artigo vamos ver o procedimento padrão para configurar qualquer dispositivo I2C na Raspberry Pi Pico e identificar seu endereço. Use como referência para qualquer outro dispositivo I2C que precisar configurar.
Repare que temos o I2C quase na placa inteira, mas a placa tem apenas duas controladoras I2C. Isso significa que podemos usar 2 I2C nos pinos desejados, mas apenas 1 barramento pode ser habilitado em cada canal.
I2C Controller | GPIO Pins |
I2C0 – SDA | GP0/GP4/GP8/GP12/GP16/GP20 |
I2C0 – SCL | GP1/GP5/GP9/GP13/GP17/GP21 |
I2C1 – SDA | GP2/GP6/GP10/GP14/GP18/GP26 |
I2C1 – SCL | GP3/GP7/GP11/GP15/GP19/GP27 |
Em nosso exemplo vamos configurar o GP0 e GP1, que são os pinos 1 e 2 da placa. No barramento I2C podemos ter até 127 dispositivos conectados à MCU, desde que cada um esteja em um endereço exclusivo. Se por alguma razão for necessário configurar um segundo barramento, veja na tabela acima quais os pinos disponíveis para o segundo canal I2C (que são as últimas duas linhas).
Se precisasse de mais um barramento ainda, dá pra fazer por software. Enfim, não faltam recursos.
Essa é mais uma vantagem em utilizar MicroPython. Não precisamos criar o programa de expansão do GPIO na Raspberry e então experimentar posteriormente. Já podemos testar "inflow". Usando a Thonny IDE podemos digitar código Python no prompt, da mesma forma que o fluxo de nosso programa seguiria. Ou não, podemos apenas escrever um código de teste, como faremos aqui para pegar o endereço do dispositivo. O procedimento é simples:
E a execução é tão simples quanto:
from machine import Pin
from machine import I2C
sda_pin = Pin(0)
scl_pin = Pin(1)
i2c = I2C(0,sda=sda_pin,scl=scl_pin,freq=400000)
print(i2c.scan())
A velocidade padrão do barramento é 400000, já temos isso de cabeça. Na linha que definimos a configuração, apontamos para o canal 0, passamos os pinos e a velocidade do barramento. Só isso. Em seguida fazemos um scan. Não tem como ser mais fácil!
Agora que já temos o dispositivo configurado e o endereço do dispositivo identificado, passemos ao objetivo do artigo.
Agora vamos começar a brincadeira. Primeiro, para saber quais os métodos disponíveis na classe I2C, chamamos o help (o qual tenho enfatizado fortemente nos vídeos relacionados, não perca essa playlist).
Bem, tem alguma limitação. Agora não dá pra chamar o help no método, então não dá pra saber quais os parâmetros necessários. Mas sem problema, a documentação é sua amiga.
Para escrever para um dispositivo, usamos os parâmetros ADDR, VALUE, sendo endereço e valor. Para ler, podemos usar a função readfrom passando o endereço e o número de bytes desejados.
O PCF8574 é um expansor de IO de 8 bits, ou seja, 1 Byte. Assim, podemos escrever valores entre 0 e 255 e ler 1 byte.
Qual o valor inicial dos pinos do PCF8574? Vimos que o endereço é o 34. Vamos ler 1 byte:
Isso pode ser uma pegadinha pra quem não está habituado. Pra dizer a verdade, nem sei se tem outra forma de fazer a conversão porque sempre usei assim, mas de qualquer modo, eis a forma de manipular a leitura:
Lembre-se: A leitura do PCF8574 retornou um byte; em byte, portanto devemos ler o índice 0 para converter o valor.
Já sabemos ler, agora vamos escrever e confirmar a mudança. Temos que escrever em bytes, o formato é bytes tanto para leitura quanto para escrita. Mas não dá pra ficar manipulando bits assim, precisamos fazer a conversão, certo? Mas o primeiro passo é entender como escrever um valor inteiro para o dispositivo. A ordem é essa:
Na primeira conversão dizemos que queremos 1 byte, na primeira ordem. Meio bagunçado escrever assim, particularmente não gosto, mas a outra forma não é tão melhor:
Ao menos parece mais intuitivo, não?
Agora vamos aos conceitos do PCF8574 para entender como manipular cada bit.
Mesmo que já conheça a base binária, preciso referenciar os conceitos.
Os bits são lidos da direita para a esquerda. Temos 8 pinos de IO para usar, portanto, 1 byte. Assim, os valores para cada um dos pinos é:
128 64 32 16 8 4 2 1.
Suponhamos que todos os pinos estejam em LOW: 0x00. Se quisermos levantar o primeiro e o último bit, devemos escrever o valor 129 para o PCF8574, É fácil entender; onde deve ficar em HIGH, basta colocar o valor 1. Se passar o valor em decimal, basta somar o valor que está na posição dos bits. Se quiser escrever diretamente em binário, também pode. Basta escrever em binário: 0b10000001. A leitura e escrita ficaria assim:
Fácil ou não? Mas...
Suponhamos que você já tenha colocado esses 2 bits em HIGH; o primeiro e o oitavo bit, portanto, o valor atual no PCF8574 é 129. Se quisermos levantar o bit no quarto pino, devemos escrever o valor 8. Só que já existem 2 bits em HIGH, se escrevermos 8 diretamente, os demais pinos irão para o estado 0. Como mantê-los em HIGH? Dá pra fazer de dois modos, sendo que o segundo modo que exemplificarei é o que chamo de "limpo". O primeiro é mais fácil de entender, então vamos lá (não esperava que aumentar os GPIO na Raspberry fosse transparente, certo?).
Essa forma, como citei, não é a melhor. Mas vai funcionar.
Pense que o valor de cada bit, do bit 7 ao bit 0 é 128, 64, 32, 16, 8, 4, 2, 1. Se desejássemos levantar os 3 primeiros bits precisaríamos somar 4+2+1:
A imagem desse ponto se perdeu na migração, mas o curso (gratuito) de Raspberry em nosso canal no Youtube tem tudo o que está nesse artigo.
bits_value = 4+2+1
i2c.writeto(34,bytes([bits_value])
Se quisermos então adicionar o sétimo bit, somamos mais 128. Se quisermos baixar o primeiro bit, subtraímos 1. Pra não precisar ficar fazendo esse tipo de contas, podemos criar um array correspondente à sequência de bits, mas pra ficar fácil de interpretar, montamos o array invertido:
pins = [1,2,4,8,16,32,64,128]
bits_value = pins[0] + pins[7] #1 + 128
i2c.writeto(34,bytes([bits_value])
E para baixar o pino, subtrair. Mas para subtrair será necessário validar que já não estava em LOW. Por exemplo, suponhamos que o bit 7 esteja em LOW e subtraímos 128, será o caos. Por isso a segunda solução é ideal, utiliza menos código (não vai precisar de condicionais pra avaliar os estados dos pinos) e é o que se utiliza normalmente:
Usando bitwise, manipulamos os bits sem preocupação com o estado atual. Podemos inverter um estado, levantar se já estiver em HIGH, baixar mesmo que já esteja em LOW e preservar os estados dos demais pinos usando "máscara".
Vamos ver cada uma das respectivas iterações e então voltamos ao exemplo anterior.
Com AND podemos apenas testar o valor do bit na posição desejada. Vamos iniciar com os 8 bits em LOW, e testar o sétimo bit:
valor_lido&(1<<7)
Isso não muda o valor, apenas o testa. Se o oitavo pino estiver em LOW, retornará 0, senão retorna o valor posicional binário.
Usando OR, levantamos o bit na posição indicada, não importa o valor anterior do pino. Para preservar o valor anterior, usamos a máscara:
valor_lido|(1<<0)
Com isso, teremos o valor anterior e adicionalmente o bit 0, que guarda o valor 1. Considerando o oitavo bit que já havíamos colocado em HIGH, significa que agora devemos ter um valor de 128+1:
Do mesmo modo, podemos fazer o contrário; baixar um pino independente do estado anterior.
valor&~(1<<7)
Considerando que o bit 7 e o bit 0 estejam levantados, com essa última instrução teremos apenas o bit 0 levantado, ou seja, o valor será 1.
Repare que o valor era 128, que é o valor do oitavo bit. No bit 0 o valor é 1. O que fizemos aqui foi levantar o bit, não escrever o valor. Quando levantamos o bit 0, temos o valor do bit 7 + o valor do bit 0, que dá 129. Para guardar o valor, reatribuímos o resultado à variável valor. Por fim, baixamos o bit com AND NOT. Com o bit 7 em LOW resta apenas o bit 0 em HIGH, que em decimal é 1.
Com XOR podemos inverter o valor do bit na posição desejada. Já fiz isso em algum artigo, exemplificando com o blink. A única coisa que muda é o símbolo, que passa a ser o circunflexo:
valor^(1<<7)
Veja o exemplo:
Para recapitulação, eis o que usamos:
AND | valor&(1<<7) | 128 | Testa o valor do bit na posição 7. |
OR | valor = valor | (1<<0) | 128+1 |
AND NOT | valor = valor&~(1<<7) | 129-128 | Baixa o bit na posição 0. |
XOR | valor = valor^(1<<0) | 0 | Inverte o valor do bit na posição 0. |
Dá pra fazer outros tipos de manipulação, deslocar com estouro de base etc. Tem muita aplicação, como por exemplo, em esteganografia.
Para não precisar montar um circuito, vou exemplificar usando o LED builtin da Raspberry Pi Pico, mas essa é a prova do bitwise, não da expansão de GPIO na Raspberry:
from machine import Pin
from time import sleep_ms as delay
builtin = Pin(25,Pin.OUT)
while True:
builtin.value(builtin.value()^1)
delay(500)
Isso faz um blink de 500ms de duração. Fácil ou não? Não sei se dará tempo de editar o vídeo a tempo dessa publicação, mas acompanhe a playlist de MicroPython em nosso canal no Youtube. Aproveite para se inscrever!
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.