ESP8266 – Sming I2C

This brief post is about how to use the I2C bus/protocol with the Sming framework running on the ESP8266.
Since one of the Sming framework objectives is to be as close as possible to Arduino compatibility, it comes as no surprise, that I2C usage on the Sming framework is almost, if not identical, to the Wire Library on the Arduino.

Starting up:
Before using the I2C functions we need to initialize the I2C bus, otherwise the ESP8266 will crash when using it.
There is no need to include any .h file to access the global Wire object, so just initialize it with the Wire.begin() function. By default the bus will be on the pins GPIO0 and GPIO2:

  1. GPIO0 – SCL (Clock)
  2. GPIO2 – SDA (Data)

But those default pins can be changed before initializing the I2C bus with the function Wire.pins( SCL , SDA).

So a possible initialization sequence could be something like:

void startMain()  {  // Called from the Wifi connected callback
 ...
 ...
 Wire.pins ( 0 , 2 ); // SCL, SCA -> 0 and 2 are the defaults. This is optional.
 Wire.begin();        // Initialize the bus.  This is mandatory.
 ...
 ...

And now we can use the Wire object to access the I2C bus.

Reading and writing on the I2C bus:
After the bus is initialized we can read and write from/to I2C connected devices:

  Wire.beginTransmission(I2C_addr);       // An I2C transaction always starts with this.
  Wire.requestFrom( I2C_addr, length);    // Request for reading.

  while(Wire.available())                 // Loop as data is available
  {
	  for(uint8_t i = 0; i < length; i++)
	  {
		  buffer[i] = Wire.read();// Collect data.
	  }
  }
  Wire.endTransmission();                 // I2C transaction always ends with endTransmission()
}

On the above case the code will read lenght bytes from the I2C I2C_addr address. This will work for example for an I2C device that has no command register, so no need to define what we want to read. An example of that is the PCF7485 I2C expander.

 // The PCF7485 only reads one byte:
 byte readPCF7485( int I2C_addr ) {
     byte b;

     Wire.beginTransmission(I2C_addr);
     Wire.requestFrom( I2C_addr, 1);
     b = Wire.read();
     Wire.endTransmission();

     return b;
 }

But for devices that have a command register and return several bytes, like the BMP180, something like this is what is used:

// Taken from the BMP180 lib and adapted for use as an example.
void readBMP180(int BMP180_Address, int sub_address, int length, uint8_t buffer[])
{
  Wire.beginTransmission(BMP180_Address);
  Wire.write(sub_address);
  Wire.endTransmission();
  
  Wire.beginTransmission(BMP180_Address);
  Wire.requestFrom(BMP180_Address, length);

  while(Wire.available())
  {
	  for(uint8_t i = 0; i < length; i++)
	  {
		  buffer[i] = Wire.read();
	  }
  }
  Wire.endTransmission();
}

So the above code samples show how to read and write data to the I2C bus. A simple I2C write code looks like:

 void writePCF8574( int I2C_addr , byte data ) {
    Wire.beginTransmission(BMP180_Address);
    Wire.write( data );
    Wire.endTransmission();
 }

I2C bus scanner:
Available on the Sming code base there is an I2C bus scanner. This function will scan all possible I2C address to see if any I2C connected device is available. For reference the code is as follows:

void scanI2CBus()
{
  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();

	WDT.alive(); // Second option: notify Watch Dog what you are alive (feed it)

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

So when we are prototyping we can at startup call the scanI2cBus() to get a list of available devices on the terminal console.

Final thoughts:
The Wire object is global and it is compatible with the Arduino code and the many libraries that are available, for example the BM180 library that is available on the Sming framework.

But Sming framework also offers a TwoWire class for I2C communication, that possibly allows to have multiple I2C buses, if that makes sense, to be available. But if we use this class instead of the Wire global object, most of the libraries don’t work because of the Wire object dependency.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s