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 16×2, 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:
Board | Pinos |
Uno | A4 para SDA, A5 para SCL |
Mega | 20 para SDA, 21 para SCL |
Leonardo | 2 para SDA, 3 para SCL – ou pinos SDA e SCL |
Due | 20 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.
Addr | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
0xF0 | 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01 | |
0x20 | ||||||||
0x22 | ||||||||
0x24 | ||||||||
8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
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);

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);



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.
AND | 7&(1<<0) | 1 | Testa o valor do bit na posição 0. |
OR | 6|(1<<0) | 7 | Levanta o bit na posição 0. |
AND NOT | 7&~(1<<0) | 6 | Baixa o bit na posição 0. |
XOR | 7^(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.
Inscreva-se no nosso newsletter, ali em cima à direita e receba novos posts por email.
Siga-nos no Do bit Ao Byte no Facebook.
Prefere twitter? @DobitAoByte.
Inscreva-se no nosso canal Do bit Ao Byte Brasil no YouTube.
Nossos grupos:
Arduino BR – https://www.facebook.com/groups/microcontroladorarduinobr/
Raspberry Pi BR – https://www.facebook.com/groups/raspberrybr/
Orange Pi BR – https://www.facebook.com/groups/OrangePiBR/
Odroid BR – https://www.facebook.com/groups/odroidBR/
Sistemas Embarcados BR – https://www.facebook.com/groups/SistemasEmbarcadosBR/
MIPS BR – https://www.facebook.com/groups/MIPSBR/
Do Bit ao Byte – https://www.facebook.com/groups/dobitaobyte/
Próximo post a caminho!
Revisão: Ricardo Amaral de Andrade
11 Comentários