banner

Ir para o Forum

Expansor de IOs para ESP32, ESP8266 e Arduino



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.

#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:


8 comentários:

  1. Parabéns por mais um grande trabalho. Sempre estou seguindo o seu canal.

    ResponderExcluir
  2. Parabéns pelo trabalho, vai ser muito útil! Eu gostaria de saber como multiplicar as portas analógicas, tem como?.... Abraço!

    ResponderExcluir
  3. Parabéns, ótimo trabalho, sempre de olho.

    ResponderExcluir
  4. Bom dia Fernando
    Estou 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

    ResponderExcluir
  5. ratificando
    clarificador = map(analogRead(MODE_POT), 0, 4095, -2500, 2500);

    ResponderExcluir
  6. 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)?

    ResponderExcluir
  7. 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)?

    ResponderExcluir

Tecnologia do Blogger.