IIC总线详解

IIC简介

IIC(Inter-Integrated Circuit)集成电路总线,它是一种串行通信总线,又叫I2C,使用多主从架构,由飞利浦公司在1980年推出的。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,一般可达400kbps 以上,属于半双工同步传输型总线。

IIC物理层

IIC总线的硬件,是由一根数据线SDA,一根时钟线SCL构成。不同的器件,都是并联接在这两条线上,I2C总线上的每个设备都自己一个唯一的地址,来确保不同设备之间访问的准确性。

  • SDA(Serial data)是数据线,D代表Data也就是数据,Send Data 也就是用来传输数据的
  • SCL(Serial clock line)是时钟线,C代表Clock 也就是时钟 也就是控制数据发送的时序的

img

IIC总线通过上拉电阻接到电源VCC,电阻大小要根据设备实际测量。

  • 总线空闲的时候,SDA和SCL都是高电平。
  • 当其中一个设备拉低总线,整条线就全是低电平,器件与器件之间"与"关系。

IIC特点

通常我们为了方便把IIC设备分为主设备和从设备,基本上谁控制时钟线(控制SCL的电平高低变换)谁就是主设备。

  • IIC主设备功能:主要产生时钟,产生起始信号和停止信号
  • IIC从设备功能:可编程的IIC地址检测,停止位检测
  • IIC的一个优点是它支持多主控(multimastering),其中任何一个能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
  • 支持不同速率的通讯速度,标准速度(最高速度100kHZ或100kbit/s), 快速(最高400kHZh或400kbit/s)
  • SCL和SDA都需要接上拉电阻 (大小由速度和容性负载决定一般在3.3K-10K之间) 保证数据的稳定性,减少干扰。
  • IIC是半双工,而不是全双工 ,同一时间只可以单向通信,IIC协议首先是发送从机硬件地址,然后发送命令,再发送数据/寄存器编号或者读取数据。IIC协议可以多字节连续读写数据。
  • 各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。

IIC协议规则

总线时序图

在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。

img

初始(空闲)状态

因为IIC的 SCL 和SDA 都需要接上拉电阻,保证空闲状态的稳定性
所以IIC总线在空闲状态下SCL 和SDA都保持高电平

/**
 * I2C初始化
 */
static void I2C_Init()
{
    SCL_H();     //首先把时钟线拉高
    delay_us(4);
    SDA_H();     //在SCL为高的情况下把SDA拉高
    delay_us(4);
}

开始信号

SCL保持高电平,SDA由高电平变为低电平后,延时(>4.7us),SCL变为低电平, 比如发送设备唯一地址。

img

/**
 * 产生IIC起始信号
 * 1.先拉高SDA,在拉高SCL,空闲状态
 * 2.拉低SDA SCL
 */
static void I2C_Start(void)
{
    SDA_H();
    SCL_H();
    delay_us(5);
    SDA_L();
    delay_us(5);
    SCL_L();
}

停止信号

停止信号:SCL保持高电平,SDA由低电平变为高电平。

img

/**
 * 产生IIC停止信号
 * 1.先拉低SDA,再拉低SCL
 * 2.拉高SCL
 * 3.拉高SDA
 * 4.停止接收数据
 */
static void I2C_Stop(void)
{
    SCL_L();
    SDA_L();     //STOP:当SCL高时,数据由低变高
    delay_us(4);
    SCL_H();
    SDA_H();     //发送I2C总线结束信号
    delay_us(4);
}

应答信号

每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。
应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答。
应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。

img

每发送一个字节(8个bit), 在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据传输。
应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答

img

/**
 * 主机产生应答信号ACK
 * 1.先拉低SCL,再拉低SDA
 * 2.拉高SCL
 * 3.拉低SCL
 */
static void I2C_Ack(void)
{
    SCL_L();    //先拉低SCL,使得SDA数据可以发生改变
    SDA_L();    //拉低SDA,产生应答信号
    delay_us(2);
    SCL_H();
    delay_us(5);
    SCL_L();
}

/**
 * 主机不产生应答信号NACK
 * 1.先拉低SCL,再拉高SDA
 * 2.拉高SCL
 * 3.拉低SCL
 */
static void I2C_NAck(void)
{
    SCL_L();    //先拉低SCL,使得SDA数据可以发生改变
    SDA_H();    //拉高SDA,不产生应答信号
    delay_us(2);
    SCL_H();
    delay_us(5);
    SCL_L();
}

通信格式

起始信号之后,可以连续传输多个字节。然后以停止信号结束这一帧数据的传输。
多数从设备的地址为7位或者10位,一般都用七位。
八位设备地址=7位从机地址+读/写地址,
地址是根据硬件配置的,只有地址匹配的设备,才会处理总线上的数据。

img

IIC的每一帧数据由9bit组成,
如果是发送数据,则包含 8bit数据+1bit ACK,
如果是设备地址数据,则8bit包含7bit设备地址 1bit方向。

img

/**
 * 主机发送1个字节数据
 */
static void I2C_SendByte(uint8 data)
{
    volatile uint8 i;

    i = 8;
    while(i--) {
        if(data & 0x80) {
            SDA_H();
        } else {
            SDA_L();
        }
        data <<= 1;
        I2C_DELAY(2);
        SCL_H();
        I2C_DELAY(2);
        SCL_L();
    }
}
/**
 * 主机接收1个字节数据
 */
static uint8 I2C_GetByte(void)
{
    uint8 i,byte;

    i = 8;
    byte = 0;

    SDA_DDR_IN(); //设置当前GPIO输入模式
    while(i--) {
        SCL_L();
        I2C_DELAY(2);
        SCL_H();
        I2C_DELAY(2);
        byte = (byte<<1)|(SDA_IN() & 1); //读输入数据1个BIT
    }
    SCL_L();
    SDA_DDR_OUT(); //设置当前GPIO输出模式
    return byte;
}

数据读写三种方式

单向发送数据

比如显示设备,一般情况下只需要写入数据,传输过程如下:

img

int32 I2C_BurstWrite(uint8 chipAddr, uint32 addr, uint32 addrLen, uint8 *buf, uint32 len)
{
    uint8 *p;
    uint8 err;
    
    p = (uint8*)&addr;
    err = 0;
    chipAddr <<= 1;

    I2C_Lock(); //关闭总中断
    I2C_Start();
    I2C_SendByte(chipAddr);
    err += I2C_WaitAck();
    while(addrLen--)
    {
        I2C_SendByte(*p++);
        err += I2C_WaitAck();
    }
    
    while(len--)
    {
        I2C_SendByte(*buf++);
        err += I2C_WaitAck();  
    }

    I2C_Stop();
    I2C_Unlock(); //打开总中断
    return err;
}

读取数据

img

int32 I2C_BurstRead(uint8 chipAddr, uint32 addr, uint32 addrLen, uint8 *buf, uint32 len)
{
    uint8 *p;
    uint8 err;
    
    p = (uint8*)&addr;
    err = 0;
    chipAddr <<= 1;

    I2C_Lock();   //关闭总中断
    I2C_Start();
    I2C_SendByte(chipAddr);
    err += I2C_WaitAck();
    
    while(addrLen--)
    {
        I2C_SendByte(*p++);
        err += I2C_WaitAck();
    }
    
    I2C_Start();
    I2C_SendByte(chipAddr+1);
    err += I2C_WaitAck();
    
    while(len--)
    {
        *buf++ = I2C_GetByte();
        if(len)
        {
            I2C_Ack();
        }
    }
    
    I2C_NAck();
    I2C_Stop();
    I2C_Unlock(); //打开总中断
    return err;
}

先写寄存器地址,再读取该寄存器的数据

比如存储芯片,一般情况下要先制定存储地址,再读取数据。传输过程如下:

img

问题

IIC最多能有多少个从机

IIC总线是主从设备,从机地址位数为7位。 一个主机在总线上最多可以挂127个从机(7位地址最多128,去除0x0广播地址,还剩127),设备间通过不同的设备地址来区分。

参考引用

IIC总线详解
IIC原理超详细讲解—值得一看
IIC

  • 12
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啊唯0o

如果能够帮助到您,不介意的话,

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值