目录
- 1.I2C框架介绍
- 1.1 I2C硬件框架
- 1.2 I2C软件框架
- 1.3 对于Linux
- 2. I2C协议
- 2.1 传输数据的格式
- 2.2 时序图
- 2.3 协议细节
- 专栏
1.I2C框架介绍
1.1 I2C硬件框架
I2C(Inter-Integrated Circuit)是一种用于短距离通信的总线协议,它允许多个设备共享相同的总线,在嵌入式系统、传感器、EEPROM、实时时钟(RTC)等场景中广泛使用。
I2C 硬件框架可以分为以下几个关键点:
- I2C 控制器:通常嵌入在 SoC(系统级芯片)内部,管理 I2C 通信协议。一个 SoC 可以包含多个 I2C 控制器。
- I2C 设备:通过 I2C 总线连接到控制器的外围设备,如传感器、EEPROM、ADC、DAC 等。一个 I2C 总线上可以挂载多个设备。
- I2C 总线:I2C 通信仅需要两条总线:SCL(时钟线)和 SDA(数据线)。SCL 由 I2C 控制器生成时钟信号,SDA 则用于双向数据传输。SCL 和 SDA 线通常都需要上拉电阻来确保正确的信号电平。
1.2 I2C软件框架
以 I2C EEPROM 存储设备 AT24C02 为例,软件框架可以分成以下几层:
- 应用层 (APP):
- 直接向设备提出需求,例如将字符串
"www"
写入到 AT24C02 EEPROM 的某个地址。 - 不关心底层细节,只调用设备驱动提供的接口来实现需求。
- 直接向设备提出需求,例如将字符串
- I2C 设备驱动 (AT24C02 驱动):
- 设备驱动负责与 I2C 设备(如 AT24C02)打交道,知晓设备的地址和数据格式。
- 根据 I2C 协议构造数据和命令,例如写入、读取数据,发送地址等。
- 知道如何判断操作是否成功,例如通过 I2C ACK/NACK 信号。
- 最终通过控制器驱动将命令和数据发送给设备。
- I2C 控制器驱动:
- 负责根据 I2C 协议处理信号交互,如生成 SCL 时钟、发送/接收 SDA 上的数据。
- 执行 I2C 协议所需的启动信号、停止信号、发送设备地址、发送数据等低级操作。
- 控制器驱动通过硬件或 GPIO 模拟方式实现通信。
1.3 对于Linux
从I2C 框架在 Linux 中可以划分为以下几个层次:
- I2C 协议:
- I2C 协议规定了主设备与从设备之间如何通信,如主设备先发送设备地址,再发送读/写命令,以及如何握手(通过 ACK/NACK 确认操作是否成功)。
- 访问 I2C 设备的两类驱动程序:
- I2C 设备驱动程序:直接为 I2C 设备提供接口,帮助用户应用层与 I2C 设备通信。
- i2c-dev.c 驱动程序:这是 Linux 内核自带的通用 I2C 驱动,允许用户空间通过
/dev/i2c-X
设备文件直接访问 I2C 总线上的设备。用户空间可以通过 I2C-tools 提供的工具(如i2cget
,i2cset
等)直接与设备通信。
- I2C 控制器驱动程序
- 负责驱动芯片内的 I2C 控制器。每个 I2C 控制器驱动程序被称为 adapter,这些驱动程序负责执行 I2C 总线的实际传输任务。
- GPIO 模拟 I2C 控制器:如
i2c-gpio.c
,通过 GPIO 来模拟 I2C 总线信号,实现软 I2C 控制。
I2C 在 Linux 中的工作流程
- 应用程序 (APP):应用通过设备文件
/dev/i2c-X
或通过设备驱动程序发起 I2C 请求。 - I2C 设备驱动:设备驱动程序将设备地址、数据等转换为 I2C 命令,并发给 I2C 控制器驱动。
- I2C 控制器驱动:控制器驱动解析命令,并生成符合 I2C 协议的信号传输给从设备。
- I2C 设备:接收数据并执行相应操作,如写入 EEPROM 或读取传感器数据。
2. I2C协议
2.1 传输数据的格式
- 写操作
Start Addr Wr [A] Data [A] Data [A] … [A] Data [A] P
传输的数据格式,表黄的表示此时传输的数据来自从设备
- Start:开始信号
- Addr:要传输的I2C设备的地址,一般是7bit
- Wr:方向,写。1bit表示,写是0
- [A]:I2C设备的回应,找到I2C设备了,它是不是得给个回应说我是你要找到人。然后主设备发送数据给I2C设备,它是不是也得回应说我收到了
- Data:不用多说了,就是数据,一般是8bit
- P:结束信号
- 读操作
需要注意的是,主角是主设备哦,比如I2C控制器
Start Addr Rd [A] [Data] A [Data] A … A [Data] NA P
- Start:开始信号
- Addr:要传输的I2C设备的地址
- Rd:方向,读。1bit表示,读是1
- [A]:I2C设备的回应,找到I2C设备了,它是不是得给个回应说我是你要找到人。然后I2C设备就可以开始传输信号了
- A:主设备的回应,诶?I2C外设给我发消息了,我收到了是不是得给个回应??
- Data:数据
- P:结束信号
2.2 时序图
这里讲的是以传输addr找到I2C设备为例子画的,而找到后,I2C控制器(主设备)拉低SCL上的电平超过一定时间,就可以开始传输数据了。
- 开始信号 (Start Condition, S):
- 定义: 当时钟线 (SCL) 为高电平时,数据线 (SDA) 从高电平跳变为低电平,表示开始传输数据。
- 主设备(Master)向从设备(Slave)发起通信前必须发送开始信号。
- 字节传输 (Data Transmission):
- 数据格式: 数据传输是逐位进行的,总共传输 8 位(1 个字节)。
- 传输顺序: I2C 协议规定,数据传输是 从最高位 (MSB) 开始,依次传输到最低位 (LSB)。
- 时钟同步:
- 数据在线(SDA)上的数据必须在时钟线(SCL)为 高电平期间保持稳定,SCL 高电平表示数据有效。
- 数据的状态只能在 SCL 为 低电平时 发生变化。
- 响应信号 (Acknowledge, ACK):
- 定义: 在每个 8 位数据传输完成后,接收方会在第 9 个时钟周期拉低 SDA 线,表示成功接收数据,这就是 ACK 信号。
- 角色区分:
- 从设备在接收到来自主设备的数据后发送 ACK。
- 主设备在接收到来自从设备的数据后也会发送 ACK 以确认接收。
- 失败信号 (NACK): 如果接收方不拉低 SDA(即 SDA 保持高电平),则表示发送失败(NACK)。
- 结束信号 (Stop Condition, P):
- 定义: 当时钟线 (SCL) 为高电平时,数据线 (SDA) 从低电平跳变为高电平,表示数据传输结束。
- 重要性: 在数据传输完成后,主设备发送停止信号,释放总线以便其他设备使用。
I2C 信号的电平要求
- SDA 数据线上的数据只能在 SCL 时钟线为 低电平时 变化。
- SDA 数据线必须在 SCL 时钟线为 高电平时 保持稳定。任何高电平期间的变化都可能被解读为开始或停止信号。
I2C 数据传输示例
- 开始信号 (S):
- 主设备拉低 SDA(在 SCL 高电平期间),发起通信。
- 数据传输:
- 发送 8 位数据:
1 0 1 1 0 1 0 0
。 - 在 SCL 每次上升沿时,SDA 上的数据被从设备读取。
- 发送 8 位数据:
- 响应信号 (ACK):
- 第 9 个时钟周期时,接收方(从设备)在 SCL 高电平时,将 SDA 拉低,表示成功接收到数据。
- 结束信号 (P):
- 主设备在 SCL 高电平时,将 SDA 拉高,表示结束通信。
SCL: __|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|_|‾|___
SDA: --------|数据位|--------|ACK|-------- (Start/Stop信号)
数据稳定 SDA在时钟上升沿前准备好数据,时钟高电平期间数据不变
2.3 协议细节
I2C 总线是通过 SDA 线实现双向数据传输的,主设备和从设备都可以通过该线传输数据。I2C 协议的设计确保了不同设备不会同时驱动 SDA,从而避免总线冲突,以下是具体的工作机制:
- SDA 双向传输的实现
- SDA 线上有两个方向的数据流,一个是主设备向从设备发送数据,另一个是从设备向主设备发送数据。
- 连接到 SDA 线的引脚内部包含 两个端口(发射端和接收端),这样主设备可以在同一根 SDA 线上既发数据也接收数据。
- 主从设备如何错开时间来发送数据?
I2C 协议规定,传输过程中,时钟线 (SCL) 上的 9 个时钟周期分为两部分:
- 前 8 个时钟:由主设备(或从设备)发送数据;
- 第 9 个时钟:用于接收设备发送回应信号(ACK 或 NACK),即数据确认信号。
这种安排避免了主设备和从设备在同一时刻发送数据,从而实现了通信的同步协调。
- 如果主设备在前 8 个时钟发送数据,则第 9 个时钟由从设备通过 SDA 发送确认信号。
- 如果从设备在前 8 个时钟发送数据,则第 9 个时钟由主设备发送确认信号。
- 如何确保一个设备发送数据时,另一个设备不会干扰 SDA?
为了确保在一个设备发送数据时,另一个设备不会影响 SDA 上的数据传输,I2C 采用了 开漏电路(Open-Drain/开极电路) 配合 上拉电阻 来实现。
- 开漏电路(Open-Drain):设备的 SDA 引脚接入三极管,并采用开漏配置(或 CMOS 管的开极配置),可以通过三极管的导通与否控制 SDA 线的电平。具体控制机制如下:
- 三极管导通时:SDA 被拉低到 0(低电平);
- 三极管不导通时:SDA 通过上拉电阻被拉高到 1(高电平)。
- 上拉电阻的作用:
- 当某一设备不想影响 SDA 线时,它通过不驱动三极管(不导通)让 SDA 维持高电平;
- 如果设备想输出低电平,它会驱动三极管让 SDA 变为低电平。
来看一下SDA内部,两个NPN三极管,三极管导通的时候,其实就相当于SDA整条线都接地了,那肯定就被拉低为0
三极管的特性就是,当E接地的时候,B作为控制引脚,当B输出高电平,那么三极管就导通了,如果B输出低电平那么就不导通。
而只要主设备和从设备的三极管都不导通,那么SDA就不是接地,也就是被上拉电阻3.3V拉高,成高电平,就是bit1了,比较好懂吧
上图就是SDA什么时候为0,什么时候为1的真是表,A和B就是三极管各自的控制引脚
- 工作示例:主设备发送 8-bit 数据给从设备
- 前 8 个时钟周期:
- 主设备控制 SDA 线,决定发送的数据。
- 如果主设备发送 1,它不驱动三极管,SDA 被上拉电阻拉高为 1;
- 如果主设备发送 0,它驱动三极管,让 SDA 变为 0;
- 从设备在此期间不驱动 SDA 引脚(三极管处于不导通状态),避免干扰。
- 第 9 个时钟周期(ACK/NACK):
- 轮到从设备在第 9 个时钟周期通过 SDA 发送应答信号。
- 如果从设备接收到数据并确认,则驱动三极管,让 SDA 变为 0,表示发送 ACK(应答信号)。
- 如果从设备不接收数据,则 SDA 保持高电平,表示 NACK(非应答信号)。
通过这种机制,I2C 实现了在单根 SDA 线上进行主从设备的双向数据传输。
- SCL 上拉电阻的作用
I2C 总线中的 SCL 时钟线 也使用上拉电阻,目的是在特定情况下允许主或从设备控制时钟线的状态,确保通信的同步。
- 时钟拉低(Clock Stretching):
- 在第 9 个时钟周期之后,如果从设备需要更多时间处理数据,它可以拉低 SCL(通过驱动三极管),暂停通信。此时,I2C 总线将保持暂停状态,直到从设备完成操作。
- 当从设备就绪时,它会停止驱动三极管,SCL 通过上拉电阻恢复为高电平,通信可以继续。
这种设计允许从设备控制通信的节奏,确保数据能够被正确处理和传输。
专栏
设备树和总线
感兴趣也可以看看这里面的文章哦