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:
- Using read() and write(). Every time you do one of these an entire I2C transaction takes place (i.e. start bit, address, data, stop).
- 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.
- i2c_smbus_write_quick( int file, __u8 value)
- i2c_smbus_read_byte(int file)
- i2c_smbus_write_byte(int file, __u8 value)
- i2c_smbus_read_byte_data(int file, __u8 command)
- i2c_smbus_write_byte_data(int file, __u8 command, __u8 value)
- i2c_smbus_read_word_data(int file, __u8 command)
- i2c_smbus_write_word_data(int file, __u8 command, __u16 value)
- i2c_smbus_process_call(int file, __u8 command, __u16 value)
- i2c_smbus_read_block_data(int file, __u8 command, __u8 *values)
- i2c_smbus_write_block_data(int file, __u8 command, __u8 length, __u8 *values)
- i2c_smbus_read_i2c_block_data(int file, __u8 command, __u8 *values)
- i2c_smbus_write_i2c_block_data(int file, __u8 command, __u8 length, __u8 *values)
- i2c_smbus_block_process_call(int file, __u8 command, __u8 length, __u8 *values)
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 0×7F - 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 I2 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.
5 Responses to “Using I2C from userspace in Linux”
Leave a Reply
Site by Robert Spanton. ©2008
November 11th, 2007 at 12:04 pm
Cracking writeup there, top marks. Enjoying the gotchas list - Tom and I spent a good 2 hours on the 7-bit address point!
November 12th, 2007 at 12:25 am
Good stuff! wish id had this before we started coding. I recon it was more like 4 hours!
February 3rd, 2008 at 2:26 pm
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.
February 19th, 2008 at 11:34 pm
Wow, very good Information for me. Thank you for your writing~
July 17th, 2008 at 5:48 am
Very good info colletion