大话I2C与smbus 开发

1. I2C & smbus 的区别

在平常的开发中,一直以为I2C & smbus 只是硬件上的差异,软件上基本没有什么差异,后来的开发中发现,其实两者还是有很多的差异。如果非要把所有的差异概括为一句话,那就是:

smbus 是I2C 协议里的一个子集,并不是I2C协议的全部。

理解上面的这句话很重要,以至于我们要写下面的文章来描述它。


2. 一些符号关键字

其实这个来自linux kernel 的说明文档:

符号描述
S (1 bit)Start bit
P (1 bit)Stop bit
Rd/Wr (1 bit)Read/Write bit. Rd equals 1, Wr equals 0.
A, NA (1 bit)Accept and reverse accept bit.
Addr (7 bits)I2C 7 bit address. Note that this can be expanded as usual to get a 10 bit I2C address.
Comm (8 bits)Command byte, a data byte which often selects a register on the device.
Data (8 bits)A plain data byte. Sometimes, I write DataLow, DataHigh for 16 bit data.
Count (8 bits)A data byte containing the length of a block operation.
[…]Data sent by I2C device, as opposed to data sent by the host adapter.

3. I2C 协议

关于I2C 协议,我们从以下几个方面来描述。

3.1 I2C 的简单传输协议

I2C 的简单发送传输协议如下

S Addr Wr [A] Data [A] Data [A] … [A] Data [A] P

简单发送协议的实现,对应于内核中的i2c_master_send().
通过以上可以发现,start bit + slave addr +WR 后,就传输的就是数据,而不像smbus 那样还有command & data length. I2C 协议没有规定这些,所以I2C 协议更通用,更灵活(比smbus 协议).
用户层应用编程中,需要做I2C 简单的发送协议的API如下

 #include <unistd.h>
 ssize_t write(int fd, const void *buf, size_t count);

该函数便是i2c-dev 向用户层提供的通用I2C 简单发送协议的API。因为smbus是I2C 的一个子集,所以smbus 的相关写的API 都可以用write 函数来模拟实现。

I2C 的简单接受传输协议如下

S Addr Rd [A] [Data] A [Data] A … A [Data] NA P

简单接受协议的实现,对应于内核中的i2c_master_recv().
通过以上可以发现,start bit + slave addr +Rd 后,就是接受数据,而不像smbus 那样还有data length. I2C 协议没有规定这些,所以I2C 协议更通用,更灵活(比smbus 协议).
用户层应用编程中,需要做I2C 简单的接收协议的API如下

 #include <unistd.h>
 ssize_t read(int fd, void *buf, size_t count);

该函数便是i2c-dev 向用户层提供的通用I2C 简单接收协议的API。因为smbus是I2C 的一个子集,所以smbus 的相关写的API 都可以用read 函数来模拟实现。

3.2 I2C 的复杂传输协议

说其I2C 的复杂传输协议,就是在一次I2C 传输协议里,可能包含上面的好几个简单I2C 传输(但不完全是简单的I2C传输协议), 只不过,他们只有一个stop bit 【P】。我们用这仅仅一个Stop bit来表示这次传输的结束。在传输中遇到start bit 【S】,表示会继续传输 以该start bit 表示的I2C帧。举个简单的例子:

S Addr Rd [A] [Data] NA S Addr Wr [A] Data [A] P

在这一次复杂的I2C 传输中,包含两个I2C 的简单传输帧,第一个是S Addr Rd [A] [Data] NA,第二个是 S Addr Wr [A] Data [A] P。
通过上面的描述里,相信大家应该理解了什么叫I2C 的复杂传输了,可以看到在复杂传输中,I2C 仍然只有纯数据(除了S Addr Rd/Wr外),没有其概念。
用户层应用编程中,需要做I2C 复杂传输协议的API如下

 #include <sys/ioctl.h>
 int ioctl(int fd, unsigned long request, ...);

该函数便是i2c-dev 向用户层提供的通用I2C 复杂传输协议的API。该函数需要3 个参数,request 必须等于I2C_RDWR, 还有一个参数为args, args 必须是struct i2c_rdwr_ioctl_data 类型。
以上说的这两个参数定义于以下文件:

//linux/i2c-dev.h
#define I2C_RDWR	0x0707	/* Combined R/W transfer (one STOP only) */
struct i2c_rdwr_ioctl_data {
	struct i2c_msg *msgs;	/* pointers to i2c_msgs */
	__u32 nmsgs;			/* number of i2c_msgs */
};

3.3 I2C 块传输

I2c 支持块传输协议,但是I2C 的块传输没有限定传输的数据量,然而smbus 却限定smbus 块传输的最大数据量不能超过32bytes. 这是一个很大的区别。
I2C 读一个块的协议如下:

S Addr Wr [A] Comm [A]
S Addr Rd [A] [Data] A [Data] A … A [Data] NA P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_READ_I2C_BLOCK。
I2C 写一个块的协议如下:

S Addr Wr [A] Comm [A] Data [A] Data [A] … [A] Data [A] P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_WRITE_I2C_BLOCK。
Note that command lengths of 0, 2, or more bytes are
supported as they are indistinguishable from data.
I2C 的块传输协议中,没有数据长度的概念。
用户层应用编程中,可以参考4.7章节来实现,但是由于4.7章节主要是基于smbus 的block,也就是最大数据传输尺寸不能超过32byte,如果超过32bytes,章节4.7描述的方法就不再适用, 此时就可以参考3.1 & 3.2 章节来实现。

3.4 I2C 的克制化传输协议

下面我将linux kernel 中的一些内容直接copy 过来,供大家学习参考:
The following modifications to the I2C protocol can also be generated by
setting these flags for i2c messages. With the exception of I2C_M_NOSTART, they are usually only needed to work around device issues:

I2C_M_IGNORE_NAK:
Normally message is interrupted immediately if there is [NA] from the
client. Setting this flag treats any [NA] as [A], and all of
message is sent.
These messages may still fail to SCL lo->hi timeout.

I2C_M_NO_RD_ACK:
In a read message, master A/NA bit is skipped.

I2C_M_NOSTART:
In a combined transaction, no ‘S Addr Wr/Rd [A]’ is generated at some
point. For example, setting I2C_M_NOSTART on the second partial message
generates something like::

S Addr Rd [A] [Data] NA Data [A] P

If you set the I2C_M_NOSTART variable for the first partial message,
we do not generate Addr, but we do generate the startbit S. This will
probably confuse all other clients on your bus, so don’t try this.

This is often used to gather transmits from multiple data buffers in
system memory into something that appears as a single transfer to the
I2C device but may also be used between direction changes by some
rare devices.

I2C_M_REV_DIR_ADDR:
This toggles the Rd/Wr flag. That is, if you want to do a write, but
need to emit an Rd instead of a Wr, or vice versa, you set this
flag. For example::

  S Addr Rd [A] Data [A] Data [A] ... [A] Data [A] P

I2C_M_STOP:
Force a stop condition § after the message. Some I2C related protocols
like SCCB require that. Normally, you really don’t want to get interrupted
between the messages of one transfer.

以上便是对I2C 标准协议的描述,下面我们将描述smbus 协议。


4. smbus 协议

正如前面提到的,smbus 是I2C协议的一个子集。所以有些厂商提供的就是基于smbus 的adapter,一些是基于I2C的adapter。 基于I2C 的adapter 一定可以模拟出smbus, 但基于smbus 的adapter一定模拟不出来I2C 的adapater,这是个值的关注的问题。
在驱动开发中, 如果在条件允许的情况下(I2C 操作满足smbus 协议的情况下),kernel 开发者推荐我们尽可能使用smbus 的API 去操作,这是因为,基于smbus 的API 会自动地把I2C 的传输交给I2C adapter,而I2C 的API 只能交给I2C 的adapter(这个应该很好理解)。
smbus 协议在I2C 的基础上,对DATA 位于的前两位数据做了指定,规定了command & data length 的规格,我们将在下文详细介绍。下面我们来看smbus 规定的一些命令协议。

4.1 smbus 快读命令

它只发送了一个读写bit 给设备,一般用来探测smbus设备是否存在

A Addr Rd/Wr [A] P

注意这是smbus 的命令,I2C 协议里时没有这样的规范的。在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_QUICK。该命令是smbus 独有的,I2C 没有这样的规范。对应的API 我们后面会讲。

4.2 smbus 接受/发送 byte 命令

smbus 接受/发送一个byte 的命令,其接收一个byte 的协议如下

S Addr Rd [A] [Data] NA P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_READ_BYTE。
发送一个 byte 的协议如下:

S Addr Wr [A] Data [A] P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_WRITE_BYTE。
可以发现,我们使用I2C 的read/write API 完全可以模拟它。

4.3 smbus 读/写 byte数据 命令

和4.2 章节不一样,该章节是smbus 读写一个byte 数据,其读一个byte 数据的协议如下:

S Addr Wr [A] Comm [A] S Addr Rd [A] [Data] NA P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_READ_BYTE_DATA。
其写一个byte 数据的协议如下:

S Addr Wr [A] Comm [A] Data [A] P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_WRITE_BYTE_DATA。
以上两个命令中,smbus 协议规定Comm 关键字,这是I2C 中没有的。同时发现读一个byte 的命令是一个I2C 的复杂传输协议。
可以发现,我们使用I2C 的read/write API 完全可以模拟它。

4.4 smbus 读/写 word 数据 命令

smbus 读写2 个byte (1个word)的命令协议,其读一个word数据的协议如下:

S Addr Wr [A] Comm [A] S Addr Rd [A] [DataLow] A [DataHigh] NA P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_READ_WORD_DATA。
其写一个word数据的协议如下:

S Addr Wr [A] Comm [A] DataLow [A] DataHigh [A] P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_WRITE_WORD_DATA。
以上两个命令中,smbus 协议规定Comm 关键字,这是I2C 中没有的。同时发现读一个word 的命令是一个I2C 的复杂传输协议。
可以发现,我们使用I2C 的read/write API 完全可以模拟它。

4.5 smbus Process Call

这个命令是smbus 通过Comm 字段选择一个device 寄存器,写入16 bit 数据,并读回来16 bit 数据的过程。其协议规格如下:

S Addr Wr [A] Comm [A] DataLow [A] DataHigh [A]
S Addr Rd [A] [DataLow] A [DataHigh] NA P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_PROC_CALL。
该命令是一个I2C 的复杂传输协议。该命令是smbus 独有的,I2C 没有这样的规范。

4.5 smbus 块传输命令

这两个命令是指 smbus 通过Comm 字段选择一个device 寄存器,读取/写入一个块大小 的数据,这个块的数据的数量,包含在count 字段。 注意这个数据块最大长度不能超过32 byte,这是smbus 没有的规定,不是I2C 协议的规定(也就是I2C 不会限制block 数据的大小)。
smbus 读一个块的协议如下:

S Addr Wr [A] Comm [A]
S Addr Rd [A] [Count] A [Data] A [Data] A … A [Data] NA P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_READ_BLOCK_DATA。
该命令是一个I2C 的复杂传输协议。
smbus 写一个块的协议如下:

S Addr Wr [A] Comm [A] Count [A] Data [A] Data [A] … [A] Data [A] P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_WRITE_BLOCK_DATA。
可以看见smbus 中,规定了Comm & count,而I2C 是没有这些规范的。I2C 的block read/write 请查阅3.3 章节。

4.6 smbus block Process Call

这是smbus 2.0 的规格,和章节4.4 类似,只不过4.4 中传输2 个字节,而该章节传输一个块。但这个块最大支持的数据是31 bytes。
这个命令的协议描述如下:

S Addr Wr [A] Comm [A] Count [A] Data [A] …
S Addr Rd [A] [Count] A [Data] … A P

在API 开发中,它的功能flag 为I2C_FUNC_SMBUS_BLOCK_PROC_CALL。
该命令是smbus 独有的,I2C 没有这样的规范。

4.7 smbus 协议的用户层 应用开发

章节3.3, 和章节4.1~4.6 中描述的通讯协议均可以通过下面描述的API 来进行编程。

 #include <sys/ioctl.h>
 int ioctl(int fd, unsigned long request, ...);

该api 需要三个参数,requet 等于以上章节描述的flag, args 的数据类型必须为struct i2c_smbus_ioctl_data 。
以上说的这两个参数定义于以下文件:

//linux/i2c-dev.h
#define I2C_RDWR	0x0707	/* Combined R/W transfer (one STOP only) */
struct i2c_smbus_ioctl_data {
	__u8 read_write;
	__u8 command;
	__u32 size;
	union i2c_smbus_data *data;
};

//linux/i2c.h
#define I2C_FUNC_I2C			0x00000001
#define I2C_FUNC_10BIT_ADDR		0x00000002
#define I2C_FUNC_PROTOCOL_MANGLING	0x00000004 /* I2C_M_IGNORE_NAK etc. */
#define I2C_FUNC_SMBUS_PEC		0x00000008
#define I2C_FUNC_NOSTART		0x00000010 /* I2C_M_NOSTART */
#define I2C_FUNC_SLAVE			0x00000020
#define I2C_FUNC_SMBUS_BLOCK_PROC_CALL	0x00008000 /* SMBus 2.0 */
#define I2C_FUNC_SMBUS_QUICK		0x00010000
#define I2C_FUNC_SMBUS_READ_BYTE	0x00020000
#define I2C_FUNC_SMBUS_WRITE_BYTE	0x00040000
#define I2C_FUNC_SMBUS_READ_BYTE_DATA	0x00080000
#define I2C_FUNC_SMBUS_WRITE_BYTE_DATA	0x00100000
#define I2C_FUNC_SMBUS_READ_WORD_DATA	0x00200000
#define I2C_FUNC_SMBUS_WRITE_WORD_DATA	0x00400000
#define I2C_FUNC_SMBUS_PROC_CALL	0x00800000
#define I2C_FUNC_SMBUS_READ_BLOCK_DATA	0x01000000
#define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000
#define I2C_FUNC_SMBUS_READ_I2C_BLOCK	0x04000000 /* I2C-like block xfer  */
#define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK	0x08000000 /* w/ 1-byte reg. addr. */
#define I2C_FUNC_SMBUS_HOST_NOTIFY	0x10000000

#define I2C_FUNC_SMBUS_BYTE		(I2C_FUNC_SMBUS_READ_BYTE | \
					 I2C_FUNC_SMBUS_WRITE_BYTE)
#define I2C_FUNC_SMBUS_BYTE_DATA	(I2C_FUNC_SMBUS_READ_BYTE_DATA | \
					 I2C_FUNC_SMBUS_WRITE_BYTE_DATA)
#define I2C_FUNC_SMBUS_WORD_DATA	(I2C_FUNC_SMBUS_READ_WORD_DATA | \
					 I2C_FUNC_SMBUS_WRITE_WORD_DATA)
#define I2C_FUNC_SMBUS_BLOCK_DATA	(I2C_FUNC_SMBUS_READ_BLOCK_DATA | \
					 I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)
#define I2C_FUNC_SMBUS_I2C_BLOCK	(I2C_FUNC_SMBUS_READ_I2C_BLOCK | \
					 I2C_FUNC_SMBUS_WRITE_I2C_BLOCK)

#define I2C_FUNC_SMBUS_EMUL		(I2C_FUNC_SMBUS_QUICK | \
					 I2C_FUNC_SMBUS_BYTE | \
					 I2C_FUNC_SMBUS_BYTE_DATA | \
					 I2C_FUNC_SMBUS_WORD_DATA | \
					 I2C_FUNC_SMBUS_PROC_CALL | \
					 I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | \
					 I2C_FUNC_SMBUS_I2C_BLOCK | \
					 I2C_FUNC_SMBUS_PEC)
#define I2C_SMBUS_BLOCK_MAX	32	/* As specified in SMBus standard */
union i2c_smbus_data {
	__u8 byte;
	__u16 word;
	__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
			       /* and one more for user-space compatibility */
};

关于smbus 的其他功能,如告警,ARP,PEC 等,留待后续博客研究,这里不在赘述。至此smbus 的最基本的协议规范我们就讲完了。


5. I2C & smbus 功能判断

因为smbus 只是I2C 协议的子集,有些厂商的adapter 支持I2C, 有些只支持smbus, 在应用/驱动开发时,我们就需要搞清楚,这个adapter 到底支持那些功能。为此我们需要了解那些宏是代表I2C,那些代表smbus。
一般地,一个典型的I2C adapter实现如下:

static u32 i2c_pxa_functionality(struct i2c_adapter *adap)
  {
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
  }

一个典型的只支持smbus 的adapter 在驱动中实现如下:

static u32 piix4_func(struct i2c_adapter *adapter)
  {
	return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
	       I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
	       I2C_FUNC_SMBUS_BLOCK_DATA;
  }

由以上可得,I2C adapter 一般都支持smbus 的所有功能(I2C_FUNC_SMBUS_EMUL 代表smbus 的所有功能,请参考章节4.7), 而smbus 不支持I2C 功能。

我们给出一个用户层 判断adapter 功能的例子:

  int file;
  unsigned long funcs;

  if (file = open("/dev/i2c-0", O_RDWR) < 0) {
	/* Some kind of error handling */
	exit(1);
  }
  if (ioctl(file, I2C_FUNCS, &funcs) < 0) {
	/* Some kind of error handling */
	exit(1);
  }
  if (!(funcs & I2C_FUNC_SMBUS_QUICK)) {
	/* Oops, the needed functionality (SMBus write_quick function) is
           not available! */
	exit(1);
  }
  /* Now it is safe to use the SMBus write_quick command */

至此,我们描述完了软件上smbus 是I2C 协议的一个子集的论述,献给正在为此迷茫的你!

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值