I2C-SMBus协议和I2C Tool

1 SMBus协议#

1.1 SMBus 是 I2C 协议的一个子集#

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

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

img

1.2 和I2C协议特性对比#

SMBus 有哪些更严格的要求?跟一般的 I2C 协议有哪些差别?

  1. VDD 的极限值不一样
      1.1 I2C 协议:范围很广,甚至讨论了高达 12V 的情况
                    1.2 SMBus:1.8V~5V
  2. 速率更高
      I2C 协议:时钟频率最小值无限制,Clock Stretching 时长也没有限制
                    SMBus:时钟频率最小值是 10KHz,Clock Stretching 的最大时间值也有限制
  3. 地址回应(Address Acknowledge):一个 I2C 设备接收到它的设备地址后, 是否必须发出回应信号?
      I2C 协议:没有强制要求必须发出回应信号
                    SMBus:强制要求必须发出回应信号,这样对方才知道该设备的状态: busy,failed,或是被移除了
  4. SMBus 协议明确了数据的传输格式
      I2C 协议:它只定义了怎么传输数据,但是并没有定义数据的格式,这完全由设备来定义
                    SMBus:定义了几种数据格式
  5. REPEATED START Condition(重复发出 S 信号)
      比如读 EEPROM 时,涉及 2 个操作:
                  ​ 把存储地址发给设备
                  ​ 读数据
                  在写、读之间,可以不发出 P 信号,而是直接发出 S 信号:这个 S 信号就是
                  REPEATED START

img

1.3 SMBus数据格式#

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

1
2
3
4
5
6
7
8
9
10
11
symbols(符号):
S (1 bit) : Start bit(开始位)
 Sr (1 bit) : 重复的开始位
 P (1 bit) : Stop bit(停止位)
 R/W# (1 bit) : Read/Write bit. Rd equals 1, Wr equals 0.(读写位)
 A, N (1 bit) : Accept and reverse accept bit.(回应位)
 Address(7 bits): I2C 7 bit address. Note that this can be expanded as usual to get a 10 bit I2C address.(地址位,7 位地址)
 Command Code (8 bits): Command byte, a data byte which often selects a register on the device.(命令字节,一般用来选择芯片内部的寄存器)
Data Byte (8 bits): A plain data byte. Sometimes, I write DataLow, DataHigh for 16 bit data.(数据字节,8 位;如果是 16 位数据的话,用 2 个字节来表示:DataLow、DataHigh)
 Count (8 bits): A data byte containing the length of a block operation.(在 block 操作总,表示数据长度)
 [..]: Data sent by I2C device, as opposed to data sent by the host adapter.(中括号表示 I2C 设备发送的数据,没有中括号表示 host adapter 发送的数据)

1.3.1 SMBus Quick Command#

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

img

Functionality flag: I2C_FUNC_SMBUS_QUICK

1.3.2 SMBus Receive Byte#

img

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

Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE

1.3.3 SMBus Send Byte#

img

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

Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE

1.3.4 SMBus Read Byte#

img

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

Functionality flag: I2C_FUNC_SMBUS_READ_BYTE_DATA

1.3.5 SMBus Read Word#

img

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

Functionality flag: I2C_FUNC_SMBUS_READ_WORD_DATA

1.3.6 SMBus Write Byte#

img

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

Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE_DATA

1.3.7 SMBus Write Word#

img

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

Functionality flag: I2C_FUNC_SMBUS_WRITE_WORD_DATA

1.3.8 SMBus Block Read#

img

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

  1. 先读到一个字节(Block Count),表示后续要读的字节数
  1. 然后读取全部数据

Functionality flag: I2C_FUNC_SMBUS_READ_BLOCK_DATA

1.3.8 SMBus Block Write#

img

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

Functionality flag: I2C_FUNC_SMBUS_WRITE_BLOCK_DATA

1.3.9 SMBus Block Write - Block Read Process Call#

img

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

Functionality flag: I2C_FUNC_SMBUS_BLOCK_PROC_CALL

1.3.10 Packet Error Checking (PEC)#

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

img

1.4 I2C数据格式#

1.4.1 I2C Block Read#

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

img

I2C-tools 中的函数:`i2c_smbus_read_i2c_block_data()。先发出 Command Code(它一般表示芯片内部的寄存器地址),直接读出全部数据。

Functionality flag: I2C_FUNC_SMBUS_READ_I2C_BLOCK

1.4.2 I2C Block Write#

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

img

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

Functionality flag: I2C_FUNC_SMBUS_WRITE_I2C_BLOCK

2 I2C Tools#

2.1 I2C控制器框架#

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

img

2.2 i2c数据结构#

2.2.1 i2c_adapter#

img

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

  1. nr:第几个 I2C BUS(I2C Controller)
  2. i2c_algorithm,里面有该 I2C BUS 的传输函数,用来收发 I2C 数据

怎么表示 I2C Controller , 一个芯片里可能有多个 I2C Controller,比如第 0 个、第 1 个、……

2.2.3 i2c_algorithm#

img

2.3.4 I2c_device/I2c_client#

一个 I2C Device,一定有设备地址, 那它连接在哪个 I2C Controller 上,即对应的 i2c_adapter 是什么。

使用 i2c_client 来表示一个 I2C Device。

img

2.3.5 i2c_msg#

在上面的i2c_algorithm结构体中可以看到要传输的数据被称为:i2c_msg

img

flags: 用来表示传输方向:bit 0 等于 I2C_M_RD 表示 读,bit 0 等于 0 表示写。一个 i2c_msg 要么是读,要么是写
举例:设备地址为 0x50 的 EEPROM,要读取它里面存储地址为 0x10 的一个字节, 应该构造几个 i2c_msg?
要构造 2 个 i2c_msg :

  1. 第一个 i2c_msg 表示写操作,把要访问的存储地址 0x10 发给设备

  2. 第二个 i2c_msg 表示读操作,并且返回读出的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
  //例如:
  u8 data_addr = 0x10;
  i8 data;
  struct i2c_msg msgs[2];
  
  msgs[0].addr = 0x50;
  msgs[0].flags = 0;
  msgs[0].len = 1;
  msgs[0].buf = &data_addr;
  msgs[1].addr = 0x50;
  msgs[1].flags = I2C_M_RD;
  msgs[1].len = 1;
  msgs[1].buf = &data;

2.3 I2C-Tools移植#

2.3.1 源码下载#

https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/

2.3.2 工具链配置#

1
2
3
export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueab ihf_sdk-buildroot/bin

2.3.3 编译#

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

1
2
3
CC ?= gcc
AR ?= ar
STRIP ?= strip

改为(指定交叉编译工具链前缀, 去掉问号):

1
2
3
CC = $(CROSS_COMPILE)gcc
AR = $(CROSS_COMPILE)ar
STRIP = $(CROSS_COMPILE)strip

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

执行 make 时,是动态链接,需要把 libi2c.so 也放到单板上。 想静态链接的话,执行:make USE_STATIC_LIB=1

img

img

img

2.4 I2C Tools使用#

2.4.1 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

img

img

2.4.2 i2cget: I2C 读(SMBus协议)#

img

img

使用示例:

img

img

2.4.3 i2cset: I2C 写(SMBus协议)#

img

使用示例:

img

img

2.4.4 i2ctransfer:I2C传输(I2C协议)#

img

使用示例:

img

img

从i2c总线0,往0x1e设备地址,写2个字节,第一个字节写入寄存器地址0,第二个字节表示往寄存器0地址写入0x4。
从i2c总线0,往0x1e设备地址,写2个字节,第一个字节写入寄存器地址0,第二个字节表示往寄存器0地址写入0x3。
从i2c总线0,往0x1e设备地址,写1个字节,写入寄存器地址0xc表示要读0xc里面的值,再从0xc读2个字节。

2.5 I2C-Tools 访问 I2C 设备的方式#

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

1
2
Open("dev/i2c-0");
ioctl(file, I2C_SLAVE, address);

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

1
ioctl(file, I2C_SLAVE_FORCE, address);

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

2.5.1 数据传输方式#

2.5.1.1 使用I2C方式#

ioctl(file, I2C_RDWR, &rdwr);(使用I2C方式)

img

该结构体表示一个或者多个i2c_msg。

示例代码:i2ctransfer.c

i2ctransfer -f -y 0 w1@0x1e 0xe r2为例:

流程如下:

img

构造i2c_rdwr_ioctl_data结构,要用2个i2c_msg结构,第一个表示写入0xe寄存器地址给设备,第二个表示要从0xe寄存器读出2个字节。

2.5.1.2 使用SMBus方式#

ioctl(file, I2C_SMBUS, &args) ;(使用SMBus方式)

示例代码:i2cget.c i2cset.c

img

2.5.1.3 直接使用read()/write()方式#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
int gc2053_read_register(VI_PIPE ViPipe, int addr) {
int ret, data;
CVI_U8 buf[8];
CVI_U8 idx = 0;

if (g_fd[ViPipe] < 0)
return CVI_FAILURE;

if (gc2053_addr_byte == 2)
buf[idx++] = (addr >> 8) & 0xff;

// add address byte 0
buf[idx++] = addr & 0xff;

ret = write(g_fd[ViPipe], buf, gc2053_addr_byte);
if (ret < 0) {
CVI_TRACE_SNS(CVI_DBG_ERR, "I2C_WRITE error!\n");
return ret;
}

buf[0] = 0;
buf[1] = 0;
ret = read(g_fd[ViPipe], buf, gc2053_data_byte);
if (ret < 0) {
CVI_TRACE_SNS(CVI_DBG_ERR, "I2C_READ error!\n");
return ret;
}

// pack read back data
data = 0;
if (gc2053_data_byte == 2) {
data = buf[0] << 8;
data += buf[1];
} else {
data = buf[0];
}

syslog(LOG_DEBUG, "i2c r 0x%x = 0x%x\n", addr, data);
return data;
}

int gc2053_write_register(VI_PIPE ViPipe, int addr, int data) {
CVI_U8 idx = 0;
int ret;
CVI_U8 buf[8];

if (g_fd[ViPipe] < 0)
return CVI_SUCCESS;

if (gc2053_addr_byte == 1) {
buf[idx] = addr & 0xff;
idx++;
}

if (gc2053_data_byte == 1) {
buf[idx] = data & 0xff;
idx++;
}

ret = write(g_fd[ViPipe], buf, gc2053_addr_byte + gc2053_data_byte);
if (ret < 0) {
CVI_TRACE_SNS(CVI_DBG_ERR, "I2C_WRITE error!\n");
return CVI_FAILURE;
}

ret = read(g_fd[ViPipe], buf, gc2053_addr_byte + gc2053_data_byte);
//syslog(LOG_DEBUG, "i2c w 0x%x 0x%x\n", addr, data);

return CVI_SUCCESS;
}