Pular para o conteúdo

Expansor de IO PCF8575 e bitwise

pcf8575

Escrevi diversos artigos sobre o PCF8574 (como o “Dominando PCF8574 com Arduino“), que é um expansor de IO de 8 bits (ou se preferir, “um byte”). Hoje vamos ver o expansor de IO PCF8575, da mesma família, porém com 16 bits (ou, bem; 2 bytes).

Os jumpers de garrinha da imagem de destaque são da Mekanus. Já recomendei em outro artigo e, devido à dificuldade de encontrar, recomendarei sempre que possível. O analisador lógico que usei nesse artigo tem um vídeo próprio, com uma boa apresentação.

O que é um expansor de IO?

Se não conhece ainda, um expansor de IO aumenta o número de pinos disponíveis em uma MCU para, por exemplo, acionar relés, controlar display matricial etc. Esses displays de matriz 16×2 comumente já vem com um PCF8574 nele; se for I2C, pode ter certeza.

Podemos usar um expansor de IO não apenas como saída, mas também como entrada. Porém não é prático controlar um expansor de IO sem utilizar bitwise (por isso recomendo mais uma vez o tradicional artigo “Dominando PCF8574 com Arduino“). Se estiver utilizando o PCF8574 mas não estiver com disposição para aprender a manipular bits nesse momento, instale a biblioteca EasyPCF8574 , disponível no repositório do Arduino (e cuja biblioteca eu criei). Para esse expansor de 16 bits ainda estarei escrevendo a biblioteca, com oferecimento da Indústria Pontalti, que fabrica bojo de sutiã para grandes marcas. Bem, o que tem a ver sutiã? – Calma. É que estou fazendo uma automação meio sigilosa, mas algumas coisas genéricas poderão ser ofertadas aos makers, como a biblioteca para PCF8575 que devo escrever em uns 2 ou 3 dias após a publicação desse artigo.

Características do PCF8575

O expansor de IO PCF8575 possui (os já citados) 16 bits. Não é necessário controle de sinal e ele é chamado de quasi-bidirectional. Hábil com alta corrente, pode ser usado para alimentar LEDs.

Ao iniciá-lo, seu estado mantém todos os IOs em HIGH. Não precisamos nos preocupar com a sinalização usando a biblioteca Wire, nativa da API do Arduino (bastando fazer seu include, como exemplificado mais adiante).

Essa placa tem um pino de interrupção também, que pode ser conectado à MCU e tratado no código como interrupção, para qualquer mudança de estado – isso significa que escrita pelo seu programa também gerará interrupção, portanto a ISR deverá tratar o evento antes de agir.

16 bits são 2 bytes. O primeiro byte refere à porta 0, que vai de P07 à P00. O segundo byte refere à porta 1, de P17 à P10. Não se preocupe com essas informações, porque o artigo exemplifica o uso e o vídeo demonstra o conceito. Apenas leia para ter o conhecimento e tudo ficará claro mais adiante.

Podemos usar até 8 expansores de IO PCF8575 interconectados, totalizando 16 bytes, totalizando 128 bits ! É IO demais!

Endereçamento

Podemos simplesmente usar um scan e descobrir o endereço do dispositivo, mas se quisermos interconectar módulos, é bom já saber previamente quais são os endereços possíveis. Abaixo, a tabela de endereços, retirada do datasheet da Texas Instruments:

Absolute Maximum Ratings

Essa tabela é o limite do limite. Não devemos usar como referência de normalidade. Por exemplo, VCC pode ir de -0.5 à 6.5, mas claro, vamos usar 3v3 ou 5v, considerando que se houver uma variação, não afetará o funcionamento do dispositivo.

Ele oferece até 50mA de corrente de saída. Podemos conectar 2 LEDs sim, desde que não ultrapasse 40mA, mas também não vejo benefício em usar um expansor de IO para acender LEDs, já que podemos usar multiplexadores, que é mais adequado para esse propósito.

A recomendação de operação é entre 2.5v e 5.5v, além de não mais de 25mA. Para constar, ele trabalha no barramento I2C na velocidade de 400kHz e tem tempo de resposta de 4us.

bitwise – Rememorando

Antes de seguirmos com bitwise, tenha em mente que o expansor de IO PCF8575 é I2C, portanto usa 2 IOs da MCU, além de GND e VCC. Com um PCF8575 você terá 16 IOs. Tirando os 2 que usou da MCU para fazer a interface, o aumento de IOs de fato é de 14 pinos. “Mas”, podemos serializar dispositivos I2C, bastando mudar seu endereço. Para fazê-lo, bastar fechar um ou mais jumper na parte de trás da placa. Lindo ou não?

Wire

Para fazer a comunicação, usamos a biblioteca Wire. No expansor de IO PCF8575 os bytes são escritos de forma sequencial. Isso é, se enviarmos 2 bytes, ele escreverá para a primeira porta, então para a segunda. Se escrevermos 3 bytes, ele escreverá na primeira porta, segunda porta e então sobrescreverá o valor da primeira porta. Essa informação é “fundamental” para debug, porque podemos estar escrevendo um valor que ultrapasse os 2 bytes e então o comportamento será inesperado. Se ocorrer algo do tipo, depure escrevendo os bytes, então leia-os em seguida para saber que bits foram manipulados. Use uma máscara e compare o valor passado e o valor escrito.

Uma mensagem de envio poderia seguir um desses formatos:

#include <Wire.h>

void setup(){
    //sempre deve ser inicializado
    Wire.begin();
    
    //Uma escrita em binário:
    Wire.beginTransmission(0x20); //O endereço do PCF. Depois explico como pegar
    Wire.write(0b10000000); //Escrita em binário
    //ou..
    Wire.write(0xF0);
    //ou..
    Wire.write(128);
}

Aparentemente é mais fácil usar o binário, porém 16 bits teriam uma representação demasiadamente longa e de difícil leitura. Assim sendo, o mais fácil seria “empurrar” o bit para a posição desejada, lembrando que isso ocorre da direita para a esquerda a partir da posição 0:

Wire.write(1<<7);

Ou seja, não precisamos saber o valor, apenas onde queremos colocar o bit em HIGH.

Preservar os valores

Supondo que erguemos o bit 7 (outra vez, lembrando que começa em 0) e agora queremos levantar o bit 1. Não basta fazer isso:

Wire.write(1);

A razão é que o PCF8575 (ou o predecessor, PCF8574) escreve bytes. Para preservar o valor anterior e levantar o bit 0, também não precisamos ter uma variável para guardar isso. Basta fazer assim:

buf = buf|(1<<0)

“Mas isso não é uma variável?” – Claro que sim, no exemplo. Mas podemos ler o valor, modificar e reescrever diretamente. Mostro no vídeo.

Baixar um bit

Se desejar colocar um bit em LOW, pode parecer que é só trocar o “1” por “0”, mas seria um engano. Para baixar agora o bit 7, fazemos:

buf = buf&~(1<<7)

Não é complicado e, sempre que esquecer, basta fazer um teste rápido (ou consultar esse artigo – coisa que obviamente eu prefiro).

Inverter um bit

Se quisermos fazer um blink, não temos compromisso com o estado do pino em nenhum momento, apenas queremos que o valor de inverta dentro de um período. Para isso, usamos outro operador. Por exemplo, em loop() nós poderíamos fazer assim para fazer blink no bit 0:

void loop(){
    buf = buf^(1<<7);
    Wire.beginTransmission(0x20);
    Wire.write(buf);
    Wire.endTransmission();
}

Testar um bit

A maneira mais simples de testar se um bit está em HIGH ou LOW é assim:

buf&(1<<7)

Com essas operações, podemos facilmente interagir com o expansor de IO PCF8575 ou seu antecessor.

Tabela

Para ter uma referência rápida, podemos consultar a tabela a seguir:

OPERADOROPERAÇÃORETORNOAÇÃO
AND7&(1<<0)1Testa o valor do bit na posição 0. (00000111)
OR6|(1<<0)7Levanta o bit na posição 0. (00000111)
AND NOT7&~(1<<0)6Baixa o bit na posição 0. (00000110)
XOR7^(1<<0)6 Inverte o valor do bit na posição 0 (se 1, vira zero e vice-versa).

Como ler o expansor de IO PCF8575?

É quase igual o PCF8574, mas tem mais bits. Lá pro começo do artigo vimos como escrever um valor no expansor de IO, agora vamos ver como é feita a leitura.

Wire.requestFrom(pcf_addr, num_of_bytes);

Fácil ou não? Simplesmente usamos a função requestFrom, passando o endereço do PCF (nos exemplos aqui, 0x20) e o número de bytes a ser lido. No caso do expansor de IO PCF8575, podemos ler 2 bytes, que correspondem a 16 bits.

O código para escrita com diversos detalhes pode ser visto no artigo “ESP32 com PCF8575” (publicado 2 artigos após esse).

Encontrar o endereço I2C do dispositivo

A placa vem com o endereço padrão 0x20, mas fica a dica para quando precisar encontrar o endereço de outro dispositivo.

Já escrevi sobre esse scanner I2C, há algum tempo já tem um sketch nos exemplos da IDE do Arduino e hoje vou recomendar esse link do Arduino.cc. Mas se você tiver um dispositivo qualquer com MicroPython, pode seguir o exemplo do artigo “Factory Defaults“. Se não for uma RPi Pico, mudará o pinout, mas o código será o mesmo.

Como preciso escrever uma biblioteca para esse dispositivo, vou começar os testes com MicroPython na Raspberry Pi Pico mesmo, que acaba sendo uma ótima ferramenta para teste e planejamento.

Leitura do PCF8575

Primeiro, vamos a uma leitura. Conforme a descrição, o dispositivo inicia com todos os pinos em HIGH. Vejamos:

Ok, lidos os 2 bytes do endereço 0x20. Tudo em HIGH. Qual é o LSB?

No datasheet vemos que o primeiro byte está à esquerda e o segundo byte à direita. Os bits sempre são lidos da direita para a esquerda, por isso temos o bit P00 na oitava posição e o bit 17 na nona.

No artigo “Dominando PCF8574” eu mostro a interação com o modelo de 1 byte, sem biblioteca. Para Arduino (ou qualquer MCU em que estiver usando C++) fiz uma biblioteca que está disponível no repositório oficial; a EasyPCF8574.

Sendo meu primeiro contato com o PCF8575, antes de escrever uma biblioteca, precisaria testar a leitura e verificar os bits correspondentes. Para isso, usei a RPi Pico como analisador de protocolos e sinais. Se não leu o artigo, aproveite para fazer seu analisador lógico com a RP Pico porque sai muito mais em conta que comprar um analisador de grife.

Escrita no PCF8575

A leitura é aquela mais acima. Para escrever, sempre precisamos preservar o valor anterior, se for necessário manter o estado dos pinos. Por essa razão iniciei o artigo discorrendo sobre bitwise.

Basicamente, apontamos o endereço e passamos uma lista de bytes – No caso, 2 bytes. Coloquei o analisador lógico no bit 0 e no 15, então fiz uma captura usando o PulseView (“tem” que seguir o artigo supracitado para fazer seu analisador lógico funcionar no PulseView). Iniciei a escrita baixando todos os pinos:

i2c.writeto(0x20,bytes([0,0]))

Então iniciei a captura de 5 mil amostras na frequência padrão do programa e seguidamente, levantei os bits 0 e 15:

i2c.writeto(0x20,bytes([1,128]))

A leitura com mudança de nível lógico (canal 12 e 16):

Tendo constatado a ordem dos bits, agora é só desenvolver a biblioteca. Logo trarei novidades, acompanhe aqui! Ah, mas também farei vídeo, por isso peço sua gentileza, inscrevendo-se em nosso canal DobitaobyteBrasil no Youtube e clicando no sininho para receber notificações!

Sair da versão mobile