Quer expandir os IOs do seu
ESP32, ESP8266 ou Arduino? E que tal 16 novos GPIOs que serão controlados
utilizando o barramento I2C? Então, hoje vou te apresentar o expansor de GPIO
MCP23016. Ainda neste texto, vou te mostrar como comunicar um microcontrolador
com o MCP23016 e criar um programa no qual utilizaremos apenas 2 pinos deste
microcontrolador para se comunicar com o expansor, isso para controlar LEDs e
botão.
Introdução
O dispositivo MCP23016 fornece
16 bits para expansão de GPIOs utilizando o barramento I2C. Cada bit pode ser
configurado individualmente (entrada ou saída).
O MCP23016 consiste em
múltiplas configurações de 8 bits para entrada, saída e seleção de polaridade.
Os expansores fornecem uma
solução simples quando os IOs são necessários para interruptores, sensores,
botões, LEDs, entre outros exemplos.
Características
- 16 pinos de Entrada / Saída (padrão 16 entradas)
- Frequência rápida do clock do barramento I2C (0-400 kbits/s)
- Três pinos de endereço de hardware permitem o uso de até oito dispositivos
- Registrador de captura de porta de interrupção
- Registrador de inversão de polaridade para configurar a polaridade dos dados da porta de entrada
- Compatível com a maioria dos microcontroladores
ESP01 pode ter 128 GPIOs!
Um exemplo que mostra a
magnitude deste expansor é o uso com o ESP01, que, com apenas dois IOs poderá
ser ligado a até oito expansores, chegando a 128 GPIOs.
MCP23016
Aqui temos o esquemático do
expansor, que tem dois grupos de oito bits, ou seja, um total de 16 portas.
Além de um pino de interrupção, ele tem o pino CLK, que é para ligar o
capacitor e o resistor, que internamente estão ligados em uma porta lógica.
Isso é para formar o clock, utilizando a ideia de um cristal oscilador, que
precisa de 1MHz de clock. O pino TP serve para medir o clock. Os pinos A0, A1 e
A2 são endereçamentos binários.
CLOCK
O MCP23016, portanto, usa um
circuito RC externo para determinar a velocidade do Clock interno. É necessário
um clock interno de 1 MHz (normalmente) para o dispositivo funcionar
corretamente. O clock interno pode ser medido no pino TP. Os valores
recomendados para o REXT e CEXT são mostrados abaixo.
Endereço
Para definir o endereço do
MCP23016, então, utilizamos os pinos A0, A1 e A2. Basta deixa-los em estado
HIGH ou LOW para a troca de endereço.
O endereço será formado da
seguinte forma:
MCP_Address = 20 + (A0 A1 A2)
Onde A0 A1 A2 podem tomar
valores HIGH/LOW, formando assim um número binário de 0 a 7.
Por exemplo:
A0 > GND,
A1 > GND, A2 > GND (significa 000, logo 20+0 = 20)
Ou então,
A0 > HIGH,
A1 > GND, A2 > HIGH (significa 101, logo 20+5 = 25)
Comandos
Abaixo segue uma tabela com os
comandos para comunicação. Vamos utilizar o GP0 e GP1, bem como o IODIR0 e o
IODIR1.
Acessos
GP0 / GP1 – Data Port
Registers
São dois registradores que
fornecem acesso às duas portas do GPIO.
A leitura do registrador
fornece o estado dos pinos naquela porta.
Bit = 1 > HIGH Bit = 0 > LOW
OLAT0 / OLAT1 – Output LACTCH
REGISTERS
São dois registradores que
fornecem acesso às travas de saída das duas portas.
IPOL0 / IPOL1 – Input Polarity
Registers
Esses registradores permitem
ao usuário configurar a polaridade dos dados da porta de entrada (GP0 e GP1).
IODIR0 / IODIR1
São dois registradores que
controlam o modo de pino. (Entrada ou Saída)
Bit = 1 > INPUT Bit = 0 >
OUTPUT
INTCAP0 / INTCAP1 – Interrupt
Capture Registers
São registradores que contém o
valor da porta que gerou a interrupção.
IOCON0 / IOCON1 – I/O Expander
Control Register
Controla a funcionalidade do
MCP23016.
Setando o bit 0 (IARES >
Interrupt Activity Resolution) controla a frequência de amostragem dos pinos da
porta GP.
Bit0 = 0 > (default) tempo
máximo de detecção de atividade na porta é 32ms (baixo consumo de energia)
Bit0 = 1 > tempo máximo de
detecção de atividade na porta é 200usec (maior consumo de energia)
Estrutura para a comunicação
Aqui mostro a classe Wire, que
é a comunicação I2C no nosso core Arduino, o que permite também o funcionamento
do expansor com o Arduino Uno e o Mega, embora este último já conte com
diversos IOs. Tratamos aqui, então, do endereço do chip, do comando de acesso,
ou seja, os códigos dos registradores, bem como os dados.
Programa
Nosso programa consiste em
comunicar o ESP32 com o MCP23016, a fim de termos mais GPIOs para utilizar.
Teremos, então, um botão e alguns LEDS conectados ao MCP23016. Controlaremos
todos eles utilizando apenas o barramento I2C, ou seja, apenas DOIS pinos do
ESP32 serão utilizados. Você pode ver o circuito da imagem abaixo em funcionamento
no vídeo.
ESP01
Deixo aqui o Pinout do ESP01.
Montagem ESP01
Neste exemplo temos o GPIO0
ligado no SDA e o GPIO2 ligado no SCL. Temos ainda uma placa de relé, um buzzer
e um LED. Na outra porta, no GP1.0, temos mais um LED com resistor.
NodeMCU ESP-12E
Aqui, o Pinout do NodeMCU
ESP-12E.
Montagem NodeMCU ESP-12E
Neste caso, a única diferença
do primeiro exemplo está no fato de ter ligado o D2 e D1 no SDA e SCL,
respectivamente.
WiFi NodeMCU-32S ESP-WROOM-32
Eis o Pinout do WiFi
NodeMCU-32S ESP-WROOM-32.
Montagem WiFi NodeMCU-32S ESP-WROOM-32
Desta vez, a diferença
principal dos outros dois exemplos está no botão e nos três LEDs que piscam.
Aqui, o SDA está ligado no GPIO19, enquanto o SCL está ligado ao GPIO23.
Bibliotecas e variáveis
Primeiramente, vamos incluir o
Wire.h, responsável pela comunicação i2c, bem como definir o endereço i2c do
MCP23016. Deixo ainda diversos comandos, até mesmo alguns que não utilizamos
neste projeto.
No vídeo eu explico que o valor tem que ser HEXADECIMAL. Porém, preciso pontuar melhor: o valor pode ser inteiro ou binário, mas deve-se ter a correspondência correta.
Por exemplo: 0x20 em hexadecimal é o mesmo que 32 em decimal ou B00100000.
No vídeo eu explico que o valor tem que ser HEXADECIMAL. Porém, preciso pontuar melhor: o valor pode ser inteiro ou binário, mas deve-se ter a correspondência correta.
Por exemplo: 0x20 em hexadecimal é o mesmo que 32 em decimal ou B00100000.
#include <Wire.h> // specify use of Wire.h library. //endereço I2C do MCP23016 #define MCPAddress 0x20 // COMMAND BYTE TO REGISTER RELATIONSHIP : Table: 1-3 of Microchip MCP23016 - DS20090A //ENDEREÇOS DE REGISTRADORES #define GP0 0x00 // DATA PORT REGISTER 0 #define GP1 0x01 // DATA PORT REGISTER 1 #define OLAT0 0x02 // OUTPUT LATCH REGISTER 0 #define OLAT1 0x03 // OUTPUT LATCH REGISTER 1 #define IPOL0 0x04 // INPUT POLARITY PORT REGISTER 0 #define IPOL1 0x05 // INPUT POLARITY PORT REGISTER 1 #define IODIR0 0x06 // I/O DIRECTION REGISTER 0 #define IODIR1 0x07 // I/O DIRECTION REGISTER 1 #define INTCAP0 0x08 // INTERRUPT CAPTURE REGISTER 0 #define INTCAP1 0x09 // INTERRUPT CAPTURE REGISTER 1 #define IOCON0 0x0A // I/O EXPANDER CONTROL REGISTER 0 #define IOCON1 0x0B // I/O EXPANDER CONTROL REGISTER 1
Setup
Temos aqui as funções para
inicializar quatro diferentes tipos de microcontroladores. Ainda, checamos a
frequência, configuramos os GPIOs e setamos pinos. No Loop, verificamos o
estado do botão.
void setup() { Serial.begin(9600); delay(1000); Wire.begin(19,23); //ESP32 // Wire.begin(D2,D1); //nodemcu ESP8266 // Wire.begin(); //arduino // Wire.begin(0,2);//ESP-01 Wire.setClock(200000); //frequencia //configura o GPIO0 como OUTPUT (todos os pinos) configurePort(IODIR0, OUTPUT); //configura o GPIO1 como INPUT o GP1.0 e como OUTPUT os outros GP1 configurePort(IODIR1, 0x01); //seta todos os pinos do GPIO0 como LOW writeBlockData(GP0, B00000000); //seta todos os pinos do GPIO1 como LOW writeBlockData(GP1, B00000000); } void loop() { //verifica e o botão GP foi pressionado checkButton(GP1); } // end loop
configurePort
Nesta etapa, configuramos o
modo dos pinos GPIO e identificamos o modo das portas.
//configura o GPIO (GP0 ou GP1) //como parametro passamos: //port: GP0 ou GP1 //custom: INPUT para todos as portas do GP trabalharem como entrada // OUTPUT para todos as portas do GP trabalharem como saida // custom um valor de 0-255 indicando o modo das portas (1=INPUT, 0=OUTPUT) // ex: 0x01 ou B00000001 ou 1 : indica que apenas o GPX.0 trabalhará como entrada, o restando como saida void configurePort(uint8_t port, uint8_t custom) { if(custom == INPUT) { writeBlockData(port, 0xFF); } else if(custom == OUTPUT) { writeBlockData(port, 0x00); } else { writeBlockData(port, custom); } }
writeBlockData & checkButton
Aqui, enviamos dados para o
MCP23016 através do barramento i2c, verificamos o estado do botão e indicamos o
próximo passo levando em conta a condicional de ser pressionado ou não.
//envia dados para o MCP23016 através do barramento i2c //cmd: COMANDO (registrador) //data: dados (0-255) void writeBlockData(uint8_t cmd, uint8_t data) { Wire.beginTransmission(MCPAddress); Wire.write(cmd); Wire.write(data); Wire.endTransmission(); delay(10); }
//verifica se o botão foi pressionado //parametro GP: GP0 ou GP1 void checkButton(uint8_t GP) { //faz a leitura do pino 0 no GP fornecido uint8_t btn = readPin(0,GP); //se botão pressionado, seta para HIGH as portas GP0 if(btn) { writeBlockData(GP0, B11111111); } //caso contrario deixa todas em estado LOW else{ writeBlockData(GP0, B00000000); } }
readPin & valueFromPin
Tratamos aqui da leitura de um
pino específico e do retorno do valor do bit na posição desejada.
//faz a leitura de um pino específico //pin: pino desejado (0-7) //gp: GP0 ou GP1 //retorno: 0 ou 1 uint8_t readPin(uint8_t pin, uint8_t gp) { uint8_t statusGP = 0; Wire.beginTransmission(MCPAddress); Wire.write(gp); Wire.endTransmission(); Wire.requestFrom(MCPAddress, 1); // ler do chip 1 byte statusGP = Wire.read(); return valueFromPin(pin, statusGP); } //retorna o valor do bit na posição desejada //pin: posição do bit (0-7) //statusGP: valor lido do GP (0-255) uint8_t valueFromPin(uint8_t pin, uint8_t statusGP) { return (statusGP &( 0x0001 << pin)) == 0 ? 0 : 1; }
Programa do ESP8266
A partir daqui vamos ver como
foi criado o programa que utilizamos no ESP-01 e no nodeMCU ESP-12E, o que nos
possibilita perceber como a diferença é mínima entre eles.
Modificaremos apenas a linha
do construtor da comunicação i2c, ou seja, o método begin do objeto Wire.
Basta descomentar a linha de
acordo com a placa que vamos compilar.
// Wire.begin(D2,D1); //nodemcu ESP8266 // Wire.begin(0,2); //ESP-01
Setup
Repare que o construtor ainda
está comentado. Sendo assim, descomente de acordo com sua placa (ESP-01 ou
nodeMCU ESP12-E).
void setup() { Serial.begin(9600); delay(1000); // Wire.begin(D2,D1); //nodemcu ESP8266 // Wire.begin(0,2); //ESP-01 Wire.setClock(200000); //frequencia //configura o GPIO0 como OUTPUT (todos os pinos) configurePort(IODIR0, OUTPUT); //configura o GPIO1 como OUTPUT (todos os pinos) configurePort(IODIR1, OUTPUT); //seta todos os pinos do GPIO0 como LOW writeBlockData(GP0, B00000000); //seta todos os pinos do GPIO1 como LOW writeBlockData(GP1, B00000001); }
Loop
No loop, estamos apenas
alternando a ligação dos pinos a cada 1 segundo, ou seja, quando o pino7 do GP0
estiver ligado, os pinos do GP1 estarão desligados. Quando o pino0 do GP1
estiver ligado, os pinos de GP0 estarão desligados.
void loop() { //seta o pino 7 do GP0 como HIGH e os demais como LOW writeBlockData(GP0, B10000000); //seta todos os pinos do GPIO1 como LOW writeBlockData(GP1, B00000000); delay(1000); //seta todos os pinos do GPIO0 como LOW writeBlockData(GP0, B00000000); //seta o pino 0 do GP1 como HIGH e os demais como LOW writeBlockData(GP1, B00000001); delay(1000); } // end loop
IMPORTANTE
As variáveis e a biblioteca
utilizadas são as mesmas do programa que fizemos para o ESP32, assim como os
métodos configurePort e writeBlockData.
Faça download dos arquivos:
15 Comentários
Parabéns pelo trabalho.
ResponderExcluirParabéns por mais um grande trabalho. Sempre estou seguindo o seu canal.
ResponderExcluirParabéns pelo trabalho, vai ser muito útil! Eu gostaria de saber como multiplicar as portas analógicas, tem como?.... Abraço!
ResponderExcluirParabéns, ótimo trabalho, sempre de olho.
ResponderExcluirBom dia Fernando
ResponderExcluirEstou fazendo um projetinho, e estou tendo um problema em relação a leitura de um pot.20k em uma entrada analógica, estou usando o stm32, o que acontece : o valor lido teria que ser 0 no centro do pot., -2500 no inicio e 2500 no final, porém o valor lido fica instável, segue o meu código para a sua apreciação:
#define MODE_POT PA0
pinMode(MODE_POT,INPUT_ANALOG); // pot.20k
digitalWrite(MODE_POT, LOW);
clarificador = map(analogRead(MODE_POT), 0, 4095, -4095, 4095);
//clarificador = clarificador * 10;
Serial.println(clarificador);
Ficaria muito grato pela sua ajuda
ratificando
ResponderExcluirclarificador = map(analogRead(MODE_POT), 0, 4095, -2500, 2500);
Olá, estou buscando algo e não encontrei a resposta ainda. Com esse MCP23016 eu consigo ler dados de sensores, como sensor dht22, ou um sensor de temperatura e humidade? Ou é apenas digital (0/1)?
ResponderExcluirCaro Paulo Coutinho, boa noite.
ExcluirJá fiz a mesma pergunta e ainda não obtive resposta, será que é tão fácil que os mais experintes não possam responder ?
Olá, estou buscando algo e não encontrei a resposta ainda. Com esse MCP23016 eu consigo ler dados de sensores, como sensor dht22, ou um sensor de temperatura e humidade? Ou é apenas digital (0/1)?
ResponderExcluir0 e 1
ExcluirBoa noite gostei do seu trabalho gostaria de uma informação teria como usar este circuito em leds mapeado tipo ws2812b e esp8266 com sistema artnet por exemplo 32 universo dmx
ResponderExcluiro link do código não esta disponível !!! alguém tem o novo link ?
ResponderExcluirobrigado
Pra eu conseguir expandir para os 128 GPIOs devo manter as ligações do A0, A1 e A2 no GND como na figura2 ou devo mudar o endereçamento alternando entre GND e +5v até finalizar a sequência? Alguém sabe tirar essa minha duvida?
ResponderExcluirSim, existe uma tabela que exemplifica a sequência
Excluirnesse post.
e possivel usar pwm neste expanssor ?
ResponderExcluir