Connecting an Arduino to a Raspberry PI using I2C

Some time ago I created a weather station using a Raspberry PI and an off the shelf weather station, connecting the two via USB.

However, for some time not I’ve been meaning to create a weather station from scratch – i.e. one or more Raspberry PI’s which connect to the network (via Ethernet or WiFi) and directly monitor the sensors directly.

Now the problem here is that some sensors are analog – for example the leaf, soil and UV sensors I have generate an analog signal so we need an ADC (Analogue to Digital Converter) which the Raspberry PI doesn’t have.

So we have two possible solutions:

  1. Add a Raspberry PI compatible ADC
  2. Use an Arduino

With the parts I have available, the Arduino won, not just on available ADC channels but also with the additional digital ports available.

Now how to connect it to the PI? Well the easiest way is to use USB, however the PI only has two USB ports (one for the Model A) and as I’m intending to use Model A’s for the final station I need that for WiFi (there won’t be room or power for hubs) so USB is out.

There’s RS232 which both support, however the PI runs on 3v3 whilst the Arduino (UNO) is 5v so I need to add a level converter between the two. It also limits me to just one arduino and I might need to use more than one so another solution is needed.

Enter I2C

Both the PI and Arduino support two additional types of communication for talking to peripheral devices. There’s SPI which is a high speed serial protocol and I2C. Like RS232, SPI needs level shifters, but not exactly so for I2C.

I2C is a 2 wire protocol allowing for 127 devices to be connected to a single bus. One device is the master (The PI in our case) and then the peripherals.

An example I2C network (From Wikipedia)

An example I2C network (From Wikipedia)

In the above diagram you can see that there’s two connections between devices (other than ground), SDA (Serial Data Line) which is where the data is carried, and SCL (Serial Clock Line). There’s also a pair of resistors which pull up the signals to Vdd.

Now the trick, Vdd is only there to pull those signals up and in I2C a 1 is when the signal is pulled down to 0V. It’s not there to power the devices so, as long as we keep Vdd at 3v3 and no device has a pull up resistor on them (i.e. to 5V) then we are save to connect it to the PI. There’s only a problem if any device on the I2C bus also has a pull up resistor.

Now do the Arduino’s have pullup resisitors? Well they actually don’t, they actually cannot as the I2C interface is shared by two of the analogue inputs (4 & 5 to be precise) so there cannot be a resistor there else it would affect those pins when not being used for I2C.

So, we have a solution as long as the Raspberry PI is the I2C Master which is what we want. Also, of the available GPIO pins, only SDA and SCL have pull up resistors, so we are set.

First the obligitory warning

If you are uncertain of anything, like blowing up your PI etc then don’t follow this any further. You do this at your own risk.

Configuring the PI for I2C

First we need to enable the I2C module on the PI.

Remove I2C from the module blacklist

As root edit /etc/modprobe.d/raspi-blacklist.conf and comment out the line blacklisting i2c-bcm2708

$ cat /etc/modprobe.d/raspi-blacklist.conf
# blacklist spi and i2c by default (many users don't need them)
blacklist spi-bcm2708
#blacklist i2c-bcm2708

Next add i2c-dev to the /etc/modules file so it’s loaded on boot:

# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
# Parameters can be specified after the module name.

snd-bcm2835
ipv6
i2c-dev

Finally install i2c-tools:

$ sudo apt-get install i2c-tools
$ sudo adduser pi i2c

Now reboot the PI.

Configuring the Arduino

The following sketch implements a simple I2C slave with two commands:

Command 1 will toggle the onboard led on the Arduino.

Command 2 will return the arduino’s temperature in Celsius.

#include <Wire.h>

#define SLAVE_ADDRESS 0x04
int number = 0;
int state = 0;

double temp;

void setup() {
 pinMode(13, OUTPUT);

 // initialize i2c as slave
 Wire.begin(SLAVE_ADDRESS);

 // define callbacks for i2c communication
 Wire.onReceive(receiveData);
 Wire.onRequest(sendData);
}

void loop() {
 delay(100);
 temp = GetTemp();
}

// callback for received data
void receiveData(int byteCount){

 while(Wire.available()) {
  number = Wire.read();

  if (number == 1){
   if (state == 0){
    digitalWrite(13, HIGH); // set the LED on
    state = 1;
   } else{
    digitalWrite(13, LOW); // set the LED off
    state = 0;
   }
  }

  if(number==2) {
   number = (int)temp;
  }
 }
}

// callback for sending data
void sendData(){
 Wire.write(number);
}

// Get the internal temperature of the arduino
double GetTemp(void)
{
 unsigned int wADC;
 double t;
 ADMUX = (_BV(REFS1) | _BV(REFS0) | _BV(MUX3));
 ADCSRA |= _BV(ADEN); // enable the ADC
 delay(20); // wait for voltages to become stable.
 ADCSRA |= _BV(ADSC); // Start the ADC
 while (bit_is_set(ADCSRA,ADSC));
 wADC = ADCW;
 t = (wADC - 324.31 ) / 1.22;
 return (t);
}

The Raspberry PI client

Here’s a simple C application which will now talk to the Arduino over I2C:

#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>

// The PiWeather board i2c address
#define ADDRESS 0x04

// The I2C bus: This is for V2 pi's. For V1 Model B you need i2c-0
static const char *devName = "/dev/i2c-1";

int main(int argc, char** argv) {

  if (argc == 1) {
    printf("Supply one or more commands to send to the Arduino\n");
    exit(1);
  }

  printf("I2C: Connecting\n");
  int file;

  if ((file = open(devName, O_RDWR)) < 0) {
    fprintf(stderr, "I2C: Failed to access %d\n", devName);
    exit(1);
  }

  printf("I2C: acquiring buss to 0x%x\n", ADDRESS);

  if (ioctl(file, I2C_SLAVE, ADDRESS) < 0) {
    fprintf(stderr, "I2C: Failed to acquire bus access/talk to slave 0x%x\n", ADDRESS);
    exit(1);
  }

  int arg;

  for (arg = 1; arg < argc; arg++) {
    int val;
    unsigned char cmd[16];

    if (0 == sscanf(argv[arg], "%d", &val)) {
      fprintf(stderr, "Invalid parameter %d \"%s\"\n", arg, argv[arg]);
      exit(1);
    }

    printf("Sending %d\n", val);

    cmd[0] = val;
    if (write(file, cmd, 1) == 1) {

      // As we are not talking to direct hardware but a microcontroller we
      // need to wait a short while so that it can respond.
      //
      // 1ms seems to be enough but it depends on what workload it has
      usleep(10000);

      char buf[1];
      if (read(file, buf, 1) == 1) {
	int temp = (int) buf[0];

	printf("Received %d\n", temp);
      }
    }

    // Now wait else you could crash the arduino by sending requests too fast
    usleep(10000);
  }

  close(file);
  return (EXIT_SUCCESS);
}

Save that as main.c and compile it:

pi@mimas ~ $ gcc main.c -o main

Now you’ll notice there’s a couple of usleep() waits in this code, once between sending the command and again after reading the response. I’ve found that this is necessary for two reasons.

  1. The arduino is emulating an I2C device, so it won’t respond immediately unlike a dedicated device so you need to wait a short while before reading it otherwise you won’t get a response.
  2. Without the second delay you can confuse the arduino by requesting another command too quickly, necessitating the arduino to be reset before it can be used again.

I found that 10000 (10ms) is enough here.

Wiring the two together

Now this is simple: First power down both the Arduino and the Raspberry PI – never connect things whilst they are powered up!

Next simply connect the two with 3 wires using the following table:

Raspberry PI Arduino
GPIO 0 (SDA) <–> Pin 4 (SDA)
GPIO 1 (SCL) <–> Pin 5 (SCL)
Ground <–> Ground

Testing

Power up both the Arduino and Raspberry PI.  Once it’s up and running log in and run i2cdetect:

pi@mimas ~ $ i2cdetect -y 1
 0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- 04 -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

What you are now seeing is a list of all I2C devices connected. The one you are interested in is 04 which happens to be your arduino.

Lets toggle the led:

pi@mimas ~ $ ./main 1
I2C: Connecting
I2C: acquiring buss to 0x4
Sending 1
Received 1

You should now see the onboard led turn on. Run it again and the led goes out.

How about the temperature?

pi@mimas ~ $ ./main 2
I2C: Connecting
I2C: acquiring buss to 0x4
Sending 2
Received 35

35 just happens to be the Arduino’s internal temperature in Celsius (in this example we’ve just returned the integer temperature).

That’s about it. The next thing I now need to do is to get those additional sensors working with the arduino and wrap them in an I2C slave.

Oh, one last thing, as I said earlier and in that diagram, you can have many devices on the one I2C bus, so to add another arduino all you need to do is to connect the three wires to that arduino as well and make certain it is using a different address.

About these ads

28 thoughts on “Connecting an Arduino to a Raspberry PI using I2C

  1. Henrik says:

    Looks dangerous to connect the Pi and Arduino together without some voltage conversion since Pi is not 5v tolerant.

    • petermount1 says:

      Normally you would be right but here we are driving the i2c bus at the pi’s voltage not that of the arduino, hence the retirements of the pi being the master & no pull up resistors on the slaves as the Pi’s resistors run the bus at 3V3

  2. […] Peter Mount has a tutorial on how to connect an arduino to a raspberry pi using I2C. […]

  3. hashkf says:

    The Arduino sketch does delay the I2C slave communication of the Arduino accidentially in it’s loop() method.
    If you would only read the temperature when really needed or by breaking up the “delay(100);” into a more sophisticated non-blocking delay (shorter delay or only read temperatures every 100 milliseconds by using the “millis()” method) the responsiveness of the Arduino as the I2C slave should greatly improve.

    • petermount1 says:

      Yes, the sketch is there as an example to show it actually working whilst keeping things as simple as possible, although thanks for pointing this out. I had based this on several other articles so I’m not the only one to get caught out on that snag.

      That said, it’s the first time I’ve done I2C on the arduino side, so easily done.

  4. […] 通过I2C将Arduino连接到树莓派上 […]

  5. marcolastri says:

    Thank you Peter…very beautiful post!
    It’s a very good start for my project on a weather station!!!

  6. […] found this interesting post by Peter Mount in order to safely connect Arduino (as slave) and Raspberry (as master) using […]

  7. […] 通过I2C将Arduino连接到树莓派上 […]

  8. John Gaby says:

    Thanks for the post. It all works as flawlessly, except I get a ‘Segmentation fault’ when I try and close ‘file’ just before the return. Do you have any idea why that would be?

    Thanks.

    • John Gaby says:

      Never mind, I see the problem. It should be closing the file with a ‘close’ not ‘fclose’.

      Thanks again.

  9. IIn order to get this to compile I had to move the “setup” and “loop” functions to the bottom of the code in order to avoid errors along the lines of “error: ‘receiveData’ was not declared in this scope”.

  10. Pete says:

    There are now some html problems getting into the RasPI C++ source code. If you get problems with & lt; (no spaces) this should be the < less than symbol. & amp; should be replaced with the & ampersand symbol.

    on line 73 change:
    fclose(file);
    close(file);
    since the file was opened with open and not fopen. This will fix the warning during compilation and the Segmentation fault echoed after the temperature reading when running.

    Thanks for the code Pete, this really helped me get started connecting Arduino and RasPI

    • petermount1 says:

      Thanks for spotting that, not sure how fclose() got in there as I had copied it from my working copy at the time.

      As for lt & <, it's a problem I've had with wordpress, sometimes it breaks code whilst writing, can be a real problem when trying to put xml into an article.

  11. vat says:

    pi@raspberrypi ~ $ gcc main.c -o main
    main.c: In function ‘main’:
    main.c:27:39: error: ‘lt’ undeclared (first use in this function)
    main.c:27:39: note: each undeclared identifier is reported only once for each function it appears in
    main.c:27:41: error: expected ‘)’ before ‘;’ token
    main.c:34:42: error: expected ‘)’ before ‘;’ token
    main.c:41:30: error: expected ‘)’ before ‘;’ token
    main.c:45:39: error: ‘amp’ undeclared (first use in this function)
    main.c:45:42: error: expected ‘)’ before ‘;’ token
    main.c:73:3: warning: passing argument 1 of ‘fclose’ makes pointer from integer without a cast [enabled by default]
    /usr/include/stdio.h:234:12: note: expected ‘struct FILE *’ but argument is of type ‘int’

    i create main.c and try 2 gcc it…
    whats wrong?

    • petermount1 says:

      In the 2 if statements theres < which should actually be < – I've fixed it in the article now.

      It's an old problem with wordpress reformatting code examples when it shouldn't do.

      • Pete says:

        you got most of them. Line 41 and 45 still has that html code for the characters sneaking in on you.

      • petermount1 says:

        Thanks, missed those two :-(

      • vat says:

        pi@raspberrypi ~ $ gcc main.c -o main
        main.c: In function ‘main’:
        main.c:41:22: error: ‘lt’ undeclared (first use in this function)
        main.c:41:22: note: each undeclared identifier is reported only once for each function it appears in
        main.c:41:30: error: expected ‘)’ before ‘;’ token
        main.c:45:39: error: ‘amp’ undeclared (first use in this function)
        main.c:45:42: error: expected ‘)’ before ‘;’ token

        I am a loser :)

      • petermount1 says:

        No the wordpress editor occasionally substitutes html entities in source code. I’ve fixed the example for the second time now, so it should work now.

      • vat says:

        Thank you very much! It works! :)

  12. Dan Piponi says:

    I’ve been struggling to get reliable communication between my raspberry pi and an arduino micro. It works 99% of the time and fails 1% of the time.

    I attached a logic analyzer. When the address of a slave is sent over the bus, there is a short acknowledge step. When communication fails, for the first bit after this step, the clock consistently looks weird. Either there’s a long clock cycle, or a super-short one. I’ve tested many times now. After searching on the web I found that it is known that some Atmel devices “stretch” the clock at this point. This is legal, but the current raspberry pi drivers don’t know how to handle this correctly and the communication fails. If you write your code defensively you can deal with this but you may need to watch out for this. I’m going to try SPI instead…

    Slowing the clock makes it fail less often. Haven’t checked this case with the logic analyzer.

    • petermount1 says:

      Sorry for the late reply. As you point out This is a known issue with the current I2C kernel drivers and clock stretching.

      What I’ve been doing to get around this problem is I manually add delays to my code – one after sending the command and before reading the response and another after the response.

      The delay size doesn’t have to be much but it varies per device, so having an Arduino on it usually has a longer delay depending on the code it’s got to run.

      The other trick I use in the weatherstation project is that I ensure that all I2C calls are run sequentially by using a dedicated thread. I found that if I tried to run concurrent commands on the I2C bus it always caused problems.

      SPI is an option – the problem I have is that I have multiple devices that are I2C only (the UV Index sensor is one) but the solution I now have works pretty well.

      For info to anyone else reading this:

      • sigfpe says:

        The problem is, the issue happens within the space of a single call to the wire library and I don’t see how delays around those calls can change anything and I’m finding delays don’t help. (Delays make the system run for longer, but that’s only because it does less!) But the Arduino is surely a fairly a deterministic device so there must be *something* that makes the Arduino delay sometimes and not other times.

  13. Diego Cueva says:

    WORKS FINE !!!! Thanks a lot.
    I have replaced the line 28 with this one:
    fprintf(stderr, “I2C: Failed to access %s : %s\n”, devName, strerror (errno));

  14. Would you have a schematic or fritzing diagram of the whole project. Curious on what and how you used the pull-up resistors or if you didn’t have to, what the connections between the raspberry pi & arduino looks like

  15. Wow I lost my post. I was just wondering if there is a full wiring schematic or better yet a fritzing for this project? You lost me when you were talking about the pullup resistors and if they were even needed and what value. Would be nice to see the actual final photo of the finished product. Thanks for the article.

    • petermount1 says:

      I’ve only just got around to approving your last post :-)

      As for fritzing, no I haven’t but I will have something in the next week as I’m finally finishing off the build for the projects this article was the precursor to. The cabling is pretty straight forward however, its just connect GND, SCL & SDA directly between both boards.

      For the pullup resistors, they aren’t needed as the PI has them already on the I2C lines, & thats the trick here, as the arduino is the slave, it relies on the resistors on the PI to pull up those lines.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 1,767 other followers

%d bloggers like this: