i2c_msg浅析
在学习i2c设备驱动的时候,不经意间发现一个关于结构体i2c_msg的问题,查阅了两天的资料,发现网上基本说的都差不多,当时不理解,以为别人说的不对,理解之后发现都是对的,只是当时不懂。为了防止有小伙伴和我一样钻牛角尖,白白耽误时间,就大概说一下,当然了,我也是在学习的过程中,难免会有些地方说的不对。
i2c的读写时序讲解
在讲解之前,先大概介绍以下i2c的读写时序问题,当然了,都学到i2c了,时序肯定也懂了,我也就不细讲了,主要是需要配合时序图进行后面i2c_msg的讲解。
i2c的读操作
i2c的读时序如图所示,通过时序图可以发现,i2c进行读操作时可以概括为“两大步”:写和读。概括来说也就是先告诉指定设备我要读取的寄存器时哪个,再从这个寄存器中读取数据。希望读者可以尽量理解划分为两步,这对于编写Linux中的i2c设备驱动读操作时很有帮助。
i2c的写操作
i2c的写时序如图所示,通过时序图可以发现,i2c在进行写操作的时候只有一个方向:那就是直接向指定的寄存器中写入数据,也就是只有一个方向,可以将其理解为只有“一步”写操作。
i2c_msg结构体
i2c_msg结构体的成员变量如下所示,大概浏览即可,对于其中的某些定义不清楚的可以百度查阅。关键的地方在于,一个i2c_msg结构的变量,代表着一次单方向的传输,这一点很重要,也是我一开始迷糊的地方。
struct i2c_msg {
__u16 addr; /* 从机地址 */
__u16 flags; /* 标志位,指定进行的操作 */
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length(单位为字节,需要注意) */
__u8 *buf; /* pointer to msg data */
};
i2c设备驱动读函数编写
通过上面对于读时序的描述,将i2c的读操作分为“两步”,即写方向和读方向,因此可以有两个i2c_msg结构的变量,分别通过对结构体成员变量flag的赋值来确定传输方向,函数编写如下:
/* 从ap3216c读取多个寄存器数据
* 入口参数@client:从机地址
* @reg :寄存器地址
* @buffer:保存读取数据
* @length:reg/buffer的长度
*/
static int ap3216c_read_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
{
int err = 0;
/* msg[0]是发送要读取的寄存器首地址 */
struct i2c_msg msg[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1, //表示寄存器地址字节长度,是以byte为单位
.buf = ®,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = length, //表示期望读到数据的字节长度(寄存器长度),是以byte为单位
.buf = buffer, //将读取到的数据保存在buffer中
},
};
err = i2c_transfer(client->adapter, msg,2);
if(err != 2)C
{
err = -EINVAL;
printk("read regs from ap3216c has been failed\n\r");
}
return err;
}
函数解析:由于读操作分为了写和读两个方向,因此可以采用两个i2c_msg结构体变量,采用结构体数组无疑是一个很好的选择。读函数比较好理解,本身没有存在比较疑惑的地方,只需要注意length是以字节为单位即可。
i2c设备驱动写函数编写
在具体讲解之前,先看看下面的写函数,看是否可以发现其中的问题。
/* 向ap3216c写多个寄存器数据
* 入口参数@client:从机地址
* @reg :寄存器地址
* @buffer:需要写入的数据
* @length:reg/buffer的长度
*/
static int ap3216c_write_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
{
int err = 0;
struct i2c_msg msg[] = {
{
.addr = client->addr,
.flags = 0,
.len = 1,
.buf = ®,
},
{
.addr = client->addr,
.flags = 0,
.len = length,
.buf = buffer,
},
};
err = i2c_transfer(client->adapter, msg,2);
if(err != 2)
{
err = -EINVAL;
printk("write data to ap3216c has been failed\n\r");
}
return err;
}
上述函数也是采用了i2c_msg结构体数组进行数据的传输,i2c_msg[0].flag = 0,表示是写,指定了需要写入的寄存器为reg,i2c_msg[1].flag=0,表示是写,需要写入的数据为buffer,根据写时序图来看,也没有什么问题,先是地址,再是数据。按下如下方式编写程序,编译并加载至内核中运行,发现读取出来的数据并不是0x03,而是0x0。程序哪里错了呢?
/* open函数 */
static int ap3216c_open(struct inode *inode, struct file *filp)
{
u8 data;
struct i2c_client *client = (struct i2c_client *)ap3216cdev.private;
filp->private_data = &ap3216cdev;
/* 初始化ap3216c */
ap3216c_write_reg(client,AP3216C_SYSTEMCONFIG, 0x04); //复位
mdelay(50);
ap3216c_write_reg(client,AP3216C_SYSTEMCONFIG, 0x03);//打开三个功能
ap3216c_read_reg(client,AP3216C_SYSTEMCONFIG,&data);
printk("the AP3216c SYSTEMCONFIG = %#x\n\r",data);
return 0;
}
经过排查,read函数并没有问题,那就是write函数出问题了,但是根据时序图来看没有问题呀,先是写地址,再是写数据,难道是时序图错了?
如果读者没有发现错误的话,表示读者还是没有正确理解一个i2c_msg是一次完整的单向传输以及i2c写时序可以理解为“一步”的写操作。
错误点分析
首先,i2c_msg是一次完整的单向数据传输,也就是每一个完整的i2c_msg传输过程中,当flag为0时,那么就意味着在进行写寄存器操作,并不是说有两个i2c_msg结构体连续且都为写的情况下,第二个i2c_msg结构体变量就是向第一个i2c_msg结构体变量中的reg进行写数据了,i2c_msg成员变量buf远远没有达到这么智能的程度,它只能知道在一个单次且完整的写过程中,第一个传递给buf的是寄存器地址。因此,尽管程序中用了结构体数组来定义了两个连续的i2c_msg结构体变量,但是它们依旧是两个单次的写数据过程,因此i2c_msg[1]还是写地址,而不是写数据,所以在指定地址读取不到0x03,而是其他值(不一定是0x0).
正确函数
static int ap3216c_write_regs(struct i2c_client *client,u8 reg, u8 *buffer, int length)
{
int err = 0;
u8 b[256];
struct i2c_msg msg;
b[0] = reg;
memcpy(&b[1],buffer,length);
msg.addr = client->addr,
msg.flags = 0;
msg.len = length + 1; /* +1是因为还有一个b[0]所存储的寄存器 */
msg.buf = b;
err = i2c_transfer(client->adapter, &msg,1);
if(err != 1)
{
err = -EINVAL;
printk("write data to ap3216c has been failed\n\r");
}
return err;
}
在上述函数中,只使用了一个i2c_msg结构变量,因此整个函数是一次完整单向的数据传输,使用数组是为了让数据连续。
对于i2c_msg的浅析就到这里了,表达可能有不清除的地方,如果读者在阅读的过程中有不明白或者发现错误的地方,可以留言探讨
今天的文章i2c_msg浅析分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/33830.html