i2c esp8266: how to, network 5v, 3.3v, speed and custom pins

Spread the love

I love the I2C protocol. When I need a sensor, every time I try to find one with this protocol, I have also written some libraries for various sensors that use I2C. So I want to write some articles explaining (Arduino, Arduino SAMD MKR, esp8266, and esp32) some interesting features, and I will try to explain how to solve the problems you can have when working with multiple I2C devices.

esp8266 Wemos i2c protocol: how to, network 5v 3v and address scanner
esp8266 Wemos i2c protocol: how to, network 5v 3v and address scanner

I use esp8266 in a lot of situations, and it’s one of the cheapest devices in commerce. In this text, we will delve into the specifics of the I2C protocol on the ESP8266, including the methods of communication between devices operating on different voltage levels, such as 5V and 3.3V. We will also learn how to manipulate the speed of communication and use custom pins to suit the requirements of our projects.

Introduction to the I2C protocol

I2C (Inter-Integrated Circuiteye-squared-C) and is alternatively known as I2C or IIC. It is a synchronousmulti-master, multi-slave, packet switched, single-ended, serial communication bus. Invented in 1982 by Philips Semiconductors. It is widely used for attaching lower-speed peripheral ICs to processors and microcontrollers in short-distance, intra-board communication. (cit. WiKi)

Speed

I2C supports 100 kbps, 400 kbps, and 3.4 Mbps. Some variants also support 10 Kbps and 1 Mbps.

ModeMaximum
speed
Maximum
capacitance
DriveDirection
Standard-mode (Sm)100 kbit/s400 pFOpen drainBidirectional
Fast-mode (Fm)400 kbit/s400 pFOpen drainBidirectional
Fast-mode Plus (Fm+)1 Mbit/s550 pFOpen drainBidirectional
High-speed mode (Hs)1.7 Mbit/s400 pFOpen drainBidirectional
High-speed mode (Hs)3.4 Mbit/s100 pFOpen drainBidirectional
Ultra Fast-mode (UFm)5 Mbit/sPush-pullUnidirectional

Interface

Like UART communication, I2C only uses two wires to transmit data between devices:

  • SDA (Serial Data) – The line for the master and slave to send and receive data.
  • SCL (Serial Clock) – The line that carries the clock signal (common clock signal between multiple masters and multiple slaves).

I2C is a serial communication protocol, so data is transferred bit by bit along a single wire (the SDA line).

Like SPI, I2C is synchronous, so the output of bits is synchronized to the sampling of bits by a clock signal shared between the master and the slave. The clock signal is always controlled by the master.

There will be multiple slaves and multiple masters and all masters can communicate with all the slaves.

i2c data packet
i2c data packet
  • Start: The SDA line switches from a high voltage level to a low voltage level before the SCL line switches from high to low.
  • Stop: The SDA line switches from a low voltage level to a high voltage level after the SCL line switches from low to high.
  • Address Frame: A 7 or 10 bit sequence unique to each slave that identifies the slave when the master wants to talk to it.
  • Read/Write Bit: A single bit specifying whether the master is sending data to the slave (low voltage level) or requesting data from it (high voltage level).
  • ACK/NACK Bit: Each frame in a message is followed by an acknowledge/no-acknowledge bit. If an address frame or data frame was successfully received, an ACK bit is returned to the sender from the receiving device.

Devices connections

i2c wiring one master one slave
i2c wiring one master one slave

Because I2C uses addressing, multiple slaves can be controlled by a single master. With a 7 bit address, 128 (27) unique addresses are available. Using 10 bit addresses is uncommon, but provides 1,024 (210) unique addresses. Up to 27 slave devices can be connected/addressed in the I2C interface circuit.

i2c wiring one master multiple slave
i2c wiring one master multiple slaves

Multiple masters can be connected to a single slave or multiple slaves. The problem with multiple masters in the same system comes when two masters try to send or receive data at the same time over the SDA line. To solve this problem, each master needs to detect if the SDA line is low or high before transmitting a message. If the SDA line is low, this means that another master has control of the bus, and the master should wait to send the message. If the SDA line is high, then it’s safe to transmit the message. To connect multiple masters to multiple slaves

i2c wiring multiple master multiple slave
i2c wiring multiple master multiple slave

esp8266 how to

First, I think It’s better if you read “WeMos D1 mini (esp8266), pinout, specs and IDE configuration“.

Here the esp8266 on Aliexpress esp-12 - Aliexpress esp-07

The ESP8266 is a low-cost Wi-Fi microchip with a full TCP/IP stack and microcontroller capability, produced by Espressif Systems in Shanghai, China.

It has a good set of interfaces:

This device has the same limitations, for first, It can’t work like a slave, but you can choice all pins you want for I2C.

WeMos D1 mini esp8266 pinout mischianti low resolution
WeMos D1 mini esp8266 pinout mischianti low resolution

As you can see in the image D1 and D2 are the default SCL and SDA pin.

Slave issue with not esp device

From 2.5.0 esp8266 core there is a support for SLAVE mode, but don’t work properly like explained here.

You can try if your chip works as a slave by adding these settings to your device.

	  Wire.pins(D2, D1);               // Default pins you can remove It

	  Wire.begin(0x12);                // join i2c bus with address 0x12
	  Wire.setClockStretchLimit(1500);
	  Wire.setClock(10000L);           // Max 50kHz

And It’s very important you can compile at 160Mhz CPU.

I’m doing some tests, and with WeMos D1 mini (China clone), when an Arduino UNO via logic converter, I obtain this result in i2c scan address.

Scanning... 
Unknow error at address 0x12
done

It finds the device but does not work properly. With Arduino MKR 1010 without a logic converter, I have this result.

Scanning...
I2C device found at address 0x25  !
I2C device found at address 0x60  !
I2C device found at address 0x6B  ! 
done

It finds a device with an address 0x25.

Network master-slave with clock management

Here the WeMos I use for these examples WeMos D1 mini - NodeMCU V2 V2.1 V3 - esp01 - esp01 programmer

You can get some basic concepts from the Arduino article “i2c Arduino: how to create network, parameters and address scanner”.

WeMos D1 esp8266 i2c master slave
WeMos D1 esp8266 i2c master slave

Here some esp8266 mic on WeMos D1 mini - NodeMCU V2 V2.1 V3 - esp01 - esp01 programmer

For slave configuration, you must limit the I2C speed to MASTER and to SLAVE, the limit on direct connection with WeMos D1 mini is 40kHz, so you must add this command after beginning.

	Wire.begin();          // join i2c bus (address optional for master)
	Wire.setClock(40000L); // Set speed at 40kHz

To send a parameter to the slave device, you must establish a connection, then send data and alert that the communication is ended.

	Wire.beginTransmission(0x12); 	// Start channel with slave 0x12
	Wire.write(GET_NAME);        	// send data to the slave
	Wire.endTransmission();       	// End transmission

The slave must register an event to manage this operation.

	Wire.onReceive(receiveEvent);    // register an event handler for received data

In the function set as a parameter, you must manage the data reception, I send one-byte length instruction, so the numBytes was 1, then you must only retrieve the one-byte information and cast in the format you need and save to the main scope of the sketch.

// function that executes whenever data is received by master
// this function is registered as an event, see setup()
void receiveEvent(int numBytes) {
	if (numBytes==1){
		int requestVal = Wire.read();

		Serial.print(F("Received request -> "));
		Serial.println(requestVal);
		request = static_cast<REQUEST_TYPE>(requestVal);
	}else{
		Serial.print(F("No parameter received!"));
	}
}
WeMos D1 esp8266 i2c master slave
WeMos D1 esp8266 i2c master slave

Now the Master is ready to request the data It needs.

	Wire.requestFrom(0x08, 14);    // request 14 bytes from slave device 0x08

The slave, to the request made from master, return the relative information.

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
	switch (request) {
		case NONE:
			Serial.println(F("Not good, no request type!"));
			break;
		case GET_NAME:
			Wire.write("esp8266       "); // send 14 bytes to master
			request = NONE;
			break;
		case GET_AGE:
			Wire.write((byte)45); // send 1 bytes to master
			request = NONE;
			break;
		default:
			break;
	}
}

Now the complete Master sketch:

/**
 * i2c network: send parameter to client and receive response
 * with data relative to the request
 *
 * by Renzo Mischianti <www.mischianti.org>
 *
 * https://mischianti.org
 *
 * esp8266 	<------> 	esp8266
 * GND					GND
 * D1					D1
 * D2					D2
 *
 */

#include <Wire.h>

enum REQUEST_TYPE {
	NONE = -1,
	GET_NAME = 0,
	GET_AGE
};

void setup() {
	Wire.begin();          // join i2c bus (address optional for master)
	Wire.setClock(40000L); // Set speed at 40kHz

	Serial.begin(9600);  // start serial for output

	while (!Serial){}

	Serial.println();
	Serial.println(F("Starting request!"));


	Wire.beginTransmission(0x12); 	// Start channel with slave 0x12
	Wire.write(GET_NAME);        		// send data to the slave
	Wire.endTransmission();       	// End transmission

	delay(1000); // added to get better Serial print

	Wire.requestFrom(0x12, 14);    // request 14 bytes from slave device 0x12

	while (Wire.available()) { // slave may send less than requested
	      char c = Wire.read(); // receive a byte as character
	      Serial.print(c);         // print the character
	}

	Serial.println();

	delay(1000); // added to get better Serial print

	Wire.beginTransmission(0x12); 	// Start channel with slave 0x12
	Wire.write(GET_AGE);        		// send data to the slave
	Wire.endTransmission();       	// End transmission

	delay(1000); // added to get better Serial print

	Wire.requestFrom(0x12, 1);    // request 1 bytes from slave device 0x12

	while (Wire.available()) { // slave may send less than requested
		int c = (int)Wire.read(); // receive a byte as character
		Serial.println(c);         // print the character
	}
	delay(1000); // added to get better Serial print

	Serial.println();

}

void loop() {
}

And now the complete Slave sketch:

/**
 * i2c network: send parameter to client and receive response
 * with data relative to the request. SLAVE SKETCH
 *
 * by Renzo Mischianti <www.mischianti.org>
 *
 * https://mischianti.org
 *
 * esp8266 	<------> 	esp8266
 * GND					GND
 * D1					D1
 * D2					D2
 *
 */

#include <Wire.h>

enum REQUEST_TYPE {
	NONE = -1,
	GET_NAME = 0,
	GET_AGE
};

void requestEvent();
void receiveEvent(int numBytes);

REQUEST_TYPE request = NONE;

void setup() {
//	  Wire.pins(D2, D1);

	  Wire.begin(0x12);                // join i2c bus with address 0x12
//	  Wire.setClockStretchLimit(1500);
//	  Wire.setClock(50000L);

	Serial.begin(9600);  // start serial for output

	while (!Serial){}

	Serial.println(F("Starting!!"));

	// event handler initializations
	Wire.onReceive(receiveEvent);    // register an event handler for received data
	Wire.onRequest(requestEvent);   // register an event handler for data requests
}

void loop() {
//  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
	switch (request) {
		case NONE:
			Serial.println(F("Not good, no request type!"));
			break;
		case GET_NAME:
			Wire.write("esp8266       "); // send 14 bytes to master
			request = NONE;
			break;
		case GET_AGE:
			Wire.write((byte)45); // send 1 bytes to master
			request = NONE;
			break;
		default:
			break;
	}
}

// function that executes whenever data is received by master
// this function is registered as an event, see setup()
void receiveEvent(int numBytes) {
	if (numBytes==1){
		int requestVal = Wire.read();

		Serial.print(F("Received request -> "));
		Serial.println(requestVal);
		request = static_cast<REQUEST_TYPE>(requestVal);
	}else{
		Serial.print(F("No parameter received!"));
	}
}

And now the Master serial output:

17:10:06: Starting request!
17:10:07: esp8266       
17:10:09: 45

and the Slave serial output:

17:10:06: Received request -> 0
17:10:08: Received request -> 1

I2C address scanner

One of the useful sketches when you use i2c is the Address scanner. This simple program tries to find all devices connected to the I2C bus.

#include <Wire.h>

void setup()
{
  Wire.begin();

  Serial.begin(9600);
  Serial.println("\nI2C Scanner");
}


void loop()
{
  byte error, address;
  int nDevices;

  Serial.println("Scanning...");

  nDevices = 0;
  for(address = 1; address < 127; address++ ) 
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16) 
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error==4) 
    {
      Serial.print("Unknow error at address 0x");
      if (address<16) 
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");

  delay(5000);           // wait 5 seconds for next scan
}

Network: one master and multiple slaves (3.3v and 5v logic)

When you try to connect heterogeneous devices like an Arduino UNO you probably must modify your code.

WeMos D1 esp8266 Arduino UNO i2c master multiple slave logic converter
WeMos D1 esp8266 Arduino UNO i2c master multiple slave logic converter

Here the logic level converter I use for this example Aliexpress

Here the Arduino UNO Arduino UNO - Arduino MEGA 2560 R3 - Arduino Nano - Arduino Pro Mini

First, you can set the clock stretch limit and set the I2C clock to 40kHz, and very important, you are going to compile the sketch with 160MHz of processor speed.

	  Wire.begin(0x12);                // join i2c bus with address 0x12
// ------ If you have throuble uncomment these lines --------
// ------ and compile with processor speed at 160Mhz --------
	  Wire.setClockStretchLimit(1500);
	  Wire.setClock(40000L);
// ----------------------------------------------------------

Clock Stretching

While control of the SCL line is the domain of the I2C master, an optional feature of the protocol allows slaves to temporarily control it to slow down transmission before it is ready to accept more data.

To stretch the clock, the slave device simply holds the SCL line down. In that state, the master device must wait for the clock rises back up to high before resuming transmission.

This feature is sometimes a source of trouble: not every device supports it, and a master device with no support for clock stretching will hit errors if a slave device attempts to hold the clock down.

esp8266 and Arduino UNO i2c master multiple slave with logic converter
esp8266 and Arduino UNO i2c master multiple slave with logic converter

Then the sketch becomes like the other, here the master.

/**
 * i2c network: send parameter to client and receive response
 * with data relative to the request
 *
 * by Renzo Mischianti <www.mischianti.org>
 *
 * https://mischianti.org
 *
 *
 * Arduino UNO <------> 	Logic converter <------> 	esp8266 Master <------> 	esp8266 Slave
 * GND						GND			GND				GND							GND
 * 5v						HV			LV				3.3v
 * A4						HV1			LV1				D1							D1
 * A5						HV2			LV2				D2							D2
 *
 */

#include <Wire.h>

enum REQUEST_TYPE {
	NONE = -1,
	GET_NAME = 0,
	GET_AGE
};

void setup() {
	Wire.begin();          // join i2c bus (address optional for master)
	Wire.setClock(40000L); // Set speed at 40kHz

	Serial.begin(9600);  // start serial for output

	while (!Serial){}

	Serial.println();
	Serial.println(F("Starting request!"));


	Wire.beginTransmission(0x08); 	// Start channel with slave 0x08
	Wire.write(GET_NAME);        		// send data to the slave
	Wire.endTransmission();       	// End transmission

	delay(1000); // added to get better Serial print

	Wire.requestFrom(0x08, 14);    // request 14 bytes from slave device 0x08

	while (Wire.available()) { // slave may send less than requested
	      char c = Wire.read(); // receive a byte as character
	      Serial.print(c);         // print the character
	}

	Serial.println();

	delay(1000); // added to get better Serial print
	Wire.beginTransmission(0x12); 	// Start channel with slave 0x12
	Wire.write(GET_NAME);        		// send data to the slave
	Wire.endTransmission();       	// End transmission

	delay(1000); // added to get better Serial print

	Wire.requestFrom(0x12, 14);    // request 14 bytes from slave device 0x12

	while (Wire.available()) { // slave may send less than requested
	      char c = Wire.read(); // receive a byte as character
	      Serial.print(c);         // print the character
	}

	Serial.println();

	delay(1000); // added to get better Serial print

	Wire.beginTransmission(0x08); 	// Start channel with slave 0x08
	Wire.write(GET_AGE);        		// send data to the slave
	Wire.endTransmission();       	// End transmission

	delay(1000); // added to get better Serial print

	Wire.requestFrom(0x08, 1);    // request 1 bytes from slave device 0x08

	while (Wire.available()) { // slave may send less than requested
		int c = (int)Wire.read(); // receive a byte as character
		Serial.println(c);         // print the character
	}
	delay(1000); // added to get better Serial print

	Serial.println();

	delay(1000); // added to get better Serial print

	Wire.beginTransmission(0x12); 	// Start channel with slave 0x12
	Wire.write(GET_AGE);        		// send data to the slave
	Wire.endTransmission();       	// End transmission

	delay(1000); // added to get better Serial print

	Wire.requestFrom(0x12, 1);    // request 1 bytes from slave device 0x12

	while (Wire.available()) { // slave may send less than requested
		int c = (int)Wire.read(); // receive a byte as character
		Serial.println(c);         // print the character
	}
	delay(1000); // added to get better Serial print

	Serial.println();

}

void loop() {
}

Here the slave Arduino UNO

/**
 * i2c network: send parameter to client and receive response
 * with data relative to the request. SLAVE SKETCH
 *
 * by Renzo Mischianti <www.mischianti.org>
 *
 * https://mischianti.org
 *
 * Arduino UNO <------> 	Logic converter <------> 	esp8266 Master <------> 	esp8266 Slave
 * GND						GND			GND				GND							GND
 * 5v						HV			LV				3.3v
 * A4						HV1			LV1				D1							D1
 * A5						HV2			LV2				D2							D2
 *
 */

#include <Wire.h>

enum REQUEST_TYPE {
	NONE = -1,
	GET_NAME = 0,
	GET_AGE
};

void requestEvent();
void receiveEvent(int numBytes);

REQUEST_TYPE request = NONE;

void setup() {
	Wire.begin(0x08);                // join i2c bus with address #8

	  Serial.begin(9600);  // start serial for output

	while (!Serial){}

	// event handler initializations
	Wire.onReceive(receiveEvent);    // register an event handler for received data
	Wire.onRequest(requestEvent);   // register an event handler for data requests
}

void loop() {
//  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
	switch (request) {
		case NONE:
			Serial.println(F("Not good, no request type!"));
			break;
		case GET_NAME:
			Wire.write("ArduinoUNO    "); // send 14 bytes to master
			request = NONE;
			break;
		case GET_AGE:
			Wire.write((byte)43); // send 1 bytes to master
			request = NONE;
			break;
		default:
			break;
	}
}

// function that executes whenever data is received by master
// this function is registered as an event, see setup()
void receiveEvent(int numBytes) {
	if (numBytes==1){
		int requestVal = Wire.read();

		Serial.print(F("Received request -> "));
		Serial.println(requestVal);
		request = static_cast<REQUEST_TYPE>(requestVal);
	}else{
		Serial.print(F("No parameter received!"));
	}
}

And you can retrieve the previous WeMos D1 mini slave Sketch.

If you try to launch the I2C scanner from the master (after the slave configuration), you obtain this result.

22:33:55: Scanning...
22:33:55: I2C device found at address 0x08  !
22:33:55: I2C device found at address 0x12  !
22:33:55: done

With the Master parameter sketch you obtain this serial output.

22:51:28: Starting request!
22:51:29: ArduinoUNO    
22:51:31: esp8266       
22:51:33: 43
22:51:36: 45

Simple network with non-standard I2C pin

With esp8266, you can’t add a new I2C interface, but you can use almost all available pins to the standard channel, so if you cant use D6 as SDA and D5 as SCL you must add this command before beginning.

	  Wire.pins(D6, D5);
WeMos D1 esp8266 i2c master slave alternate pins
WeMos D1 esp8266 i2c master slave alternate pins

The sketch becomes like this.

/**
 * i2c network: send parameter to client and receive response
 * with data relative to the request. SLAVE SKETCH
 *
 * by Renzo Mischianti <www.mischianti.org>
 *
 * https://mischianti.org
 *
 * esp8266 	<------> 	esp8266
 * GND					GND
 * D1					D5
 * D2					D6
 *
 */

#include <Wire.h>

enum REQUEST_TYPE {
	NONE = -1,
	GET_NAME = 0,
	GET_AGE
};

void requestEvent();
void receiveEvent(int numBytes);

REQUEST_TYPE request = NONE;

void setup() {
	  Wire.pins(D6, D5);

	  Wire.begin(0x12);                // join i2c bus with address 0x12
// ------ If you have throuble uncomment these lines --------
// ------ and compile with processor speed at 160Mhz --------
	  Wire.setClockStretchLimit(1500);
	  Wire.setClock(40000L);
// ----------------------------------------------------------

	Serial.begin(9600);  // start serial for output

	while (!Serial){}

	Serial.println(F("Starting!!"));

	// event handler initializations
	Wire.onReceive(receiveEvent);    // register an event handler for received data
	Wire.onRequest(requestEvent);   // register an event handler for data requests
}

void loop() {
//  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
	switch (request) {
		case NONE:
			Serial.println(F("Not good, no request type!"));
			break;
		case GET_NAME:
			Wire.write("esp8266       "); // send 14 bytes to master
			request = NONE;
			break;
		case GET_AGE:
			Wire.write((byte)45); // send 1 bytes to master
			request = NONE;
			break;
		default:
			break;
	}
}

// function that executes whenever data is received by master
// this function is registered as an event, see setup()
void receiveEvent(int numBytes) {
	if (numBytes==1){
		int requestVal = Wire.read();

		Serial.print(F("Received request -> "));
		Serial.println(requestVal);
		request = static_cast<REQUEST_TYPE>(requestVal);
	}else{
		Serial.print(F("No parameter received!"));
	}
}

Thanks

  1. WeMos D1 mini (esp8266), specs and IDE configuration
  2. WeMos D1 mini (esp8266), integrated SPIFFS Filesystem
  3. WeMos D1 mini (esp8266), debug on secondary UART
  4. WeMos D1 mini (esp8266), the three type of sleep mode to manage energy savings
  5. WeMos D1 mini (esp8266), integrated LittleFS Filesystem
  6. esp12 esp07 (esp8266): flash, pinout, specs and IDE configuration
  7. Firmware and OTA update management
    1. Firmware management
      1. esp8266: flash firmware binary (.bin) compiled and signed
      2. esp8266: flash firmware and filesystem binary (.bin) compiled with GUI tools
    2. OTA update with Arduino IDE
      1. esp8266 OTA update with Arduino IDE: filesystem, signed and password
    3. OTA update with Web Browser
      1. esp8266 OTA update with Web Browser: firmware, filesystem and authentication
      2. esp8266 OTA update with Web Browser: sign the firmware and HTTPS (SSL/TLS)
      3. esp8266 OTA update with Web Browser: custom web interface
    4. Self OTA uptate from HTTP server
      1. esp8266 self OTA update firmware from server
      2. esp8266 self OTA update firmware from server with version check
      3. esp8266 self OTA update in HTTPS (SSL/TLS) with trusted self signed certificate
    5. Non standard Firmware update
      1. esp8266 firmware and filesystem update from SD card
      2. esp8266 firmware and filesystem update with FTP client
  8. esp32 and esp8266: FAT filesystem on external SPI flash memory
  9. i2c esp8266: how to, network 5v, 3.3v, speed, and custom pins
  10. […]
  1. i2c Arduino: how to create a network, parameters, and address scanner
  2. i2c Arduino SAMD MKR: additional interface SERCOM, network, and address scanner
  3. i2c esp8266: how to, network 5v, 3.3v, speed, and address scanner

Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *