关于I2C的那点事:i2c_master_send 和 i2c_master_recv i2c_transfer

做linux 嵌入式驱动,离不开调试i2c 外设,这里对i2c做一下3点总结:

1.先要知道i2c的4个信号;

a) 起始信号:当 SCL 线是高电平时 SDA 线从高电平向低电平切换。 

b) 停止信号:当 SCL 线是高电平时 SDA 线由低电平向高电平切换。

c)   ACk信号: 当scl线由低变高时候,SDA线保持低电平;

d)  NACK信号:当scl线由低变高时候,SDA线保持高电平;

2、通信接口
i2c发送或者接收一次数据都以数据包 struct i2c_msg 封装
[cpp] 
struct i2c_msg { 
    __u16 addr;     // 从机地址 
    __u16 flags;    // 标志 
#define I2C_M_TEN   0x0010  // 十位地址标志 
#define I2C_M_RD    0x0001  // 接收数据标志 
    __u16 len;      // 数据长度 
    __u8 *buf;      // 数据指针 
}; 
其中addr为从机地址;flags则是这次通信的标志,发送数据为0,接收数据则为 I2C_M_RD;len为此次通信的数据字节数;buf 为发送或接收数据的指针。在设备驱动中我们通常调用 i2c-core 定义的接口 i2c_master_send 和 i2c_master_recv 来发送或接收一次数据。
[cpp]
int i2c_master_send(struct i2c_client *client,const char *buf ,int count) 

    int ret; 
    struct i2c_adapter *adap=client->adapter;  // 获取adapter信息 
    struct i2c_msg msg;                        // 定义一个临时的数据包 
 
    msg.addr = client->addr;                   // 将从机地址写入数据包 
    msg.flags = client->flags & I2C_M_TEN;     // 将从机标志并入数据包 
    msg.len = count;                           // 将此次发送的数据字节数写入数据包 
    msg.buf = (char *)buf;                     // 将发送数据指针写入数据包 
 
    ret = i2c_transfer(adap, &msg, 1);         // 调用平台接口发送数据 
 
     
    return (ret == 1) ? count : ret;           // 如果发送成功就返回字节数 

EXPORT_SYMBOL(i2c_master_send); 
i2c_master_send 接口的三个参数:client 为此次与主机通信的从机,buf 为发送的数据指针,count 为发送数据的字节数。
[cpp] 
int i2c_master_recv(struct i2c_client *client, char *buf ,int count) 

    struct i2c_adapter *adap=client->adapter;  // 获取adapter信息 
    struct i2c_msg msg;                        // 定义一个临时的数据包 
    int ret; 
 
    msg.addr = client->addr;                   // 将从机地址写入数据包 
    msg.flags = client->flags & I2C_M_TEN;     // 将从机标志并入数据包 
    msg.flags |= I2C_M_RD;                     // 将此次通信的标志并入数据包 
    msg.len = count;                           // 将此次接收的数据字节数写入数据包 
    msg.buf = buf; 
 
    ret = i2c_transfer(adap, &msg, 1);         // 调用平台接口接收数据 
 
     
    return (ret == 1) ? count : ret;           // 如果接收成功就返回字节数 

EXPORT_SYMBOL(i2c_master_recv); 
i2c_master_recv 接口的三个参数:client 为此次与主机通信的从机,buf 为接收的数据指针,count 为接收数据的字节数。我们看一下 i2c_transfer 接口的参数说明:
[cpp
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num); 

其中 adap 为此次主机与从机通信的适配器;msgs 为通信的数据包,这里可以是单个或多个数据包;num 用于指定数据包的个数,如果大于1则表明将进行不止一次的通信。通信一次就需要寻址一次,如果需要多次通信就需要多次寻址,前面2个接口都是进行一次通 信,所以 num 为1;有的情况下我们要读一个寄存器的值,就需要先向从机发送一个寄存器地址然后再接收数据,这样如果想自己封装一个接口就需要将 num 设置为2。接口的返回值如果失败则为负数,如果成功则返回传输的数据包个数。

首先我们先来看一下写数据的时序图:进行了1次寻址操作 


结合I2C总线协议的知识,我们可以知道OZ9350的I2C写数据由一下10个步骤组成。 
  第一步,发送一个起始信号。 
  第二步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。 
  第三步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。 
  第四步,发送寄存器地址,8bit数据。 
  第五步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。 
  第六步,发送一个数据,8bit数据。 
  第七步,产生一个ACK应答信号,此应答信号为从机器件产生的应答信号。 
  第八步,发送一个CRC校验码,此CRC校验值为2、4、6步数据产生的校验码。 
  第九步,既可以发送一个应答信号,也可以发送一个无应答信号,均有从机器件产生。 

  第十步,发送一个停止信号。

读数据的时序图如下图所示:进行了2次寻址操作

 

通过分解后的时序图,可以看到OZ9350的读数据由以下13个步骤组成。 
  第一步,发送一个起始信号。 
  第二步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。 
  第三步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。 
  第四步,发送寄存器地址。 
  第五步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。 
  第六步,再次发送一个骑士信号。 
  第七步,发送7bit从机地址,即OZ9350的地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+r/w。最低位为1表示读,为0表示写。 
  第八步,产生一个ACK应答信号,此应答信号为从机器件产生的应答。 
  第九步,读取一个字节(8bit)的数据。 
  第十步,产生一个ACK应答信号,此应答信号为CPU产生。 
  第十一步,读取一个CRC校验码。 
  第十二步,产生一个NACK信号。此无应答信号由CPU产生。 

  第十三步,产生一个停止信号。

3.I2C读写设备寄存器封装例子(短包:向8bit地址,读写8bit位的数据,读写函数原型返回值都是int):

rt5651_i2c_write(0x02, 0x08);

int cydat1[1] = {0};

cydat1[0] = rt5651_i2c_read(0xFF);

static int rt5651_i2c_write(u8 reg, u8 writedata)   //由上知识可知,要进行1次寻址操作

{
u8 databuf[2] = {0};
int ret = 0;
databuf[0] = reg;
databuf[1] = writedata;
ret = i2c_master_send(rt5651_i2c_client, databuf, 2);  //第一次寻址
printk(" codec  --- rt5651_i2c_write ret=%d, databuf[0]=%0x, databuf[1]=%0x\n", ret, databuf[0], databuf[1]);
if(ret < 0)
{
printk(" codec  --- rt5651_i2c_write send data failed !\n");
return -1;
}
return ret;
}
 
 static int rt5651_i2c_read(u8 reg)  //由上知识可知,要进行2次寻址操作
{
unsigned char val[1] = {0}; 
//其实这里就相当于u8 val[1] = {0};
int ret = 0;
val[0] = reg;
ret = i2c_master_send(rt5651_i2c_client, val, 1); //第一次寻址
if (ret < 0) 
{
printk(" codec  --- rt5651_i2c_read I2C i/o error ret = %d\n", ret);
return ret;
}
mdelay(10);
ret = i2c_master_recv(rt5651_i2c_client, val, 1); //第二次寻址
if (ret < 0) 
{
printk(" codec  --- rt5651_i2c_read I2C read error ret = %d\n", ret);
return ret;
}
return val[0];
}

4.I2C读写设备寄存器封装例子(长包:向8bit地址,读写16bit位的数据,读写函数原型返回值都是指针型*int):

//向0xff寄存器里面写0x1f2c

        u8 Wdata[3];
         Wdata[0]=0x1f;
Wdata[1]=0x2c;

ALC5623_WriteReg(0xff, Wdata);


u8 * rt5651id;
rt5651id=ALC5623_ReadReg(0xFF);  
printk("rt5651_i2c_probe rt5651id[0]=%x,rt5651id[1]=%x\n",rt5651id[0],rt5651id[1]); //这里把指针里面的值打印出来了。


下面一共给了2组例子,一个用了指针,一个用数组,很是经典:首先下面是别人的例子:

static void ALC5623_WriteReg( uint8 Reg, uint8 *data)
{
uint16 Ret=0;
uint8 *Wdata = NULL;                                //1.申请一个指针
Wdata=(uint8 *)PanicUnlessMalloc(4);     //2.给指针开辟一个4个u8的空间,其实这里相当于申请一个有4个u8数据的数组
Wdata[0]=Reg;
memcpy(&Wdata[1], data, 2);                  //这里非常巧妙,很有意思,指针这么好用,跟数据一样;
Ret = I2cTransfer( ALC5623_addr |Codec_Write, Wdata,3, NULL,0);
free(Wdata);                                            //3.之前有申请了空间,所以这里要释放
}
static uint8* ALC5623_ReadReg( uint8 Reg)
{
uint8 * ReadREG= malloc(16);
uint8 REGADDR[1];
memset(ReadREG,0,16);
REGADDR[0]=Reg;
I2cTransfer( ALC5623_addr |Codec_Write, REGADDR,1, NULL,0);
I2cTransfer( ALC5623_addr |Codec_Read, NULL,0, ReadREG,2);
return ReadREG;
}

它山之石可以攻玉:本人参照上面的例子写的,已在mt6737 andorid_m0上验证通过:

static int ALC5623_WriteReg( u8 Reg, u8 *data)

{
int ret = 0;
u8 databuf[4] = {0};

databuf[0] = Reg;

memcpy(&databuf[1], data, 2);

ret = i2c_master_send(rt5651_i2c_client, databuf, 3);
printk(" codec  --- rt5651_i2c_write ret=%d, databuf[0]=0x%x, databuf[1]=0x%x,databuf[1]=0x%x\n", ret, databuf[0], databuf[1],databuf[2]);
if(ret < 0)
{
printk(" codec  --- rt5651_i2c_write send data failed !\n");
return -1;
}
return ret;
}

static u8* ALC5623_ReadReg( u8 Reg)
{       u8 ret;
u8 ReadREG[2]= {0};
        u8 REGADDR[1]= {0};
REGADDR[0]=Reg;
ret = i2c_master_send(rt5651_i2c_client, REGADDR, 1);
if (ret < 0) 
{
printk(" codec  --- rt5651_i2c_read I2C i/o error ret = %d\n", ret);
return ret;
}
mdelay(10);
ret = i2c_master_recv(rt5651_i2c_client, ReadREG, 2);
if (ret < 0) 
{printk(" codec  --- rt5651_i2c_read I2C read error ret = %d\n", ret);
return ret;
}
printk(" codec  --- rt5651_i2c_read I2C read ReadREG[0]= %x ,ReadREG[1]= %x\n", ReadREG[0],ReadREG[1]);
return ReadREG;
}



  • 9
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
当使用i2ctransfer函数进行通信时,如果返回值是-11,表示i2c_transfer函数执行失败。i2c_transfer函数是用于在I2C务中传输数据的低级别函数。它可以通过i2c_msg结构表示一次I2C务的一个段落。该函数可见于驱动程序的i2c_transfer()过程,从i2c-dev传输到用户空间,以及通过i2c_adapter.master_xfer()方法传递给I2C适配器驱动程序。 要解决i2c_transfer失败的问题,有时可以尝试重置I2C总线。在代码示例中的i2c_reset_adapter函数中,重置I2C总线的方法如下: 1. 请求I2C总线数据引脚和时钟引脚的GPIO资源。 2. 将数据引脚设置为输入模式。 3. 检查数据引脚的状态,如果其值为低电平,表示I2C总线可能存在问题。 4. 在一定次数内,通过控制时钟引脚产生时钟脉冲来尝试修复I2C总线。 5. 如果重试次数达到限制仍然无法修复I2C总线,则重置失败。 这是一个示例代码,具体的修复方法可能因不同的硬件和系统环境而有所不同。如果重置I2C总线失败,可能需要进一步检查硬件连接、I2C设备驱动程序和操作系统的相关设置。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [linux i2c 的通信函数i2c_transfer在什么情况下出现错误](https://blog.csdn.net/weixin_33910759/article/details/86011199)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [i2c_master_send()、i2c_master_recv()和i2c_transfer()](https://blog.csdn.net/qq_36288184/article/details/106230187)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值