Skip to content

LV060-I2CTools简介

了解下 I2C-Tools。若笔记中有错误或者不合适的地方,欢迎批评指正 😃。

一、参考资料

1. Linux 驱动程序

i2c-dev.c - drivers/i2c/i2c-dev.c

2. I2C-Tools

这个工具官网在:I2C Tools - Linux i2c Wiki

github 仓库在这里:GitHub - oudream/i2c-tools

下载页面在这里:Index of /pub/software/utils/i2c-tools/

二、I2C-Tools 简介

1. I2C-Tools 是什么?

i2c-tools 工具是一个专门调试 i2c 的,开源,可获取挂载的设备及设备地址,还可以在对应的设备指定寄存器设置值或者获取值等功能。通过这个工具,我们无需编写驱动程序即可访问 I2C 设备。

image-20250324195139434

对于 APP 访问硬件肯定是需要驱动程序的,对于 I2C 设备,内核提供了驱动程序 i2c-dev.c - drivers/i2c/i2c-dev.c,通过它可以直接使用下面的 I2C 控制器驱动程序来访问 I2C 设备。基本框架如下:

image-20210224172517485

i2c-tools 是一套好用的工具,也是一套示例代码。

2. I2C-Tools 的访问 I2C 设备的 2 种方式

I2C-Tools 可以通过 SMBus 来访问 I2C 设备,也可以使用一般的 I2C 协议来访问 I2C 设备。使用一句话概括 I2C 传输:APP 通过 I2C Controller 与 I2C Device 传输数据。在 APP 里,有这几个问题:

  • 怎么指定 I2C 控制器?

i2c-dev.c 提供为每个 I2C 控制器(I2C Bus、I2C Adapter)都生成一个设备节点:/dev/i2c-0、/dev/i2c-1 等

open 某个/dev/i2c-X 节点,就是去访问该 I2C 控制器下的设备

  • 怎么指定 I2C 设备?通过 ioctl 指定 I2C 设备的地址

(1)I2C_SLAVE

c
ioctl(file,  I2C_SLAVE, address)

如果该设备已经有了对应的设备驱动程序,则返回失败。

(2)I2C_SLAVE_FORCE

c
ioctl(file,  I2C_SLAVE_FORCE, address)

如果该设备已经有了对应的设备驱动程序,但是还是想通过 i2c-dev 驱动来访问它,则使用这个 ioctl 来指定 I2C 设备地址。

  • 怎么传输数据?

(1)一般的 I2C 方式:ioctl(file, I2C_RDWR, &rdwr)

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

3. 交叉编译

  • 设置交叉编译工具
shell
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export PATH=$PATH:/home/sumu/2software/gcc-arm-linux-gnueabihf-8.3.0/bin
  • 修改 I2C-Tools 的 Makefile 指定交叉编译工具链
shell
CC      ?= gcc
AR      ?= ar
STRIP   ?= strip
#改为(指定交叉编译工具链前缀, 去掉问号):
CC      = $(CROSS_COMPILE)gcc
AR      = $(CROSS_COMPILE)ar
STRIP   = $(CROSS_COMPILE)strip

在 Makefile 中,“?=”在第一次设置变量时才会起效果,如果之前设置过该变量,则不会起效果。

  • 执行 make 即可。
shell
make
# 或者我们之前添加过交叉编译工具刀环境变量的话,可以直接执行以下命令
make cc=arm-linux-gnueabihf-gcc AR=arm-linux-gnueabihf-ar STRIP=arm-linux-gnueabihf-strip

执行 make 时,是动态链接,需要把 libi2c.so 也放到单板上:

shell
sudo cp -avf libi2c.so libi2c.so.0 libi2c.so.0.1.1 ~/4nfs/imx6ull_rootfs/lib/

想静态链接的话,执行:make USE_STATIC_LIB=1

三、使用 I2C-Tools

使用一句话概括 I2C 传输:APP 通过 I2C Controller 与 I2C Device 传输数据。所以使用 I2C-Tools 时也需要指定:

  • 哪个 I2C 控制器(或称为 I2C BUS、I2C Adapter)
  • 哪个 I2C 设备(设备地址)
  • 数据:读还是写、数据本身

1. i2cdetect:I2C 检测

1.1 使用说明

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

# 打印某个 I2C Adapter 的 Functionalities, I2CBUS 为 0、1、2 等整数
i2cdetect -F I2CBUS

# 看看有哪些 I2C 设备, I2CBUS 为 0、1、2 等整数
i2cdetect -y -a I2CBUS

注意:i2cdetect 的工作原理是对指定的 I2C 总线上的每个地址发送一个读请求,并根据设备的响应来判断该地址上是否有设备存在。如果设备存在并且响应,i2cdetect 会显示该地址;如果没有响应,则显示 --。此外,如果地址已经被内核驱动程序使用,则显示 UU

1.2 使用实例

shell
# i2cdetect -l
i2c-1   i2c             STM32F7 I2C(0x40013000)                 I2C adapter
i2c-2   i2c             STM32F7 I2C(0x5c002000)                 I2C adapter
i2c-0   i2c             STM32F7 I2C(0x40012000)                 I2C adapter

# i2cdetect -F 0
Functionalities implemented by /dev/i2c-0:
I2C                              yes
SMBus Quick Command              yes
SMBus Send Byte                  yes
SMBus Receive Byte               yes
SMBus Write Byte                 yes
SMBus Read Byte                  yes
SMBus Write Word                 yes
SMBus Read Word                  yes
SMBus Process Call               yes
SMBus Block Write                yes
SMBus Block Read                 yes
SMBus Block Process Call         yes
SMBus PEC                        yes
I2C Block Write                  yes
I2C Block Read                   yes

# i2cdetect -y -a 0  
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00: 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- UU -- -- -- 1e --
20: -- -- UU -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 说明:--表示没有该地址对应的设备, UU 表示有该设备并且它已经有驱动程序, 数值表示有该设备但是没有对应的设备驱动

2. i2cget:I2C 读

2.1 使用说明如下

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

2.2 使用示例

shell
# 读一个字节: 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

3. i2cset:I2C 写

3.1 使用说明

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

3.2 使用示例

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

4. i2ctransfer:I2C 传输(不是基于 SMBus)

4.1 使用说明

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-

4.2 使用实例

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 //如果设备地址不变,后面的设备地址可省略

四、AP3216C 的操作

1. AP3216C 简介

正点原子的开发板上有光感芯片 AP3216C:

image-20250324233757890

I.MX6U-ALPHA 开发板上通过 I2C1 连接了一个三合一环境传感器: AP3216C, AP3216C 是由敦南科技推出的一款传感器,其支持环境光强度(ALS)、接近距离(PS)和红外线强度(IR)这三个环境参数检测。该芯片可以通过 IIC 接口与主控制相连,并且支持中断。寄存器也比较简单:

image-20250324234208125

0X00 这个寄存器是模式控制寄存器,用来设置 AP3216C 的工作模式,一般开始先将其设置为 0X04,也就是先软件复位一次 AP3216C。接下来根据实际使用情况选择合适的工作模式,比如设置为 0X03,也就是开启 ALS+PS+IR。从 0X0A~0X0F 这 6 个寄存器就是数据寄存器,保存着 ALS、 PS 和 IR 这三个传感器获取到的数据值。如果同时打开 ALS、PS 和 IR 则读取间隔最少要 112.5ms,因为 AP3216C 完成一次转换需要 112.5ms。

我们编写程序会先检测 AP3216C 是否存在,一般的芯片是有个 ID 寄存器,通过读取 ID 寄存器判断 ID 是否正确就可以检测芯片是否存在。但是 AP3216C 没有 ID 寄存器,所以我们就可以通过向寄存器 0X00 写入一个值,然后再读取 0X00 寄存器,判断读出得到值和写入的是否相等,如果相等就表示 AP3216C 存在,否则的话 AP3216C 就不存在。

2. 硬件原理图

image-20250324234306894

3. 设备地址

AP3216 的设备地址为 0X1E ,这个我们可以看芯片手册:datasheet_my.pdf

image-20250324234940023

4. 读写步骤

AP3216C 是红外、光强、距离三合一的传感器,以读出光强、距离值为例,步骤如下:

  • 复位:往寄存器 0 写入 0x4。
  • 使能:往寄存器 0 写入 0x3。
  • 读光强:读寄存器 0xC、0xD 得到 2 字节的光强。
  • 读距离:读寄存器 0xE、0xF 得到 2 字节的距离值。

5. I2C-Tools 的使用

5.1 i2cdetect

  • 列出当前的 I2C Adapter(或称为 I2C Bus、I2C Controller)
shell
# i2cdetect -l
i2cdetect -l
image-20250324235328001
  • 打印某个 I2C Adapter 的 Functionalities, I2CBUS 为 0、1、2 等整数
shell
# i2cdetect -F I2CBUS
i2cdetect -F 0
i2cdetect -F 1
image-20250324235435680
  • 看看有哪些 I2C 设备, I2CBUS 为 0、1、2 等整数
shell
#i2cdetect -y -a I2CBUS
i2cdetect -y -a 0
i2cdetect -y -a 1
image-20250324235723228
shell
-- :表示没有该地址对应的设备
UU :表示有该设备并且它已经有驱动程序
数值:表示有该设备但是没有对应的设备驱动

前面我们知道 AP3216C 接在 I2C1 上面,这里对应的是 i2c-0。

5.2 使用 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 # 读距离
image-20250325000439916

5.3 使用 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 # 读距离
image-20250325000549469

五、源码分析

1. 使用 I2C 方式

示例代码:i2ctransfer.c

image-20210224191404322

2. 使用 SMBus 方式

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

image-20210224192345075