IO Expander for ESP32, ESP8266, and Arduino

Published

Introduction: IO Expander for ESP32, ESP8266, and Arduino

About: Do you like technology? Follow my channel on Youtube and my Blog. In them I put videos every week of microcontrollers, arduinos, networks, among other subjects. New tutorials on Tuesdays and Fridays.

Would you like to expand the IOs of your ESP32, ESP8266, or Arduino? And have you thought about the possibility of 16 new GPIOs that can be controlled using the I2C bus? Well today, I'm going to introduce you to the GPIO expander MCP23016. Also, I’ll show you how to communicate a microcontroller with the MCP23016. I’ll also talk about creating a program where we’ll use only 2 pins of this microcontroller to communicate with the expander. We’ll use these for controlling the LEDs and the button.

Step 1: Introduction

The MCP23016 device provides 16 bits for GPIO expansion using the I2C bus. Each bit can be configured individually (input or output).

The MCP23016 consists of multiple 8-bit settings for input, output, and polarity selection.

The expanders provide a simple solution when the IOs are needed for switches, sensors, buttons, and LEDs, among other examples.

Step 2: Characteristics

  • 16 Input / Output pins (16 input standard)
  • Fast I2C bus clock frequency (0-400 kbits/s)
  • Three hardware address pins allow the use of up to eight devices
  • Interrupt Port Capture Recorder
  • Polarity reversing register for setting the polarity of the input port data
  • Compatible with most microcontrollers

Step 3: ESP01 Can Have 128 GPIOs!

An example that shows the magnitude of this expander is its use with ESP01, which can be connected to up to eight expanders with only two IOS, reaching 128 GPIOs.

Step 4: MCP23016

Here, we have the schematic of the expander, which has two groups of eight bits. This makes for a total of 16 ports. In addition to an interrupt pin, it has the CLK pin, which connects the capacitor and the resistor, which are internally connected in a logic port. This is to form the clock, using the idea of a crystal oscillator, which needs 1MHz clock. The TP pin is used to measure the clock. Pins A0, A1, and A2 are binary addresses.

Step 5: CLOCK

The MCP23016 therefore uses an external RC circuit to determine the speed of the internal Clock. An internal clock of 1 MHz is required (usually) for the device to work properly. The internal clock can be measured on the TP pin. The recommended values for REXT and CEXT are shown below.

Step 6: Address

To define the address of the MCP23016, we then use pins A0, A1, and A2. Just leave them at HIGH or LOW for the address change.

The address will be formed as follows:

MCP_Address = 20 + (A0 A1 A2)

Where A0 A1 A2 can take HIGH / LOW values, this forms a binary number from 0 to 7.

For example:

A0> GND, A1> GND, A2> GND (means 000, then 20 + 0 = 20)

Or else,

A0> HIGH, A1> GND, A2> HIGH (meaning 101, then 20 + 5 = 25)

Step 7: Commands

Below is a table with the commands for communication. Let's use GP0 and GP1, as well as IODIR0 and IODIR1.

Step 8: Categories:

GP0 / GP1 - Data Port Registers

There are two registers that provide access to the two GPIO ports.

The register reading provides the status of the pins on that port.

Bit = 1> HIGH Bit = 0> LOW


OLAT0 / OLAT1 - Output LACTCH REGISTERS

There are two registers that provide access to the output ports of the two ports.

IPOL0 / IPOL1 - Input Polarity Registers

These registers allow the user to configure the polarity of the input port data (GP0 and GP1).

IODIR0 / IODIR1

There are two registers that control the pin mode. (Input or Output)

Bit = 1> INPUT Bit = 0> OUTPUT


INTCAP0 / INTCAP1 - Interrupt Capture Registers

These are registers that contain the value of the port that generated the interrupt.

IOCON0 / IOCON1 - I / O Expander Control Register

This controls the functionality of the MCP23016.

Setting bit 0 (IARES> Interrupt Activity Resolution) controls the sampling frequency of the GP port pins.

Bit0 = 0> (default) Maximum port activity detection time is 32ms (low-power consumption)

Bit0 = 1> maximum activity detection time on the port is 200usec (higher-power consumption)

Step 9: Structure for Communication

I show here the Wire class, which is the I2C communication in our core Arduino, which also allows the expander to work with the Arduino Uno and Mega. However, the latter already has several IOs. We deal here with the addresses of the chip, the access control, which are the codes of the registers, as well as the data.

Step 10: Program

Our program consists of communicating the ESP32 with the MCP23016 to have more GPIOs to use. We will then have a button and some LEDs connected to the MCP23016. We will control all of them using only the I2C bus. Thus, only two ESP32 pins will be used. You can see the picture circuit below in the video.

Step 11: ESP01

Here, I show the Pinout of ESP01.

Step 12: Mounting ESP01

In this example, we have the GPIO0 connected in the SDA, and the GPIO2 connected in the SCL. We also have a relay board, a buzzer, and an LED. On the other port, in GP1.0, we have one more LED with a resistor.

Step 13: NodeMCU ESP-12E

Here, we have the Pinout of the NodeMCU ESP-12E.

Step 14: Mounting NodeMCU ESP-12E

In this case, the only difference from the first example is that you have connected D1 and D2 in the SDA and SCL, respectively.

Step 15: WiFi NodeMCU-32S ESP-WROOM-32

Here's the Pinout of the WiFi NodeMCU-32S ESP-WROOM-32.

Step 16: WiFi Mounting NodeMCU-32S ESP-WROOM-32

This time, the main difference from the other two examples is the button, and the three blinking LEDs. Here, the SDA is connected to the GPIO19, while the SCL is connected to the GPIO23.

Step 17: Libraries and Variables

First, we’ll include Wire.h, which is responsible for i2c communication, as well as setting the i2c address of MCP23016. I show several commands, even some that we do not use in this project.

#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

Step 18: Setup

Here we have the functions to initialize four different types of microcontrollers. We also check the frequency, set up the GPIOs, and set the pins. In the Loop, we check the status of the button.

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

Step 19: ConfigurePort

In this step, we configure the mode of the GPIO pins and identify the mode of the ports.

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

Step 20: WriteBlockData & CheckButton

Here, we send data to the MCP23016 through the i2c bus, check the status of the button, and indicate the next step while taking into account the condition of being pressed or not.

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

Step 21: ReadPin & ValueFromPin

We deal here with the reading of a specific pin, and the return of the bit value to the desired position.

//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; }

Step 22: ESP8266 Program

From here, we will see how the program we used in ESP-01 and in the nodeMCU ESP-12E was created, which allows us to understand how differences between them are minimal.

We will only modify the line of the i2c communication constructor, which is the beginning method of the Wire object.

Just uncomment the line according to the plate that we are going to compile.

// Wire.begin(D2,D1); //nodemcu ESP8266
// Wire.begin(0,2); //ESP-01

Setup

Notice that the builder is still commented out. Therefore, uncomment according to your board (ESP-01 or 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

In the loop, we switch the pins every 1 second. Thus, when pin0 of GP0 is on, the pins of GP1 are off. When pin0 of GP1 is on, the GP0 pins are off.

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

Step 23: IMPORTANT

The variables and library used are the same as those of the program we did for ESP32, as well as the configurePort and writeBlockData methods.

Step 24: Files

Download the files:

PDF

INO (ESP8266)

INO (ESP32)

Share

    Recommendations

    • Casting Contest

      Casting Contest
    • Clocks Contest

      Clocks Contest
    • Planter Challenge

      Planter Challenge
    user

    We have a be nice policy.
    Please be positive and constructive.

    Tips

    Questions

    Comments