xGoat
More tea please.

Using I2C from userspace in Linux

I’ve been using various I2C things in Linux for the past year and a bit, and I’ve learnt a few things about it from that. Here I hope to collate some of this information.

The Basics

Linux has an i2c subsystem. This provides methods for interacting with I2C clients connected to the same bus as the machine running Linux. As far as I know, Linux can only be a master at the moment. If you’ve got an I2C client that needs to interact with your Linux system then you’ve got two options. The first is to write a driver as a Linux kernel module. This is (surprisingly) straightforward but isn’t what I’m going to focus on here. The second method is to use the “i2c-dev” kernel module. This lets you interact with your devices from userspace and is what I’ll be covering here. If you want to write a kernel driver, then there are good sources of information for this lurking around the place – also have a look in Documentation/i2c/writing-clients in the Linux source.

Preparing your system

The first thing to do is to make sure that the kernel that you’re going to be running this program under has the “I2C device interface” driver either compiled in or as a module. The Linux build config option for this is CONFIG_I2C_CHARDEV. The second thing that you need to make sure you have is the i2c-dev.h header file from the lm-sensors project. For some reason, it took me a whlie to realise that this header file needed to come from there. Download their tarball and find the header somewhere in there. This header file contains some inline functions that wrap around the ioctls of the /dev/i2c-N device files that the i2c-dev kernel driver creates.

When one has both the i2c-dev module and the kernel module for the I2C adapter loaded, a device file called /dev/i2c-0 (or /dev/i2c-1, /dev/i2c-2 etc.) will be created. It’s important to note that it will only be created if you’re running udev. You might need to run:

% mknod /dev/i2c-0 c 89 0

Which will create the device file.

Opening the device

In your program, you’ll need to open the device file for the i2c adapter:

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

After checking for opening errors, the next stage is to set the address of the I2C slave that we want to communicate with:

#define ADDRESS 0x38

...

        if( ioctl( fd, I2C_SLAVE, ADDRESS ) < 0 )
        {
                fprintf( stderr, "Failed to set slave address: %m\n" );
                return 2;
        }

To get that I2C_SLAVE constant you'll need to include the i2c-dev.h file I wrote about above.

Operations

So now that we've set the address, then only thing that remains is to actually interact with the device. There are two ways of doing this:

  1. Using read() and write(). Every time you do one of these an entire I2C transaction takes place (i.e. start bit, address, data, stop).
  2. Using the wrapper functions that i2c-dev.h provides.

The read() and write() method is fairly straightforward so I won't go over it here. i2c-dev.h provides many functions to do various i2c transactions. I'll go over them in a moment - first I'll provide an example of how to write a single byte to the I2C client:

    if( i2c_smbus_write_byte( fd, 0xAA ) < 0 )
        fprintf( stderr, "Failed to write 0xAA to I2C device: %m\n" );

This example writes the byte 0xAA to whatever address was configured using the I2C_SLAVE ioctl. All of the functions in i2c-dev.h will return a number less than 0 upon failure. errno will get set accordingly.

SMBus functions

SMBus is a protocol that defines a set of ways for using an I2C bus. i2c-dev.h provides a function for each of these operations. The best place to read about these is in the SMBus spec in section 5.5 "Bus Protocols" - it has nice pictures.

Given the SMBus spec and the list of functions, it's quite easy to decide what each of them does. If you're in any doubt, then look for comments in i2c-dev.h around the function. Some of the functions above read data that's less than 32 bits in length yet return a 32 bit number. This is so that an error can be returned. If the returned value is 0 or above then just cast it into a type of the correct size.

Gotchas

During development, there were a few things that caught me out. I also introduced a couple of other people into using this interface and they got caught out by some things too. Hopefully I'll assist some others by listing those problems here.

The i2c-dev.h header file

The i2c-dev.h header file comes from the lm-sensors project. You'll need to grab this from their source.

I2C addresses are 7 bits wide

Apart from the rare 10-bit I2C addressing that some devices employ, all I2C addresses are 7 bits wide. Until I discovered the error of my ways, I would write addresses down as the 7-bit number shifted left by one bit to create an 8-bit word. Basically all I2C devices that do address encoding/detection (including the Linux Kernel) take the address as a 7-bit number.

So if your I2C address was all '1's, you would write it as 0x7F - not 0xFE.

Load the modules

There's almost always this short period just after I've booted a LInux device in which I'll try to use I2 but nothing will work. Then I remember that I need to load the right modules - the one for the I2C controller itself and i2c-dev for the userspace interface. I know it's simple, but for some reason I always forget.

Use Checksums

Always use checksums. SMBus already defines a method of generating checksums. They're also supported by the Linux kernel. You can enable them using an ioctl:

if( ioctl( fd, I2C_PEC, 1) < 0) { fprintf( stderr, "Failed to enable PEC\n"); return -1; }

Disable them by changing the argument to the ioctl to 0.

Block transfers

After painstakingly implementing SMBus block read/write on my I2C client, I discovered that the SMBus block transfers weren't supported by the I2C driver for my machine's adapter. So always check that the I2C driver supports the types of transfer that you want to do using the I2C_FUNCS ioctl.

The Linux kernel documentation

The documentation in the /Documentation/i2c directory of the Linux source is a good source of information about using I2C under Linux.

Examples

You want examples? Ok. My Student Robotics colleagues and I have been writing various test utilities that you might like to have a look at. I'm not guaranteeing that they work of course!

I hope this was helpful. I'd be most interested in hearing any feedback that you have.

Posted at 2:55 am on Sunday 11th November 2007

20 responses to “Using I2C from userspace in Linux”

  1. Jeff says:

    Cracking writeup there, top marks. Enjoying the gotchas list – Tom and I spent a good 2 hours on the 7-bit address point!

  2. Tom says:

    Good stuff! wish id had this before we started coding. I recon it was more like 4 hours!

  3. Paul says:

    Just spent 2 days chasing a bug that didn’t exist – yep, the 8 vs 7 bit numbering system *sigh*. Only clicked when I looked at the values in binary ( D0h vs 68h :) )

    Thanks for the site – good info.

  4. Youngman says:

    Wow, very good Information for me. Thank you for your writing~

  5. Srinath says:

    Very good info colletion

  6. Chris says:

    I’m getting a errno 19 on:

    int fd = open(“/dev/i2c-0”, O_RDWR);

    after creating my block device with mknod as you had pointed out…any suggestions?

  7. rob says:

    Hi Chris,

    After some poking around, it looks like errno 19 is “No such device” (by the way, did you know that there’s a printf format string for displaying the string relating to the value of errno: “%m” — you don’t need to add errno as an argument to printf to use it).

    If the mknod had been successful then the file would exist. Make sure you’re running mknod as root, and also make sure that /dev/i2c-0 exists after running it.

    Just out of interest, what system are you running this on? I guess it’s an embedded device since you’re not running udev.

    Rob

  8. Philip says:

    Hi Chris and rob,
    Thank for info in your site.
    I have the same problem : “No such device” when opening /dev/i2c-0; I have create /dev/i2c-0 with mknod as root and it exist.
    Any ideas ?
    I am using ARM9 EP9302 olimex board with kernel 2.6.
    Thank
    Phil

  9. rob says:

    @Phil: Hi. Sounds like there’s something wrong with the drivers for your I2C adapter.

  10. veenu says:

    How can we add our custom IOCTLs, when we use the dev-interface.

  11. Feng says:

    @Philip
    Hi I am using ARM9 EP9302 olimex board with kernel 2.6. And I can open the i2c-0 device.
    I think you should make sure you have Enable the I2C module when you compile the Linux(zImage).
    But I get this pro, when I write the I2c cilent:

    i2c_adapter i2c-0: sendbytes: error – bailout.

    :(

  12. i2c-give-up says:

    As on all website about i2c, nothing is said about the way to compile and edit link.
    No makefile, even no “gcc….”.
    No information if the I2C_SLAVE line fails.

    Impossible to use !!

    I give up with i2c on linux.

  13. rob says:

    @Anonymous (“i2c-give-up” above): I’ll attempt to answer your questions (even though they were more of a moan).
    There aren’t any libraries that need to be linked in when using i2c from userspace — all the required gubbins are in the header file. So you should just need to run:

    gcc -o test test.c

    I didn’t write this as a tutorial on how to use make or gcc, so I didn’t include these bits of information.

    The I2C_SLAVE ioctl could fail for a few different reasons. The one reason I’m familiar with is when there’s an in-kernel driver that’s already using the address (in which case you can use I2C_SLAVE_FORCE if you really need to use it from userspace too).

    It is far from impossible to use!

  14. Jasper says:

    I’m just starting to try and get my i2c device to work on an embedded environment. So i’m glad i found your hints at forehand :)

    I just wanted to let you know your like to the examples is broken.
    Thanks a lot for all..

    and Cheers.

    Jasper

  15. rob says:

    Hi Jasper,

    Yea, the link broke because we moved stuff around in subversion. Try these new links:

    I hope they’re useful.

    Rob

  16. Chris says:

    Hi,

    Trying to control what seems to be a simple i2c device (PCA9532). I’m trying to set the control register using the code below.

    std::cout << "Opening the i2c device." << std::endl;
    int fd = open( "/dev/i2c-0", O_RDWR );

    if ( fd == -1 )
    {
    std::cout << "Failed to open /dev/i2c-0: " << strerror( errno ) << std::endl;
    exit (1);
    }

    std::cout << "Setting the slave address." << std::endl;
    if ( ioctl( fd, I2C_SLAVE, 0x60 ) < 0 )
    {
    std::cout << "Failed to set slave address: " << strerror( errno ) << std::endl;
    exit (1);
    }

    if( ioctl(fd, I2C_PEC, 1) < 0)
    {
    std::cout << "Failed to enable PEC " << std::endl;
    exit(3);
    }

    std::cout << "Address to slave." << std::endl;
    if ( i2c_smbus_write_byte( fd, 0x06 ) == -1 )
    {
    std::cout << "Failed to address data to the slave : " << strerror( errno ) << std::endl;
    }

    But when this program runs I get:

    Opening the i2c device.
    Setting the slave address.
    Address to slave.
    Failed to address data to the slave : No such device or address
    Closing the file.

    Running dmesg give me:

    i2c-adapter i2c-0: ioctl, cmd=0x703, arg=0x60
    i2c-adapter i2c-0: ioctl, cmd=0x708, arg=0x01
    i2c-adapter i2c-0: ioctl, cmd=0x720, arg=0xbfb68920
    i2c-adapter i2c-0: master_xfer[0] W, addr=0x60, len=2
    i2c-adapter i2c-0: NAK from device addr 0x60 msg #0

    The hardware settable piece of the device address is 000. The device seems to be responding (hence the NAK). Does anyone see the problem in my code that could be causing this issue?

    Regards,

    Chris

  17. Chris says:

    I found my problem.

    The hardware guys neglected to tell me that the i2c/smbus that I was talking to wasn’t ever brought to the surface of the motherboard. The device I’m trying to talk to is on the i2c bus of a daughter board.

    Chris

  18. Augustin says:

    Hi all,

    I am facing a problem in Smbus communication.

    Sm Bus read or write gives me a Error:

    Error No :29 , Operation not permitted.

    Wat could be the problem? Suggestions please

  19. rob says:

    @Augustin: You need to provide a bit more context about what you’re doing before anyone can help you…

  20. Augustin says:

    Hi Rob,

    Problem got solved…Its because of slave addressing.

    Rob could you please explain me how addressing works, Like we are communicating with two slave devices one is RTC (0xA2) and other one is battery charger(0x16).

    For RTC we used as 0x51 to make it as equivalent of 7 bit addressing.

    For battery charger even though it is 7 bit address(0x16), it doesn’t work. We shifted and we used as 0x0B as address it communicates with the battery device properly . How?

Site by Rob Gilton. © 2008 - 2019