RPi Tutorial EGHS:Communicating With Other Micro-controllers

Raspberry Pi and 5V I2C
 (As of 9/1/12) 

I2C:
''' What is it? '''

I2C ("eye squared cee") is a versatile bus invented by Philips, running over two wires. The bus has been extended to be faster and support more devices on a bus, but the standard definition holds up to 128 devices or so (7-bit addressing) and speeds of up to 100kb/sec. (Theoretical max, some overhead applies.) Being a digital bus, I don't believe a specific voltage is specified, but 5V and 3.3V are very common, and this example will deal with both.

''' Why is it useful? '''

Unlike standard serial, you can have more than a single pair of sender-receiver. So it is extensible. You may only want to interface the rPi with one device now, but this scheme leaves you open to having 127 or so more. The bus design in hardware is relatively simple, a pair of pull-up resistors and two wires is usually all you need. (The exception here being the voltage level shift, and that only adds a pair of MOS-FETs and two more pull-ups.) A good amount of pre-written software libraries exist, and there is also built-in hardware support in many micros and peripherals.

Using a micro or some good part-selection, this opens the door to ADCs, many more lines of GPIO input/output, various sensors, light-weight LCD displays, motor control, PWM output, and so on.

(A great worked example for an I2C real-time clock, which the rPi lacks natively, is over here at element14: )

''' What are its limitations? '''

It is not designed for great ranges, though you can get over this with repeaters and better wire. Quick internet research suggests 25ft might be possible over Cat5 without repeating. YMMV

Finally, 100kbps might not be up to the task if you're throwing a lot of data around. This bus is useful for sensors and passing data around between more lightweight devices. If you're clocking tons of data, consider something else, or the high speed variants.

Worked Example, Hardware and Software:
For the sake of illustration this will show how to get an Arduino (5V), an LCD (5V), and the rPi (3.3V) communicating. The example extends out to other devices and micros programmed through other means, but the Arduino is straightforward as a litmus test for functionality, and the LCD was on hand.

 Hardware design: 

(There is some disagreement as to how necessary level-shifting is. The rPi is not designed to be 5V-tolerant, but most reports have suggested it works just fine without it. No reports on longevity are out yet, at time of writing. Four more inexpensive components buys you some peace-of-mind.

3.3V microcontrollers are also an option that does not require level-shifting, but they are less common than 5V at the moment.)

This example follows Philip's datasheet for I2C 5V/3.3V: 

In lieu of circuit diagram, here's an example hookup (albeit a messy one):



There is some flexibility as to which MOS-FETs to use. See the datasheet for electrical characteristics. I used 2N7000, digikey part no. 2N7000TACT-ND. TO-92 package, low-cost and no minimum quantities.

When you're done with this circuit, you'll have a 3.3V "side" and a 5V "side." Hook devices to the bus as you see fit, making sure to match the voltage of your device with the right side.

 Software: 

 Kernel Stuff:

In addition your rPi running linux flavor of your choice (at time of writing Raspbian "Wheezy" is the latest, but that may change. Many apt-get stuff assumes it.) you will need:

1) The latest rPi firmware.

Download here:

-or-

Use Hexxeh's updater tool:

Short version:

As root: wget http://goo.gl/1BOfJ -O /usr/bin/rpi-update && chmod +x /usr/bin/rpi-update sudo apt-get install ca-certificates git-core # IF NEEDED rpi-update

2) Kernel with I2C built in:

Easy Way: Go to Chris Boot's site and download the prebuilt image w/ I2C and put it in your /boot/kernel.img 

Hard Way: Set up cross-compiler, download current kernel sources, patch in I2C driver, build kernel. A good exercise but outside the scope of this article.

Put both of these in your /boot directory and reboot the rPi. When you start up check /dev for a device by the name of /dev/i2c-X (X usually being 0). If you don't see it, you may need to load the module:

sudo modprobe i2c-dev

(If you choose the hard way above, you may be able to compile this into the kernel permanently instead of module. Otherwise, it can be set in a startup script, put a line in: /etc/modules)

Finally, the default permissions on /dev/i2c-X are not terribly forgiving, and you'll find yourself having to run everything as root. To alleviate this problem:

sudo chmod 666 /dev/i2c-0

Once you see the device (and it persists on startup if you so choose), then you're done with the kernel hacking. Phew.

 User-space Software: 

Tools:

There is a nice set of tools one command apt-get command away:

sudo apt-get install i2c-tools

See the man page for details, but this is a good time to run:

sudo i2cdetect -y 0

to check your circuit design. If all's well, you'll get something like:

0 1  2  3  4  5  6  7  8  9  a  b  c  d  e  f 00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- 15 -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- 28 -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --

This shows my LCD responding at 0x28. The Arduino is at 0x15.

(A note about addressing: Many datasheets quote an 8-bit address, which needs to be shifted one bit right to get the 7-bit address like shown above. For instance, my LCD datasheet quotes its address at 0x50. (0x50 >> 1 == 0x28) i2cdetect is helpful for this purpose)

 Your own software: 

Kernel driver? Check. Circuit hooked up? Check. The rest is software. Here are a few hello world examples to get started with (Both print "Hello World" every second to 0x28):

 Python: 

Get the library:

sudo apt-get install python-smbus

The code (i2cTest.py):

import smbus import time

LCD_ADDR = 0x28

def StringToBytes(val): retVal = [] for c in val: retVal.append(ord(c)) return retVal

def SayHello: bus = smbus.SMBus(0) messageInBytes = StringToBytes("Hello World") bus.write_i2c_block_data(LCD_ADDR, 0, messageInBytes)

if __name__ == "__main__": while True: SayHello time.sleep(1)

Run with: python i2cTest.py

 C/C++: 

The code (i2cTest.cpp):

 (includes might be overkill) 


 * 1) include
 * 2) include 
 * 3) include 
 * 4) include 
 * 5) include 
 * 6) include 
 * 7) include 


 * 1) define LCD_ADDR (0x50 >> 1)

int main (void) { int value; int fd;

fd = open("/dev/i2c-0", O_RDWR);

if (fd < 0) { printf("Error opening file: %s\n"); return 1; }

if (ioctl(fd, I2C_SLAVE, LCD_ADDR) < 0) { printf("ioctl error: "); return 1; }

char buffer[12]; strcpy(buffer, "Hello World");

while(true) {		write(fd, buffer, 12); sleep(1); // Sleep 1s }

return 0; }

Compile with: g++ -o i2cTest i2cTest.cpp

 Related reading: 

Chris Boot's site: Kernel Images

Chris Boot's site: Cross Compiling

Arduino and I2C

Adding RTC to rPi via I2C