I2c协议详解(时序+上板实测)

1、简介

        I2c是一种串行通信总线,支持多主从架构。其使用两根信号线进行通信:一根时钟线SCL,一根数据线SDA。I2c将SCL处于SDA拉低的动作作为开始信号,SCL处于SDA拉高的动作作为结束信号;传输数据时,SDA在SCL低电平时改变数据,在SCL高电平时保持数据,每个SCL脉冲的高电平传递一位数据。要求两根信号线都使用开漏输出接上拉电阻的配置,以实现所有节点SDA、SCL信号线上的线与逻辑关系。

2、线与逻辑和总线仲裁

        线与逻辑关系是指,当SDA\SCL总线上所连的任意设备发送低电平时,整个总线就会变为低电平。因此设备主控制器可以通过检测SDA\SCL上的总线电平和自身要发送的总线电平是否一样,来判断是否发生总线冲突,遵循低电平优先的原则(线与逻辑),谁先发送低电平谁就会掌握对总线的控制权。

         对于SDA而言,假设DATA1是主节点1,DATA2是主节点2,SDA是总线上的状态。在红线之间可以发现,SDA线上由于DATA2继续发送低电平,继而保持低电平。而DATA1此时想要发送高电平,发现与SDA线上的电平不一致,根据低电平优先的原则,主节点1就会断开数据输出,变为从机接收状态,主节点2就赢得了总线,并且数据没有丢失。因为总线的数据与主节点2发送的数据一致,而主节点1在转为从节点后会继续接收数据,同样也没有丢失SDA线上的数据。

        对于SCL而言,SCL线会被拥有最长低电平周期的器件保持低电平,而其他低电平周期短的器件就会进入等待状态。当所有器件完成低电平周期后,时钟线才会被释放位高电平,所有器件开始数它们的高电平周期,最先完成高电平周期的器件会再次将SCL线拉低。 所以产生的同步SCL时钟的低电平周期由低电平时钟周期最长的决定。高电平时钟周期由高电平时钟周期最短的器件决定。

3、时序

        I2c时序可以分为开始\结束信号和数据读取两部分。

        开始\结束信号都要求SCL处于高电平状态,不同的是,开始信号SDA线上的电平变化为由高到低。结束信号SDA线上的电平变化为由低到高

        数据传输时(以字节为单位传输,每次传8位),要求当SCL为高电平时,需保持SDA线电平不产生变化。设备会在SCL为高电平时,对SDA线上的电平进行读取。高为1,低为0。 

        传输完8位数据后,第9位代表应答\非应答信号,拉低SDA代表应答,每个字节后面都有一个应答\非应答信号,不管传输的是地址还是数据。

        主机接收数据的过程中,等数据接收完毕,主机会向从机发送一个非应答信号,告诉从机不要发送了,再发送一个停止信号,释放总线结束。

4、设备地址

        I2c从机的设备地址有两种,一种7bit,一种10bit

        7bit设备的地址传输,在START信号后的首字节由7位从机地址和1位读写地址组成。

        发送完这个字节后,主机释放SDA总线等待从机给出ACK应答。如果从机给出了ACK应答,表示从机地址正确(有从机响应)并且已知是读还是写,便可以开始读写数据。如果从机没有给出ACK应答,则说明接收设备可能没有收到数据(如寻址的设备不存在或是设备正在忙碌)或无法解析收到的数据,如果是这样,则由master来决定如何处理(STOP或RESTART)。

         10bit设备地址不常用。用于应对i2c设备增多7bit地址数量不能满足使用的问题。10bit设备需要消耗两个字节来传输从设备的地址信息。第一个字节位1111 0+地址高两位(第10、9bit)+读写控制位,第二个字节为地址低8位。因为“1111 0xxx”为i2c中特地保留的16个特殊指令地址中的一个,所以7bit设备不会响应该首字节的呼叫。

5、Code     

        测试代码基于linux操作系统,开发板为 Bananapi-r2-pro。

        LINUX操作系统内核将I2c通信封装成了i2c_transfer函数,我们不需要手动模拟SCL\SDA的上拉\下拉过程,这些操作由i2c_transfer完成。我们需要做的是构造一个i2c_msg结构体,并设置好所接入的i2c设备地址,以及需要写入的设备寄存器地址和要写入的数据即可。

        例如,如果要从MPU6050读出数据,根据手册,MPU6050读取数据有两种方式。一种是读取一个字节,一种是读取多个字节。其规则一样,需要主设备发送设备地址(AD+W)+寄存器地址,随后发送设备地址(AD+R)并释放总线,随后即可读取数据。

        其中设备地址由AD+W\R组成。AD由110100x构成,共7bit。x取决于MPU6050上的AD0引脚,若AD0接低电平,x=0。若AD0接高电平,x=1。W=0,R=1。

         在实际操作中,根据协议,需要定义buffer用于存储发送和接收的数据,定义i2c_msg设置消息类型。写入操作需要定义两条消息。第一条为写入(AD+W),告知将要通信的设备地址。第二条为读取(AD+R)。

ssize_t i2c_read(struct file *filp, char __user*buf, size_t size, loff_t *off)
{
    int err,data_l,data_h;
    struct i2c_msg msg[2];  
    char *kern_buf;
    kern_buf = kmalloc(10,GFP_KERNEL);
    msg[0].addr = g_client->addr;
    msg[0].flags = 0;//W
    msg[0].buf = kern_buf;
    kern_buf[0]=0x43;
    msg[0].len = 1;

    msg[1].addr = g_client->addr;
    msg[1].flags = I2C_M_RD;//R
    msg[1].buf = kern_buf;
    msg[1].len = 2;//读两字节

    err = i2c_transfer(g_client->adapter,msg,2);
    if(err<0)
    {
        printk("i2c_transfer error: %d!\n",err);
        return -1;
    }
    /*copy_to_user*/
    err = copy_to_user(buf,kern_buf,2);
    kfree(kern_buf);
    return size;
};

6、MPU6050读取数据全为0

        在实测过程中发现对MPU6050众多寄存器进行读取,其值均为0。以及对大部分寄存器无法进行写入操作(即写入无效)。不过地址为0x75的寄存器可以正常读取,其值为MPU设备地址。默认为0x68。可以先行读取这个寄存器(0x75)中的值,以判断i2c是否正常工作。

        如果能够正常读到0x68,但其他寄存器的值又为0。那么有可能是MPU6050进入了SLEEP模式。通过读取0x6B寄存器中的值发现,其值为0x60,确实进入了睡眠模式。通过设置寄存器0x6B的值为0x00来取消睡眠。

        写和读的逻辑一样,分为写一个字节和写多个字节。

ssize_t i2c_write(struct file *filp, const char *buf, size_t size, loff_t *off)
{
    int err;
    struct i2c_msg msg[2];  
    char kern_buf[9];
    int len;
    kern_buf[1]=0x00;
    msg[0].addr = g_client->addr;
    msg[0].flags = 0;
    msg[0].buf = kern_buf;
    kern_buf[0]=0x6B;
    msg[0].len = 2;
    err = i2c_transfer(g_client->adapter,msg,1);
    if(err<0)
    {
        printk("i2c write error\n");
        return -1;
    }
    return size;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值