接口/总线/驱动
I2C(也常写作IIC,I2C),全称为Inter-Integrated Circuit(“互连集成电路”),用于在集成电路之间进行短距离数据传输。它由Philips(现在的NXP半导体)公司于1980年代初开发,并成为一种广泛应用于电子设备之间通信的标准。I2C协议简单、灵活且广泛支持,常用于连接传感器、存储器、显示屏和其他外设到微控制器、微处理器或其他集成电路上。这是一种简单的双向双线总线,非常适合用于微控制器与外设之间,或者多个微控制器之间的高效互连控制。
其名称反映了最初的设计目的:它专为“集成电路”间的通信设计。I2C的两条线包括SDA(串行数据线),用于传输信息;和SCL(串行时钟线),负责同步。I2C的一个优点是它只需要这两根线进行通信,这可以在复杂系统中有效地利用资源。
它也是一种在许多产品中广泛使用的标准,扩展和集成相对简单。
I2C的主要特点包括:
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线都保持高电平,总线将被认为是空闲的。
主设备访问从设备的一般过程如下:
I2C通信通过主设备发送起始条件(START condition)来初始化,并通过主设备发送停止条件(STOP condition)来终止。在SCL线为高电平时,SDA线上的高至低的跳变定义了起始条件。在SCL线为高电平时,SDA线上的低至高的跳变定义了停止条件。
起始和停止条件示例
重复的起始条件(Repeated START condition)类似于起始条件(START condition),用作连续的停止条件和起始条件的替代。它在外观上与起始条件相同,但与起始条件不同,因为它发生在停止条件之前(总线不处于空闲状态)。这在主设备希望启动新的通信,但不希望通过停止条件使总线空闲,从而可能失去对总线的控制权(在多主设备环境中)时非常有用。
在每个SCL时钟脉冲期间传输一个数据位。一个字节由SDA线上的八个位组成。一个字节可以是设备地址、寄存器地址或从从设备写入或读取的数据。数据以最高有效位(MSB)优先的顺序传输。在START和STOP条件之间,可以从主设备向从设备传输任意数量的数据字节。在时钟周期的高电平期间,SDA线上的数据必须保持稳定,因为当SCL为高电平时,数据线的变化被解释为控制命令(START或STOP)。
单字节传输示例
接收器在每个数据字节(包括地址字节)后发送一个ACK位。ACK位允许接收器向发送器传达字节已成功接收,并且可以发送另一个字节。
在接收器发送ACK之前,发送器必须释放SDA线。为了发送ACK位,接收器在ACK/NACK相关的时钟周期(周期9)的低电平期间拉低SDA线,以便在ACK/NACK相关的时钟周期的高电平期间,SDA线保持稳定低电平。必须考虑到设置时间和保持时间。
当SDA线在ACK/NACK相关的时钟周期中保持高电平时,这被解释为非应答(NACK)。有几种情况会导致产生NACK:
NACK波形示例
必须通过读取或写入从设备的寄存器来发送和接收数据。寄存器是从设备内存中包含信息的位置,无论是配置信息还是一些采样数据用于发送回主设备。主设备必须向这些寄存器中写入信息,以指示从设备执行任务。
虽然在I2C从设备中常见的是寄存器,但请注意,并非所有的从设备都会有寄存器。一些设备非常简单,只包含一个寄存器,可以通过在从设备地址之后立即发送寄存器数据来直接写入该寄存器,而无需寻址寄存器。单寄存器设备的一个例子是8位I2C开关,它通过I2C命令进行控制。由于它只有一个位来启用或禁用通道,因此只需要一个寄存器,主设备只需在从设备地址之后写入寄存器数据,跳过寄存器编号。
要在I2C总线上写入数据,主设备将在总线上发送一个带有从设备地址的起始条件,并将最后一位(R/W位)设置为0,表示写入操作。在从设备发送应答位后,主设备将发送要写入的寄存器地址。从设备再次发送应答位,通知主设备它已准备好。然后,主设备将开始向从设备发送寄存器数据,直到主设备发送完所有需要的数据(有时仅为一个字节),然后通过发送停止条件终止传输。
下图显示了向从设备寄存器写入单个字节的示例:
向从设备寄存器写数据示例
从从设备读取数据与写入数据非常相似,但有一些额外的步骤。为了从从设备读取数据,主设备首先必须告知从设备它希望从哪个寄存器读取数据。主设备通过类似写入操作的方式开始传输,发送带有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总结
全部0条评论
快来发表一下你的评论吧 !