I2C Bus and the Arduino

Updated 29 April 2022

A diagram showing the nature of inter-inter circuit communication protocolI²C stands for the inter-integrated circuit and refers to a communication protocol we are going to use to communicate between our Arduino devices. In the past we have investigated the SPI and Asynchronous Serial Communications protocols for this purpose, check them out before we delve into the new topic of I²C communications.

I²C uses 2 signals for transferring data between devices, this halves the SPI’s 4 wire communication protocol. The signals we will use are called:

  • Data signal (SDA) – This is where all of our control information and data is sent/received. Everything is sent on this signal and can be sent anywhere on the bus.
  • A clock signal (SCL) – This is purely for regulating the speed the data is sent. The slave devices will sample a bit from the data stream in synchronization with this signal.

Devices that use I²C are “open drain” on the data signal. This means that they are only able to pull the line low, eliminating the risk of bus contention which can damage components. Every signal line has an internal pull-up resistor that drive the line back to high when a device is using it.

I²C is a multi-master system, meaning you can have more than one master controlling data being sent on the bus. The bus refers to the family of integrated circuits we are communicating between and the limit for how many devices can ‘be on’ one bus is 1008.

So how do we differentiate between our 1008 devices? We give them all a unique 7-bit address. With each device that uses I2C on the same bus needing an address.

We start the communications on our bus by sending the data line low and holding the clock signal high. This alerts all devices on the bus that we are about to begin communications. We send the address byte after our start condition the first 7 bits is the address with the final bit being a read/write bit.

The slave device then sends back an Acknowledge bit, letting the master know it is on the bus and ready for communications. After the ACK bit, the master begins sending data 1 byte at a time and waits for the acknowledge bit from the slave.

When the master is finished sending the data, it will set the clock line high and reset the data line to high. This effectively ends data transfer on the bus.


A diagram showing a complete breakdown of one frame of inter-inter circuit (i2c) communication


Why use I2C?

  • Far easier to use than SPI.
  • Uses fewer connections than SPI.
  • Supports a multi-master system, rather than SPI’s one master, infinite-slaves system.
  • 100% compatible with our Arduino Uno.

The Project

If you remember the SPI tutorial, we controlled 8 LEDs with an 8-Pin Shift Register using SPI principles. Today we will be using I²C to build upon that same design, this time controlling 16 LEDs with a 28 Pin Input/output expander called the MCP23017.

We will add the concept of a simple 8-bit binary counter, making our I²C device control a 16-bit binary counter with 16 LEDs, each representing one bit on the counter.

Components needed:

Software needed:

  • Wire.h library
  • Arduino IDE

The MCP23017 IC package

The MCP is a 28 Pin, DIP IC that we are going to use as a slave in our LED circuit. If you google for the datasheet of the part and search through it, you will find that the MCP allows us to set the last 3 bits of its 7-bit address (see pins 15-17 in Fritzing Sketch). The first four being set to 0100.

A pinout of the MCP23017, with all information tabulated

The only relevant Arduino Pins in this project are:

  • Pin A4 = SDA
  • Pin A5 = SCL

This table represents what each leg of the IC we are using is for. The legs of the IC are labeled as follows: Pin 1 is left of indent on the top of IC, pin 14 is the bottom left pin, pin 15 in the bottom right pin and pin 24 is the top right pin. This convention of increasing counter-clockwise will be the same for all DIP ICs we use. That’s almost everything we need to get started with our circuit. Let’s connect it all up!

28 Pin DIP Integrated Circuit, labelled pin numbers

 The Setup

MCP23017 connected to the Arduino Uno r3 via I2C

  1. Connect your MCP23017 to the center of your breadboard over the vertical break, orient the chip such that Pin 1 is at the top left and Pin 24 is at the top right.

  2. Connect your 16 LEDs and resistors in series on separate lines.

  3. Connect 5v and GND from the Arduino to the breadboard as shown.

  4. Connect the MCP23017 to the Arduino according to both the sketch (left) and the table above.

  5. Use a jumper wire to connect pin 10, 15, 16 and 17 of the MCP to the GND rails. Pin 10 is ground for the IC. 15, 16 and 17 are hardware address pins that we are going to set to 0.

  6. Use a jumper wire to connect pin 9 and 18 to the 5v rail. Pin 9 is Vcc for the IC and pin 10 is the reset pin, so tying it to Vcc means we will never reset the transfer.

  7. Finally, connect the LED array in the way shown.

 The Code

#include <wire.h>
int counter = 0;
void setup()
  Wire.begin();                 // wake up I2C bus
  // set I/O pins to outputs
  Wire.write(0x00);             // IODIRA register
  Wire.write(0x00);             // set all of port A to outputs
  Wire.write(0x01);             // IODIRB register
  Wire.write(0x00);             // set all of port B to outputs
void binaryCount()
 counter=counter 1;             //add 1 to counter variable initialised as 0 above
void updateLEDs()
  byte porta = counter;         // send bits 0..7 of 'counter' to porta
  byte portb = counter >> 8;    // send bits 8..15 of 'counter' to portb
  Wire.beginTransmission(0x20); //Start sending to GPA LEDs
  Wire.write(0x12); // GPIOA    //Writing to GPA0-GPA7
  Wire.write(porta); // port A  //Writing bits 8-15 of counter to these pins, Least Significant Bit first 
  Wire.endTransmission();       //End sending to GPA LEDs
  Wire.beginTransmission(0x20); //Start sending to GPA LEDs
  Wire.write(0x13);             //Writing to GPB0-GPB7  
  Wire.write(portb);            //Writing bits 0-7 of counter to these pins, Least Significant Bit first
  Wire.endTransmission();       //End sending to GPB LEDs
void delayTime()
 delay(1);                      //Delay time in ms between GPA_LED updates

void loop()
  binaryCount();                //Increases counter by one each iteration
  updateLEDs();                 //Changes LEDs to represent "counter's" value
  delayTime();                  //Sets a speed to the counter

Upload the code to your board, if you want a more thorough understanding of the coding process read through the comments within the code.

Remember how I mentioned this was going to be similar to our SPI project? Set your delay to <50 to see the counter get through the first 8 bits nice and fast. What you should see after that is port A begin to light up in the same sequence. We have extended our counter a 16-bit binary counter!

Congratulations, you have just used the wire.h library to configure and communicate between 2 devices on the I2C protocol

Have a question? Ask the Author of this guide today!

Please enter minimum 20 characters

Your comment will be posted (automatically) on our Support Forum which is publicly accessible. Don't enter private information, such as your phone number.

Expect a quick reply during business hours, many of us check-in over the weekend as well.



Please continue if you would like to leave feedback for any of these topics:

  • Website features/issues
  • Content errors/improvements
  • Missing products/categories
  • Product assignments to categories
  • Search results relevance

For all other inquiries (orders status, stock levels, etc), please contact our support team for quick assistance.

Note: click continue and a draft email will be opened to edit. If you don't have an email client on your device, then send a message via the chat icon on the bottom left of our website.

Makers love reviews as much as you do, please follow this link to review the products you have purchased.