3. I2C通信协议:时序、地址、读写与速率配置

I2C在摄像头调试里有多重要?这么说吧,你调MIPI之前,得先确保I2C能把寄存器写进去。我见过不少新手,MIPI信号调得漂漂亮亮,结果图像出不来——最后发现是I2C根本没通。所以这一章,咱们把I2C的底细彻底摸清楚。

3.1 I2C总线的基本结构

I2C只有两根线:SCL(时钟)和SDA(数据)。所有设备都挂在这两根线上,靠地址区分。说白了,这就是一条“多主多从”的串行总线。

关键点:I2C是开漏输出,必须外接上拉电阻。阻值选错了,整条总线都可能挂掉。

我习惯用4.7kΩ做默认上拉,但如果总线电容大(比如线长超过10cm),就换2.2kΩ。你想想看,上拉太小会拉低信号边沿速率,太大又会让上升沿变缓——这是个平衡。

3.2 I2C时序:START、STOP与数据有效性

I2C的时序其实不复杂,记住几个关键动作就行:

  • START条件:SCL高电平时,SDA从高变低
  • STOP条件:SCL高电平时,SDA从低变高
  • 数据采样:SCL低电平时改变SDA,SCL高电平时采样SDA

嗯,这里要注意:每次传输的数据是8位,后面跟一个ACK(应答位)。从机如果拉低SDA表示ACK,不拉低就是NACK。

实战技巧:我在调试OV5640时遇到过一个问题——写寄存器后读回来全是0xFF。后来用示波器一看,发现STOP条件没发完整,从机根本没收到结束信号。所以,START和STOP的时序一定要用示波器确认。

3.3 从机地址:7位与10位模式

摄像头模组绝大多数用7位地址。比如OV5640的地址是0x3C(写)和0x3D(读),实际上7位地址是0x1E,左移一位加上读写位。

地址类型 位宽 常见场景 举例
7位地址 7 bits 绝大多数摄像头模组 0x1E → 写0x3C,读0x3D
10位地址 10 bits 少数复杂传感器 首字节1111 0XX + 后8位

我个人习惯在代码里直接用8位地址(左移后的值),这样读写函数调用时不用再移位,少犯一次错。

避坑指南:我曾经被一个模组坑过——它的数据手册写的是0x20,但实际是0x40(7位地址0x20左移一位)。所以拿到新模组,先用I2C扫描工具确认真实地址,别信手册。

3.4 读写操作:单字节与多字节

摄像头模组的寄存器读写,通常有两种方式:

3.4.1 单字节写

START + 从机地址(写) + ACK + 寄存器地址 + ACK + 数据 + ACK + STOP

3.4.2 单字节读

START + 从机地址(写) + ACK + 寄存器地址 + ACK + 
START + 从机地址(读) + ACK + 数据 + NACK + STOP

注意读操作有个“重复起始条件”(Repeated START),不是STOP再START。为什么?因为如果先STOP再START,总线可能被其他主机抢走。

3.4.3 多字节读写(突发模式)

很多摄像头支持连续读写,比如一次写多个寄存器配置。这能大幅提高初始化速度。

// 多字节写示例(伪代码)
i2c_write_burst(dev_addr, reg_addr, data_array, length);
// 内部实现:START + addr_w + ACK + reg_addr + ACK + 
//           data[0] + ACK + data[1] + ACK + ... + data[n-1] + ACK + STOP

效率对比:单字节写100个寄存器需要200次总线操作(100次写地址+100次写数据),而突发模式只需要2次(1次地址+1次数据块)。初始化时间能差几十毫秒。

3.5 速率配置:标准、快速与高速模式

I2C有几种标准速率:

模式 最大速率 适用场景
标准模式 (Sm) 100 kHz 兼容性要求高的场景
快速模式 (Fm) 400 kHz 大多数摄像头模组
快速模式+ (Fm+) 1 MHz 高速传感器初始化
高速模式 (Hs) 3.4 MHz 极少用于摄像头

摄像头模组我一般用400kHz。为什么不是1MHz?因为很多模组内部有RC振荡器,频率高了时序容易出错。我遇到过IMX219在1MHz下写寄存器偶尔失败,降到400kHz就稳了。

速率选择建议:

  • 调试阶段用100kHz,信号完整性好,容易抓波形
  • 量产阶段用400kHz,兼顾速度和稳定性
  • 如果总线挂载多个设备,选最慢的那个设备支持的速率

3.6 I2C时序的SVG流程图

下面这张图把I2C的完整读写流程画出来了,包括START、地址、数据、ACK和STOP。我建议你把它存下来,调试时对照着看。

I2C读写操作完整时序流程 START条件 从机地址(写) ACK 寄存器地址 ACK 数据字节 ACK STOP条件 重复START 从机地址(读) ACK 数据字节 NACK STOP条件 说明: 写操作:START → 地址(写) → ACK → 寄存器地址 → ACK → 数据 → ACK → STOP 读操作:写寄存器地址后,发重复START,再发地址(读),从机返回数据,主机发NACK结束

3.7 实际调试中的I2C问题

我总结几个最常见的I2C坑:

  1. SDA被拉死:从机没释放总线,通常是时序错误导致从机卡在某个状态。解决办法:发9个SCL时钟强制复位从机。
  2. ACK丢失:地址不对或从机没上电。先确认电源,再用示波器看地址波形。
  3. 数据错位:SCL频率太高,从机跟不上。降速试试。
  4. 总线冲突:多个主机同时发起传输。检查是否有其他I2C主设备在抢总线。

避坑指南:我曾经调试一个GC2053模组,I2C怎么都写不进去。折腾了两天,最后发现是模组的I2C地址引脚没拉对——有个地址选择引脚悬空了,导致地址漂移。所以,硬件上电后先用逻辑分析仪抓一下地址,确认从机真的有应答。

3.8 速率配置的代码示例

以Linux内核I2C驱动为例,配置速率通常这样写:

// 设置I2C适配器速率
struct i2c_adapter *adap = i2c_get_adapter(bus_num);
adap->timeout = msecs_to_jiffies(100);  // 超时100ms

// 通过ioctl设置速率(用户空间)
int fd = open("/dev/i2c-1", O_RDWR);
ioctl(fd, I2C_TIMEOUT, 1);     // 超时1个jiffy
ioctl(fd, I2C_RETRIES, 3);     // 重试3次

// 设置从机地址
struct i2c_msg msg;
msg.addr = 0x3c;  // 8位写地址
msg.flags = 0;    // 写操作
msg.len = 2;
msg.buf = reg_data;  // 寄存器地址+数据

struct i2c_rdwr_ioctl_data work;
work.msgs = &msg;
work.nmsgs = 1;
ioctl(fd, I2C_RDWR, &work);

嗯,这里要注意:不同平台的I2C控制器,速率配置方式差别很大。有的在设备树里配,有的在驱动里写死。我建议你先把速率调到100kHz确认通信正常,再逐步提高。

我的习惯:调试阶段用100kHz,所有功能验证通过后,再改到400kHz做压力测试。如果连续读写1000次不出错,才算稳定。

I2C看起来简单,但坑不少。记住一句话:示波器是I2C调试最好的朋友。别光看代码,把波形抓出来,什么问题都一目了然。