题外话:
前些日子带了一位小弟调试硬件电路,我让他帮忙测试一下TOF的IIC时序,分析一下IIC主控与TOF之间的IIC硬件是否正常,他竟然说不会测试……
他自己从学校毕业出来一年多,在这里担任硬件工程师几个月了,竟然还没搞清楚什么是IIC,也不知道怎么测试IIC。
这让我萌发了一个念头,写一篇文章,扒一扒我眼中对IIC底层逻辑的理解,用我的理解方法去理解它。

 

I²C 介绍:

  I²C( Inter-Integrated Circuit),中文名称:集成电路总线 ,由飞利浦半导体公司(现恩智浦半导体)在1980年代设计出来的,主要是为了解决嵌入式系统中I/O端口不足的问题,它的优点是可以简化布线连接多个低速设备,是一种简单、双向、二线制、同步串行总线。

在硬件开发中,I²C 就像一条繁忙的信息高速公路,连接着各个设备,让它们能够高效地交流和协作。

 

  • I²C总线的硬件连接

SDA(Serial Data Line):串行数据线 ,用于传输数据。

SCL(Serial Clock Line):串行时钟线 ,用于同步数据传输的节奏。

GND:I²C 总线是板级总线,所有的设备都需要与主设备共地。

  I²C 总线的硬件连接就像是搭建一座简单却精巧的桥梁,仅由两根线构成:串行数据线 SDA 和串行时钟线 SCL 。这两根线通过开漏结构连接到各个设备,并配合上拉电阻实现高低电平的稳定输出,空闲时两根线电平均为高电平。

  在 I²C 设备中,通常采用 N - MOS 管来控制信号电平,当 MOS 管的栅极(G 极)为低电平时,MOS 管截止,I²C 总线由于上拉电阻的作用呈现高电平;当 G 极为高电平时,MOS 管导通,I²C 总线相当于直接接地,呈现低电平 。总线上的每个设备都有一个唯一的地址,这就好比每个人都有一个独特的身份证号码,设备之间通过这个地址来识别和通信。

  • I²C总线的信号传输

  I²C 的输入则是通过 TTL 肖特基触发器将数据传输到输入数据的寄存器中,再提供给处理器进行处理。这种硬件连接方式非常简洁高效,为 I²C 总线的数据传输奠定了坚实的基础。在实际应用中,我们可以看到多个 I²C 设备通过这两根线并联在一起,就像多个节点连接在同一座桥梁上,实现了设备之间的互联互通。

  • I²C的电平标准与特性

  I²C 的电平标准较为特殊,由于可能连接不同类型的设备,如 CMOS、NMOS 等,其高电平和低电平的标准并非固定不变 。一般来说,高电平为 0.7VDD(VDD 为电源电压),低电平为 0.3VDD 。这意味着在不同的电源电压下,I²C 的高低电平阈值会相应变化。

  I²C 总线具有一个重要的特性 ——“与” 特性。总线上的所有设备的数据线(SDA)和时钟线(SCL)在电气上是 “与” 的关系。也就是说,只要有一个设备将 SDA 或 SCL 线拉低,整个总线的相应线路就会被拉低,只有当所有设备都释放总线,即输出高阻态时,总线才会在被上拉电阻拉高到高电平。这种 “与” 特性在 I²C 总线的仲裁机制中起着关键作用,当多个主设备同时尝试控制总线时,通过 “与” 特性和仲裁规则,可以确保只有一个主设备能够成功占用总线进行数据传输,避免数据冲突问题。

  • I²C的通信模式

  I²C 采用多主多从的通信模式,允许多个主机和多个从机同时连接在总线上。主机负责发起和控制数据传输,从机则响应主机的请求。举个例子:在一场演奏会中,主机就像是一个指挥家,掌控着整个通信的节奏和流程。而从机则像是演奏者,按照指挥家的指示进行演奏。

  • I²C的传输速率

  在 I²C 总线上,发送的数据是以字节为单位进行传输每个字节后面都会跟随一个应答位,用于确认数据是否被正确接收。 其数据传输速率在不同模式下传输不同,具体如下:

    标准模式(Standard-mode):速率可达 100Kbps(每秒可发送100,000位)。

    快速模式(Fast-mode):速率可达 400Kbps(每秒可发送400,000位)。

    快速模式+(Fast-mode Plus):速率可达1 Mbps(每秒1,000,000位),这是快速模式的增强版,主要是进一步提高了数据传输速率。

    高速模式(High-speed mode):速率为3.4 Mbps(每秒3,400,000位),这种模式适用于需要更高数据传输速率的应用。

    超高速模式(Ultra Fast-mode) :速率为5 Mbps(每秒5,000,000位),I2C协议中最快的速率,适用于高速数据传输需求。

  多种传输速度可满足不同场景的需求,在实际应用中,I²C 广泛用于连接各种低速外围设备,如 EEPROM、传感器、LCD 驱动器等。比如在智能家居系统中,温湿度传感器、光照传感器等可以通过 I²C 总线将采集到的数据传输给主控芯片,实现环境数据的实时监测和控制;在手机、平板电脑等移动设备中,I²C 也用于连接摄像头、触摸屏等组件,确保设备的正常运行。

  • I²C的传输距离:

  I²C总线的传输距离与多种因素有关,在理论上,不考虑干扰、传输速率等因素,I²C 总线的理想传输距离可达 15 米,一般情况下其传输距离较短,通常在几米以内。以下是具体分析:

    标准模式:标准模式下,I²C 总线的传输速度通常为 100kbps,此时传输距离一般可以达到 2 米左右。在实际应用中,如果布线合理、干扰较小等,可能会略超过 2 米,但一般不会太长。

    快速模式:当 I²C 工作在快速模式时,传输速度可达到 400kbps,此时传输距离一般在 1 米左右。因为随着传输速度的提高,信号的上升沿和下降沿时间变短,信号的完整性容易受到影响,所以传输距离会相应缩短。

    高速模式:在高速模式下,I²C 的传输速度可高达 3.4Mbps,此时传输距离通常在几十厘米以内。高速传输时,信号对线路的寄生电容、电感等更加敏感,信号衰减和畸变会比较严重,所以能  保证稳定传输的距离更短。

  此外,传输距离还与硬件电路的设计、使用的线材质量、是否有干扰源等因素密切相关。例如,使用质量较好的屏蔽线、合理进行接地和电源去耦设计、减少电磁干扰等措施,都可以在一定程度上延长传输距离,但通常也很难超过 5 米。

如何看懂I²C通信协议时序图?

一段完整的I²C时序图顺序如下:

  I²C写操作时序起始位(SCL高电平时,SDA发生跳变,由高电平变为低电平)-->地址位(由发送方发送接收方设备地址,发送第一组数据8位中的前7位表示为设备地址位)-->写操作(由发送方发送读写操作,发送第一组数据8位中的最后1位表示为读写操作:写为:0/读为:1)-->应答信号(接收方收到数据后给发送方一个回应,发送应答信号,ACK:已应答,电平为低电平,NACK:不能应答,电平为高电平)-->指针(也叫寄存器地址。发送方发送完第一组数据并接收到应答后开始发送第二组数据,第二组数据则为寄存器地址位)-->应答信号-->发送数据(发送方发送要写的数据,每发送8位(1个字节)收到1个应答信号,直到全部发完为止。数据为SCL高电平时采集到的SDA数据)-->应答信号~~~~--->结束位(当所有数据发送完后,发送方发送结束位,此时SCL在高电平时,SDA发生跳变,由低电平变为高电平)。

例如:

VL53L1 TOF器件

(注:INDEX:表示指针或寄存器地址,As:表示从设备收到ACK应答信号)

  I²C读操作时序I²C读操作前需要先执行一下写操作,主要发送设备地址、写操作位、寄存器地址。起始位-->地址位-->写操作-->应答信号-->指针(也叫寄存器地址)-->应答信号-->结束位-->起始位-->地址位-->读操作<-->应答信号<-->读取数据(读取数据的寄存器地址位是根据写操作的地址位开始读取)<--->应答信号~~~~--->结束位。

例如:

VL53L1 TOF器件

为什么读的地址是0x53而不是0x52(01010010)呢?因为读数据时,当发完写地址和寄存器后再发一次地址时要把第一组数据的第8位写改为读,即0变为1,所以地址就变成0x53(01010011)。

  • 起始与停止信号

  在 I²C 通信的舞台上,起始信号和停止信号就像是开幕与闭幕的钟声,宣告着通信的开始与结束。

  起始信号:

    当 SCL(串行时钟线)处于高电平期间,SDA(串行数据线)从高电平向低电平的跳变,这个瞬间被定义为起始信号(Start Condition) 。起始信号如同一声嘹亮的号角,向总线上的所有设备宣告:“通信即将开始,大家做好准备!” 此时,总线上的设备会立即停止当前的其他操作,集中精力准备接收或发送数据。例如,在一个由微控制器和多个传感器组成的 I²C 系统中,当微控制器需要读取传感器的数据时,它首先会发送起始信号,通知传感器准备传输数据。

一句话概括起始位:SCL时钟高电平期间,SDA数据发生跳变,由高电平跳到低电平,则为起始位。

  结束信号:

    当 SCL 为高电平时,SDA 从低电平向高电平的跳变则表示停止信号(Stop Condition) 。停止信号就像是一曲终了的旋律,意味着本次通信已经结束,总线上的设备可以恢复到空闲状态,准备迎接下一次的通信请求。在上述例子中,当微控制器完成对传感器数据的读取后,它会发送停止信号,告诉传感器通信已经结束,双方可以暂时 “休息”。

一句话概括结束位:SCL时钟高电平期间,SDA数据发生跳变,由低电平跳到高电平,则为结束位。

所有的SDA数据采集都是在SCL为高电平时进行的,SCL为低电平时不采集数据。

  • 数据传输格式

  在 I²C 总线的数据传输过程中,SDA 上传输的数据就像是一列有序的火车,按照特定的规则行驶。每个字节由 8 位数据组成,传输时先传最高位(MSB,Most Significant Bit),再依次传输低位 。这就好比我们在书写数字时,总是先写高位数字,再写低位数字。例如,要传输数据 0x45(二进制为 0100 0101),在 I²C 总线上会先传输最高位 D7(值为 0),然后依次是 D6、D5…… 直到最低位 D0 。SDA数据传输在SCL为高电平时始终保持在高电平或低电平状态下进行传输,即SCL为高时,SDA要么为高电平,要么为低电平。

  • 扩展内容:

    在数据传输中,“先传最高位(MSB,Most Significant Bit),再依次传输低位” 是一种数据传输顺序的规定,以下从几个方面来理解:

  数据的位表示:

    数据在计算机中是以二进制的形式存储和传输的。对于一个二进制数,每一位都有其特定的权重。例如,对于一个 8 位二进制数10101101,从左到右,每一位的权重依次为27、26、25、24、23、22、21、20。这里最左边的位(1)就是最高位(MSB),它对整个数值的贡献最大,因为它代表的权重最高;最右边的位(1)是最低位(Least Significant Bit,LSB),对数值的贡献最小。

  传输顺序:

    当按照 “先传最高位,再依次传输低位” 的方式进行传输时,就意味着在数据发送过程中,首先发送的是二进制数中最左边的最高位。以刚才的10101101为例,发送方会先将最高位的1发送出去,接着依次发送次高位的0、第三位的1…… 直到最后发送最低位的1。接收方在接收数据时,会按照这个顺序依次接收每一位数据,先收到最高位,然后再按顺序接收后面的低位,最终还原出完整的数据10101101。

    I²C 一次通信过程中传输的字节数是不受限制,可以根据实际需求传输多个字节 。每传输完一个字节后,都会紧跟一个应答位(ACK,Acknowledge) 。应答位是接收方对发送方的一种回应,就像是在对话中,一方说完话后,另一方会回应 “我听到了”。当接收方成功接收到一个字节的数据后,会在第 9 个时钟周期将 SDA 线拉低,向发送方发送 ACK 信号,表示 “我已成功接收数据,请继续发送”;如果接收方由于某种原因(如数据错误、接收缓冲区已满等)无法接收数据,它会保持 SDA 线为高电平,发送非应答信号(NACK,Not Acknowledge),告诉发送方 “我无法接收数据,请停止发送或重新发送”。

  • 从机地址与读写位

  在 I²C 总线上,每个从机都有一个唯一的 7 位地址,这个地址就像是每个设备的 “身份证号码”,用于在通信中标识不同的从机 。主机在与从机通信时,首先要发送一个包含从机地址和读写位的字节。其中,读写位(R/W,Read/Write Bit)占据第 8 位,用于指示本次通信是读操作还是写操作 。当读写位为 1 时,表示主机要从从机读取数据;当读写位为 0 时,表示主机要向从机写入数据 。

  例如,假设某个从机的地址为 0x50(二进制为 01010000),如果主机要向该从机写入数据,它会发送的地址字节为 0x50(01010000)与 0(写操作)组成的 0x50(01010000);如果主机要从该从机读取数据,发送的地址字节则为 0x51(01010001) 。通过这种方式,主机可以准确地与总线上的特定从机进行通信,并明确通信的方向。

  • 实战应用:硬件与软件的 I²C

 1 /**************************************
 2 基于 Linux 平台与 BH1750 光照传感器通信
 3 代码思路:
 4 BH1750 是一款常用的光照强度传感器,通过 I2C 接口与主控设备通信。
 5 代码主要实现 I2C 设备的初始化、向传感器发送命令以及读取光照强度数据的功能。
 6 ****************************************/
 7 
 8 #include <stdio.h>
 9 #include <stdlib.h>
10 #include <fcntl.h>
11 #include <unistd.h>
12 #include <sys/ioctl.h>
13 #include <linux/i2c-dev.h>
14 
15 #define BH1750_ADDR 0x23
16 #define BH1750_POWER_ON 0x01
17 #define BH1750_CONTINUOUS_H_RES_MODE 0x10
18 
19 // 向 I2C 设备写入一个字节数据
20 int i2c_write_byte(int fd, uint8_t value) {
21     if (write(fd, &value, 1) != 1) {
22         perror("Write failed");
23         return -1;
24     }
25     return 0;
26 }
27 
28 // 从 I2C 设备读取两个字节数据
29 int i2c_read_two_bytes(int fd, uint16_t *value) {
30     uint8_t buf[2];
31     if (read(fd, buf, 2) != 2) {
32         perror("Read failed");
33         return -1;
34     }
35     *value = (buf[0] << 8) | buf[1];
36     return 0;
37 }
38 
39 // 初始化 I2C 设备
40 int init_i2c(const char *device_path, uint8_t address) {
41     int fd = open(device_path, O_RDWR);
42     if (fd < 0) {
43         perror("Failed to open I2C device");
44         return -1;
45     }
46 
47     if (ioctl(fd, I2C_SLAVE, address) < 0) {
48         perror("Failed to set I2C device address");
49         close(fd);
50         return -1;
51     }
52 
53     return fd;
54 }
55 
56 int main() {
57     const char *i2c_device = "/dev/i2c-1";  // 根据实际情况修改
58     int fd = init_i2c(i2c_device, BH1750_ADDR);
59     if (fd < 0) {
60         return -1;
61     }
62 
63     // 开启传感器
64     if (i2c_write_byte(fd, BH1750_POWER_ON) != 0) {
65         close(fd);
66         return -1;
67     }
68 
69     // 设置连续高分辨率模式
70     if (i2c_write_byte(fd, BH1750_CONTINUOUS_H_RES_MODE) != 0) {
71         close(fd);
72         return -1;
73     }
74 
75     // 等待测量完成
76     sleep(1);
77 
78     uint16_t light_level;
79     if (i2c_read_two_bytes(fd, &light_level) != 0) {
80         close(fd);
81         return -1;
82     }
83 
84     float lux = light_level / 1.2;
85     printf("Light level: %.2f lux\n", lux);
86 
87     close(fd);
88     return 0;
89 }
90 
91 
92 /********************************
93 代码说明
94 i2c_write_byte 函数:向 I2C 设备写入一个字节的数据。
95 i2c_read_two_bytes 函数:从 I2C 设备读取两个字节的数据,并将其组合成一个 16 位无符号整数。
96 init_i2c 函数:打开指定的 I2C 设备文件,并设置要通信的设备地址。
97 main 函数:初始化 I2C 设备,开启传感器,设置测量模式,等待测量完成后读取光照强度数据并计算光照强度(单位:lux)。
98 *********************************/

硬件 I²C与软件I²C

  在实际应用中,I²C 通信有两种实现方式:硬件 I²C 和软件 I²C ,它们各有优劣,就像是不同类型的交通工具,适用于不同的出行场景。

  • (一)硬件 I²C的优缺点

  硬件 I²C 是利用芯片内部集成的 I²C 硬件外设来实现通信 。以 STM32 系列微控制器为例,其内部的硬件 I²C 模块包含专门的寄存器和逻辑电路,能够自动生成 I²C 通信所需的时序信号,如起始信号、停止信号、时钟信号等 。这就好比一辆配备了先进自动驾驶系统的汽车,能够自动按照预定的路线和规则行驶。

  硬件 I²C 的速度非常快,通信速率可以达到几十 MHz ,能够满足对数据传输速度要求较高的应用场景,如高速数据采集、实时图像传输等 。而且,由于硬件 I²C 是由硬件电路实现,其稳定性和可靠性较高,不容易受到软件干扰和其他任务的影响 。

  然而,硬件 I²C 也有一些局限性。它需要占用芯片的特定引脚,这些引脚通常是固定的,不能随意更改 。这就像是一辆只能在特定轨道上行驶的火车,受到轨道的限制。如果这些引脚被其他功能占用,或者需要连接多个 I²C 设备但引脚数量不足,就会面临硬件资源紧张的问题 。此外,硬件 I²C 的配置和使用相对复杂,需要对芯片的寄存器和硬件特性有深入的了解,增加了开发的难度和工作量 。

  • (二)软件 I²C的优缺点

  软件 I²C 则是通过软件编程的方式,利用普通的 GPIO(通用输入输出)引脚来模拟 I²C 通信的时序 。以 51 单片机为例,它没有硬件 I²C 接口,但可以通过编写代码控制 GPIO 引脚的电平变化,来模拟 SDA 和 SCL 信号的时序,从而实现 I²C 通信 。这就好比一个人按照地图和导航的指示,自己驾驶汽车到达目的地。

  软件 I²C 的最大优势在于其灵活性,它不需要特定的硬件资源,只要有空闲的 GPIO 引脚,就可以实现 I²C 通信 。这使得在一些资源有限的微控制器上,或者需要连接多个 I²C 设备但硬件资源紧张的情况下,软件 I²C 成为了一种可行的解决方案 。此外,软件 I²C 的代码实现相对简单,易于理解和调试,对于初学者来说更容易上手 。

  不过,软件 I²C 的速度相对较慢,通信速率一般在几十 kHz 到几百 kHz 之间 ,这是因为软件模拟时序需要占用 CPU 的时间,而且软件执行的速度也受到 CPU 性能的限制 。在数据量较大、对传输速度要求较高的场景下,软件 I²C 可能无法满足需求 。同时,由于软件 I²C 依赖于 CPU 的运行,当 CPU 被其他任务占用时,可能会导致 I²C 通信的延迟或中断,影响通信的稳定性 。

 

结尾:

  I²C 底层逻辑是硬件开发中不可或缺的基础知识,它通过简单的两根线实现了设备之间的高效通信 。从物理基础上的 SDA 与 SCL 信号传输,到通信规则中的起始停止信号、数据传输格式和从机地址读写位,再到实战应用中的硬件 I²C 和软件 I²C 实现方式,每一个环节都紧密相连,共同构成了 I²C 通信的完整体系 。

  理解 I²C 底层逻辑,就像是掌握了一把开启硬件世界大门的钥匙,能够让我们更加深入地理解硬件设备之间的交互方式,为解决硬件开发中的各种问题提供有力的支持 。无论是在智能家居、工业控制,还是在移动设备、物联网等领域,I²C 都发挥着重要的作用 。

  希望有缘人能通过本文的介绍,能够对 I²C 底层逻辑有更深入的理解和认识。在今后的硬件开发中,灵活运用 I²C 技术,不断探索和创新,将所学知识应用到实际项目中,创造出更多有价值的作品 。如果你在学习过程中有任何疑问或心得,欢迎在评论区留言分享,让我们一起共同进步 。