Using I2C - FrankBau/meta-marsboard-bsp GitHub Wiki
Using a I2C device is similar to using SPI device.
root@marsboard:~# ls -l /dev/i2c-*
crw------- 1 root root 89, 0 Jan 1 1970 /dev/i2c-0
crw------- 1 root root 89, 1 Jan 1 1970 /dev/i2c-1
crw------- 1 root root 89, 2 Jan 1 1970 /dev/i2c-2
If the i2c device is not yet present, you must execute the following three steps:
There is a set of convenient tools available for i2c communication from the command line, the i2c-tools. There is already a bitbake recipe to build these tools, and possibly the i2c-tools are already included in your image. You can test this from the command line on the target by typing
root@marsboard:~# i2cdetect -y 1
Which, if the i2c-tools are installed, gives you an overview over the attached I2C devices on bus 1:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: UU -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
Explanation: The command scans all possible device addresses (0x03 - 0x77) on I2C bus 1 and finds that address 0x50 is used. This is the I2C driver for the HDMI monitor connection. HDMI contains a read-only I2C bus device containing a monitor description EDID. So, when a HDMI monitor is connected, one might get an output similar to:
root@marsboard:~# i2cdump -f -y 1 0x50
No size specified (using byte-data access)
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 00 ff ff ff ff ff ff 00 15 c3 56 23 01 01 01 01 ........??V#????
10: 20 16 01 03 80 34 21 78 ea 33 05 ae 4e 35 ae 26 ????4!x?3??N5?&
20: 0d 50 54 20 00 00 01 01 01 01 01 01 01 01 01 01 ?PT ..??????????
30: 01 01 01 01 01 01 02 3a 80 18 71 38 2d 40 58 2c ???????:??q8-@X,
...
For example, the data array of device 0x50 on i2c bus 1 contains at offset 0x08 the value 0x15 and at offset 0x09 the value 0xC3 which together represent the manufacturer ID (cf. EDID specs).
If no monitor is connected, the read will fail and you get a lot of XX
.
Other useful i2c commands are
-
i2cget get (read) a register, e.g.
i2cget -f -y 1 0x50 0x08
yields the result0x15
. - i2cset set (write) a register (EDID devices are read-only, cannot be tested this way).
Read the datasheet of your i2c device carefully, because some devices require special command sequences. The most flexible way to access i2c devices is from a C program, see below.
If the i2c-tools are not found, you must build them from source:
bitbake i2c-tools
and install the packages built on the target root file system.
See Configure the Image, especially the line
IMAGE_INSTALL_append += " i2c-tools"
Note, that you have to rebuild the image afterwards and install it on the sd card, see Prepare SD card.
The following code will read "register" 0x08 of device 0x50 on i2c bus 1 and print its value (0x15, see above). For this special type of device (EDID), a "register" is just an offset into the data array. Register access within a device is described in the device's datasheet. Different devices may require different access sequences.
#include <stdio.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
int main()
{
int file = open( "/dev/i2c-1", O_RDWR);
if (file < 0) perror("open"); //TODO: error handling
unsigned char inbuf[1];
unsigned char outbuf[1];
struct i2c_rdwr_ioctl_data packets;
struct i2c_msg msg[2];
// prepare the first i2c request (a write)
msg[0].addr = 0x50; // device address
msg[0].flags = 0; // type of request: 0=write
msg[0].len = sizeof(outbuf); // number of bytes to write
msg[0].buf = outbuf; // pointer to the first data byte
outbuf[0] = 0x08; // first data byte: register to read
msg[1].addr = 0x50; // device address
msg[1].flags = I2C_M_RD | I2C_M_NOSTART; // read request without an intermediate start condition
msg[1].len = sizeof(inbuf); // number of bytes to read
msg[1].buf = inbuf; // // pointer to the first byte to read
// send both requests to the device
packets.msgs = msg;
packets.nmsgs = 2;
if(ioctl(file, I2C_RDWR, &packets) < 0) perror("ioctl"); //TODO: error handling
close(file);
printf("reg[0x%02x]=0x%02x\n", outbuf[0], inbuf[0] );
return 0;
}
For many i2c devices there is a specific higher-level protocol. Lets take a real-time clock (RTC) as an example like MAX6900.
A RTC is a separate chip counting date and time independently of the main CPU. It is usually powered by a backup battery or large capacitor and keeps track of date and time even if the main CPU is powered off.
The high level protocol for a real-time clock consists of two functions:
- read_time: read the current date and time stored in the RTC
- set_time: set the RTC date and time to a new value (e.g. after the backup battery was replaced).
I2C bus is merely used for transporting the date and time values from/to the RTC device. The following link shows the implementation of both functions in a RTC driver: http://lxr.free-electrons.com/source/drivers/rtc/rtc-max6900.c?v=3.14
Note that I2C (like SPI) devices are "platform resources" that cannot be detected automatically by using some "plug-and-play" mechanism (like PCI or USB devices). Therefore, a I2C device driver must be explicitly mentioned in the device tree.