i2c_msg浅析

i2c_msg浅析i2c_msg浅析在学习i2c设备驱动的时候,不经意间发现一个关于结构体i2c_msg的问题,查阅了两天的资料,发现网上基本说的都差不多,当时不理解,以为别人说的不对,理解之后发现都是对的,只是当时不懂。为了防止有小伙伴和我一样钻牛角尖,白白耽误时间,就大概说一下,当然了,我也是在学习的过程中,难免会有些地方说的不对。i2c的读写时序讲解在讲解之前,先大概介绍以下i2c的读写时序问题,当然了,都学到i2c了,时序肯定也懂了,我也就不细讲了,主要是需要配合时序图进行后面i2c_msg的讲解。i2c的读

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   = &reg,
        },
        {
            .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   = &reg,
        },
        {
            .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

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注