Introduction: Arduino and I2C
Hi there,
Welcome to our Instructables page. Here we will talk about I2C communication between two Arduinos.
First we will mention how to set up an Arduino as either a master or a slave. After the basics are explained we will show an example how to blink an LED using the I2C through a serial monitor.
Basics
I2C communication is very popular and broadly used, because of its easy implementation in electronic devices. Reason for I2C easy implementation comes from the fact that only 2 wires are needed for communication, we do need to connect the devices to the common ground as well but the ground is not directly used in the communication.
I2C uses only 2 wires because each device has a unique address assigned to it. Therefore the master can easily know with which slave it communicates. Simply said the signal from the master goes to all of the slaves but only the slave with the correct address is going to react on the signal.
The two wires are: Serial Clock (SLC) and Serial Data (SDA). The SLC is the clock signal which synchronizes the data transfer between devices and it is generated by the master device. The SDA is the data carrier signal, basically the transmission wire.
Supplies
- 2x Arduino Uno (or Arduino Nano)
- Small LED
- Breadboard (any)
- Jumper Wires
Step 1: First Mode: MasterWriter---SlaveReceiver
Following code examples are taken from https://www.arduino.cc/en/Reference/Wire
After we have connected the hardware properly now it is time to start the programing. For enabling the I2C communication we will need WIRE LIBRARAY, which should come with Arduino programing tool by default. In case that is not there you can download it form HERE.
Now that we have everything set up properly we will show two modes of programing the I2C.
First one will be MasterWriter---SlaveReciever, second one is MasterReader---SlaveSender. These mods can be run simultaneously on the devices that are connected.
First mode: MasterWriter---SlaveReceiver
With this "mode" you can only send instructions form the master device to slave, but there is no feedback form the slave device. Because of that you master device has no idea what the slave device is doing. To remedy that there is the second "mode". Below you can see the MasterWriter and the SlaveReceiver.
Here is the MasterWriter code, you can upload it on the Arduino you choose to be a Master, you can also find this code in the attachment with more detailed explanation.
#include <Wire.h> byte x = 0; void setup() { Wire.begin(); } void loop() { Wire.beginTransmission(4); Wire.write("x is "); Wire.write(x); Wire.endTransmission(); x++; delay(500); }
Here is the SlaveReceiver code, you can upload it on the Arduino you choose to be a Slave, you can also find this code in the attachment with more detailed explanation.
#include <Wire.h> void setup() { Wire.begin(4); Wire.onReceive(receiveEvent); Serial.begin(9600); } void loop() { delay(100); } void receiveEvent(int howMany) { while(1 < Wire.available()) { char c = Wire.read(); Serial.print(c); } int x = Wire.read(); Serial.println(x); }
The code will be explained in the next step.
Step 2: MasterWriter---SlaveReceiver Explained
Here we would like to explain in detail what the individual code lines do. Starting from the MasterWriter code.
1. We have to include the library for I2C inside our code.
#include <Wire.h>
2. We define the variable x in the form of a byte. We will be sending all of the information in byte form through I2C connection.
byte x = 0;
3. Now we will start the I2C communication between a master and a slave.
void setup() { Wire.begin(); }
4. Next we will create a loop for transmission. We start the transmission with the device that has an address 4. We can choose any number here that can be stored in 1 byte (0-255).
void loop() { Wire.beginTransmission(4);
5. We will than send 5 bytes in the form of a text which will read on the slaves Serial Monitor "x is ".
Wire.write("x is ");
6. We send 1 byte now with the value of the variable x. First time sending this number will be 0.
Wire.write(x);
7. We will than stop the transmission to clear the Arduino for other possible operations.
Wire.endTransmission();
8. At the end we will only increase the variable x by 1, so that we can see the difference in transmission.
x++; delay(500); }
That was the MasterWriter code, now we will explain the SlaveReceiver. We will not go into details of the same code lines that were mentioned in the MasterWriter code.
1. Include the wire library as before, begin the I2C communication but this time we need to input the address of the slave, which if you remember was number 4.
#include <Wire.h> void setup() { Wire.begin(4);
2. Now we have to register an event that will activate every time that new data is received via I2C. This event we input as a function later in the code. Basically with this line we simply say: call this function (receiveEvent) each time you receive some information from I2C.
Wire.onReceive(receiveEvent);
3. Now we will begin the serial communication with the "PC", so that we can display information received from the I2C communication.
Serial.begin(9600); }
4. Our loop will simply wait for 100 ms before it ends. This is done so that the Arduino has some time to "rest".
void loop() { delay(100); }
5. We will now define a function mentioned before, the function which is called each time we receive new information from the Master. This function returns a value inside a variable howMany.
void receiveEvent(int howMany) {
6. Now we create a while loop which will go through all of the bytes received but the last one. We will store these bytes inside a character variable, and show them individually on the screen. This will if you remember from the Master code display on the screen (letter by letter) text "x is ".
while(1 < Wire.available()) { char c = Wire.read(); Serial.print(c); }
7. Now we will store the last byte inside an integer x and print it on the screen. This will show the x number which increases constantly on the Masters side of the code.
int x = Wire.read(); Serial.println(x); }
You can test this code by uploading the Master on one board and the slave on the second one. Connect the boards according to the wiring shown in the next step. To see the code in action you have to select the port of the slave and turn on the serial monitor, and both Arduinos need to be connected to the same ground and both need to be powered up.
In the next step we will show you a more interesting example which will help you control an LED by inputting on or off in the serial monitor and send it from the master to the slave.
Step 3: I2C Wiring of Two Arduinos
Here we are going to show how to connect two Arduinos together with I2C connection. You can simply slide the images above or read the steps below.
- Connect the grounds of both Arduinos together. (Image 1)
- Now we are going to connect both A5 analog pins together, these pins are SCL, or clock pins explained later in the tutorial. (Image 2)
- Connect both A4 analog pins together, these pins are SDA, or data transfer pins. (Image 3)
To upload the code we need to connect to both Arduinos a USB cable, recognize which port is for which Arduino. Through the USB we power up both Arduinos and we are ready to start to upload the code.
Step 4: LED ON/OFF With I2C
Here we are going to control an LED attached to the D13 on the Slave Arduino with the Master Arduino. Master Arduino will have a Serial Monitor opened and we will write a command saying ON or OFF to toggle the LED on the Slave.
Here you can copy the master code or simply download it from the attachment down below. The attached code has an explanation for each line of the code, but in the next step of the tutorial we will explain the code in detail.
#include <Wire.h> String readString; byte I2C_OnOff; void setup() { Wire.begin(); Serial.begin(9600); Serial.println("Type On to turn on the LED and Off to shut it down!"); } void loop() { while (Serial.available()) { delay(2); char c = Serial.read(); readString += c; } if (readString == "On" or readString == "ON" or readString == "on") { I2C_OnOff = 1; } else if (readString == "Off" or readString == "OFF" or readString == "off") { I2C_OnOff = 0; } if (readString.length() > 0) { Serial.println(readString); readString = ""; } Wire.beginTransmission(1); Wire.write(I2C_OnOff); Wire.endTransmission(); }
Here you can copy the slave code or simply download it from the attachment down below.
#include <Wire.h> int DO_Blink = 13; byte I2C_OnOff; void setup() { pinMode(DO_Blink, OUTPUT); Wire.begin(1); Wire.onReceive(BlinkLED); } void loop() { delay(100); } void BlinkLED(int Press) { I2C_OnOff = Wire.read(); if (I2C_OnOff == 1) { digitalWrite(DO_Blink, HIGH); } else if (I2C_OnOff == 0) { digitalWrite(DO_Blink, LOW); } }
Attachments
Step 5: Code Explained: LED ON
We will try to explain the code in as much detail as we can, but the code lines explained earlier will be only mentioned.
1. We include the wire library for I2C in our master code, we define a string variable called readString this variable will be used to store on or off commands given in the serial monitor.
#include <Wire.h> String readString;
2. We define a byte variable called I2C_OnOFF with which we will send information to the slave via I2C. We start the I2C communication and Serial communication, and write on the monitor for note for a user.
byte I2C_OnOff; void setup() { Wire.begin(); Serial.begin(9600); Serial.println("Type On to turn on the LED and Off to shut it down!"); }
3. Now we have to check if the user has inserted anything in the serial monitor and read it. We store this information through char c variable letter by letter and move these characters inside a readString string.
while (Serial.available()) { delay(2); char c = Serial.read(); readString += c; }
4. Now we create an if statement which will determine the value of a I2C_OnOff variable depending on the users input. If the user inserts "On", "ON" or "on" the I2C_OnOff variable will store number 1. If the user inserts "Off", "OFF" or "off" the I2C_OnOff variable will store 0.
if(readString == "On" or readString == "ON" or readString == "on") { I2C_OnOff = 1; } else if(readString == "Off" or readString == "OFF" or readString == "off") { I2C_OnOff = 0; }
5. Now we write a if statement for displaying the users input on the Serial Monitor of the Master. If the readString string has any value display this value and empty the string.
if (readString.length() >0) { Serial.println(readString); readString=""; }
6. Now we will open the transmission with the slave that has an address 1, send it a byte I2C_OnOff value and end the transmission.
Wire.beginTransmission(1); Wire.write(I2C_OnOff); Wire.endTransmission(); }
Now we will talk about the Slave code
1. We load the library as with the master, assign a variable to the digital pin 13 where our LED is connected (or built in). We define the same byte variable I2C_OnOff as in the master. This variable will be used with an if statement to toggle the LED.
#include <Wire.h> int DO_Blink = 13; byte I2C_OnOff;
2. We need to set the pin mode of the LED as an Output, start the I2C communication by stating that our slave address is 1. Than we have to call register an event, here called BlinkLED.
void setup() { pinMode(DO_Blink, OUTPUT); Wire.begin(1); Wire.onReceive(BlinkLED); }
3. We place a delay inside a loop, so that we don't "overwhelm" the Arduino.
void loop() { delay(100); }
4. We have to create a function for the event called BlinkLED which will output an integer Press.
void BlinkLED(int Press) {
5. Than we read the data that the master sends to the slave, we store that data in the byte I2C_OnOff.
I2C_OnOff = Wire.read();
6. We write a if statement to turn on or off the LED depending on the received data from the master.
if (I2C_OnOff == 1) { digitalWrite(DO_Blink, HIGH); } else if (I2C_OnOff == 0) { digitalWrite(DO_Blink, LOW); } }
Now we have to simply connect the two Arduinos as before. After we have uploaded the code to both boards and made sure that both have grounds connected and that both are powered up, we will choose the port of the master and turn on the serial monitor. Here we input on or off statements to toggle the LED.
Step 6: Second Mode: MasterReader---SlaveSender
This "mode" complements the first one because it enables the feedback loop between the master and the slave. Which in turn allows master to react on slaves feedback. This is very useful when master has code that uses slaves feedback for calculations, or as conditions for further code execution or as an input on the second slave that is hooked up on the master.
Here is the MasterReader code, you can upload it on the Arduino you choose to be a Master, you can also find this code in the attachment with more detailed explanation.
#include <Wire.h> void setup() { Wire.begin(); Serial.begin(9600); } void loop() { Wire.requestFrom(8, 6); while (Wire.available()) { char c = Wire.read(); Serial.print(c); } delay(500); }
Here is the SlaveSender code, you can upload it on the Arduino you choose to be a Slave, you can also find this code in the attachment with more detailed explanation.
#include <Wire.h> void setup() { Wire.begin(8); Wire.onRequest(requestEvent); } void loop() { delay(100); } void requestEvent() { Wire.write("hello "); }
The code will be explained in the next step.
Step 7: MasterReader---SlaveSender Explained
Here we would like to explain in detail what the individual code lines do. Starting from the MasterReader code.
1. We include the library wire as before, we begin the I2C communication between two boards, and serial communication between a master and a Serial Monitor (PC).
#include <Wire.h> void setup() { Wire.begin(); Serial.begin(9600); }
2. Now we request the slave to give information in the form of bytes to the master. We specifically ask for 6 bytes from the slave with the address number 8.
void loop() { Wire.requestFrom(8, 6);
3. Now we read these bytes inside a while loop like before, but now we will read everything and each individual byte will be stored as a character only until it's displayed on the Serial Monitor. What we display here will be seen in the SlaveSender code.
while (Wire.available()) { char c = Wire.read(); Serial.print(c); } delay(500); }
Now we will explain the SlaveSender code
1. We include the wire library as usually, begin the I2C communication and define the address of the slave device with the number 8, than we register an event (we call a function to be run each time the master requests information from the slave). As before we also make a small delay inside a void loop.
#include <Wire.h> void setup() { Wire.begin(8); Wire.onRequest(requestEvent); } void loop() { delay(100); }
2. Now we will define the event (or a function) which is run each time the master requests information from the slave. Here we will simply send 6 bytes of information which form a word "hello ", if you remember from the masters code, we had to specify exactly the number of bytes that master requests.
void requestEvent() { Wire.write("hello "); }
You can test this code by uploading the Master on one board and the slave on the second one. Connect the boards according to the wiring shown at the beginning of the tutorial step. To see the code in action you have to select the port of the master and turn on the serial monitor, and both Arduinos need to be connected to the same ground and both need to be powered up.
In the next tutorial about I2C we will show you a more interesting example which will stop the motor if an object is detected by the slave. The master requests from the slave the information about the distance from the object, and if the object is too close the master stops the motor. Here we will also make a simple object avoidance algorithm.