I2C communications have become the de facto method of communicating between microcontrollers, microcomputers and a variety of integrated circuits and sensors. It has been around since 1982 and was originally developed for use in television receivers.

Although we have used many I2C sensors and displays in previous articles we have not actually looked into how I2C works and how it can be used to communicate between microcontrollers.

Today we will correct that and learn more about I2C. We’ll also see how it can be used to exchange information between two Arduinos and how it can be used to allows one Arduino to control another one.

I2C Part 1-Using 2 Arduinos

This will be the first of four articles on I2C. In future articles we will see how we can build our own I2C devices, how to interface a Raspberry Pi and an Arduino using I2C and how to do some advanced I2C configurations, including using multiple masters on one I2C bus.

Let’s get started!

I2C Communications

I2C is a serial protocol used on a low-speed 2-wire interface. It was originally developed by Phillips in 1982 to allow integrated circuits within television receivers to communicate with one another.

Times have changed, Phillips is now NXP and I2C has become a communication standard that is supported by virtually every major semiconductor manufacturer.

I2C is an abbreviation for “Inter-Integrated Circuit”. It is also called “IIC” or ‘I squared C”.

Uses and Limitations

I2C is used with microcontrollers like the Arduino and with microcomputers like the Raspberry Pi. Many displays and sensors interface to their host controller using I2C.

I2C does have several limitations however. It is not particularly fast, although for most of its intended uses it is plenty fast enough.

I2C can only be used over short distances, after all, it was originally meant to communicate between integrated circuits on the same printed circuit board. The maximum distance of reliable transmission decreases as the speed increases, at the slowest speed (100 Kbaud or a clock rate of 100 KHz) the maximum distance is about a metre.

I2C Speeds

The original I2C bus had a maximum speed of 100 KHz. Most common applications still use this speed, as it is quite sufficient for transferring data from sensors and to simple displays.

I2C and has some higher speed modes. Not all I2C devices support these modes:

  • Fast Mode – This has a maximum clock speed of 400 KHz.
  • Hi-Speed Mode – A maximum clock frequency fo 3.4 MHz
  • Ultra Fast Mode – Maximum clock frequency of 5 MHz

On an I2C bus it is the master that determines the clock speed.

How I2C Works

An I2C bus has two signals, along with a power and ground connection.

I2C Bus Communications

The two signal lines are as follows:

  • SDA – This is the bidirectional data line.
  • SCL – This is the clock signal.

There are two pull-up resistors attached to each signal line, they pull the bus up to the supply voltage when it is inactive.

Note that the supply voltage is not standard, it can be either 3.3 or 5-volts. It can also be a lower voltage for some high-speed I2C implementations.

This difference in supply voltages can cause issues when you are interfacing I2C devices that use different logic levels. We will discuss this more in a future article when I show you how to interface a Raspberry Pi (3.3-volt logic) with an Arduino Uno (5-volt logic).

There are two types of devices that can be interfaced to the I2C bus – Masters and Slaves.

The Master device controls the bus and supplies the clock signal. It requests data from the slaves individually.  There can be more than one master device on the bus but only one can be the active master at any given moment.

The master devices do not have an address assigned to them.

Slave devices do have an address, and this address needs to be unique on the bus. They use a 7-bit addressing scheme, so up to 128 slaves can be on one I2C bus. In real life this large collection of devices is never used, it is rare to see over a dozen I2C devices on one bus.

A newer, 10-bit addressing scheme has been implemented, it is backward-compatible with the existing 7-bit addressing method.

Commercial I2C devices are allocated I2C address by NXP, who maintain the bus specifications. Although I2C has been open source since 2006 there is a fee charged for obtaining a slave address from NXP.  No fee is required for master devices, or for devices that are not meant for commercial manufacture.

Some I2C devices are assigned multiple addresses, usually variances in the lower address bits. These devices can be manually configured for different addresses, allowing multiple devices of the same type to be used on a single I2C bus.

Other I2C Derivatives

There are other buses that have been derived from the I2C bus, and which are in many ways compatible with I2C.

  • TWI – The Twin Wire Interface is virtually identical to the I2C bus. This is actually the bus that the Arduino uses, TWI was developed when the I2C bus was not open source and Atmel did not want to risk a trade name violation. The only major difference between TWI and I2C is that TWI does not support an advanced technique called “clock stretching”.
  • SMBus is another I2C equivalent bus, developed by Intel. Like TWI it supports most I2C features.

In a future article I will explain how the data on the I2C bus is structured. But now we have some basic I2C information, enough to start experimenting.

Arduino Wire Library

The Arduino has a built-in library for working with I2C called the Wire Library. It makes it very easy to communicate on the I2C bus, and it can configure the Arduino to become either a master or a slave.

The Wire library has several useful functions for working with I2C.

  • begin() – This initiates the library and sets up the Arduino to be either master or slave.
  • requestFrom() – This function is used by the master to request data from a slave.
  • beginTransmission() – This function is used by the master to send data to a specified slave.
  • endTransmission() – This function is used by the master to end a transmission started with the beginTransmission function.
  • write() – Used by both master and slave to send data on the I2C bus.
  • available() – Used by both master and slave to determine the number of bytes in the data they are receiving.
  • read() – Reads a byte of data from the I2C bus.
  • SetClock() – Used by the master to set a specific clock frequency.
  • onReceive() – Used by the slave to specify a function that is called when data is received from the master.
  • onRequest() – Used by the slave to specify a function that is called when the master has requested data.

We will use some of these functions in our sketches.

Arduino I2C Connections

The SDA and SCL connections for I2C are different between Arduino models. The experiments I’m about to show you were done using two Arduino Unos, but you can use other models of the Arduino providing you change the pins accordingly.

I’ve put together a chart to help you get it figured out. It includes some common Arduino boards, as well as a few of the discrete chips.  The pinouts for the chips I list (ATTiny and ATmega328P) are with the DIP package, not the surface-mount ones.

Arduino Board or Chip SDA SCL
Uno A4 A5
Mega2560 20 21
Nano A4 A5
Pro Mini A4 A5
Leonardo 2 3
Due (has two I2C) 20 + SDA1 20 + SCL1
ATTiny85 & ATTiny45 5 7
ATmega328P 27 28

Some Arduino Uno clones have separate SDA and SCL pins and you can use them instead of the two analog pins if you wish. They are internally connected to the same place.

Note that the Arduino Due actually has two I2C ports.

Also, be aware that there are some incorrect hookup diagrams on the internet for the Pro Mini. Use the two analog pins, A4 and A5, as shown in the table above.

I2C Between 2 Arduino’s

For our first experiment we will hoo two Arduinos together and exchange data between them. One Arduino will be the master, the other will be the slave.

I’m using two Arduino Unos, but you can substitute other Arduino’s if you don’t have two Unos. Use the previous chart for the connections.

Hooking up 2 Arduino’s

Here is how I connected my two Arduino Unos together:

I2C Arduino to Arduino

It is quite a simple hookup, essentially you just tie the ground and the two I2C pins together.

One thing to be aware of is that my diagram does not show the use of pull-up resistors, I found that everything seemed to work correctly without them.  However, you might want to include them, especially if you experience errors or intermittent operation.

To hook up some pull-up resistors attache a couple of 10k resistors to the SDA and SCL lines. Attach the other end to the 5-volt output on one of the Arduino’s.

Master Demo Sketch

Here is the sketch that will be used on the Arduino that you have designated as being the master.

As with all I2C sketches, we start by including the Wire library.

Next we define a few constants to represent the I2C address of the slave and the number of bytes of data that we expect to retrieve from it.

In the Setup we initialize the I2C communications as a master. We know it is a master as there is no address parameter in the begin function.  We also setup a serial monitor and print a line of text to it.

Now to the Loop.

We start with a tiny time delay, mostly to slow things down enough so that we can read the display on the serial monitor.

Next we use the beginTransmission function to send data to the slave. In this case the data we send is just a number zero.  We finish sending with a call to the endTransmission function.

Next we request some data back from the slave using the requestFrom function.

After that we formulate a response string by reading the data, a byte at a time, from the slave.

We print the details of what we are doing and of the data we receive to the serial monitor.  And then we finish the Loop and do it all over again.

Slave Demo Sketch

Now onto the sketch used by the slave.

Once again we start by including the Wire library.  As with the previous sketch we also define the I2C address for the slave, as well as the number of bytes we are planning to send back to the master.

Next we define the string that we are going to send back to the master, in this case just the word “Hello”. If you decide to change this make sure that you adjust the ANSWERSIZE constant in both sketches to be correct.

In the Setup we initialize the connection to the I2C bus with a begin function. Take note of the different way we do this, as this is a slave we specify the I2C address we are going to be using. By doing this the Wire library knows we want to operate in slave mode.

Now we need to define the names of the functions that we will call when two events occur – a data request received from the master and data received from the master.  We also setup and print to the serial monitor.

The function receiveEvent is called when we receive data from the master.  In this function we read data while the data is available and assign it to a byte (remember, the data will be received one byte at a time).

The requestEvent function is called whenever we get a request for data from the master.  We need to send our string “Hello” back to the master. As we need to send the data one byte at a time we divide the characters in “Hello” into individual items in an array and then send them one-by-one.

We report all of our progress in both functions to the serial monitor.

The Loop in this sketch just adds a time delay, which matches the one used in the master sketch.

Running the Demo Sketches

To run these sketches you’ll need to be able to view the Serial monitor on each Arduino. If you have two computers with the Arduino IDE installed then that will make it a lot easier.

I2C Experiment 1

On Microsoft Windows it is possible to open up two instances of the Arduino IDE. If that is done you could display both serial monitors side-by-side on the same screen.

Alternately, you could use one computer and power up the second Arduino with its own power supply. You would have to switch the computer and power between the two Arduino’s. By doing this you could monitor both screens one-by-one.

Arduino Remote Using I2C

In the next demonstration we will hook a potentiometer to the master Arduino and an LED to the slave. We will use the potentiometer to control the blink rate of the LED.

This is another simple demonstration, you can build upon it to create something more practical.

Remote Demo Hookup

Here is how this experiment is put together.

I2C Arduino Control

It is essentially the same hookup as the previous experiment, with the addition of the potentiometer on the master and the LED on the slave.

Note that the LED on the slave has been attached to pin 13. As the Arduino Uno has a built-in LED on pin 13 you may eliminate the LED and its dropping resistor if you wish.

The remarks about pull-up resistors also apply to this hookup.

Remote Demo Master Sketch

The sketch for the master side of this experiment is very simple, in some ways the I2C side is even simpler than the one used in the first demonstration. This is because we are just sending data to the slave and are not expecting to get any back.

As always we need to include the Wire library at the beginning of the sketch.  We also will define a constant to hold the slave address.

Since we are using a potentiometer we will need to define both the pin it is connected to and a variable to hold its value.

All that we do in the Setup is to initialize the I2C connection as a master.

In the Loop we read the potentiometer value and map it to a range of 01-255. We need to do that as we are sending one byte of information and can only hold this many values in a single byte.

Note that we reverse the numbering sequence in the Arduino Map function, this is done so that the system behaves the way we expect it to – turning the potentiometer to the right increases the flash rate. As the “flash rate” is specified by a time delay a bigger number being sent will equate to a longer flash rate.

Also note that we don’t send the value 0, which would just hold the LED at one state. We set our range to end at 1 instead.

Now it’s just a matter of sending the byte to the slave and repeating the Loop again.

Remote Demo Receive Sketch

The slave side needs to receive data from the master and use it to flash the LED.


We start with the usual inclusion of the Wire library, as well as defining the slave address. We also define a pin for the LED.

A couple of additional variables are defined, one holding the received data while the other carries the time delay value for the blink rate.

In the Setup we set the I/O pin for the LED as an output and initialize the I2C bus. As we use the slave address in the begin function the Wire library knows we are acting as a slave.

We only need to define an onReceive function, unlike the last demo we are not expecting any requests from the master.  We also set up and print to the Serial monitor, we will use the monitor to view the incoming data.

The receiveEvent function reads the incoming data and assigns it to the I variable. It also prints the value to the serial monitor.

Finally, in the Loop we use the incoming data to blink the LED. Once again we use the Map function to accomplish this, changing the incoming values of 1-255 to a wider range.  You can experiment with changing this range to make the LED blink faster or slower if you wish.

The last few statements are essentially the Arduino Blink sketch in disguise! We turn the LED on and off for a time period we determined in the last step.

And then we repeat the loop.

Running the Remote Demo

Load the code and power both Arduino’s. You can use your serial monitor on the slave Arduino to view the incoming data.

I2C Experiment 2

Turning the potentiometer should now vary the LED blink rate on the slave.


This concludes our first detailed look at I2C. In the next installment, we will learn more about the structure of the data that is being exchanged. We will also take a regular sensor and turn it into an I2C sensor.

Happy communicating!



Sketches – All of the I2C sketches used in this article.

I2C information – Information regarding the I2C protocol


I2C Communications Part 1 – Arduino to Arduino
I2C Communications Part 1 - Arduino to Arduino
Article Name
I2C Communications Part 1 - Arduino to Arduino
In this first part of a series of articles about I2C you will learn what I2C is. You'll also see how the Arduino Wire library makes communications over I2C very simple.
Publisher Name
DroneBot Workshop
Publisher Logo
Tagged on:

If you have a question...

Comments about this article are encouraged and appreciated. However, due to the large volume of comments that I receive, it may not be possible for me to answer you directly here on the website.

You are much more likely to get answers to technical questions by making a post on the DroneBot Workshop Forum. Your post will be seen not only by myself, but by a large group of tech enthusiasts who can quickly answer your question. You may also add code samples, images and videos to your forum posts.

Having said that, please feel free to leave constructive comments here. Your input is always welcome. Please note that all comments may be held for moderation.

Newest Most Voted
Inline Feedbacks
View all comments
1 year ago

In you slave LED example you have 2 setup() functions.

Paul Bigelow
1 year ago

Just a note that the I2C Slave Control Demo code ended up with the first part of it pasted in twice. It won’t compile without removing the duplicated section.

1 year ago

Nice article, thanks. When I ran the I2C Master and Slave Demo on two Arduino Unos, the Master successfully send the 0 to the Slave and the Slave received the data displaying “Receive Event” in the serial console. No problem there. Next, the Master sends a read command to the Slave (Wire.requestFrom), but the Slave does not perform a Request Event. The serial consoles look like this: I2C Master Demonstration Write data to slave Receive data (missing “Hello”) I2C Slave Demonstration Receive event (missing “Request Event”) Looking at the SCL and SDA signals on an oscilloscope, I could see the… Read more »

1 year ago

Congratulations ! It is a very useful video for me. I would like to communicate 4 Arduinos by I2C. One master, e three slaves. The master will send 4 bytes(the same bytes), for all 3 slaves, and then, it will request 2 bytes form each slave. They will be at 50cm longer at the maximum distance. I would like to use I2C @ 400Hz, if it is possible. I heard about a way to broadcast those 4 bytes for all 3 slaves at once time. I loved this video. It is my first step on that direction. Thank you !

Dave Wreski
1 year ago

I am trying to run the LCD I2C demo and have a problem I cannot fix. Can anyone help. The error code says “Arduino: 1.8.9 (Windows 10), Board: “Arduino/Genuino Uno”

lcd-i2c-demo:20:65: error: ‘POSITIVE’ was not declared in this scope

LiquidCrystal_I2C lcd(i2c_addr, en, rw, rs, d4, d5, d6, d7, bl, POSITIVE);

1 year ago
Reply to  Dave Wreski

FYI: I could not get the simple master/slave demo to work between an UNO and a Duemilanove 328 until I incorporated two 4K7 ohm pullup resisters on the SDA and SCL lines. Your schematics do not incorporate the pullups.

1 year ago

Nice. I have signed up to the Workshop before and looking for an acknowledgement that I have been successful. Please let me know by sending me an email.

Ron K
1 year ago

I found the experiment connecting 2 arduinos together on the I2C bus very interesting. And I would like to try it. What I don’t understand is where does the line “#define SLAVE_ADDRESS 9” come from?

1 year ago
Reply to  Ron K

Hi Ron. “#define” is a Macro Preprocessor Directive. It has the form “#define Token Value”. In the the case above “SLAVE_ADDRESS” is the token, and “9” is the Value. It mean that in the rest of the program where ever you see the Token just think of it as the Value 9. Here is a link to everything about Macro Preprocessor Directives.

1 year ago

sir in my project i want to send string variable from master to salve and multiple sensor data is need to send from salve to master(master is node mcu and salve is arduino uno)

Lalit tiwari
1 year ago

Sir, how can I write to different registers having different address, in an IC

1 year ago

I have this error when compiling SLAVE scketch:

call of overloaded ‘onReceive(void (&)())’ is ambiguous

11 months ago
Reply to  Guillermo

Declaration of receiveData must be

void receiveData (int16_t byteCount) //although parameters in () are not used themselves

7 months ago
Reply to  Harry

HI Harry, I’m getting the exact same error when compiling SLAVE for ESP8266 (works fine on UNO/MEGA)
What/Where is “void receiveData (int16_t byteCount) ” you refer to?

Beau Tooley
11 months ago

In reference to the Receive sketch, how does the loop constantly update the “br” variable when the Wire.onReceive(receiveEvent); is located in the “setup” section? I though setup is only ran once in the beginning, and the receiveEvent() function would need to be called in the loop. I may answer my own question right now, but is it because the Wire.onReceive() is always running? The way I read it on the Arduino site seems like its always looking for incoming data, and when it gets something/data, it executes whatever function you put inside the (). How close am I? Thanks!

V B Talpada
10 months ago

please share TDA7315 (DIP 20) control the audio ic program on YouTube channel

10 months ago

how i can share multiple variables data from master to salve Arduino, because it transfer only one variable data

7 months ago

Excellent tutorial, Thnx!
Experimenting with the sketches to use in my project (sending WiFi scan data from a WEMOS D1R2 ESP8266 master to an ARDUINO MEGA slave to display on a 3.2″ TFT shield) I discovered that the maximum ANSWERSIZE is 32 characters – 33 and above results in unrecognised characters.

7 months ago

exit status 1
‘receiveEvent1’ was not declared in this scope

Gîlcă Cristian
6 months ago

Hello, My name is Gîlcă Cristian and I want to ask you a question. I am a beginner in Arduino and want to implement a project on two Arduino boards one, namely a temperature, humidity and distance measurement monitoring system. On the first board I mounted the 0.96 oled display, dht22 sensor and on the second vl53lox. In addition, I have a motor connected to an ir sensor, which is not connected to the arduino boards (it is only for starting the motor), connected to a relay module and I want you to delay it by 15 seconds after not… Read more »

18 days ago

Tried to compile the example “communication between 2 Arduino Unos with I2C”. Master .ino translates fine, but slave .ino delievers an error description: ‘receiveEvent’ was not declared in this scope
Whats the reason?

18 days ago

I found out, that the observed problems with compiling the master and slave .inos were caused by the sketches of the Resources at the end of the project. Using the listings in the projects text by copy and paste works fine and correct.

Would love your thoughts, please comment.x