Sie sind auf Seite 1von 12

BLOG

CONTACT

Simple I2C protocol for advanced communication between Arduinos

RECENT POSTS

Spining BLDC(Gimbal) motors at


super slooooooow speeds with
Arduino and L6234
April 18, 2015

Driving 47 LED with 4 resistors


only (kinda Charlieplexing)
August 8, 2014

Simple I2C protocol for advanced


communication between Arduinos
July 17, 2014

Main task advanced communication between multiple Arduinos using I2C bus.
YEAH BITCH! LAZERS!
Main problem most online tutorials covers just one blinking LED with almost no practical use. Slave just

June 29, 2014

executes ONE and SAME function every time Master asks about it. Id like to outsource slave Arduinos for
Hello world!

zillions of tasks.

April 20, 2014

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

1 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

Official Wire (I2C library for Arduino ) reference doesnt 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/arduinoi2c-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 masters 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.

1.
2.
3.
4.
5.
6.
7.

Basic algorithm:

Master opens connection and sends integer value (command number) to selected Slave.
Slave receives Wire.onReceive(); code number and makes function selection.
Master requests selected(in 2. step) function to be executed on Slave by Wire.onRequest();
Slave executes specified command and returns result to Master.
Master receives result.
Master closes connection.
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:
Sensors reading functions
1 #define XSensorPin A1
2 #define YSensorPin A2
3
4 ...
5
6 int GetXSensorValue(){
7
int val = analogRead(XSensorPin);
8
return val;
9 }
10
11 int GetYSensorValue(){
12
int val = analogRead(YSensorPin);
13
return val;
14 }

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


Command decryption
1 byte LastMasterCommand = 0;
2
3 void setup(){
4
Wire.onReceive(receiveCommand); // register talk to slave event
5 }
6
7 ...
8
9 void receiveCommand(int howMany){
10
LastMasterCommand = Wire.read(); // 1 byte (maximum 256 commands), it can be easily changed to inte
11 }

Now when we have command (LastMasterCommand), we will wait for execution request from Master:
Request execution
1 void setup(){
2
Wire.onRequest(slavesRespond);
3 }
4

// Perform on master's request

2 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

5 ...
6
7 void slavesRespond(){
8
9
int returnValue = 0;
10
11
switch(LastMasterCommand){
12
case 0:
// No new command was received
13
Wire.write("NA");
14
break;
15
16
case 1:
// Return X sensor value
17
returnValue = GetXSensorValue();
18
break;
19
20
case 2:
// Return Y sensor value
21
returnValue = GetYSensorValue();
22
break;
23
24
case 3:
25
// Another useful function if received command = 3, and so on...
26
break;
27
28
}
29
30
uint8_t buffer[2];
// split integer return value into two bytes buffer
31
buffer[0] = returnValue >> 8;
32
buffer[1] = returnValue & 0xff;
33
Wire.write(buffer, 2);
// return slave's response to last command
34
LastMasterCommand = 0;
// null last Master's command and wait for next
35
36 }

Function on Masters side for getting X Sensor value from Slave Arduino:
GetXSensorValue
1 int GetXSensorValue(byte SlaveDeviceId){
2
3
// SEND COMMAND
4
Wire.beginTransmission(SlaveDeviceId);
5
Wire.write(1); // Transfer command ("1") to get X sensor value;
6
delay(10);
7
8
// GET RESPONSE
9
int receivedValue;
10
int available = Wire.requestFrom(SlaveDeviceId, (byte)2);
11
if(available == 2)
12
{
13
receivedValue = Wire.read() << 8 | Wire.read(); // combine two bytes into integer
14
}
15
else
16
{
17
Serial.print("ERROR: Unexpected number of bytes received (XSensorValue): ");
18
Serial.println(available);
19
}
20
Wire.endTransmission();
21
22
return receivedValue;
23 }

Thats all, functions can do any tasks, not only read sensors. For example they can control a motor or LEDs
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)

Master
1 // Simple I2C protocol for Arduino
2 // Master side program
3 // (c) 2014 Ignas Gramba
4 //
5
6 #include <Wire.h>
7 void setup()
8 {
Wire.begin();
// join i2c bus (address optional for master)
9

3 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

Serial.begin(9600);

// start serial for output

}
void loop()
{
int x = GetXSensorValue(1); // 1 - slave's address
Serial.print("X Sensor value: ");
Serial.println(x);
delay (100);
}
int GetXSensorValue(byte SlaveDeviceId){
// SEND COMMAND
Wire.beginTransmission(SlaveDeviceId);
Wire.write(1); // Transfer command ("1") to get X sensor value;
delay(10);
// GET RESPONSE
int receivedValue;
int available = Wire.requestFrom(SlaveDeviceId, (byte)2);
if(available == 2)
{
receivedValue = Wire.read() << 8 | Wire.read(); // combine two bytes into integer
}
else
{
Serial.print("ERROR: Unexpected number of bytes received (XSensorValue): ");
Serial.println(available);
}
Wire.endTransmission();
return receivedValue;
}

Complete Slave side program: (click to expand)

Slave
1 // Simple I2C protocol for Arduino
2 // Slave side program
3 // (c) 2014 Ignas Gramba
4 //
5 #include <Wire.h>
6
7 #define XSensorPin A1
8 #define YSensorPin A2
9
10 const byte SlaveDeviceId = 1;
11 byte LastMasterCommand = 0;
12
13 void setup(){
14
Wire.begin(SlaveDeviceId);
// join i2c bus with Slave ID
15
Wire.onReceive(receiveCommand); // register talk event
16
Wire.onRequest(slavesRespond); // register callback event
17
18
pinMode(XSensorPin, INPUT);
19
pinMode(YSensorPin, INPUT);
20 }
21
22 void loop(){
delay(100);
23
24 }
25
26 void receiveCommand(int howMany){
27
LastMasterCommand = Wire.read(); // 1 byte (maximum 256 commands)
28 }
29
30 void slavesRespond(){
31
32
int returnValue = 0;
33
34
switch(LastMasterCommand){
35
case 0:
// No new command was received
36
Wire.write("NA");
37
break;
38
39
case 1:
// Return X sensor value
40
returnValue = GetXSensorValue();
41
break;
42
43
case 2:
// Return Y sensor value
44

4 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

returnValue = GetYSensorValue();
break;
}
byte buffer[2];
buffer[0] = returnValue >> 8;
buffer[1] = returnValue & 255;
Wire.write(buffer, 2);
LastMasterCommand = 0;

// split int value into two bytes buffer

// return response to last command


// null last Master's command

}
int GetXSensorValue(){
int val = analogRead(XSensorPin);
return val;
}
int GetYSensorValue(){
int val = analogRead(YSensorPin);
return val;
}

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 doesnt need any
arguments You have to pass zeroes or any values). Also if You are not interested in return value of any Slaves
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 dont 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.

1
2
3
4
5
6

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

// Simple I2C protocol v0.2 for Arduino


// Master side program
// (c) 2014 Ignas Gramba
//
#include <Wire.h>

5 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

byte DataPacket[11];
byte command_nr;
int a, b, c, d, e; // arguments
byte SlaveDeviceId = 1;
void setup()
{
Wire.begin();
Serial.begin(9600);
command_nr = 2;
a = 105;
b = 2350;
c = 4587;
d = 12587;
e = 12;

// join i2c bus (address optional for master)


// start serial for output
// initialise test values

}
void loop()
{
makeDataPacket(command_nr, a, b, c, d, e);
sendDataPacket();
int response = receiveResponse();
Serial.print("Slave response: ");
Serial.println(response);
while(1); // Stop
}
void makeDataPacket(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;
}
void sendDataPacket(){
Wire.beginTransmission(SlaveDeviceId);
Wire.write(DataPacket, 11);
delay(10);
}
int receiveResponse(){
int receivedValue;
int available = Wire.requestFrom(SlaveDeviceId, (byte)2);
if(available == 2)
{
receivedValue = Wire.read() << 8 | Wire.read(); // combine two bytes into integer
}
else
{
Serial.print("ERROR: Unexpected number of bytes received - ");
Serial.println(available);
}
Wire.endTransmission(true);
return receivedValue;
}

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

1
2
3
4
5
6
7
8
9
10
11
12

// Simple I2C protocol v0.2 for Arduino


// Slave side program
// (c) 2014 Ignas Gramba
//
#include <Wire.h>
const byte SlaveDeviceId = 1;
byte LastMasterCommand = 0;
int a, b, c, d, e;
void setup(){
Wire.begin(SlaveDeviceId);

// join i2c bus with Slave ID

6 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

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 | Wire.read();
}
void slavesRespond(){
int returnValue = 0;
switch(LastMasterCommand){
case 0:
// No new command was received
returnValue = 1; // i.e. error code #1
break;
case 1:

// Some function

break;
case 2:
// Our test function
returnValue = sumFunction(a,b,c,d,e);
break;
}
byte buffer[2];
// split int value into two bytes buffer
buffer[0] = returnValue >> 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. Test values doesn't overflow
int result = aa + bb + cc + dd + ee;
return result;
}

Top

July 17, 2014

by Ignas Gramba

in Blog

7 Comments

7 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

Tagged with: arduino, communication, i2c, iic, master, multiple, multitasking, protocol, sensors, slave, tasks

7 COMMENTS

8 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

9 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

10 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

LEAVE A COMMENT

Name *

Email *

Website

Comment

11 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

2014 All Rights Reserved.

12 http://www.berryjam.eu/2014/07/advanced-arduino-i2c-communication/

Das könnte Ihnen auch gefallen