Wednesday, June 20, 2012

Using I²C on the Raspberry Pi

Last night I succeeded in getting my Raspberry Pi to do some basic I²C (also known as I2C, TWI, or SMBus) communication. I had to look around in a couple different places on the web to figure out how to do it, so I thought I would make this blog post to consolidate the knowledge.

The first step, of course, is to set up an SD card suitable for the Raspberry Pi. I used the official Debian image from the Raspberry Pi Foundation.

Unfortunately, this official image has no support for I²C! You need to enter Chris's Digital Realm for that. Follow the instructions on Chris Boot's site to upgrade your Raspberry Pi firmware and install his Raspberry Pi kernel, which has drivers for I²C. Now if you run lsmod you should see a lot of good stuff, such as the i2c_bcm2708 module.

Once you are running the new kernel, you will need to run sudo modprobe i2c_dev. You should also add i2c_dev to the /etc/modules.conf file so that it loads automatically the next time you boot up the Raspberry Pi. Now you should see two I2C devices:
pi@raspberrypi:~/$ ls -l /dev/i2c*
crw------- 1 root root 89, 0 Dec 31  1969 /dev/i2c-0
crw------- 1 root root 89, 1 Dec 31  1969 /dev/i2c-1

You will need to connect your device to the Raspberry Pi. I used Pololu Female-Female Premium Jumper Wires to make the following connections between the Raspberry Pi's GPIO header and my device:
  • Raspberry Pi GND to device GND
  • Raspberry Pi SCL to device SCL
  • Raspberry Pi SDA to device SDA
  • Raspberry Pi 3V3 Power to device's power input. This line can only provide a few tens of milliamps so you should look up how much current your device draws and make sure you can power it at 3.3 V. The Raspberry Pi's 5V power line is also available.
At this point, you can use simple C system calls to communicate with I2C devices. Here is some code I wrote to read the "Who Am I" register from the LSM303DLM on a Pololu MinIMU-9 Gyro, Accelerometer, and Compass (L3G4200D and LSM303DLM Carrier):
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>

#define MAG_ADDRESS        (0x3C >> 1)
#define LSM303_WHO_AM_I_M  (0x0F)

int main()
{
    char buf[10];
    const char * devName = "/dev/i2c-0";

    // Open up the I2C bus
    int file = open(devName, O_RDWR);
    if (file == -1)
    {
        perror(devName);
        exit(1);
    }

    // Specify the address of the slave device.
    if (ioctl(file, I2C_SLAVE, MAG_ADDRESS) < 0)
    {
        perror("Failed to acquire bus access and/or talk to slave");
        exit(1);
    }

    // Write a byte to the slave.
    buf[0] = LSM303_WHO_AM_I_M;
    if (write(file, buf, 1) != 1)
    {
        perror("Failed to write to the i2c bus");
        exit(1);
    }

    // Read a byte from the slave.
    if (read(file,buf,1) != 1)
    {
        perror("Failed to read from the i2c bus");
        exit(1);
    }

    printf("result: 0x%02X\n", buf[0]);

    return 0;
}
When I ran this code, it printed 0x3C which means that it successfully talked to the device! There might be a simpler way to read the register; see the Linux Kernel I2C Documentation.

In conclusion, it was not too hard to get I²C working on the Raspberry Pi. The biggest hurdle is that the official Debian image/kernel has no support for it. This is kind of surprising because the Raspberry Pi team purposefully brought the I²C lines out to their GPIO header and the product has been selling for several months now.

4 comments:

  1. Thank you for posting this! Did you have any luck reading data from the two sensors? I was able to read WHO_AM_I on both devices, but i can't read the sensor data -- all i'm getting back is gibberish. I was trying to modify the arduino code written for the chip.

    ReplyDelete
  2. I had more luck this evening -- i used i2c_smbus_write_byte_data to set registers and i2c_smbus_read_i2c_block_data to read all 6 bytes of acc/mag/gyro data. also, you have to watch out for the two's complement output, like so (this is rpi equivalent of the LSM303 code for arduino, inside of read())

    __s32 res;
    __u8 reg;
    reg = LSM303_OUT_X_L_A | (1 << 7) ;
    __u8 buf[6];
    res = i2c_smbus_read_i2c_block_data(file, reg, 6, (__u8 *)buf);
    if (res != 6) {
    printf("Failed to read acc data in read()\n");
    exit(1);
    }
    for (int i = 0; i < 6; i++) {
    buf[i] = ~buf[i];
    buf[i] += 0x01;
    }
    __u8 xla = buf[0];
    __u8 xha = buf[1];
    __u8 yla = buf[2];
    __u8 yha = buf[3];
    __u8 zla = buf[4];
    __u8 zha = buf[5];

    __s16 x = xha << 8 | xla;
    __s16 y = yha << 8 | yla;
    __s16 z = zha << 8 | zla;

    remember you need libi2c-dev installed to use i2c_smbus_xxx functions.

    Hope this helps somebody!!!

    ReplyDelete
  3. It helped me! Thanks a lot, Wojciech. I didn't know about libi2c-dev and i2c_smbus_read_i2c_block_data! I was trying to do something worse that made the kernel spit out scary error messages.

    My current sticking point is that the values I am reading from the magnetometer and accelerometer are all constant. I probably need to enable them somehow; I will look into it tomorrow by checking out the example Arduino code. In the mean time, you can see my code here:
    https://github.com/DavidEGrayson/minimu9-rpi-ahrs

    ReplyDelete
  4. Thanks for this. I just wanted to mention that the official Raspbian and Debian Wheezy image for RPi now support both I2C and SPI.

    ReplyDelete