Manual
do
Maker
.
com
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.
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.
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 |
Para utilizar a biblioteca Wire e uma comunicação simples, poucos passos são necessários:
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.
Para ler valores do PCF8574, os passos são um pouco mais elaborados, mas nada complicados também. Basicamente você precisa:
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.
É 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 |
É 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.
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:
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.
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);
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);
}
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 |
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.
Revisão: Ricardo Amaral de Andrade
Inscreva-se no nosso canal Manual do Maker no YouTube.
Também estamos no Instagram.
Autor do blog "Do bit Ao Byte / Manual do Maker".
Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.