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)
UPADATE
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)

Support

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

  • Leave a Comment