Interfacing with I2C Devices

This page is meant to provide some basic information about how to interface with I²C devices through the /dev/i2c interface. The I²C bus is commonly used to connect relatively low-speed sensors and other peripherals to equipment varying in complexity from a simple microcontroller to a full-on motherboard. I²C is extremely popular due to its ease-of-use and ability to control multiple peripherals while utilizing only two pins on the host controller. Although I2C has a variety of modes, this page will deal purely with communication between a Linux-based master and a slave peripheral for the time being.

= Code Examples =

Note: Examples shown on this page were developed based on a Texas Instruments BeagleBoard and some changes will be required depending on the system being utilized.

Beagleboard I2C2 Enable
The TI BeagleBoard has 3 I2C buses available, which control a variety of on-board peripherals, including the DVI display driver and power sequencing. As bus 2 is by far the easiest to connect to due to its presence on the 0.1" spaced expansion header, it is assumed the user will be controlling that bus for the purposes of this example. However, by default bus 2 is disabled due to a lack of pull-up resistors on the board, so external pull-ups to 1.8V must be added and the kernel recompiled to enable i2c2.

See BeagleBoard and BeagleBoardLinuxKernel for details on how to recompile your Linux kernel. To enable i2c2 specifically during that process, enable the setting during the "make menuconfig" step.

I2C2 is pinned out as pins 23 (SDA) and 24 (SCL) on the expansion header.

Basic Communication with the AD7991 ADC
The AD7991 has four inputs as well as the ability to use one of the input pins as a reference voltage the other inputs are measured against. If that input is not used as the reference voltage, it uses the supply voltage as the reference voltage. Because the AD7991 is 12-bit device, its outputs ranges linearly from 0 to 4096 as the voltage ranges from 0 to the reference voltage.

There are multiple ways to communicate with I2C devices, including the writing of a full kernel driver. This adds significant extra complexity, however, as basic bus control can be accomplished with the open, ioctl, read, and write commands.

Opening the Bus
In order to communicate with an I2C peripheral with this simple structure, you must first open the bus for reading and writing like you would any file. A call to open must be used rather than fopen so that writes to the bus are not buffered. Open returns a new file descriptor (a non-negative integer) which can then be used to configure the bus. A typical reason for failure at this stage is a lack of permissions to access /dev/i2c-2. Adding the user to a group which has permissions to access the file will alleviate this problem, as will chmod-ing the file to enable user access.

sprintf(filename,"/dev/i2c-2"); if ((file = open(filename,O_RDWR)) < 0) { printf("Failed to open the bus."); /* ERROR HANDLING; you can check errno to see what went wrong */ exit(1); }

Initiating communication with the AD7991
After successfully acquiring bus access, you must initiate communication whatever peripheral you are attempting to send data to or read data from. I2C does this by sending out the seven bit address of the device followed by a read/write bit. The bit is set to 0 for writes and 1 for reads. This is another common failure point, as manufacturers tend to report the I2C address of the peripheral in a variety of ways. Some report the address as a seven bit number, meaning that the address must be shifted left by a bit and then have the r/w bit tacked onto the end. Others will provide it as an eight bit number and assume you will set the last bit accordingly. Although a few manufacturers actually say which method they use to describe the address, the vast majority do not, and have to be tested via trial and error.

The AD7991 used for this example is the AD7991-1, which has an address reported by the datasheet as 0101001. To use this properly, zero pad the address on the left and store it as 0b00101001. The calls to read and write after the ioctl will automatically set the proper read and write bit when signaling the peripheral.

int addr = 0b00101001;         // The I2C address of the ADC

if (ioctl(file,I2C_SLAVE,addr) < 0) { printf("Failed to acquire bus access and/or talk to slave.\n"); /* ERROR HANDLING; you can check errno to see what went wrong */ exit(1); }

Reading from the ADC
The read system call is used to obtain data from the I2C peripheral. Read requires a file handle, a buffer to store the data, and a number of bytes to read. Read will attempt to read the number of bytes specified and will return the actual number of bytes read, which can be used to detect errors.

The code in the else block below calculates the voltage present at the ADC pin assuming a 5 volt reference/supply voltage. The AD7991 samples a 12 bit value, which is read back as two eight bit values. Rather than waste the empty bits, two of them are also used to signal the channel data being sent. See page 21 of the datasheet for more details.

char buf[10] = {0}; float data; char channel;

for(int i = 0; i<4; i++) { // Using I2C Read if (read(file,buf,2) != 2) { /* ERROR HANDLING: i2c transaction failed */ printf("Failed to read from the i2c bus.\n"); buffer = g_strerror(errno); printf(buffer); printf("\n\n"); } else { data = (float)((buf[0] & 0b00001111)<<8)+buf[1]; data = data/4096*5; channel = ((buf[0] & 0b00110000)>>4); printf("Channel %02d Data: %04f\n",channel,data); }    }

Writing to the ADC
The write system call is used to obtain data from the I2C peripheral. Write requires a file handle, a buffer in which the data is stored, and a number of bytes to write. Write will attempt to write the number of bytes specified and will return the actual number of bytes written, which can be used to detect errors.

Some devices require an internal address to be sent prior to the data to specify the location inside the chip the data should be written to. The AD7991 contains only one configuration register, and as such, does not require a internal register selection address to be sent to the device. For devices with more than one configuration register, the address of the register should be written first, followed by the data to be placed there. See the datasheet specific to the part for more details.

//unsigned char reg = 0x10; // Device register to access //buf[0] = reg;

buf[0] = 0b11110000; if (write(file,buf,1) != 1) { /* ERROR HANDLING: i2c transaction failed */ printf("Failed to write to the i2c bus.\n"); buffer = g_strerror(errno); printf(buffer); printf("\n\n"); }

Detecting Errors
Errors on the I2C bus range from incorrect permissions and addressing to hardware errors that may simply not allow peripherals to respond. The errno interface is used to identify these errors using the glib function g_strerror. For simple usage, calling the function as above will work. See the glib api for more details.

Completed Code

 * 1) include 
 * 2) include 
 * 3) include 
 * 4) include 
 * 5) include 
 * 6) include 
 * 7) include 
 * 8) include 
 * 9) include 
 * 10) include 
 * 11) include 
 * 12) include 

void sensors_ADC_init(void) { int file; char filename[40]; const gchar *buffer; int addr = 0b00101001;       // The I2C address of the ADC

sprintf(filename,"/dev/i2c-2"); if ((file = open(filename,O_RDWR)) < 0) { printf("Failed to open the bus."); /* ERROR HANDLING; you can check errno to see what went wrong */ exit(1); }

if (ioctl(file,I2C_SLAVE,addr) < 0) { printf("Failed to acquire bus access and/or talk to slave.\n"); /* ERROR HANDLING; you can check errno to see what went wrong */ exit(1); }

char buf[10] = {0}; float data; char channel;

for(int i = 0; i<4; i++) { // Using I2C Read if (read(file,buf,2) != 2) { /* ERROR HANDLING: i2c transaction failed */ printf("Failed to read from the i2c bus.\n"); buffer = g_strerror(errno); printf(buffer); printf("\n\n"); } else { data = (float)((buf[0] & 0b00001111)<<8)+buf[1]; data = data/4096*5; channel = ((buf[0] & 0b00110000)>>4); printf("Channel %02d Data: %04f\n",channel,data); }    }

//unsigned char reg = 0x10; // Device register to access //buf[0] = reg;

buf[0] = 0b11110000; if (write(file,buf,1) != 1) { /* ERROR HANDLING: i2c transaction failed */ printf("Failed to write to the i2c bus.\n"); buffer = g_strerror(errno); printf(buffer); printf("\n\n"); } }

= Tested Devices =


 * AD7991 Quad Input ADC
 * Utilized with Sharp GP2D12 IR Range Sensors
 * SRF08 Ultrasonic Range Sensors
 * ADXL345 3-axis Accelerometer

= External Links =


 * Wikipedia I²C Page
 * Linux Kernel I2C Documentation