I2C | i2c_msg


背景介绍:
这两天在解决客户的一个i2c传输问题时发现对i2c的理解有很多盲点,冒出一身冷汗- -!,问题解决后赶紧复盘总结一下。

带着问题去学习是最快的方法
思考题

  1. 为什么常见的i2c外设驱动中 i2c read 函数要构造 2个 i2c_msg,而 i2c write 函数只要1个i2c_msg?
  2. 常见i2c外设寄存器地址是8bits的,那么遇到16bits或者32bits的外设寄存器地址该如何构造i2c_msg呢?

如果以上问题你已经熟知,那么大佬,接下来的内容可以忽略不看了,如果有疑问,不妨在小店驻留几分钟。


一. 对 i2c_msg 的理解我认为应该分为两个主要层面:

  • i2c_msg的设计角度
    一个i2c_msg 代表着Slave(i2c client)和Host(i2c controller)之间的一次单向数据传输
  • 支持i2c传输协议的外设驱动程序使用 i2c_msg 的角度
    这里又分为四点
    • i2c 传输是以字节为单位的,具体到i2c_msg.len 指的是以字节指针i2c_msg.buf指向的buffer中字节个数。
    • 外设驱动要实现 xxx_i2c_read_bytes 和 xxx_i2c_write_bytes 两个底层函数;
    • xxx_i2c_read_bytes 由2个i2c_msg组成的数组构成
      因为这存在host send和host receive两个数据传输方向;第一个msg.buf用来暂存host向slave发出slave目标寄存器地址,msg.len表示寄存器地址字节长度;第二个msg.buf用来接收slave向host返回数据,msg.len表示期望读到数据的字节长度;
    • xxx_i2c_write_bytes 仅由1个i2c_msg构成
      因为只有host send to slave这一个数据传输方向;整个msg.buf暂存了从机目标寄存器地址和待写入其中的数据,msg.len表示整体的字节长度;
      (注:数组的目的是为了访问连续,因为数组是连续内存存储的)

注:常见的Slave有Touchscreen,Sensor;i2c controller实际上就是SOC ARM上的一组i2c registers。

二. 常见的Slave 驱动程序对 i2c read和write函数实现方式如下:

  1. xxx_i2c_read_bytes
    (host 发出 slave 地址来建立通信)(host驱动中实现)
    host 向 slave 发出 slave 目标寄存器地址;(salve驱动中实现)
    slave 向 host 返回 目标寄存器数据;(salve驱动中实现)
static int xxx_i2c_read_bytes(u8 index, u8 *rx_buff, u8 length)
{
	int ret = -1;
	struct i2c_msg msgs[] = {	/* 注:i2c read因为包含两个方向,所以只需要两个i2c_msg */
		{//第一个i2c_msg用来发送需要读取的从设备目标寄存器的地址
			.addr = xxx_client->addr, /* chip address, 7bit */
			.flags = 0,				  /* write */
			.len = lenght1,  		  /* bytes number(注意这里一定是byte为单位),这也是从设备目标寄存器地址的字节长度*/
			.buf = &index,  		  /* client register address pointer */
		},
		{//第二个i2c_msg用来设置接收从设备目标寄存器返回数据的buff以及数据长度
			.addr = xxx_client->addr,
			.flags = I2C_M_RD,		  /* read */
			.len = length2,  		  /* bytes number(注意这里一定是byte为单位)*/
			.buf = rx_buff,			  /* 用来接收数据的缓冲区指针*/
		},
	};

	ret = i2c_transfer(this_client->adapter, msgs, 2); /* i2c host master_xfer callback func */
	if (ret != 2)
		PRINT_ERR("READ ERROR!ret=%d\n", ret);
	return ret;
}

  1. xxx_i2c_write_bytes
    (host 发出 slave 地址来建立通信)(host 驱动中实现)
    host 向 slave 发出 slave 目标寄存器地址以及需要往目标寄存器中写入的数据;(salve驱动中实现)
static int xxx_i2c_write_bytes(u8 *tx_buff, u8 length)
{
	int ret = -1;
	struct i2c_msg msgs[1];  /*注:i2c write因为是单方向,所以只需要一个i2c_msg*/

	msgs[0].addr = xxx_client->addr;
	msgs[0].flags = 0;		/* write */
	msgs[0].len = length; 	/* bytes number(注意这里一定是byte为单位)*/
	msgs[0].buf = tx_buff;  /* 待写出的数据指针;数据通常为 client register address + data*/

	ret = i2c_transfer(xxx_client->adapter, msgs, 1); /* i2c host master_xfer callback func */
	if (ret != 1)
		PRINT_ERR("WRITE ERROR!ret=%d\n", ret);
	return ret;
}

总结:
好了,回答前面提出的两个问题:

第一题:一个i2c_msg代表着一次单项数据传输。因为i2c read包含host向slave发送寄存器地址和slave返回数据两个数据传输方向,所以需要两个i2c_msg;而 i2c write时host向slave发送寄存器地址和host往slave寄存器写数据属于同一个数据传输方向,所以只需要一个i2c_msg。

第二题:举例slave寄存器地址为16bits的解决方案,32bits的类似。当slave寄存器地址是16bits时,slave驱动构造的i2c read函数中的第一个i2c_msg对应的len成员表示寄存器地址字节长度,buf成员传入的是寄存器其地址指针;i2c write函数中方法同i2c read,只不过buf后面还跟着待写入寄存器的数据。

发布了60 篇原创文章 · 获赞 27 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/xiaosaerjt/article/details/99684238
I2C