Manual

do

Maker

.

com

Dominando o PCF8574 com Arduino

Dominando o PCF8574 com Arduino

O PCF8574 é um expansor de GPIO que utiliza barramento I2C para fazer a comunicação. Com ele, você consegue expandir 8 portas digitais e, dependendo do Arduino utilizado, não perde nenhuma porta digital para conectá-lo. Nesse artigo pretendo mostrar o máximo de detalhes para a utilização do PCF8574 com Arduino, para que você possa utilizá-lo sem receios.

PCF8574 com Arduino

Já escrevi diversos artigos sobre a utilização do PCF8574 em outras plataformas, e com Arduino escrevi apenas um artigo onde ele é utilizado para um display LCD 16x2, mas nesse caso tem uma biblioteca que abstrai tanto que você sequer vê como a comunicação é feita. Não que utilizar diretamente a biblioteca Wire te deixe na camada de hardware, mas nesse caso você faz a comunicação diretamente com o dispositivo e isso é bem divertido.

Pinos I2C Arduino

Cada Arduino utiliza o I²C (que se lê "ái isquéred cí") em pinos diferentes. Então, para tornar esse artigo o mais genérico possível, vamos ver primeiramente quais pinos utilizar:

BoardPinos
UnoA4 para SDA, A5 para SCL
Mega20 para SDA, 21 para SCL
Leonardo2 para SDA, 3 para SCL - ou pinos SDA e SCL
Due20 para SDA, 21 para SCL - ou pinos SDA1 e SCL1

Biblioteca Wire

Para utilizar a biblioteca Wire e uma comunicação simples, poucos passos são necessários:

  • Incluir a biblioteca
  • Inicializá-la
  • Iniciar transmissão ou recepção

Transmissão de dados

Esse é o passo mais simples. Se pretende apenas colocar pinos em HIGH ou LOW, o processo é bastante direto.

#include <Wire.h>

void setup(){
   Wire.begin();

   Wire.beginTransmission(0x20);
   //nas versões mais atuais, Wire.write()
   Wire.send(0b00000001);
   Wire.endTransmission()
}

void loop(){
   
}

Esse código manipula 1 bit dos 8 bits disponíveis. É visualmente a maneira mais simples de interagir com os bits da PCF8574, mas nem sempre é o melhor.

Recepção de dados

Para ler valores do PCF8574, os passos são um pouco mais elaborados, mas nada complicados também. Basicamente você precisa:

  • Requisitar dados
  • Garantir a comunicação
  • Ler os dados

Para fazer a requisição, utiliza-se a função requestFrom(endereço, quantidade) e em alguns dispositivos é possível utilizar um parâmetro extra, que é um boolean pra finalizar a comunicação. Mas para que seja genérico, vamos utilizar apenas esses 2 parâmetros e finalizar a comunicação com uma linha extra de código.

#include <Wire.h>
byte data = 0;

void setup(){
    Serial.begin(9600);
    Wire.begin();
    Wire.requestFrom(0x20,1); // 1 Byte
    if (Wire.available()){
        //nas versões mais atuais, Wire.read()
        data = Wire.receive();
    }
    Serial.println(data);
}

Repare que não foi necessário finalizar a comunicação para a leitura. Como estamos lendo de um dispositivo que possui apenas 8 bits, lemos apenas 1 Byte. Mas a resposta não virá formatada do modo que fizemos para escrever. A contagem dos bits é feita da direita para a esquerda, a partir do bit 0. Supondo que o bit 4 esteja em HIGH, o resultado será 16. Até aí é fácil identificar o bit porque é um número correspondente a uma posição específica. Mas supondo que fossem 2 bits; o bit 4 e o bit 3. O resultado da leitura seria 24. Para identificar as respectivas posições, é necessário saber manipular bits. A partir daqui, considere que todos os pinos estão em HIGH e para "drivar" é necessário colocá-lo em LOW.

Tabela de endereçamentos

É possível conectar até 3 módulos PCF8574. Os endereços disponíveis são 0x20, 0x22 e 0x24. A tabela abaixo mostra 3 dispositivos conectados, cada qual com a respectiva combinação de bits. No endereço 0x20, os bits dos pinos 4 e 6 estão em HIGH. Desse modo, temos o valor decimal 40 como resultado da leitura desse endereço 0x20.

Addr1286432168421
0xF00x400x200x100x080x040x020x01
0x20
0x22
0x24
87654321

Como utilizar bitwise para manipular bits

É desse modo que devemos manipular os bits da PCF8574, porque nem sempre temos todos os estados em memória. Além disso, é a maneira mais simplificada em relação a processamento, pois você economizará muitas linhas de código e por consequente, instruções.

Mudar o estado de um pino sem alterar os demais

Cada vez que uma instrução é escrita, todos os bits são reconfigurados, pois não têm uma memória de armazenamento de estados no dispositivo. Para controlar os estados, um procedimento básico seria:

  • ler os bits
  • alterar os bits desejados preservando os estados dos demais pinos
  • escrever o resultado da manipulação

Para tal, cria-se um buffer que armazena o valor do PCF8574 e trabalha-se sobre esse mesmo buffer, para depois escrevê-lo novamente no dispositivo, já modificado. Por exemplo, para acender um LED conectado no bit 2 (posição 3 - lembre-se de que a contagem de bits começa da posição 0), o comando seria:

buf = buf&~(1<<2);

20160702_223718-300x117.webp

Esse comando está invertendo o estado do terceiro bit. Supondo agora que queiramos acender um LED conectado à posição 1 (correspondente ao bit 0).

buf = buf&~(1<<0);

A partir daí temos 2 LEDs acessos. E se fosse para apagar o LED que acendemos no bit 2? Simples, sem precisar fazer nenhuma operação condicional manual. Apenas troque '&' por '|' (isso é um pipe, não um L). Isso inverte o valor do bit 2 e, para preservar os demais bits nessa mudança de estado, fazemos o seguinte:

buf = buf|(1<<2);

Simples ou não? Agora vamos fazer algo mais complexo; acender os LEDs 2, 4 e 6:

buf = 0xff&~(1<<1|1<<3|1<<5);

20160702_223633-300x115.webp

Perceba que esse é um estado inicial. O valor inicial é 0xFF, que corresponde a 255, ou seja, a soma de todos os bits:

128+64+32+16+8+4+2+1 = 0b11111111.

Máscara

A primeira parte da linha antes de '&' se chama máscara. A máscara nesse caso foi a soma dos 8 bits, mas anteriormente utilizamos os valores que foram lidos dos estados dos pinos do PCF8574. A máscara é como uma "sombra" dos dados pré-existentes, dos quais serão aplicadas as modificações. Por exemplo: uma configuração pré-existente está com a máscara 0b010. Você não sabe o que tem lá, mas não pode mexer nos valores atuais pois eles podem estar mantendo um motor ligado, por exemplo. Pretendendo modificar o bit 0b0 para 0b1 sem mexer no restante, aplica-se a máscara e a modificação desejada.

buffer = valorAtualLido&~(1<<2);

Tudo em LOW, modificando para HIGH

Se fosse ao contrário, considerando os pinos em LOW e querendo mudar seu estado para HIGH:

buf = 64;
buf = buf|(1<<2);
//o resultado acima será 68, pois o terceiro bit corresponde a 4.
//Para desligar o terceiro bit agora:
buf = buf&~(1<<2);
//agora o valor volta a ser 64

Se ainda não conhece a base binária, é bom que estude isso, lhe será fundamental na manipulação de bits.

Como exemplo, vou pegar um código que fiz no artigo Como expandir GPIO no Digispark.

#include <Wire.h>

void setup()
{
  Wire.begin();
}

void loop()
{
  Wire.beginTransmission(0x20);
  Wire.write(0b00000000);
  Wire.endTransmission();
  delay(1000);

  Wire.beginTransmission(0x20);
  Wire.write(0b00000001);
  Wire.endTransmission();
  delay(1000);

  Wire.beginTransmission(0x20);
  Wire.write(0b00000010);
  Wire.endTransmission();
  delay(1000);

  Wire.beginTransmission(0x20);
  Wire.write(0b00000011);
  Wire.endTransmission();
  delay(1000);
}

Perceba que nesse código fiz tudo diretamente, sem compromisso com o estado anterior dos pinos. Agora vejamos o mesmo funcionamento preservando o estado dos pinos.

#include <Wire.h>

byte buf = 0;
void setup()
{
  Wire.begin();
}

void loop()
{  
  Wire.beginTransmission(0x20);
  Wire.send(0x00);
  Wire.endTransmission();
  delay(1000);

  Wire.requestFrom(0x20,1);
  if (Wire.available()){
    buf = Wire.receive();
  }
  
  buf = buf|(1<<0);
  
  Wire.beginTransmission(0x20);
  Wire.send(buf);
  Wire.endTransmission();
  delay(1000);
  
  buf = buf|(1<<0)&~(1<<1);

  Wire.beginTransmission(0x20);
  Wire.write(buf);
  Wire.endTransmission();
  delay(1000);

  buf = buf|(1<<0);
  Wire.beginTransmission(0x20);
  Wire.write(buf);
  Wire.endTransmission();
  delay(1000);
}

Revisão do bitwise

Para deixar "bem" claro, vou detalhar a utilização do bitwise agora.

AND7&(1<<0)1Testa o valor do bit na posição 0.
OR6(1<<0)7
AND NOT7&~(1<<0)6Baixa o bit na posição 0.
XOR7^(1<<0)6 Inverte o valor do bit na posição 0.

Tem mais, mas com isso já fazemos tudo da maneira mais simples possível, é só diversão!

O resultado deve ser o mesmo em ambos os casos, como pode ser visto nesse vídeo.

Enfim, esse post tem um objetivo maior, que será mostrado em um próximo artigo no qual utilizarei também o PCF8574.

Revisão: Ricardo Amaral de Andrade

Inscreva-se no nosso canal Manual do Maker no YouTube.

Também estamos no Instagram.

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.