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设备的框架如下:

../../_images/030_i2ctools_and_i2c_dev.png

  1. 如下图所示:常规做法

    • 原厂做好芯片的I2C控制器驱动adapter_driver

    • 驱动开发人员户编写自己芯片的驱动也就是some_driver驱动,例如芯片mpu6050,然后会生成/dev/mpu6050的设备节点。

    • 应用开发人员通过App应用开发访问/dev/mpu6050

    ../../_images/image-20210917084320816.png

  2. 如下图所示

    该方法和第1种方法区别在于使用了模拟i2c驱动

    ../../_images/image-20210917084722518.png

    • 驱动开发人员make menuconfig开启内核自带的的模拟i2c驱动,并且通过设备树要告诉它使用哪些gpio作为模拟的sda和scl线。

    • 驱动开发人员户编写自己芯片的驱动也就是some_driver驱动,例如芯片mpu6050,然后会生成/dev/mpu6050的设备节点。

    • 应用开发人员通过App应用开发访问/dev/mpu6050

  3. 如下图所示

    ../../_images/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. 如下图所示

    ../../_images/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完成,过程如下:

../../_images/image-20210917211034184.png

交叉编译

  • 在Ubuntu设置交叉编译工具链,这里不做介绍(ps:基础知识,自己脑补)

  • 修改I2C-Tools的Makefile指定交叉编译工具链

    CC      ?= gcc
    AR      ?= ar
    STRIP   ?= strip
    #改为(指定交叉编译工具链前缀, 去掉问号):
    CC      = $(CROSS_COMPILE)gcc
    AR      = $(CROSS_COMPILE)ar
    STRIP   = $(CROSS_COMPILE)strip
    

    在Makefile中,?=在第一次设置变量时才会起效果,如果之前设置过该变量,则不会起效果,修改后内容如下:

    ../../_images/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

    ../../_images/image-20210917135933708.png

注意如果想自动安装,请执行下面的命令即可:

#我自己的网络文件系统的目录为/home/xym/nfs_rootfs 
sudo make install PREFIX=/home/xym/nfs_rootfs

安装完后的目录如下:

  • 应用程序和库文件所在目录

    ../../_images/image-20210917193227327.png

  • 头文件所在目录

    ../../_images/image-20210918085038576.png

常用命令

  • i2cdetect:I2C检测

    • i2cdetect -l :列出当前的I2C Adapter(或称为I2C Bus、I2C Controller)

      ../../_images/image-20210917201003027.png

    • i2cdetect -F I2CBUS:列出I2CBUS控制器的支持的功能,I2CBUS为0、1、2等整数

      ../../_images/image-20210917201556782.png

    • i2cdetect -y -a I2CBUS:显示当前i2c总线上挂了多少个I2C设备, I2CBUS为0、1、2等整数

      ../../_images/image-20210917201813665.png

      • –表示没有该地址对应的设备;

      • UU表示有该设备并且它已经有驱动程序,例如下面的i2c0控制器上的0x5d地址接的是触摸芯片gt911

      • 数值表示有该设备但是没有对应的设备驱动,例如下面的i2c0控制器上的0x1e地址接的是环境光传感器AP3216C和0x68地址接的是mpu6050加速度计陀螺仪传感器,查看设备树也能看得出来

        ../../_images/image-20210917203955742.png

  • 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

      ../../_images/031_i2ctransfer_flow.png

    • SMBus方式:ioctl(file, I2C_SMBUS, &args)

      示例代码:i2cget.c、i2cset.c

      ../../_images/032_i2cget_i2cset_flow.png

使用I2C-Tools操作传感器AP3216C

../../_images/image-20210917210120948.png

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】工程组织如下:

../../_images/image-20210918105039979.png

这样在应用程序【ap3216c_app.c】就可以通过下面的步骤直接通过i2c控制器访问ap3216了。

  1. 打开设备

    file = open_i2c_dev(0, filename, sizeof(filename), 0); 
    

    说明: 该函数位于i2cbusses.c里面,实际等效于下面操作,i2c-tools并未开放该接口,所以我们需要拷贝i2cbusses.c源码到工程中

    open("/dev/i2c-0", O_RDWR)
    
  2. 设置丛机地址

    set_slave_addr(file, AP3216C_ADDR, 1)    
    

    说明: 该函数位于i2cbusses.c里面,实际等效于下面操作,i2c-tools并未开放该接口,所以我们需要拷贝i2cbusses.c源码到工程中

    ioctl(file, I2C_SLAVE_FORCE , AP3216C_ADDR)
    
  3. 复位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开放出来的

  4. 读数据

    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

../../_images/image-20210918110526565.png

所以我们可以直接引用该头文件和库文件。

工程参考【19.i2c/ap3216_2】如下

../../_images/image-20210918111034269.png

这样在应用程序【ap3216c_app.c】就可以通过下面的步骤直接通过i2c控制器访问ap3216了。

  1. 打开设备:这点和上面的不太一样

    file = file = open("/dev/i2c-0", O_RDWR); 
    
  2. 设置丛机地址:这点和上面的不太一样

    ioctl(file, I2C_SLAVE_FORCE , AP3216C_ADDR) 
    
  3. 复位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 	
    
  4. 读数据:这点和上面的完全一样

    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数据 


}