一文详解I2C总线

接口/总线/驱动

1138人已加入

描述

I2C(也常写作IIC,I2C),全称为Inter-Integrated Circuit(“互连集成电路”),用于在集成电路之间进行短距离数据传输。它由Philips(现在的NXP半导体)公司于1980年代初开发,并成为一种广泛应用于电子设备之间通信的标准。I2C协议简单、灵活且广泛支持,常用于连接传感器、存储器、显示屏和其他外设到微控制器、微处理器或其他集成电路上。这是一种简单的双向双线总线,非常适合用于微控制器与外设之间,或者多个微控制器之间的高效互连控制。

其名称反映了最初的设计目的:它专为“集成电路”间的通信设计。I2C的两条线包括SDA(串行数据线),用于传输信息;和SCL(串行时钟线),负责同步。I2C的一个优点是它只需要这两根线进行通信,这可以在复杂系统中有效地利用资源。

它也是一种在许多产品中广泛使用的标准,扩展和集成相对简单。

I2C的主要特点包括:

  • 通信方式:I2C采用主从式通信方式。一个主设备(通常是微控制器或处理器)控制总线并发起通信,而一个或多个从设备被动地响应主设备的命令或请求。
  • 总线结构:I2C使用两根线路进行通信:* SCL(Serial Clock):时钟线由主设备提供,用于同步通信速度。* SDA(Serial Data):数据线用于传输数据和控制信号。
  • 信号电平:I2C使用双向开漏(open-drain)输出,这意味着信号线可以被拉低(逻辑0)但不能被主动拉高,只能通过外部上拉电阻回到高电平(逻辑1)。
  • 地址和帧格式:I2C使用7位或10位的设备地址来唯一标识每个从设备。通信帧包括设备地址、读/写位、数据字节和应答位(ACK)。
  • 速率和模式:I2C支持不同的通信速率,通常有标准模式(100 kbit/s)和快速模式(400 kbit/s)。还有更高速的模式如高速模式(3.4 Mbit/s)和超高速模式(5 Mbit/s)。
  • 启动和停止条件:I2C通信的开始由主设备发送启动信号(SDA从高电平切换到低电平,同时SCL保持高电平)表示。通信结束时,主设备发送停止信号(SDA从低电平切换到高电平,同时SCL保持高电平)表示。
  • 多主设备支持:I2C协议允许多个主设备共享同一条总线,通过仲裁机制来解决总线竞争问题。
  • 应用领域:I2C广泛应用于各种设备和应用领域,例如传感器、存储器(如EEPROM)、显示屏、温度传感器、实时时钟(RTC)、扩展IO芯片等。

I2C是一种简单、灵活且广泛支持的串行通信协议,适用于在电子设备之间进行短距离数据传输。它具有较低的硬件复杂性和通信开销,因此在许多嵌入式系统和电子设备中被广泛采用。

I2C协议还有一些其他的特性和扩展,如高速模式、高容量模式、地址扩展等,以满足更复杂的应用需求。虽然I2C并不适合所有情况(例如,它并不适用于长距离或高速应用),但它是一种常见的、实用的选择,适用于简单的内部通信。当希望许多从设备共享通信线并由一个(或多个)主设备管理时,它特别有用。

I2C总线通用连接框图:

存储器
I2C总线互联系统示意图

在单主机系统中,可以抽象为:

存储器
单主机I2C总线

典型的多主机系统则为:

存储器
多主机I2C总线

电气特性

I2C使用开漏/开集电极结构,同时在同一数据线上使用输入缓冲器,这允许单个数据线用于双向数据流。

双向通信的开漏结构

开漏指的是一种输出类型,可以将总线拉低到某个电压(通常是地线),或者"释放"总线,让其被上拉电阻拉高。当总线被主设备或从设备释放时,上拉电阻(RPU)负责将总线电压拉高到电源电平。由于没有设备可以强制将线路拉高,这意味着总线永远不会遇到通信问题,其中一个设备尝试传输高电平,而另一个设备传输低电平,导致短路(电源电平到地)。I2C要求在多主设备环境中,如果主设备发送高电平,但检测到线路低电平(另一个设备将其拉低),则停止通信,因为另一个设备正在使用总线。推挽接口不允许此类自由,这是I2C的一个优点。

存储器
SDA/SCL内部基本结构

上图显示了在SDA/SCL线上的从设备或主设备的内部结构的简化视图,包括用于读取输入数据的缓冲器和用于传输数据的下拉场效应晶体管(FET)。设备只能将总线线路拉低(与地短接)或释放总线线路(对地高阻抗),并允许上拉电阻将电压拉高。这是在处理I2C设备时需要了解的重要概念,因为没有设备可以将总线保持高电平。这个特性是实现双向通信的关键。

拉低总线

如前所述,开漏结构只能将总线拉低,或者"释放"总线并由电阻将其拉高。下图展示了将总线拉低时的电流流动。希望传输低电平的逻辑会激活下拉场效应晶体管,提供对地的短路,将线路拉低。

存储器
拉低总线

释放总线

当从设备或主设备希望传输逻辑高时,它只能通过关闭下拉场效应晶体管来释放总线。这将使总线悬空,而上拉电阻将把电压拉高到电源电平,被解释为高电平。下图展示了通过上拉电阻的电流流动,将总线拉高。

存储器
释放总线

通信速率模式

存储器
速率模式

存储器
常规模式

存储器
高速模式

存储器
超快模式

接口规范

一般操作

I2C总线是一种标准的双向接口,使用控制器(即主设备)与从设备进行通信。除非被主设备寻址,否则从设备不能传输数据。I2C总线上的每个设备都有一个特定的设备地址,以区分其他在同一I2C总线上的设备。许多从设备在启动时需要进行配置,以设置设备的行为。这通常在主设备访问从设备的内部寄存器映射时完成,这些寄存器具有唯一的寄存器地址。一个设备可以有一个或多个寄存器,用于存储、写入或读取数据。

物理的I2C接口由串行时钟(SCL)线和串行数据(SDA)线组成。SDA和SCL线都必须通过上拉电阻连接到VCC电源。上拉电阻的大小取决于I2C线路上的电容量。只有当总线处于空闲状态时,才能启动数据传输。如果在停止条件之后,SDA和SCL线都保持高电平,总线将被认为是空闲的。

主设备访问从设备的一般过程如下:

  1. 如果主设备想要向从设备发送数据:
    • 主设备-发送器发送起始条件并寻址从设备-接收器* 主设备-发送器向从设备-接收器发送数据* 主设备-发送器通过停止条件终止传输
  2. 如果主设备想要接收/读取从设备的数据:
  • 主设备-接收器发送起始条件并寻址从设备-发送器
  • 主设备-接收器向从设备-发送器发送要读取的寄存器
  • 主设备-接收器从从设备-发送器接收数据
  • 主设备-接收器通过停止条件终止传输

起始和停止条件

I2C通信通过主设备发送起始条件(START condition)来初始化,并通过主设备发送停止条件(STOP condition)来终止。在SCL线为高电平时,SDA线上的高至低的跳变定义了起始条件。在SCL线为高电平时,SDA线上的低至高的跳变定义了停止条件。

存储器
起始和停止条件示例

重复起始条件

重复的起始条件(Repeated START condition)类似于起始条件(START condition),用作连续的停止条件和起始条件的替代。它在外观上与起始条件相同,但与起始条件不同,因为它发生在停止条件之前(总线不处于空闲状态)。这在主设备希望启动新的通信,但不希望通过停止条件使总线空闲,从而可能失去对总线的控制权(在多主设备环境中)时非常有用。

数据有效性和字节格式

在每个SCL时钟脉冲期间传输一个数据位。一个字节由SDA线上的八个位组成。一个字节可以是设备地址、寄存器地址或从从设备写入或读取的数据。数据以最高有效位(MSB)优先的顺序传输。在START和STOP条件之间,可以从主设备向从设备传输任意数量的数据字节。在时钟周期的高电平期间,SDA线上的数据必须保持稳定,因为当SCL为高电平时,数据线的变化被解释为控制命令(START或STOP)。

存储器
单字节传输示例

应答(ACK)和非应答(NACK)

接收器在每个数据字节(包括地址字节)后发送一个ACK位。ACK位允许接收器向发送器传达字节已成功接收,并且可以发送另一个字节。

在接收器发送ACK之前,发送器必须释放SDA线。为了发送ACK位,接收器在ACK/NACK相关的时钟周期(周期9)的低电平期间拉低SDA线,以便在ACK/NACK相关的时钟周期的高电平期间,SDA线保持稳定低电平。必须考虑到设置时间和保持时间。

当SDA线在ACK/NACK相关的时钟周期中保持高电平时,这被解释为非应答(NACK)。有几种情况会导致产生NACK:

  1. 接收器由于执行某些实时功能并且尚未准备好与主设备开始通信,因此无法接收或发送。
  2. 在传输过程中,接收器接收到无法理解的数据或命令。
  3. 在传输过程中,接收器无法接收更多的数据字节。
  4. 主设备接收器完成数据读取,并通过NACK向从设备表示这一点。

存储器
NACK波形示例

I2C数据

必须通过读取或写入从设备的寄存器来发送和接收数据。寄存器是从设备内存中包含信息的位置,无论是配置信息还是一些采样数据用于发送回主设备。主设备必须向这些寄存器中写入信息,以指示从设备执行任务。

虽然在I2C从设备中常见的是寄存器,但请注意,并非所有的从设备都会有寄存器。一些设备非常简单,只包含一个寄存器,可以通过在从设备地址之后立即发送寄存器数据来直接写入该寄存器,而无需寻址寄存器。单寄存器设备的一个例子是8位I2C开关,它通过I2C命令进行控制。由于它只有一个位来启用或禁用通道,因此只需要一个寄存器,主设备只需在从设备地址之后写入寄存器数据,跳过寄存器编号。

向I2C总线上的从设备写入数据

要在I2C总线上写入数据,主设备将在总线上发送一个带有从设备地址的起始条件,并将最后一位(R/W位)设置为0,表示写入操作。在从设备发送应答位后,主设备将发送要写入的寄存器地址。从设备再次发送应答位,通知主设备它已准备好。然后,主设备将开始向从设备发送寄存器数据,直到主设备发送完所有需要的数据(有时仅为一个字节),然后通过发送停止条件终止传输。

下图显示了向从设备寄存器写入单个字节的示例:

存储器
向从设备寄存器写数据示例

从I2C总线上的从设备读取数据

从从设备读取数据与写入数据非常相似,但有一些额外的步骤。为了从从设备读取数据,主设备首先必须告知从设备它希望从哪个寄存器读取数据。主设备通过类似写入操作的方式开始传输,发送带有R/W位为0(表示写入)的地址,然后是要从中读取数据的寄存器地址。一旦从设备确认了寄存器地址,主设备将再次发送起始条件,然后发送带有R/W位为1(表示读取)的从设备地址。这次,从设备将确认读取请求,主设备释放SDA总线,但仍向从设备提供时钟。在此事务的这个阶段,主设备将变为主设备接收器,而从设备将变为从设备发送器。

主设备将继续发送时钟脉冲,但会释放SDA线,以便从设备可以传输数据。在每个数据字节结束时,主设备将向从设备发送一个ACK,表示主设备准备好接收更多数据。一旦主设备接收到了所期望的字节数,它将发送一个NACK,告知从设备停止通信并释放总线。主设备随后发送停止条件。

下图显示了从从设备寄存器读取单个字节的示例:

存储器
从从设备寄存器读数据示例

实例

这两天调试这个模块时捕捉了一些波形,可以结合前文一起理解:

存储器
实例1

对应代码:

result = ch347_driver.i2c_set(device_index, 1)
if result:
    print("Success to set I2C speed.")
else:
    print("Failed to set I2C speed.")

result = ch347_driver.i2c_set_delay_ms(device_index, 1)
if result:
    print("Success to set I2C delay.")
else:
    print("Failed to set I2C delay.")


result = ch347_driver.stream_i2c(device_index, b'x13', 2)
if result:
    print("Success!")
else:
    print("Failed!")

存储器
实例2

对应代码:

result = ch347_driver.i2c_set(device_index, 1)
if result:
    print("Success to set I2C speed.")
else:
    print("Failed to set I2C speed.")

result = ch347_driver.i2c_set_delay_ms(device_index, 1)
if result:
    print("Success to set I2C delay.")
else:
    print("Failed to set I2C delay.")


result = ch347_driver.stream_i2c(device_index, b'x12x13x14', 8)
if result:
    print("Success!")
else:
    print("Failed!")

总结

存储器
I2C总结

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分