web analytics

Simple I2C protocol for advanced communication between Arduinos

i2c cover

 Main task  – advanced communication between multiple Arduinos using I2C bus.

 Main problem  – most online tutorials covers just one blinking LED with almost no practical use. Slave just executes ONE and SAME function every time Master asks about it. I’d like to outsource slave Arduinos for zillions of tasks.

 Proposed solution  – simple protocol which enables to send any number of commands to Slave, opposing single return from simple Wire.onRequest();


Strong communication skills are always a key to success:


Say we have one Arduino(Slave) dedicated for position sensors and motor drives. Other slave for handling user interface tasks (displays, input controls, wifi communication). And a Master Arduino for controlling them all together. Master can ask to do any number of tasks(not just one from one Arduino).

Official Wire (I2C library for Arduino ) reference doesn’t add a lot of clarity for novices.
Good starting point is this tutorial: http://arduino.cc/en/Tutorial/MasterWriter. It just lacks of two pull-up resistors on SDA and SCL lines. Do not forget them. * Also some people report problems when multiple Arduinos are connected to one computer at the same time.

As you can see from above tutorial, communication is performed by transferring chars or bytes (max value 255). Thats not much use if you want to transfer real-life values like integers from analogRead(); or control some Digital PMW Pin.

So, second highly recommended tutorial can be found here: http://jamesreubenknowles.com/arduino-i2c-1680. It shows how to easily transfer and receive integer values by splitting them into two bytes (warning: some bitshifting involved).

Main problem for me was that I had multiple sensors on one sensor-dedicated slave. And on master’s request Wire.onRequest(); Slave could only return one value. So I made simple protocol for choosing desired sensor. The same method can be used for performing different tasks on slave, not just returning different values. You can make slave perform any function from its arsenal.

Basic algorithm:
  1. Master opens connection and sends integer value (command number) to selected Slave.
  2. Slave receives Wire.onReceive(); code number and makes function selection.
  3. Master requests selected(in 2. step) function to be executed on Slave by Wire.onRequest();
  4. Slave executes specified command and returns result to Master.
  5. Master receives result.
  6. Master closes connection.
  7. Slave resets command number and waits for next command from Master.



First, lets look at slave code. There are two functions for reading one or other sensor:

When slave receives command number, it executes receiveCommand(); function:

Now when we have command (LastMasterCommand), we will wait for execution request from Master:

Function on Master’s side for getting X Sensor value from Slave Arduino:

Thats all, functions can do any tasks, not only read sensors. For example they can control a motor or LED’s and return no useful data (just report successful execution).
Using same technique You can expand this simple protocol, i.e. pass a command number AND then some parameters to Slave, and then execute it. I showed basic principle.

Complete Master side program: (click to expand)

Complete Slave side program: (click to expand)
Update: I wrote new example for sending command AND passing data for Slave functions.

Now Master sends not only command number (one byte), but 11 bytes data packet consisting of:

    Command number (one byte)
    1-st argument value (int) (or two bytes)
    2-nd argument value (int) (or two bytes)
    3-rd argument value (int) (or two bytes)
    4-th argument value (int) (or two bytes)
    5-th argument value (int) (or two bytes)

I plan to use functions with maximum 5 integer arguments. If You need more/less or wish to change input/output variables type it can be easily done, by examining these examples. They are completely based on previous ones. Maximum size of standard buffer in Wire library is 32 bytes.

In order to work correctly, data packet has always be the same size (even if Your function doesn’t need any arguments You have to pass zeroes or any values). Also if You are not interested in return value of any Slave’s function, than You can execute commands in Wire.onReceive(); function. There is no need to ask for return Wire.onRequest(); if You are interested in just sending data from Master to Slave. But if You have just one function that returns something, use Wire.onRequest(); everywhere. Wire likes consistency. A lot.

In this example Master creates a data packet (command number, and five integer variables), sends it to Slave, Slave performs sum of all 5 variables and returns result to Master. Master prints result in serial window. Slave acts as a math-coprocessor for the Master 🙂

Tip – if You don’t see correct answer in serial window – press “reset” button on Master, while serial window is open. This will solve early power-on miscommunication issues. Later both sides will work correctly.

Complete Master side program v0.2: (click to expand)

Complete Slave side program v0.2: (click to expand)


If I helped You, please help me. Thank You!

    1. Mark Schilke January 22, 2015 at 21:08

      Hello, Ignas.

      Thank you for sharing and explaining your Arduino I2C protocol programs. I don’t know if you have done any more development work on them lately, but you may encounter some difficulty with the code as it is shown in your July 2014 blog.

      When I tried using your programs, I found that the slave did not make an immediate response to the command sent by the master. In fact, it was always one command behind. I believe that is because transmission of the data packet does not actually begin when the Wire.write statement in the sendDataPacket subroutine is executed — it is only putting the 11 bytes into the I2C buffer. Execution of the Wire.endTransmission statement in the receiveResponse subroutine is what sends out the command over the wired connection, but by then it is too late.

      After I added a Wire.endTransmission statement to the sendDataPacket subroutine (and a Wire.beginTransmission statement to the receiveResponse subroutine), the protocol worked exactly as it should. If you like, I can send you the modified code and the results I got with them.


      Mark Schilke

      • Ignas Gramba January 22, 2015 at 21:11

        Thank You Mark ! Of course send it, or put it in comments.

        • Mark Schilke January 22, 2015 at 23:57

          // Simple I2C protocol v0.2 for Arduino
          // Master side program
          // (c) 2014 Ignas Gramba

          // modified by Mark Schilke to issue a wider variety of commands to the slave


          byte DataPacket[11];
          byte command_nr;
          int a, b, c, d, e; // arguments
          byte SlaveDeviceId = 1;

          void setup() {
          Wire.begin(); // join i2c bus (address optional for master)
          Serial.begin(9600); // start serial for output
          command_nr = 0; // initialise test values
          a = 105;
          b = 2350;
          c = 4587;
          d = 12587;
          e = 12;

          void loop() {
          doDataPacket(0, a, b, c, d, e); // should return “0” (do-nothing command)
          doDataPacket(1, a, b, c, d, e); // should return the value of the first argument (“a” = 105)
          doDataPacket(2, a, b, c, d, e); // should return the value of the second argument (“b” = 2350)
          doDataPacket(3, a, b, c, d, e); // should return the value of the third argument (“c” = 4587)
          doDataPacket(4, a, b, c, d, e); // should return the value of the fourth argument (“d” = 12587)
          doDataPacket(5, a, b, c, d, e); // should return the value of the fifth argument (“e” = 12)
          doDataPacket(11, a, b, c, d, e); // should return double the first argument (105 + 105 = 210)
          doDataPacket(12, a, b, c, d, e); // should return the sum of the first and second arguments (105 + 2350 = 2455)
          doDataPacket(13, a, b, c, d, e); // should return the sum of the first and third arguments (105 + 4587 = 4692)
          doDataPacket(14, a, b, c, d, e); // should return the sum of the first and fourth arguments (105 + 12587 = 12692)
          doDataPacket(15, a, b, c, d, e); // should return the sum of the first and fifth arguments (105 + 12 = 117)
          doDataPacket(21, a, b, c, d, e);
          doDataPacket(22, a, b, c, d, e);
          doDataPacket(23, a, b, c, d, e);
          doDataPacket(24, a, b, c, d, e);
          doDataPacket(25, a, b, c, d, e);
          doDataPacket(31, a, b, c, d, e);
          doDataPacket(32, a, b, c, d, e);
          doDataPacket(33, a, b, c, d, e);
          doDataPacket(34, a, b, c, d, e);
          doDataPacket(35, a, b, c, d, e);
          doDataPacket(41, a, b, c, d, e);
          doDataPacket(42, a, b, c, d, e);
          doDataPacket(43, a, b, c, d, e);
          doDataPacket(44, a, b, c, d, e);
          doDataPacket(45, a, b, c, d, e);
          doDataPacket(51, a, b, c, d, e);
          doDataPacket(52, a, b, c, d, e);
          doDataPacket(53, a, b, c, d, e);
          doDataPacket(54, a, b, c, d, e);
          doDataPacket(55, a, b, c, d, e);
          doDataPacket(99, a, b, c, d, e); // should return the sum of all arguments (105 + 2350+ 4587 + 12587 + 12 = 16941)
          doDataPacket(100, a, b, c, d, e); // should return the minimum argument (12)
          doDataPacket(101, a, b, c, d, e); // should return the maximum argument (12587)

          void doDataPacket(byte command, int aa, int bb, int cc, int dd, int ee){
          DataPacket[0] = command;
          DataPacket[1] = aa >> 8;
          DataPacket[2] = aa & 255;
          DataPacket[3] = bb >> 8;
          DataPacket[4] = bb & 255;
          DataPacket[5] = cc >> 8;
          DataPacket[6] = cc & 255;
          DataPacket[7] = dd >> 8;
          DataPacket[8] = dd & 255;
          DataPacket[9] = ee >> 8;
          DataPacket[10]= ee & 255;


          int response = receiveResponse();

          Serial.print(“Packet to Slave: “);
          Serial.print(” / “);
          Serial.print(“, “);
          Serial.print(“, “);
          Serial.print(“, “);
          Serial.print(“, “);
          Serial.print(“Response from Slave: “);



          void sendDataPacket(){
          Wire.write(DataPacket, 11);

          int receiveResponse(){
          int receivedValue;
          int available = Wire.requestFrom(SlaveDeviceId, (byte)2);
          if(available == 2) {receivedValue = Wire.read() << 8 | Wire.read();}
          else {
          Serial.print("ERROR: Unexpected number of bytes received – ");
          return receivedValue;

    2. Mark Schilke January 23, 2015 at 00:00

      // Simple I2C protocol v0.2 for Arduino
      // Slave side program
      // (c) 2014 Ignas Gramba

      // modified by Mark Schilke to perform a wider variety of commands


      const byte SlaveDeviceId = 1;
      byte LastMasterCommand = 0;
      int a, b, c, d, e;

      void setup(){
      Wire.begin(SlaveDeviceId); // join i2c bus with Slave ID
      Wire.onReceive(receiveDataPacket); // register talk event
      Wire.onRequest(slavesRespond); // register callback event

      void loop(){}

      void receiveDataPacket(int howMany){
      // if (howMany != 11) return; // Error
      LastMasterCommand = Wire.read();
      a = Wire.read() << 8 | Wire.read();
      b = Wire.read() << 8 | Wire.read();
      c = Wire.read() << 8 | Wire.read();
      d = Wire.read() << 8 | Wire.read();
      e = Wire.read() <> 8;
      buffer[1] = returnValue & 255;
      Wire.write(buffer, 2); // return response to last command
      LastMasterCommand = 0; // null last Master’s command

      int sumFunction(int aa, int bb, int cc, int dd, int ee){
      // of course for summing 5 integers You need long type of return,
      // but this is only illustration. The test values do not overflow.

      int result = aa + bb + cc + dd + ee;
      return result;

    3. Mark Schilke January 23, 2015 at 00:05

      Copy and paste seemed to leave out some of the program. A zip file can be found here: http://www.meetup.com/Roanoke-Robotics/files/

    4. r3m February 18, 2015 at 13:59

      for situations like that, internet is PoooOWERFUUL.
      thank you both, guys!
      master and master.
      (no slaves here:)

    5. freezer April 21, 2015 at 22:10

      A more effective communication mechanism exists – master (with unique ID) gives a job to each slave (must have different IDs) and each time ends transmission by releasing the I2C bus. After the each slave finishes, it acquires the I2C bus for transmission and posts a response back to master. This is more optimal as the I2C bus is not being locked until one slave finishes off work 😛

      i.e. master should not wait for the result in the function GetXSensorValue, but just post a job for the slave and bail out immediately. Slave should respond back when it’s done.

      • Alex May 13, 2016 at 14:24

        As far as I am aware slave cannot initiate a connection on its own.
        Each time connection has to be requested by a master.

    6. pahval October 31, 2015 at 18:17

      hi! man, this is just what i was looking for, starting from scratch but working on big project (18 sensors, 6 relays, sending sensor data to master and receiving relay commands from master, and maybe parameters for slave’s part of relay automation, you know like, i send to master temp number at wich slave will start the heater), and i just wanna know what does this mean:

      DataPacket[1] = aa >> 8;
      DataPacket[2] = aa & 255;

      …this aa>>8 and aa & 255…

      and in:

      a = Wire.read() << 8 | Wire.read()

      …this wire.read() << 8 | wire.read()

      my intuition tells me you decoded that "a" value in two forms, but i dont understand (and i must, for i will burn my brain trying to figure it out just based on your code!) why, how and what do you get with it… i mean, a=105, so what, you present that number in 2 ways so that -what?- you can get -what?…

      dont have to tutor me here if you dont want to, but please, could you maybe send me some info on what this is all about so i can scan the web for it…


      well, when you are allready here, ill tell you bout my project…

      i have a growbox (for tomatoes, of course), wich has about 18 sensors and 6 relays (i like to play rough) wich are connected to mega slave,
      and in my room i (will) have rgb led strip, some sensors, ir emmiter, electric door lock etc. basically automated crib with lots of stuff i dont really need, wich would be operated on master intel edison, and i need slave to send sensor data to master wich sends them to my app as i am controlling with my app the tv buttons, blinds, coffee machine, lights, some relays and of course, reading and controling growbox (trigger lamps when master rtc hits 22:00 or whatever time i need, display sensor data etc.), so i need this type of communication between master and slave (slave send all sensor data, receive at wich value and/or when do something), but man, this cyphering of values really grinds my gears…

    7. Jp Jones January 26, 2016 at 03:21

      One of my questions is in the code where I define the Slave ID

      This may be rudimentary.

      1. // Simple I2C protocol v0.2 for Arduino
      2. // Master side program
      3. // (c) 2014 Ignas Gramba
      4. //
      5. #include
      7. byte DataPacket[11];
      8. byte command_nr;
      9. int a, b, c, d, e; // arguments
      10. byte SlaveDeviceId = (1);// HOW TO DEFINE FOR MULTIPLE SLAVE DEVICES WITH ID
      11. void setup()
      12. {

      I am assuming that Line 10 is where I have to define all of the slave ID’s
      I am not connected using the A4 & A5 pins I am using SCL and SDA If this in incorrect please assist me where to find the correct information.

      I currently have three Arduino’s on a buss proper resistors installed. Is there a way to create
      this communications bus with the SLC SDA or should I be using A4 A5 with this code?

      Also Line 7 would also need to add another DataPacket [12] for the next device? and if so what else would need to change.?

      Any help would be appreciated.

      • Jp Jones January 26, 2016 at 03:22

        I did not include all of the code, but if you need it I am using YOUR code 🙂

        • Jp Jones January 26, 2016 at 03:30

          Before I go too far, I did find the I2C Detect/Scan sketch I am running it and my master arduino does see the two arduinos

          so that is not an issue. Well I have been here 12 hours today at the office, last 3 spent won this LOL

          I will do some more checking tomorrow since I know I am seeing the devices I am going to check out some more things online

          I do love OPC 🙂

    8. armando March 26, 2016 at 22:42

      congratulations for the post
      It is the only article on the web that is seriously concerned with how data is transmitted on the i2c bus.
      and it ‘very good
      I made many tests

      I wanted to ask if there is a way to make usable variables a b c d on the slave in the main loop (void loop)
      for example to see printed on the serial monitor (of the slave)

      I mean the vars

      a = Wire.read () << 8 | Wire.read ();
      b = Wire.read () << 8 | Wire.read ();
      c = Wire.read () << 8 | Wire.read ();
      d = Wire.read () << 8 | Wire.read ();
      e = Wire.read () << 8 | Wire.read ();

      How can I transfer a, b, c, d, e, inside, interior, "void loop () {}"

      sorry for my bad English

      I'm using google traslator

      I hope that I did understand

      and to receive an answer with a practical example.

      I hope to be able to make me understand

      and to receive an answer with a practical example.


    Leave a Comment