LinuxI2C应用编程——I2C-Tools的使用

I2C 硬件框架

在这里插入图片描述

  • 在一个芯片(SoC)内部,有一个或多个 I2C 控制器
  • 在一个 I2C 控制器上,可以连接一个或多个 I2C 设备
  • I2C 总线只需要 2 条线:时钟线 SCL、数据线 SDA
  • 在 I2C 总线的 SCL、SDA 线上,都有上拉电阻

I2C 软件框架

在这里插入图片描述

I2C的驱动框架包含

  • 设备驱动 Device Driver(负责怎么读写数据)
  • 控制器驱动 Controller Driver(负责传输数据)

I2C协议(传输数据的格式)

写操作

在这里插入图片描述

读操作

在这里插入图片描述

I2C 信号

I2C 协议中数据传输的单位是字节,也就是 8 位。但是要用到 9 个时钟:前面 8 个时钟用来传输 8 数据,第 9 个时钟用来传输回应信号。传输时,先传输最高位(MSB)

  • 开始信号(S):SCL 为高电平时,SDA 山高电平向低电平跳变,开始传送数据。
  • 结束信号(P):SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
  • 响应信号(ACK):接收器在接收到 8 位数据后,在第 9 个时钟周期,拉低SDA
  • SDA 上传输的数据必须在 SCL 为高电平期间保持稳定,SDA 上的数据只能在SCL 为低电平期间变化

I2C 协议信号如下:
在这里插入图片描述
SDA可由主从设备进行控制,所以为了避免争夺线权,实现双向传输,设备的 SDA 中有一个三极管,使用开极/开漏电路(三极管是开极,CMOS 管是开漏,作用一样),这就是 SDA 要使用上拉电阻的原因

SCL 也要使用上拉电阻,在第 9 个时钟之后,如果有某一方需要更多的 时 间 来 处 理 数 据 , 它 可 以 一 直 驱 动 三 极 管 把 SCL 拉 低 。当 SCL 为低电平时候,大家都不应该使用 IIC 总线,只有当 SCL 从低电平变为高电平的时候,IIC 总线才能被使用。当它就绪后,就可以不再驱动三极管,这是上拉电阻把 SCL 变为高电平,其他设备就可以继续使用 I2C 总线了

SMBus 协议

概述

SMBus 是 是 I2C 协议的一个子集

SMBus: System Management Bus,系统管理总线。

SMBus 最初的目的是为智能电池、充电电池、其他微控制器之间的通信链路而定义的。SMBus 也被用来连接各种设备,包括电源相关设备,系统传感器,EEPROM 通讯设备等等。SMBus 为系统和电源管理这样的任务提供了一条控制总线,使用 SMBus 的系统,设备之间发送和接收消息都是通过 SMBus,而不是使用单独的控制线,这样可以节省设备的管脚数。

SMBus 是基于 I2C 协议的,SMBus 要求更严格,SMBus 是 I2C 协议的子集。

硬件和软件上的区别

VDD 的极限值不一样

  • I2C 协议:范围很广,甚至讨论了高达 12V 的情况
  • SMBus:1.8V~5V

最小时钟频率、最大的 Clock Stretching

  • Clock Stretching 含义:某个设备需要更多时间进行内部的处理时,它可以把 SCL 拉低占住 I2C 总线
    -I2C 协议:时钟频率最小值无限制,Clock Stretching 时长也没有限制
    -SMBus:时钟频率最小值是 10KHz,Clock Stretching 的最大时间值也有限制

地址回应(Address Acknowledge):一个 I2C 设备接收到它的设备地址后,是否必须发出回应信号?

  • I2C 协议:没有强制要求必须发出回应信号
  • SMBus:强制要求必须发出回应信号,这样对方才知道该设备的状态:busy,failed,或是被移除了

SMBus 协议明确了数据的传输格式

  • I2C 协议:它只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义
  • SMBus:定义了几种数据格式(含义更加具体)

REPEATED START Condition(重复发出 S 信号)
比如读 EEPROM 时,涉及 2 个操作:

  • ① 把存储地址发给设备
  • ② 读数据

在写、读之间,可以不发出 P 信号,而是直接发出 S 信号:这个 S 信号就是REPEATED START,如图 10.13 所示
在这里插入图片描述
SMBus Low Power Version:SMBus 也有低功耗的版本

SMBus 协议分析

对于 I2C 协议,它只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义。
对于 SMBus 协议,它定义了几种数据格式。

因为很多设备都实现了 SMBus,而不是更宽泛的 I2C 协议,所以优先使用SMBus。即使 I2C 控制器没有实现 SMBus,软件方面也是可以使用 I2C 协议来模拟 SMBus。所以:Linux 建议优先使用 SMBus。

注意:

  • Functionality flag 是 Linux 的某个 I2C 控制器驱动所支持的功能。
  • 比如 Functionality flag: I2C_FUNC_SMBUS_QUICK,表示需要 I2C 控制器支持 SMBus Quick Command

符号的含义

在这里插入图片描述

SMBus Quick Command

在这里插入图片描述

只是用来发送一位数据:R/W#本意是用来表示读或写,但是在 SMBus 里可以用来表示其他含义。比如某些开关设备,可以根据这一位来决定是打开还是关闭。

Functionality flag: I2C_FUNC_SMBUS_QUICK

SMBus Receive Byte

在这里插入图片描述

I2C-tools 中的函数:i2c_smbus_read_byte()。读取一个字节,Host adapter 接收到一个字节后不需要发出回应信号(上图中 N 表示不回应)。

Functionality flag: I2C_FUNC_SMBUS_READ_BYTE

SMBus Send Byte

在这里插入图片描述

I2C-tools 中的函数:i2c_smbus_write_byte()。发送一个字节。

Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE

SMBus Read Byte

在这里插入图片描述
I2C-tools 中的函数:i2c_smbus_read_byte_data()。先发出 Command Code(它一般表示芯片内部的寄存器地址),再读取一个字节的数据。上面介绍的 SMBus Receive Byte 是不发送 Comand,直接读取数据。

Functionality flag: I2C_FUNC_SMBUS_READ_BYTE_DATA

SMBus Read Word

在这里插入图片描述
I2C-tools 中的函数:i2c_smbus_read_word_data()。先发出 Command Code(它一般表示芯片内部的寄存器地址),再读取 2 个字节的数据。

Functionality flag: I2C_FUNC_SMBUS_READ_WORD_DATA

SMBus Write Byte

在这里插入图片描述
I2C-tools 中 的 函 数 : i2c_smbus_write_byte_data() 。 先发出Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的数据。

Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE_DATA

SMBus Write Word

在这里插入图片描述
I2C-tools 中 的 函 数 : i2c_smbus_write_word_data() 。 先发出Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的数据。

Functionality flag: I2C_FUNC_SMBUS_WRITE_WORD_DATA

SMBus Block Read

在这里插入图片描述

I2C-tools 中 的 函 数 : i2c_smbus_read_block_data() 。 先发出Command Code(它一般表示芯片内部的寄存器地址),再发起度操作:

  • 先读到一个字节(Block Count),表示后续要读的字节数
  • 然后读取全部数据
Functionality flag: I2C_FUNC_SMBUS_READ_BLOCK_DATA

SMBus Block Write

在这里插入图片描述

I2C-tools 中 的 函 数 : i2c_smbus_write_block_data() 。 先发出Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的 Byte Conut(表示后续要发出的数据字节数),最后发出全部数据。

Functionality flag: I2C_FUNC_SMBUS_WRITE_BLOCK_DATA

I2C Block Read

在一般的 I2C 协议中,也可以连续读出多个字节。它跟 SMBus Block Read 的差别在于设备发出的第 1 个数据不是长度 N,如下图所示:

在这里插入图片描述
I2C-tools 中的函数:i2c_smbus_read_i2c_block_data()。先发出Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的 ByteConut(表示后续要发出的数据字节数),最后发出全部数据。

Functionality flag: I2C_FUNC_SMBUS_READ_I2C_BLOCK

I2C Block Write

在一般的 I2C 协议中,也可以连续发出多个字节。它跟 SMBus BlockWrite 的差别在于发出的第 1 个数据不是长度 N,如下图所示

在这里插入图片描述

I2C-tools 中的函数:i2c_smbus_write_i2c_block_data()。先发出Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的 ByteConut(表示后续要发出的数据字节数),最后发出全部数据。

Functionality flag: I2C_FUNC_SMBUS_WRITE_I2C_BLOCK

SMBus Block Write - Block Read Process Call

在这里插入图片描述

先写一块数据,再读一块数据。

Functionality flag: I2C_FUNC_SMBUS_BLOCK_PROC_CALL

Packet Error Checking (PEC)

PEC 是一种错误校验码,如果使用 PEC,那么在 P 信号之前,数据发送方要发送一个字节的 PEC 码(它是 CRC-8 码)。以 SMBus Send Byte 为例,下图中,一个未使用 PEC,另一个使用 PEC:
在这里插入图片描述

I2C 系统的重要结构体

APP 通过 i2c_adapter 与 i2c_client 传输 i2c_msg

i2c_adapter

使用 i2c_adapter 结构体表示一个 I2C BUS,或称为 I2C Controller,里面有2 个重要的成员:

  • nr:第几个 I2C BUS(I2C Controller)
  • i2c_algorithm结构体,里面有该 I2C BUS 的传输函数,用来收发 I2C 数据

i2c_adapter 结构体原型:
在这里插入图片描述

i2c_algorithm 结构体原型:

在这里插入图片描述

i2c_client

使用 i2c_client 结构体来表示一个 I2C Device,里面有2 个重要的成员:

  • addr:表示设备地址
  • adapter:表示挂载在那个I2C控制器

在这里插入图片描述

i2c_msg

在上面的 i2c_algorithm 结构体中可以看到要传输的数据被称为:i2c_msg,里面有2 个重要的成员:

  • flags :用来表示传输方向:bit 0 等于 I2C_M_RD 表示读,bit 0 等于 0 表示写
  • addr:表示从设备的地址
  • len:表示读写的数据长度
  • buf:表示内容

i2c_msg 结构体原型:
在这里插入图片描述

内核传输数据

核心的传输函数 i2c_transfer
在这里插入图片描述

msgs是结构体数组,因为不止有一个msg

例如在读取指定设备的指定地址的数据时,需要发送2个msg,告诉从设备要访问的地址以及要执行读操作

在这里插入图片描述

I2C-Tools调试工具

下载地址:I2Ctools调试工具下载地址

该工具集包含以下命令:i2cdetect、i2cdump、i2cget、i2cset。

i2c-tools的重要意义就是开发人员既不需要编写复杂的Linux驱动、也不需要编写应用程序,只需要输入几个简单的命令就可以调试i2c设备,比如:设置i2c设备寄存器、获得i2c设备寄存器的值

I2C-Tools工具的使用

无需编写驱动程序即可访问 I2C 设备

APP 访问硬件肯定是需要驱动程序的,对于 I2C 设备,内核提供了驱动程序 drivers/i2c/i2c-dev.c,编写应用后通过它可以直接使用下面的 I2C 控制器驱动程序来访问 I2C 设备。框架如下:
在这里插入图片描述
APP 通过 I2C Controller 与 I2C Device 传输数据
在这里插入图片描述

交叉编译 I2C-Tools

确保在Ubuntu设置交叉编译工具链,此时设置了环境变量:${CROSS_COMPILE}

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

vim 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移植到开发板上

用法

i2cdetect:I2C检测

// 列出当前的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 -l
i2c-1   i2c             IMX6ULL I2C(0x40013000)                 I2C adapter
i2c-2   i2c             IMX6ULL I2C(0x5c002000)                 I2C adapter
i2c-0   i2c             IMX6ULL 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

// --表示没有该地址对应的设备, UU表示有该设备并且它已经有驱动程序,
// 数值表示有该设备但是没有对应的设备驱动

# 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: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

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

-f表示:对于UU(已经挂载且有对应驱动)的设备,需要强制写入

i2cdump:查看i2c设备所有寄存器的值(和i2cget相同)

i2cdump -y -f 0 0x57

参数 意义

  • -y 取消交互过程,直接执行命令
  • -f 强制访问该设备
  • 0 表示i2c总线编号
  • 0x57 表示i2c设备地址
  • 从命令执行结果可知i2cdump命令获得的寄存器值和i2cget命令相同。

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控制器?

设备节点与I2C控制器一一对应:

在这里插入图片描述
i2c-dev.c提供为每个I2C控制器(I2C Bus、I2C Adapter)都生成一个设备节点:/dev/i2c-0、/dev/i2c-1等等
open某个/dev/i2c-X节点,就是去访问该I2C控制器下的设备

使用I2C-Tools时怎么指定I2C设备?(需要知道地址值)

通过ioctl指定I2C设备的地址

ioctl(file, I2C_SLAVE, address)
  • 如果该设备已经有了对应的设备驱动程序,则返回失败
ioctl(file, I2C_SLAVE_FORCE, address)
  • 如果该设备已经有了对应的设备驱动程序
  • 但是还是想通过i2c-dev驱动来访问它(绕开驱动程序)
  • 则使用这个ioctl来指定I2C设备地址

使用I2C-Tools时怎么传输数据?

两种方式

  • 一般的I2C方式:ioctl(file, I2C_RDWR, &rdwr)
  • SMBus方式:ioctl(file, I2C_SMBUS, &args)

源码分析

使用I2C方式

示例代码:i2ctransfer.c
在这里插入图片描述

使用 SMBus 方式

示例代码:i2cget.c、i2cset.c
在这里插入图片描述

使用I2C-Tools操作传感器AP3216C

传感器AP3216C介绍

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

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

根据手册可知,AP3216C的设备地址是0x1E(通过i2cdetect -y -a x 检查是否在哪个总线上,地址为0x1e),假设节在I2C BUS0上

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

使用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
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值