I2C Between Arduinos

586,620

582

110

Published

Introduction: I2C Between Arduinos

About: Inventor, Author, Senior R&D Engineer, Entrepreneur, Forbes 30 Under 30

Maybe sometimes we want to share the workload of one Arduino with another. Or maybe we want more digital or analog pins. Inter-Integrated Circuit or I2C (pronounced I squared C) is the best solution.

I2C is an interesting protocol. It's usually used to communicate between components on motherboards in cameras and in any embedded electronic system.

Here, we will make an I2C bus using two Arduinos. We will program one master Arduino to command the other slave Arduino to blink its built-in LED once or twice depending on the received value.

In order to build this we need the following "ingredients":

  • 2 Arduinos
  • Jumper cables
This instructable and many more can be found in my Arduino Development Cookbookavailable here. :D

Step 1: How to Connect Them

Follow these steps to connect two Arduino UNOs using I2C:

  1. Connect pins A4 and A5 on one Arduino to the same pins on the other one.
  2. The GND line has to be common for both Arduinos. Connect it with a jumper.

We also have a schematic and a "breadboard" implementation, both easy to follow. Luckily, it's a simple implementation.

Remember never to connect 5 V and 3.3 V Arduinos together. It won't
hurt the 5V Arduino, but it will certainly annoy its 3.3 V brother!

Step 2: Code

The following code is split in two parts: the master code and the slave code, which run on two different Arduinos. First, let's take a look at the master code:

// Include the required Wire library for I2C<br>#include 
int x = 0;
void setup() {
  // Start the I2C Bus as Master
  Wire.begin(); 
}
void loop() {
  Wire.beginTransmission(9); // transmit to device #9
  Wire.write(x);              // sends x 
  Wire.endTransmission();    // stop transmitting
  x++; // Increment x
  if (x > 5) x = 0; // `reset x once it gets 6
  delay(500);
}

And here is the slave code that interprets the characters sent from the master:

// Include the required Wire library for I2C<br>#include <Wire.h>
int LED = 13;
int x = 0;
void setup() {
  // Define the LED pin as Output
  pinMode (LED, OUTPUT);
  // Start the I2C Bus as Slave on address 9
  Wire.begin(9); 
  // Attach a function to trigger when something is received.
  Wire.onReceive(receiveEvent);
}
void receiveEvent(int bytes) {
  x = Wire.read();    // read one character from the I2C
}
void loop() {
  //If value received is 0 blink LED for 200 ms
  if (x == '0') {
    digitalWrite(LED, HIGH);
    delay(200);
    digitalWrite(LED, LOW);
    delay(200);
  }
  //If value received is 3 blink LED for 400 ms
  if (x == '3') {
    digitalWrite(LED, HIGH);
    delay(400);
    digitalWrite(LED, LOW);
    delay(400);
  }
}

Step 3: Code Breakdown

First, let's look at the master. We need to include the required Wire.h library:

#include <Wire.h> 

Then, in the setup function, we begin the I2C bus using the Wire.begin() function. If no argument is provided in the function, Arduino will start as a master.

Lastly, we send a character x, which is between 0 and 5. We use the following functions to
begin a transmission to the device with the address 9, write the character, and then stop the transmission:

Wire.beginTransmission(9); // transmit to device #9
Wire.write(x);             // sends x
Wire.endTransmission();    // stop transmitting 

Now let's explore the slave Arduino code. We also include the Wire.h library here, but now we start the I2C bus using Wire.begin(9). The number in the argument is the address we want to use for the Arduino. All devices with address 9 will receive the transmission.

Now we need to react somehow when we receive an I2C transmission. The following function appends a trigger function whenever a character is received. Better said, whenever the Arduino receives a character on I2C, it will run the function we tell it to run:

Wire.onReceive(receiveEvent); 

And this is the function. Here, we simply store the value of the received character:

void receiveEvent(int bytes) { 
   x = Wire.read();
}

In loop(), we simply interpret that character to blink the built-in LED at different speeds depending on the received character.

Step 4: More About I2C

To briefly go through the theory, I2C requires two digital lines: Serial Data line (SDA) to transfer data and Serial Clock Line (SCL) to keep the clock. Each I2C connection can have one master and multiple slaves. A master can write to slaves and request the slaves to give data, but no slave can directly write to the master or to another slave. Every slave has a unique address on the bus, and the master needs to know the addresses of each slave it wants to access.

Each I2C bus can support up to 112 devices. All devices need to share GND. The speed is around 100 kb/s—not very fast but still respectable and quite usable. It is possible to have more than one master on a bus, but it's really complicated and generally avoided.

A lot of sensors use I2C to communicate, typically Inertial Measurement Units, barometers,
temperature sensors, and some Sonars. Remember that I2C is not designed for long cable lengths. Depending on the cable type used, 2 meters might already cause problems.

I2C is a complicated transmission protocol, but it's very useful. All Arduinos implement it, with a few differences in pin mappings:

Board I2C pins

Uno, Pro Mini A4 (SDA), A5 (SCL)

Mega, Due 20 (SDA), 21 (SCL)

Leonardo, Yun 2 (SDA), 3 (SCL)

Step 5: Connecting More Devices

If we need to connect more than two devices on an I2C bus, we just have to connect all SDA and SCL lines together. We will need the address of every slave to be addressed from the master Arduino.

Here is a video of a personal implementation using 1 master and 3 slaves.

More topics regarding Arduino communications such as Wireless Serial, SD cards or Ethernet can be found in my Arduino Development Cookbook available here. :D

2 People Made This Project!

Recommendations

  • Oil Contest

    Oil Contest
  • Water Contest

    Water Contest
  • Creative Misuse Contest

    Creative Misuse Contest

110 Discussions

018 12:21 am

Hi, I am making a hat that has a servo, a DC motor, your 2.2" TFT with SD card https://www.adafruit.com/product/1480 , your FX Sound Board https://www.adafruit.com/product/2341 with a separate amp https://www.adafruit.com/product/1712 , and speakers https://www.adafruit.com/product/1669 , and some neo-pixels.

I want to use I2C to control all of this. I don't know much about I2C and have only used it for simple things, following the instructions in the tutorial. This is more complex and I have been studying about how to use I2C with Arduino and wire.h. I get the jist, but not the specifics.

Instead of finding all the I2C registers for all the devices I'm using, I have decided to put a program for each of the above devices on a separate micro-controller, Flora, Gemma, Trinket Pro, and regular Trinket, whatever I already have around. I have done this and have each of the devices running on their own with their own micro-controller.

I purchased one of your I2C multiplexers https://www.adafruit.com/product/2717 and would like to use it to turn on/off each of the micro-controllers at its appointed time.

Can I do this? Can I give each of the microcontrollers an address from the multiplexer, Ox70 to Ox77, bypassing any addresses that the chip related to any of the various controllers has already? And if the answer is yes, then when I address any of the micro-controllers, the only data I want to send is to turn itself on and perform it's program. Would a simple Wire.write(1); turn the micro-controller and its program on, and a Wire.write(0); turn it off?


To give you a general overview here is what I have going:

Its a top hat that has 8 neopixels scattered around it that fade in and out randomly, controlled by a Gemma. These run all the time and are not related to the motors and music I want to synchronize. The TFT LCD is used as a diadem in the hat band, controlled by a Trinket Pro 5V. It also runs all the time.

I want hide a push button switch in the hat band that when pressed would initiate the music to begin playing. It is a 1 minute piece. Once the music begins I want the servo to turn 87 degrees, I wanted to use a Trinket 5V to do this, but for some reason the program would not compile on a Trinket or a Gemma (both USBTiny) but it would compile on a Trinket Pro and a Flora. Right now it is on a Flora. The turning of the servo moves an inner circle up and out of the top of the hat, using parts that I designed and generated with a 3D printer. Its quite amazing. Once the servo stops I want the DC motor (connected to a regular Trinket 5V and your Motor Driver breakout board https://www.adafruit.com/product/3297 , to spin the, now exposed, inner circle until near the end of the song. At which point I want it to stop and have the servo descend the inner circle back into the hat, waiting for the next time the button is pushed.

I am using an Aruino UNO for the master. I'm sure there is a more efficient way to do this without using so many controllers, but I am trying to get this hat finished in 2 days and this is the most simple way I could think of, as I was having a hard time finding all the I2C addresses, plus I don't know how to use I2C really. However, I am so close and the only thing I need to do is to get the master to control the slaves. As for the timing of the music and the motors, I am using the length of the piece of music as the guide. Once the music is initiated, the the I2C will turn on the servo, which will turn 87 degrees, wait for 50 sec, and turn back 87 degrees. The DC motor will begin to spin 4 sec after it is initiated and spin for 50 sec, then stop. So you see I don't need to have a lot of communication between the master and slaves other than turn on and off.

What I'm asking for here is to know if I can do these things and what commands do I use. Thanks.

Replace in the example the conditions from x == '0' and x == '3' to x == 0 and x == 3, otherwise it does not work.

3 replies

How do I know or find the device address number, like your #9 here? How is it determined?

1 more answer

I guess you don't have to determine. You just assign any random number that comes to your mind.

Can i use like 3 or 4 Arduinos as slaves and use the same adress to all of them? Would they make the same thing or would it make all the system go nuts?

Assuming unilateral communication (only master send info to the slaves. Slaves just do the job)
Sorry for the bad english!

2 replies

While that's not a bad concept, you have to keep in mind that !2C is a bus with everybody on at the same time. If you give two devices the same address they'll both respond to any commands sent by the master, which would be fine as long as the command sent doesn't expect a reply or acknowledgement. As soon as two devices with the same address try to respond at the same time you'll get a crash of garbled information coming back to the master. Also, if the two slave devices have opposing polarity (one sets the bus to LOW while the other to HIGH) you can overload the IO pins. If you need to send the same signal to several slaves, give them each a unique, sequential ID (4, 5, 6, 7, etc), then use a FOR-NEXT loop to count through the slave addresses and send the same command to each slave inside the loop. Arduinos are fast enough that you probably won't see any delay from when the first slave responds to when the last one does. I hope this helps.

Hey, can I have two masters and one slave?

Can you use an Arduino A like master and Arduino B like slave. And the same Arduino A like slave of the same Arduino B?

hi

i would like to make an I2C connection between an arduino uno and CJMCU Atiny85, do you think is it possible? Another question is that if I want to use 3meters of wire between 2 arduinos an I2C booster is necessary?

0
user
0xffff

11 months ago

"Remember never to connect 5 V and 3.3 V Arduinos together. It won't hurt the 5V Arduino, but it will certainly annoy its 3.3 V brother!"

As long as you use pullups (usually 1.2k to 5kOhm, depending on distance/line capacity and I2C speed) connected to 3.3v side, no problem.

The 5v arduino will recognise 3.3v as HIGH level.

More info on calculating pullup values can be found at http://www.ti.com/lit/an/slva689/slva689.pdf

In the slave code, we start the I2C bus using Wire.begin(9). The number in the argument is the address we want to use for the Arduino.

One thing I find useful when using I2C is to set a global variable at the top of my sketch called "I2C_Addr". That way when I go over my sketch later it's easier to understand what that number represents, and makes it easier to change it if you want to use multiple slave devices with the same code on the same bus.

For those wanting to use longer cables (I used a 5m cat5 cable), you will need to use pull up resistors. My breadboard has a 5v rail, so added a 4.7k resistor from that to pin 4 and another to pin 5. I didn't actually have a 4.7k resistor, so used 2 10k in parallel to give ~5k resistance. This worked straight away.

1 reply

One thing to keep in mind is that the !2C protocol, if I recall correctly, has a capacitance limit of 600pF for the conductors. The longer the wires, the higher the capacitance. If you need a REALLY long cable length, try an I2C booster IC. They boost the signal on the bus and greatly extend the max transmission distance reliably while overcoming the capacitance limitations.