I2C

From krtkl wiki
Revision as of 01:17, 2 September 2018 by Bush (talk | contribs) (Created page with "== Linux == Controlling the I2C bus from Linux is made possible at the driver/kernel layer and user space. The programming interface is made available in user space through a...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Linux

Controlling the I2C bus from Linux is made possible at the driver/kernel layer and user space. The programming interface is made available in user space through a C-library API and a set of binary applications.

Command Line

Controlling the I2C bus is possible through a set of pre-built binaries that expose common read and write operations to the Linux command line.

Writing I2C Registers

i2cset Allows writing of data to an I2C connected device with either byte, word or block (up to 255-bytes) length transmissions. The default mode performs a byte length write of a value to a register on the device at the specified addresses.

admin@pismasher:~$ sudo i2cset 0 0x48 0x05 0x20
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will write to device file /dev/i2c-0, chip address 0x48, data address
0x05, data 0x20, mode byte.
Continue? [Y/n] y

Reading I2C Registers

To perform a read operation on a device, i2cget can be used to return register values of either byte or word width.

admin@pismasher:~$ sudo i2cget 0 0x48 0x05 
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will read from device file /dev/i2c-0, chip address 0x48, data address
0x05, using read byte data.
Continue? [Y/n] y
0x20

i2cdump allows reads to extend over several register addresses. It is possible to perform consecutive register reads which expects the device to auto-increment the register address after each byte transmission. The default mode performs separate read operations on the device for each register address specified in the range.

admin@pismasher:~$ sudo i2cdump -r 0x0e-0x16 0 0x48 c 
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0, address 0x48, mode byte consecutive read
Probe range limited to 0x0e-0x16.
Continue? [Y/n] y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00:                                           00 01                  .?
10: 00 00 78 7a 0c 10 ae                               ..xz???         

Probing the I2C Bus

i2cdetect Can be used to get acknowledgement from every available device attached to the bus. This is done by initiating a read from each address within the range 0x03 and 0x77 and terminating the read before any transmission. The addresses that are acknowledged are then displayed in the terminal.

admin@snickerdoodle:~$ sudo i2cdetect -r 0
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0 using read byte commands.
I will probe address range 0x03-0x77.
Continue? [Y/n] y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- 18 -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- 
30: -- -- -- -- 34 -- 36 -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: 70 -- -- -- -- -- -- --    


User Space Applications

Each time this file is opened it will return a new file descriptor that can be used to communicate with a single device. The file can be opened multiple times within a single application to get a new file descriptor for a unique device.

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

Once a file descriptor is retrieved, the device address that the file descriptor should be used to communicate to is attached to the file descriptor using ioctl.

    ioctl(fd, I2C_SLAVE, addr);

The I2C_SMBUS ioctl request is then used to perform any transmissions to/from the device. The argument passed with this request specifies the type of transmission (read/write), the data length and the transmission length.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>

#define DEVICE_ADDR       (0x48)

int main(int argc, char *argv[])
{
        int ret, fd;
        struct i2c_smbus_ioctl_data data;
        uint8_t val;

        fd = open("/dev/i2c-0", O_RDWR);
        if (fd < 0)
                return fd;

        ret = ioctl(fd, I2C_SLAVE, DEVICE_ADDR);
        if (ret < 0)
                goto out;

        val = 0x00; 

        data.read_write = I2C_SMBUS_WRITE;
        data.size = I2C_SMBUS_BYTE_DATA;
        data.command = 0xff;
        data.data = (void *) &val;

        ret = ioctl(fd, I2C_SMBUS, &data);
out:
        close(fd);

        return ret;
}

To simplify the I2C bus access API into more easily understandable and readable register read and write functions, the I2C_SMBUS request is wrapped in several helper functions. Below is a snippet of the inline functions used to generate reads and writes on the device registers. An abstraction of such functionality can be found in the following API at [1].

int i2c_init(uint8_t addr);
int i2c_close(uint8_t addr);
int i2c_write_reg(uint16_t sl_addr, uint8_t reg_addr, uint8_t *data);
int i2c_read_reg(uint16_t sl_addr, uint8_t reg_addr, uint8_t *data);
int i2c_write_block(uint16_t sl_addr, uint8_t reg_addr, uint8_t len, uint8_t *data);
int i2c_read_block(uint16_t sl_addr, uint8_t reg_addr, uint8_t len, uint8_t *data);

With this API, a transaction with a device can be done using as few as three function calls. The code below is functionally equivalent to the code above.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>

#include "i2c.h"

#define DEVICE_ADDR       (0x48)

int main(int argc, char *argv[])
{
        int ret;
        uint8_t val;

        ret = i2c_init(DEVICE_ADDR);;
        if (ret < 0)
                return ret;

        ret = i2c_write_reg(DEVICE_ADDR, 0xff, val);
        if (ret < 0)
                return ret;

        ret = i2c_close(DEVICE_ADDR);
        if (ret < 0)
                return ret;

        return 0;
}

Device ioctl wrappers used to build the I2C register functions

static inline __s32 i2c_smbus_access(int fd, char read_write, __u8 cmd, 
                                     int size, union i2c_smbus_data *data)
{
	struct i2c_smbus_ioctl_data args;

	args.read_write = read_write;
	args.command = cmd;
	args.size = size;
	args.data = data;

	return ioctl(fd, I2C_SMBUS, &args);
}

static inline __s32 i2c_smbus_read_byte_data(int fd, __u8 cmd)
{
	union i2c_smbus_data data;

	if (i2c_smbus_access(fd, I2C_SMBUS_READ, cmd, I2C_SMBUS_BYTE_DATA, &data))
		return -1;
	else
		return 0x0FF & data.byte;
}

static inline __s32 i2c_smbus_write_byte_data(int fd, __u8 cmd, 
                                              __u8 val)
{
	union i2c_smbus_data data;

	data.byte = val;

	return i2c_smbus_access(fd, I2C_SMBUS_WRITE, cmd,
	                        I2C_SMBUS_BYTE_DATA, &data);
}

static inline __s32 i2c_smbus_read_word_data(int fd, __u8 cmd)
{
	union i2c_smbus_data data;

	if (i2c_smbus_access(fd, I2C_SMBUS_READ, cmd, I2C_SMBUS_WORD_DATA, &data))
		return -1;
	else
		return 0x0FFFF & data.word;
}

static inline __s32 i2c_smbus_write_word_data(int fd, __u8 cmd, 
                                              __u16 val)
{
	union i2c_smbus_data data;
	data.word = val;

	return i2c_smbus_access(fd, I2C_SMBUS_WRITE, cmd, I2C_SMBUS_WORD_DATA, &data);
}

static inline __s32 i2c_smbus_read_block_data(int fd, __u8 cmd, 
                                              __u8 *block)
{
	int i;
	union i2c_smbus_data data;

	if (i2c_smbus_access(fd, I2C_SMBUS_READ, cmd,
	                     I2C_SMBUS_BLOCK_DATA, &data))
		return -1;

	for (i = 1; i <= data.block[0]; i++)
		block[i-1] = data.block[i];

	return data.block[0];
}

static inline __s32 i2c_smbus_write_block_data(int fd, __u8 cmd, 
                                               __u8 len, __u8 *block)
{
	int i;
	union i2c_smbus_data data;

	if (len > 32)
		len = 32;

	for (i = 1; i <= len; i++)
		data.block[i] = block[i-1];

	data.block[0] = len;

	return i2c_smbus_access(fd, I2C_SMBUS_WRITE, cmd, I2C_SMBUS_BLOCK_DATA, &data);
}

static inline __s32 i2c_smbus_read_i2c_block_data(int fd, __u8 cmd,
                                                  __u8 len, __u8 *block)
{
	int i;
	union i2c_smbus_data data;

	if (len > 32)
		len = 32;

	data.block[0] = len;

	if (i2c_smbus_access(fd, I2C_SMBUS_READ, cmd,
	                     len == 32 ? I2C_SMBUS_I2C_BLOCK_BROKEN :
	                      I2C_SMBUS_I2C_BLOCK_DATA, &data))
		return -1;

	for (i = 1; i <= data.block[0]; i++)
		block[i-1] = data.block[i];

	return data.block[0];
}

static inline __s32 i2c_smbus_write_i2c_block_data(int fd, __u8 cmd,
                                               __u8 len, __u8 *block)
{
	int i;
	union i2c_smbus_data data;

	if (len > 32)
		len = 32;

	for (i = 1; i <= len; i++)
		data.block[i] = block[i-1];

	data.block[0] = len;

	return i2c_smbus_access(fd, I2C_SMBUS_WRITE, cmd,
	                        I2C_SMBUS_I2C_BLOCK_BROKEN, &data);
}