I2C-Tools介绍
参考资料:
Linux驱动程序:
drivers/i2c/i2c-dev.cI2C-Tools-4.2:
https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/
访问I2C设备4种方式
app访问I2C设备的框架如下:

如下图所示:常规做法
原厂做好芯片的I2C控制器驱动
adapter_driver驱动开发人员户编写自己芯片的驱动也就是
some_driver驱动,例如芯片mpu6050,然后会生成/dev/mpu6050的设备节点。应用开发人员通过App应用开发访问
/dev/mpu6050

如下图所示
该方法和第1种方法区别在于使用了模拟i2c驱动

驱动开发人员
make menuconfig开启内核自带的的模拟i2c驱动,并且通过设备树要告诉它使用哪些gpio作为模拟的sda和scl线。驱动开发人员户编写自己芯片的驱动也就是
some_driver驱动,例如芯片mpu6050,然后会生成/dev/mpu6050的设备节点。应用开发人员通过App应用开发访问
/dev/mpu6050
如下图所示

原厂做好芯片的I2C控制器驱动
adapter_driver驱动开发人员户不需要再编写对应的mpu6050驱动,只需使用内核自带的i2c-dev.c
应用开发人员通过App应用开发访问
/dev/i2c_0设备节点,通过来直接访问i2c-dev.c里面的接口即可访问底层的i2c Device
我们本章介绍的i2c-tools工具就是通过该方式访问i2c设备的。(i2c-tools本身就是个App应用程序)
如下图所示

驱动开发人员
make menuconfig开启内核自带的的模拟i2c驱动,并且通过设备树要告诉它使用哪些gpio作为模拟的sda和scl线。驱动开发人员户不需要再编写对应的mpu6050驱动,只需使用内核自带的i2c-dev.c
应用开发人员通过App应用开发访问
/dev/i2c-0设备节点,通过来直接访问i2c-dev.c里面的接口即可访问底层的i2c Device
I2C-Tools使用说明
I2C-Tools就是一个通用的i2c应用程序,通过i2c-dev.c完成,过程如下:

交叉编译
在Ubuntu设置交叉编译工具链,这里不做介绍(ps:基础知识,自己脑补)
修改I2C-Tools的Makefile指定交叉编译工具链
CC ?= gcc AR ?= ar STRIP ?= strip #改为(指定交叉编译工具链前缀, 去掉问号): CC = $(CROSS_COMPILE)gcc AR = $(CROSS_COMPILE)ar STRIP = $(CROSS_COMPILE)strip
在Makefile中,
?=在第一次设置变量时才会起效果,如果之前设置过该变量,则不会起效果,修改后内容如下:
执行make即可
执行make时,是动态链接,需要把libi2c.so也放到单板上
想静态链接的话,执行:
make USE_STATIC_LIB=1
编译完后如下:我们需要把
libi2c.so、libi2c.so.0、libi2c.so.0.1.1拷贝到主板文件系统的/lib目录下,把i2cdetect、i2cdump、i2cget、i2cset和i2ctransfer拷贝到/sbin
注意如果想自动安装,请执行下面的命令即可:
#我自己的网络文件系统的目录为/home/xym/nfs_rootfs
sudo make install PREFIX=/home/xym/nfs_rootfs
安装完后的目录如下:
应用程序和库文件所在目录

头文件所在目录

常用命令
i2cdetect:I2C检测
i2cdetect -l:列出当前的I2C Adapter(或称为I2C Bus、I2C Controller)
i2cdetect -F I2CBUS:列出I2CBUS控制器的支持的功能,I2CBUS为0、1、2等整数
i2cdetect -y -a I2CBUS:显示当前i2c总线上挂了多少个I2C设备, I2CBUS为0、1、2等整数
–表示没有该地址对应的设备;
UU表示有该设备并且它已经有驱动程序,例如下面的i2c0控制器上的0x5d地址接的是触摸芯片gt911
数值表示有该设备但是没有对应的设备驱动,例如下面的i2c0控制器上的0x1e地址接的是环境光传感器AP3216C和0x68地址接的是mpu6050加速度计陀螺仪传感器,查看设备树也能看得出来

i2cget:I2C读
使用说明如下:
# i2cget Usage: i2cget [-f] [-y] [-a] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]] I2CBUS is an integer or an I2C bus name ADDRESS is an integer (0x03 - 0x77, or 0x00 - 0x7f if -a is given) MODE is one of: b (read byte data, default) w (read word data) c (write byte/read byte) Append p for SMBus PEC
使用示例:
// 读一个字节: I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址 i2cget -f -y I2CBUS CHIP-ADDRESS // 读某个地址上的一个字节: // I2CBUS为0、1、2等整数, 表示I2C Bus // CHIP-ADDRESS表示设备地址 // DATA-ADDRESS: 芯片上寄存器地址 // MODE:有2个取值, b-使用`SMBus Read Byte`先发出DATA-ADDRESS, 再读一个字节, 中间无P信号 // c-先write byte, 在read byte,中间有P信号 i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE // 读某个地址上的2个字节: // I2CBUS为0、1、2等整数, 表示I2C Bus // CHIP-ADDRESS表示设备地址 // DATA-ADDRESS: 芯片上寄存器地址 // MODE:w-表示先发出DATA-ADDRESS,再读2个字节 i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE
i2cset:I2C写 使用说明如下:
# i2cset Usage: i2cset [-f] [-y] [-m MASK] [-r] [-a] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] ... [MODE] I2CBUS is an integer or an I2C bus name ADDRESS is an integer (0x03 - 0x77, or 0x00 - 0x7f if -a is given) MODE is one of: c (byte, no value) b (byte data, default) w (word data) i (I2C block data) s (SMBus block data) Append p for SMBus PEC
使用示例:
// 写一个字节: I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
// DATA-ADDRESS就是要写的数据
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS
// 给address写1个字节(address, value):
// I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
// DATA-ADDRESS: 8位芯片寄存器地址;
// VALUE: 8位数值
// MODE: 可以省略,也可以写为b
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE [b]
// 给address写2个字节(address, value):
// I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
// DATA-ADDRESS: 8位芯片寄存器地址;
// VALUE: 16位数值
// MODE: w
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE w
// SMBus Block Write:给address写N个字节的数据
// 发送的数据有:address, N, value1, value2, ..., valueN
// 跟`I2C Block Write`相比, 需要发送长度N
// I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
// DATA-ADDRESS: 8位芯片寄存器地址;
// VALUE1~N: N个8位数值
// MODE: s
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN s
// I2C Block Write:给address写N个字节的数据
// 发送的数据有:address, value1, value2, ..., valueN
// 跟`SMBus Block Write`相比, 不需要发送长度N
// I2CBUS为0、1、2等整数, 表示I2C Bus; CHIP-ADDRESS表示设备地址
// DATA-ADDRESS: 8位芯片寄存器地址;
// VALUE1~N: N个8位数值
// MODE: i
i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN i
i2ctransfer:I2C传输(不是基于SMBus) 使用说明如下:
# i2ctransfer Usage: i2ctransfer [-f] [-y] [-v] [-V] [-a] I2CBUS DESC [DATA] [DESC [DATA]]... I2CBUS is an integer or an I2C bus name DESC describes the transfer in the form: {r|w}LENGTH[@address] 1) read/write-flag 2) LENGTH (range 0-65535) 3) I2C address (use last one if omitted) DATA are LENGTH bytes for a write message. They can be shortened by a suffix: = (keep value constant until LENGTH) + (increase value by 1 until LENGTH) - (decrease value by 1 until LENGTH) p (use pseudo random generator until LENGTH with value as seed) Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50): # i2ctransfer 0 w1@0x50 0x64 r8 Example (same EEPROM, at offset 0x42 write 0xff 0xfe ... 0xf0): # i2ctransfer 0 w17@0x50 0x42 0xff-
使用举例:
// Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50): # i2ctransfer -f -y 0 w1@0x50 0x64 r8 // Example (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50): # i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 // Example // first: (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50) // and then: (bus 0, read 3 byte at offset 0x64 from EEPROM at 0x50) # i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3@0x50 # i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3 //如果设备地址不变,后面的设备地址可省略
I2C-Tools的访问I2C设备的2种方式
I2C-Tools可以通过SMBus来访问I2C设备,也可以使用一般的I2C协议来访问I2C设备。使用一句话概括I2C传输:APP通过I2C Controller与I2C Device传输数据。 在I2C-Tools APP里,有这几个问题:
怎么指定I2C控制器?
i2c-dev.c提供为每个I2C控制器(I2C Bus、I2C Adapter)都生成一个设备节点:/dev/i2c-0、/dev/i2c-1等,应用程序open某个/dev/i2c-x节点,就是去访问该I2C控制器下的设备
open(/dev/i2c-x)
怎么指定I2C设备?
通过ioctl指定I2C设备的地址
ioctl(file, I2C_SLAVE, address):如果该设备已经有了对应的设备驱动程序,则返回失败ioctl(file, I2C_SLAVE_FORCE, address):如果该设备已经有了对应的设备驱动程序但是还是想通过i2c-dev驱动来访问它,则使用这个ioctl来指定I2C设备地址
怎么传输数据?
两种方式
一般的I2C方式:
ioctl(file, I2C_RDWR, &rdwr)示例代码:i2ctransfer.c

SMBus方式:
ioctl(file, I2C_SMBUS, &args)示例代码:i2cget.c、i2cset.c

使用I2C-Tools操作传感器AP3216C

AP3216C是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:
复位:往寄存器0写入0x4
使能:往寄存器0写入0x3
读光强:读寄存器0xC、0xD得到2字节的光强
读距离:读寄存器0xE、0xF得到2字节的距离值
AP3216C的设备地址是0x1E,假设节在I2C BUS0上,操作命令如下:
使用SMBus协议
i2cset -f -y 0 0x1e 0 0x4
i2cset -f -y 0 0x1e 0 0x3
i2cget -f -y 0 0x1e 0xc w
i2cget -f -y 0 0x1e 0xe w
使用I2C协议
i2ctransfer -f -y 0 w2@0x1e 0 0x4
i2ctransfer -f -y 0 w2@0x1e 0 0x3
i2ctransfer -f -y 0 w1@0x1e 0xc r2
i2ctransfer -f -y 0 w1@0x1e 0xe r2
App工程直接访问i2c设备
移植源码到工程
参见工程【:boxing_glove: 19.i2c/ap3216_1】工程组织如下:

这样在应用程序【ap3216c_app.c】就可以通过下面的步骤直接通过i2c控制器访问ap3216了。
打开设备
file = open_i2c_dev(0, filename, sizeof(filename), 0);
说明: 该函数位于i2cbusses.c里面,实际等效于下面操作,i2c-tools并未开放该接口,所以我们需要拷贝i2cbusses.c源码到工程中
open("/dev/i2c-0", O_RDWR)
设置丛机地址
set_slave_addr(file, AP3216C_ADDR, 1)
说明: 该函数位于i2cbusses.c里面,实际等效于下面操作,i2c-tools并未开放该接口,所以我们需要拷贝i2cbusses.c源码到工程中
ioctl(file, I2C_SLAVE_FORCE , AP3216C_ADDR)
复位ap3216
/* 初始化AP3216C */ i2c_smbus_write_byte_data(file, AP3216C_SYSTEMCONG, 0x04); /* 复位AP3216C */ nanosleep(&req, NULL); /* 复位最少10ms */ i2c_smbus_write_byte_data(file, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR
说明:
i2c_smbus_write_byte_data函数位于smbus.c文件中,这些函数都是i2c-tools开放出来的读数据
ir = i2c_smbus_read_word_data(file, AP3216C_IRDATALOW); /* ir传感器数据 */ als = i2c_smbus_read_word_data(file, AP3216C_ALSDATALOW); /* als传感器数据 */ ps = i2c_smbus_read_word_data(file, AP3216C_PSDATALOW); /* ps传感器数据 */
说明:
i2c_smbus_read_word_data函数位于smbus.c文件中,这些函数都是i2c-tools开放出来的
直接使用libi2c.so库文件
前面介绍到我们可以通过下面的方式把i2c-tools安装到我们的文件系统中如下:
#我自己的网络文件系统的目录为/home/xym/nfs_rootfs
sudo make install PREFIX=/home/xym/nfs_rootfs

所以我们可以直接引用该头文件和库文件。
工程参考【19.i2c/ap3216_2】如下

这样在应用程序【ap3216c_app.c】就可以通过下面的步骤直接通过i2c控制器访问ap3216了。
打开设备:这点和上面的不太一样
file = file = open("/dev/i2c-0", O_RDWR);
设置丛机地址:这点和上面的不太一样
ioctl(file, I2C_SLAVE_FORCE , AP3216C_ADDR)
复位ap3216:这点和上面的完全一样
/* 初始化AP3216C */ i2c_smbus_write_byte_data(file, AP3216C_SYSTEMCONG, 0x04); /* 复位AP3216C */ nanosleep(&req, NULL); /* 复位最少10ms */ i2c_smbus_write_byte_data(file, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR
读数据:这点和上面的完全一样
ir = i2c_smbus_read_word_data(file, AP3216C_IRDATALOW); /* ir传感器数据 */ als = i2c_smbus_read_word_data(file, AP3216C_ALSDATALOW); /* als传感器数据 */ ps = i2c_smbus_read_word_data(file, AP3216C_PSDATALOW); /* ps传感器数据 */
说明:实际i2c_smbus_read_word_data原型也是调用ioctl实现的如下:
__s32 i2c_smbus_write_word_data(int file, __u8 command, __u16 value)
{
union i2c_smbus_data data;
data.word = value;
return i2c_smbus_access(file, I2C_SMBUS_WRITE, command,
I2C_SMBUS_WORD_DATA, &data);
}
__s32 i2c_smbus_access(int file, char read_write, __u8 command,
int size, union i2c_smbus_data *data)
{
struct i2c_smbus_ioctl_data args; // 构造ioctl的传递参数
args.read_write = read_write;
args.command = command;
args.size = size;
args.data = data;
err = ioctl(file, I2C_SMBUS, &args);// 调用ioctrl来读取i2c数据
}