3、I2C协议详解:I2C总线物理层、起始条件与停止条件、数据帧格式、地址与读写位、应答机制

3.1 物理层:两根线打天下

I2C协议,说白了就是两根线的事。一根叫SCL(时钟线),一根叫SDA(数据线)。你别看它只有两根线,却能挂载几十个设备,这在硬件设计上其实挺巧妙的。

我刚开始做加密狗项目时,第一反应是「两根线够用吗?」后来发现,正是这种简洁的设计,让I2C成了嵌入式世界里的「通用语」。几乎所有MCU、传感器、加密芯片都支持它。

物理层有几个关键点,我简单列一下:

  • 开漏输出:SCL和SDA都是开漏结构。这意味着它们只能拉低,不能主动拉高。高电平靠上拉电阻实现。
  • 上拉电阻:一般选4.7kΩ或10kΩ。总线速度越高,电阻值要越小。我遇到过有人用100kΩ上拉,结果400kHz模式下波形惨不忍睹。
  • 线数与设备数:理论上最多挂127个设备(7位地址)。实际中受限于总线电容,一般不超过20个。
  • 速率模式:标准模式100kHz,快速模式400kHz,高速模式3.4MHz。加密狗通信通常用100kHz或400kHz。
个人经验: 我建议你在画PCB时,把SCL和SDA走线尽量短,且远离高频信号。曾经有个项目,I2C通信老是丢包,查了半天发现是SDA线跟SPI时钟线并行走了一截。分开后问题就解决了。

3.2 起始条件与停止条件

I2C总线上,什么时候开始通信,什么时候结束,都有明确的信号。这两个信号由主机(通常是MCU)产生。

起始条件(Start Condition):SCL为高电平时,SDA从高电平跳变到低电平。

停止条件(Stop Condition):SCL为高电平时,SDA从低电平跳变到高电平。

你想想看,这两个信号其实很巧妙。平时总线空闲时,SCL和SDA都是高电平。起始条件打破了这种空闲状态,告诉所有设备:「注意了,我要开始发数据了!」停止条件则恢复空闲状态。

避坑指南: 我曾经在调试加密狗时,发现起始条件总是发不出去。查了示波器才发现,MCU的I2C外设初始化时,SDA被意外拉低了。总线一直处于「半起始」状态。解决办法是在初始化时先让SDA和SCL都输出高电平,再产生起始条件。

3.3 数据帧格式

I2C的数据传输是以字节为单位的。每个字节8位,后面跟一个应答位。所以实际上每传输一个字节,需要9个SCL时钟周期。

数据帧的格式如下:

起始条件 | 7位地址 | 读写位 | 应答位 | 数据字节1 | 应答位 | ... | 数据字节N | 应答位 | 停止条件

这里有个细节:数据在SCL低电平时变化,高电平时采样。也就是说,SDA上的数据必须在SCL高电平期间保持稳定。这个规则保证了数据传输的可靠性。

我习惯用表格来理解这个过程:

阶段 SCL状态 SDA行为 说明
起始条件 高电平 高→低跳变 通知所有设备开始通信
地址传输 时钟脉冲 逐位发送地址 高位在前,低位在后
读写位 时钟脉冲 0=写,1=读 紧跟在地址之后
应答位 时钟脉冲 从机拉低SDA 确认收到地址
数据传输 时钟脉冲 逐位发送/接收数据 每个字节后跟应答
停止条件 高电平 低→高跳变 释放总线

3.4 地址与读写位

I2C的地址有7位和10位两种。加密狗芯片大多用7位地址,所以我重点讲这个。

7位地址范围是0x00到0x7F。但有些地址是保留的,比如0x00用于通用呼叫,0x01用于启动字节等。实际可用的地址大约112个。

地址后面紧跟一个读写位:

  • 0:表示主机要写数据到从机
  • 1:表示主机要从从机读数据

举个例子:加密狗地址是0x50(二进制01010000)。如果主机要写数据,发送的字节是0xA0(01010000 + 0)。如果要读数据,发送0xA1(01010000 + 1)。

注意: 很多新手会把设备地址和发送的地址字节搞混。设备地址是7位的,但发送时左移一位,最低位补读写位。比如设备地址0x50,发送时是0xA0或0xA1。我见过有人直接发0x50,结果从机根本不响应。

3.5 应答机制

应答机制是I2C协议里最人性化的设计之一。每个字节传输完成后,接收方必须给出应答,告诉发送方:「我收到了,继续发下一个。」

具体流程是这样的:

  1. 发送方发出8位数据后,释放SDA线(拉高)
  2. 发送方产生第9个SCL时钟脉冲
  3. 接收方在这个脉冲期间拉低SDA,表示应答(ACK)
  4. 如果接收方不拉低SDA(保持高电平),表示非应答(NACK)

NACK通常意味着:

  • 地址错误,总线上没有这个设备
  • 设备忙,无法处理数据
  • 数据传输结束,接收方不想再接收了

我记得有一次调试加密狗,主机发送地址后一直收不到ACK。用示波器一看,地址字节是对的,但加密狗的电源纹波太大,导致它无法正常工作。加了个滤波电容就搞定了。

实用技巧: 在代码里,我习惯在每次发送地址后检查ACK。如果连续3次收不到ACK,就报错并重新初始化I2C总线。这个机制帮我避免了很多「假死」问题。

3.6 实际应用中的注意事项

说了这么多理论,最后聊点实战经验。加密狗与主控芯片通过I2C通信时,有几个坑你一定要注意:

  • 时钟拉伸:有些从机处理数据慢,会拉低SCL让主机等待。你的MCU必须支持时钟拉伸,否则会超时。
  • 总线冲突:多个主机同时发起通信时,I2C有仲裁机制。但加密狗场景一般只有一个主机,所以不用太担心。
  • 电平匹配:如果MCU是3.3V,加密狗是5V,需要加电平转换。直接用电阻分压可能会出问题。
  • 上拉电阻位置:上拉电阻应该靠近主机端,而不是从机端。这样可以减少信号反射。

嗯,I2C协议看起来简单,但用好了真能省不少事。加密狗通信中,I2C的稳定性和简洁性让它成了首选。你只要把物理层、起始停止条件、数据帧格式、地址读写位和应答机制这五点吃透,基本就能搞定大部分问题了。

下一章我会讲SPI协议,那个速度更快,但线也更多。到时候咱们再细聊。