Connecting an Arduino to a Raspberry PI using I2C

Post navigation

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:

Add a Raspberry PI compatible ADC

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)

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

# /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

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.

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

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.

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.

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

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

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.

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.

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.

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

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.

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.

Cool!!! I have three devices connected to my PI. One Aduino Uno, one Arduino mini pro and a PCF8574. This make it necessary to select a address. How can I change the main.c in that way that the first parameter is the Bus address? Can any one help me with this?

Has anyone got a solution to this? I have multiple ATMegas (Arduino Uno chips on breadboard) – and while I can get one to communicate effectively (Great tutorial!!) – when I add a second chip and try to modify the programs, it does not work. No failure message, just “unknown command” when I enter a number.
Ideas? Sample code for solution? Any help is appreciated!!!

I did get the two chips working, but only with 2 separate programs on the pi. I call one for the first arduino, and call the second program for the second chip. Each ATMega chip has its own address – and each ATmega (arduino) has unique commands (1 and 2 for chip 1, 3 and 4 for chip 2).
So to get it to work, I call:
./main1 1 (turns on/off LED on chip 1)
./main1 2 (returns temp from chip 1)
./main2 3 (turns on/off LED on chip 2)
./main2 4 (returns temp from chip 2)

I’m not certain why that’s happening. The only thing I can think of is that you are running both ATMega’s with the same I2C address? As they are separate devices then they should have individual addresses.

Since writing this article I’ve been using code that’s evolved from this to talk to multiple devices on the one I2C bus. In the instance thats been running since April there’s no ATMega’s in there but it’s got a Light meter, UV sensor and a temp/pressure board all communicating with a single process on the PI & have not seen any problems like this. Each of these devices are physically separate units each with their own address (defined by the manufacturer).

Thank you so much. It worked perfectly, of course. But there was one GOTCHA / WARNING. Maybe obvious to others, but not me, I connected to Arduino Uno pins 4 and 5. When it needs to be Arduino Uno pins A4 and A5.

I now have 4 ATMegas and one magnetometer as slaves on my I2C bus, with a raspberry Pi as the master.
Everything seems to be running great – EXCEPT – any time I call my stepper function (nothing is actually hooked up to the ATMega chip – simply a test call), my Pi loses the I2C bus (can’t see any device) and ends the program with a segmentation fault.

If I unplug the chip from the bus that the stepper function was called on, the Pi can see the devices on the bus again. If I reset the chip, I can plug it back into the bus again, and the Pi can see it and interact with it again.

I am at my wits end trying to figure out what is happening here! I have verified this only happens when I call this stepper library function. It doesn’t pertain to the chip, any other calls to all other functions read/write fine with no problems.

I am using the standard arduino stepper library to call myStepper.step(rotations) – there is nothing fancy going on here so far as I can see.

Double check that you are not pulling the i2c bus up to 5v – that could damage your PI as that runs at 3V3.

It sounds to me that your stepper chip might be doing that. It might be as simple as the board the chip is on has pull-up resistors, you you may be able to get around this by removing them. If not then you will most likely need to put a level converter between the bus and that particular chip.

This is a common problem where vendors put the resistors in when they are not needed.

The Arduino chips are just on a breadboard, with only a resonator (clock) and 10k resistor (reset to 5V) for each chip. They are currently not plugged into anything except the i2c bus (plus obviously the resonator, resistor, 5V and ground).
In other words – no chips, pull-up resistors or any sort of peripheral is involved here.

The pi is being powered from a 5V breadboard rail, through a micro usb power port.
The pi functions without any problems like this – and it can complete dozens of any other type of i2c call – read and write – as long as it is not to a stepper.step() function on the arduino!

When I run the test from the pi – it is only to send commands to each arduino (on breadboard – just an ATMega chip) so the arduino will run its own functions. With nothing plugged in – this test was only meant to confirm i2c communication back and forth through all chips, and that the function calls are all set correctly.

It was a real surprise then that the mere action of calling the function step() from the Arduino Stepper library causes such a problem! With no stepper, chip, or even an LED plugged in – the action was simply expected to give a confirmation. Instead – segfault. Bus stops working. Chip reset required. Unexpected chaos!!!

It’s as if when calling the stepper step function, it starts filling the bus with so much junk it (the bus) no longer functions. That is my best hypothesis – but I can not find any documentation anywhere that could support such a conclusion. The closest I found is that the step function call causes blocking – and the chip should not be called to do anything else until the action completes. There is a formula to determine how much time to wait, based on the speed the rotation is set to (in my case, 90) – but even if I wait 5 seconds for a single rotation, the result is the same.

Ah, at first I thought it was const int brakeB3 = 4; but then remembered you said are using Megas?

So on those you’re connected to pins 20 & 21 for I2C (looking at the wire docs as not used Megas myself). The reason I thought that line was on the Uno’s it’s pins 4 & 5 so thought it was a pin clash so trying to control the stepper caused the bus to crash. As the uno has those pins exposed in 2 locations on the boards I was thinking that was a possibility but, if the megas do have separate pins then that’s not it.

Other than that I can’t see anything obvious in the source. Timings seem ok and you aren’t using threading – I did find there was a bug in the Linux i2c kernel code when writing to two devices on two threads could cause the bus to get confused but you have everything in a single thread so can’t be it.

It was an awesome post. I managed to connect the raspberry pi with one arduino. However, I tried to connect 2 Arduinos but for some reason just one works. What am I doing wrong? I am using 2 different address and it still doesn’t work. I detect both addresses with the i2cdetect. I connect both arduinos with the same ground, same 3v3, SDA and SCL. What else should I do.

I honestly made only a feel changes. I guess that was mainly because I did not get exactly what which line is doing.
On my Arduino Sketch I just changed the address for each Arduino and for the Rpi I just did the following:

// 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);
}

I am new on the Raspberry Pi, so that's why I am having a hard time. lol

However when you are acquiring the buss for ADDRESS2, you are logging it but the ioctl() call is using ADDRESS. It’s also storing the file handle into the same variable as that for ADDRESS.

So, you need to:
1: on the line where it says int file; replace it with int file1, file2.
2: on the ioctl() call for ADDRESS change the variable to file1.
3: on the ioctl() call for ADDRESS2, after you change it to use that address, change file to file2.
4: in the write() call change it to file1.
5: duplicate that if block but have the second write() to use file2.
6: close(file) becomes close(file1); close(file2);

In theory that should then send to both arduinos.

What was happening was the PI was opening a filehandle twice to the first one but not the second, and you were making just a single command, so the second arduino never got the command.

Before you told me that, I sat for a hour straight and understood mostly what was going on and made it work. But thank you anyway for using your time to help me. I really appreciate it.

What I really want to do with the Rpi and the Arduino is to constantly send a string to the arduino that contains the time and a set point for a water tank temperature. I need the time from the Pi because I will have more than 5 arduinos working together and they must be synchronized. So if the pi send this info, that helps a lot. I want to send the temp because I do not want to upload the sketch all the time if I want to change the set point. So my question is, how can I make it an infinite loop so the time changes constantly? Do you have any idea?

Hello Peter, when I run main.c I get the following lines and error: “I2c: Connecting” followed by “I2C: Failed to access 34900”. Running I2Cdetect shows a ’04’ in the correct place according to you diagram so as far as I know i2c has been configured on my rpi properly. Do you have any other explanations for my problem? Would be appreciated, thanks.

Hello Peter, I used this solution for a while now to switch on and off some switches. It all works fine, but I like to get some analogue values. The values from the analogue port are larger than 255 (one byte) how can I send and receive values to and from a Arduino and a Pi. I am a noop at C so could you help me with that? Would appreciate it very much.

Hi Peter;
Thanks so much for sharing your code with everyone. I too, am looking to poll the arduino’s analogue ports and use that information in the Raspberry Pi. I am also a C noob, so can you please direct me where in your github that you’ve shown that . I’ve done a simple modification to your C program to ask the arduino for the data, but need to work on the arduino side now.
Thanks again,
Robin (Canada)

I’m currently using an arduino connected to my RPi by USB, to serve as a keyboard. Basically, it takes outside info and sends key strikes to the Pi (using the keyboard library). Works great through USB, but I was wondering whether it would also work it I were to hook it through I2C (to liberate the USB port). Any thought?

I really don’t understand how this happened, but when I shut down my Raspberry connected to an Arduino Uno over I2c, there was power being backfed somehow to the RPi as the power led was still dimly lit … Fastforward today, my RPi is completely fried now… Is it possible the Arduino sometimes leak power over the I2C pins?

I’m really annoyed because otherwise, I always use diodes and resistors to make sure this doesn’t happen… but dropped my guard with the I2C connection.

hi, we want Pi with PIC (18F67K22) comm. by I2C but not working… example we add address inside PIC 0x04 ID, after pi we enter “sudo i2cdetect -y 1” read first time half address! like this 0x02, then we again enter “sudo i2cdetect -y 1” then no any seen address… what will we do for this… since 2 week we cant do it… 😦