Using I2C - FrankBau/meta-marsboard-bsp GitHub Wiki

Using a I2C device is similar to using SPI device.

Check for i2c Devices on the Target

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:

Check/edit the device tree source (.dts file)

Compile the device tree source to a binary (.dtb file)

Put the .dtb on the boot partition of the SD card

Accessing I2C Devices from the Command Line

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 result 0x15.
  • 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

bitbake i2c-tools

and install the packages built on the target root file system.

Include i2c-tools in your image

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.

Accessing I2C Devices from a C Program

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;
}

Accessing I2C Devices from another Kernel Module (Driver)

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.

Further Reading

⚠️ **GitHub.com Fallback** ⚠️