## I2C-Tools介绍 参考资料: * Linux驱动程序: `drivers/i2c/i2c-dev.c` * I2C-Tools-4.2: `https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/` ## 访问I2C设备4种方式 app访问I2C设备的框架如下: ![](media/030_i2ctools_and_i2c_dev.png) 1. 如下图所示:常规做法 - 原厂做好芯片的I2C控制器驱动`adapter_driver` - 驱动开发人员户编写自己芯片的驱动也就是`some_driver`驱动,例如芯片mpu6050,然后会生成`/dev/mpu6050`的设备节点。 - 应用开发人员通过App应用开发访问`/dev/mpu6050` ![](media/image-20210917084320816.png) 2. 如下图所示 该方法和第1种方法区别在于使用了模拟i2c驱动 ![](media/image-20210917084722518.png) - 驱动开发人员`make menuconfig`开启内核自带的的模拟i2c驱动,并且通过设备树要告诉它使用哪些gpio作为模拟的sda和scl线。 - 驱动开发人员户编写自己芯片的驱动也就是`some_driver`驱动,例如芯片mpu6050,然后会生成`/dev/mpu6050`的设备节点。 - 应用开发人员通过App应用开发访问`/dev/mpu6050` 3. 如下图所示 ![](media/image-20210917085220445.png) - 原厂做好芯片的I2C控制器驱动`adapter_driver` - 驱动开发人员户不需要再编写对应的mpu6050驱动,只需使用内核自带的i2c-dev.c - 应用开发人员通过App应用开发访问`/dev/i2c_0`设备节点,通过来直接访问i2c-dev.c里面的接口即可访问底层的i2c Device 我们本章介绍的i2c-tools工具就是通过该方式访问i2c设备的。(i2c-tools本身就是个App应用程序) 4. 如下图所示 ![](media/image-20210917085633092.png) - 驱动开发人员`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`完成,过程如下: ![](media/image-20210917211034184.png) ### 交叉编译 * 在Ubuntu设置交叉编译工具链,这里不做介绍(ps:基础知识,自己脑补) * 修改I2C-Tools的Makefile指定交叉编译工具链 ```shell CC ?= gcc AR ?= ar STRIP ?= strip #改为(指定交叉编译工具链前缀, 去掉问号): CC = $(CROSS_COMPILE)gcc AR = $(CROSS_COMPILE)ar STRIP = $(CROSS_COMPILE)strip ``` 在Makefile中,`?=`在第一次设置变量时才会起效果,如果之前设置过该变量,则不会起效果,修改后内容如下: ![](media/image-20210917091053784.png) * 执行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` ![](media/image-20210917135933708.png) 注意如果想自动安装,请执行下面的命令即可: ```shell #我自己的网络文件系统的目录为/home/xym/nfs_rootfs sudo make install PREFIX=/home/xym/nfs_rootfs ``` 安装完后的目录如下: - 应用程序和库文件所在目录 ![](media/image-20210917193227327.png) - 头文件所在目录 ![](media/image-20210918085038576.png) ### 常用命令 * i2cdetect:I2C检测 - `i2cdetect -l` :列出当前的I2C Adapter(或称为I2C Bus、I2C Controller) ![](media/image-20210917201003027.png) - `i2cdetect -F I2CBUS`:列出I2CBUS控制器的支持的功能,I2CBUS为0、1、2等整数 ![](media/image-20210917201556782.png) - `i2cdetect -y -a I2CBUS`:显示当前i2c总线上挂了多少个I2C设备, I2CBUS为0、1、2等整数 ![](media/image-20210917201813665.png) - --表示没有该地址对应的设备; - UU表示有该设备并且它已经有驱动程序,例如下面的i2c0控制器上的0x5d地址接的是触摸芯片gt911 - 数值表示有该设备但是没有对应的设备驱动,例如下面的i2c0控制器上的0x1e地址接的是环境光传感器AP3216C和0x68地址接的是mpu6050加速度计陀螺仪传感器,查看设备树也能看得出来 ![](media/image-20210917203955742.png) * i2cget:I2C读 - 使用说明如下: ```shell # 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写 使用说明如下: ```shell # 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 ``` 使用示例: ```shell // 写一个字节: 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) 使用说明如下: ```shell # 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- ``` 使用举例: ```shell // 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控制器下的设备 ```c 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 ![](media/031_i2ctransfer_flow.png) * SMBus方式:`ioctl(file, I2C_SMBUS, &args)` 示例代码:i2cget.c、i2cset.c ![](media/032_i2cget_i2cset_flow.png) ## 使用I2C-Tools操作传感器AP3216C ![](media/image-20210917210120948.png) AP3216C是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下: * 复位:往寄存器0写入0x4 * 使能:往寄存器0写入0x3 * 读光强:读寄存器0xC、0xD得到2字节的光强 * 读距离:读寄存器0xE、0xF得到2字节的距离值 AP3216C的设备地址是0x1E,假设节在I2C BUS0上,操作命令如下: * 使用SMBus协议 ```shell 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协议 ```shell 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】工程组织如下: ![](media/image-20210918105039979.png) 这样在应用程序【ap3216c_app.c】就可以通过下面的步骤直接通过i2c控制器访问ap3216了。 1. 打开设备 ```c file = open_i2c_dev(0, filename, sizeof(filename), 0); ``` 说明: 该函数位于i2cbusses.c里面,实际等效于下面操作,i2c-tools并未开放该接口,所以我们需要拷贝i2cbusses.c源码到工程中 ``` open("/dev/i2c-0", O_RDWR) ``` 2. 设置丛机地址 ```c set_slave_addr(file, AP3216C_ADDR, 1) ``` 说明: 该函数位于i2cbusses.c里面,实际等效于下面操作,i2c-tools并未开放该接口,所以我们需要拷贝i2cbusses.c源码到工程中 ```c ioctl(file, I2C_SLAVE_FORCE , AP3216C_ADDR) ``` 3. 复位ap3216 ```c /* 初始化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开放出来的 4. 读数据 ```c 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安装到我们的文件系统中如下: ```c #我自己的网络文件系统的目录为/home/xym/nfs_rootfs sudo make install PREFIX=/home/xym/nfs_rootfs ``` ![](media/image-20210918110526565.png) 所以我们可以直接引用该头文件和库文件。 工程参考【19.i2c/ap3216_2】如下 ![](media/image-20210918111034269.png) 这样在应用程序【ap3216c_app.c】就可以通过下面的步骤直接通过i2c控制器访问ap3216了。 1. 打开设备:这点和上面的不太一样 ```c file = file = open("/dev/i2c-0", O_RDWR); ``` 2. 设置丛机地址:这点和上面的不太一样 ```c ioctl(file, I2C_SLAVE_FORCE , AP3216C_ADDR) ``` 3. 复位ap3216:这点和上面的完全一样 ```c /* 初始化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 ``` 4. 读数据:这点和上面的完全一样 ```c 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实现的如下: ```c __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数据 } ```