1 SMBus协议#
1.1 SMBus 是 I2C 协议的一个子集#
SMBus: System Management Bus,系统管理总线。SMBus 最初的目的是为智能电池、充电电池、其他微控制器之间的通信链路而定义的。SMBus 也被用来连接各种设备,包括电源相关设备,系统传感器,EEPROM 通讯设备等等。SMBus 为系统和电源管理这样的任务提供了一条控制总线,使用 SMBus 的系 统,设备之间发送和接收消息都是通过 SMBus,而不是使用单独的控制线,这样可以节省设备的管脚数。
SMBus 是基于 I2C 协议的,SMBus 要求更严格,SMBus 是 I2C 协议的子集。
1.2 和I2C协议特性对比#
SMBus 有哪些更严格的要求?跟一般的 I2C 协议有哪些差别?
- VDD 的极限值不一样
1.1 I2C 协议:范围很广,甚至讨论了高达 12V 的情况
1.2 SMBus:1.8V~5V - 速率更高
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
1.3 SMBus数据格式#
下面文档中的 Functionality flag 是 Linux 的某个 I2C 控制器驱动所支持的功能。比如 Functionality flag: I2C_FUNC_SMBUS_QUICK,表示需要I2C 控制器支持 SMBus Quick Command。
1 | symbols(符号): |
1.3.1 SMBus Quick Command#
只是用来发送一位数据:R/W#本意是用来表示读或写,但是在 SMBus 里可以用来表示其他含义。比如某些开关设备,可以根据这一位来决定是打开还是关闭.
Functionality flag: I2C_FUNC_SMBUS_QUICK
1.3.2 SMBus Receive Byte#
I2C-tools 中的函数:i2c_smbus_read_byte()
。读取一个字节,Host adapter 接收到一个字节后不需要发出回应信号(上图中 N 表示不回应)。
Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE
1.3.3 SMBus Send Byte#
I2C-tools 中的函数:i2c_smbus_write_byte()
。发送一个字节。
Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE
1.3.4 SMBus Read Byte#
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#
I2C-tools 中的函数:i2c_smbus_read_word_data()
。先发出 Command Code(它一般表示芯片内部的寄存器地址),再读取 2 个字节的数据。
Functionality flag: I2C_FUNC_SMBUS_READ_WORD_DATA
1.3.6 SMBus Write Byte#
I2C-tools 中的函数:i2c_smbus_write_byte_data()
。先发出 Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的数据。
Functionality flag: I2C_FUNC_SMBUS_WRITE_BYTE_DATA
1.3.7 SMBus Write Word#
I2C-tools 中的函数:i2c_smbus_write_word_data()
。先发出 Command Code(它一般表示芯片内部的寄存器地址),再发出 1 个字节的数据。
Functionality flag: I2C_FUNC_SMBUS_WRITE_WORD_DATA
1.3.8 SMBus Block Read#
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#
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#
先写一块数据,再读一块数据。
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:(一般很少使用)
1.4 I2C数据格式#
1.4.1 I2C Block Read#
在一般的 I2C 协议中,也可以连续读出多个字节。它跟 SMBus Block Read 的差别在于设备发出的第 1 个数据不是长度 N,如下图所示:
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,如下图所示:
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 设备。
2.2 i2c数据结构#
2.2.1 i2c_adapter#
i2c_adapter 表示一个 I2C BUS,或称为 I2C Controller,里面有 2 个重要的成员:
- nr:第几个 I2C BUS(I2C Controller)
- i2c_algorithm,里面有该 I2C BUS 的传输函数,用来收发 I2C 数据
怎么表示 I2C Controller , 一个芯片里可能有多个 I2C Controller,比如第 0 个、第 1 个、……
2.2.3 i2c_algorithm#
2.3.4 I2c_device/I2c_client#
一个 I2C Device,一定有设备地址, 那它连接在哪个 I2C Controller 上,即对应的 i2c_adapter 是什么。
使用 i2c_client 来表示一个 I2C Device。
2.3.5 i2c_msg#
在上面的i2c_algorithm结构体中可以看到要传输的数据被称为:i2c_msg
flags: 用来表示传输方向:bit 0 等于 I2C_M_RD 表示 读,bit 0 等于 0 表示写。一个 i2c_msg 要么是读,要么是写
举例:设备地址为 0x50 的 EEPROM,要读取它里面存储地址为 0x10 的一个字节, 应该构造几个 i2c_msg?
要构造 2 个 i2c_msg :
第一个 i2c_msg 表示写操作,把要访问的存储地址 0x10 发给设备
第二个 i2c_msg 表示读操作,并且返回读出的数据
1 | //例如: |
2.3 I2C-Tools移植#
2.3.1 源码下载#
https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/
2.3.2 工具链配置#
1 | export ARCH=arm |
2.3.3 编译#
修改 I2C-Tools 的 Makefile 指定交叉编译工具链
1 | CC ?= gcc |
改为(指定交叉编译工具链前缀, 去掉问号):
1 | CC = $(CROSS_COMPILE)gcc |
在 Makefile 中,“?=”在第一次设置变量时才会起效果,如果之前设置过该变量,则不会起效果。
执行 make 时,是动态链接,需要把 libi2c.so 也放到单板上。 想静态链接的话,执行:make USE_STATIC_LIB=1
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
2.4.2 i2cget: I2C 读(SMBus协议)#
使用示例:
2.4.3 i2cset: I2C 写(SMBus协议)#
使用示例:
2.4.4 i2ctransfer:I2C传输(I2C协议)#
使用示例:
从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 | Open("dev/i2c-0"); |
如果该设备已经有了对应的设备驱动程序,则返回失败。
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方式)
该结构体表示一个或者多个i2c_msg。
示例代码:i2ctransfer.c
以i2ctransfer -f -y 0 w1@0x1e 0xe r2
为例:
流程如下:
构造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
2.5.1.3 直接使用read()/write()方式#
1 | int gc2053_read_register(VI_PIPE ViPipe, int addr) { |