4. I2C/SPI 控制总线实战:Sensor 寄存器读写、时序分析、Linux 下 I2C 设备驱动编写、调试技巧
摄像头 Sensor 的控制总线,说白了就是它的“神经”。你给 Sensor 上电、配分辨率、调增益、设帧率,全得通过 I2C 或 SPI 往寄存器里写值。我见过不少新手,Sensor 死活不出图,最后发现是 I2C 时序不对,或者地址写错了。这一章,咱们就把这根“神经”彻底打通。
4.1 为什么 Sensor 离不开 I2C/SPI?
Sensor 的像素阵列只管光电转换,但怎么转、转成多大、要不要开 HDR,这些配置信息都存在内部的寄存器里。主控(SoC)必须通过串行总线去读写这些寄存器。I2C 和 SPI 就是最常用的两种。
| 特性 | I2C | SPI |
|---|---|---|
| 引脚数 | 2 根(SCL, SDA) | 4 根(SCLK, MOSI, MISO, CS) |
| 通信方式 | 半双工,主从架构 | 全双工,主从架构 |
| 速率 | 标准 100k/400k,高速 1M~3.4M | 通常 10M~50M,甚至更高 |
| 寻址 | 7 位或 10 位设备地址 | 片选信号(CS)选择设备 |
| 适用场景 | 配置寄存器、低速控制 | 高速数据流、多设备级联 |
我个人习惯,对于摄像头 Sensor 的寄存器配置,99% 都用 I2C。因为 Sensor 寄存器读写频率不高,I2C 的 400kHz 完全够用,而且省引脚。只有极少数需要高速 OTP 烧录或特殊配置的 Sensor,才会用到 SPI。
4.2 I2C 时序分析:从波形到寄存器
很多驱动工程师把 I2C 当成黑盒子,调不通就换电阻、改频率。其实你只要看懂示波器上的波形,问题就解决了一半。
4.2.1 标准 I2C 写时序
写一个 Sensor 寄存器,通常需要 3 个阶段:
- 起始条件:SCL 高电平时,SDA 从高拉低。
- 发送设备地址 + 写位:7 位地址 + 0(写),等待 ACK。
- 发送寄存器地址 + 数据:先发 16 位或 8 位寄存器地址,再发数据,每字节后跟 ACK。
- 停止条件:SCL 高电平时,SDA 从低拉高。
举个例子,写 Sensor 的 0x3012 寄存器为 0x10:
// 伪代码:I2C 写操作
Start();
WriteByte(0x30 << 1 | 0); // 设备地址 0x30,写操作
WaitAck();
WriteByte(0x30); // 寄存器高字节
WaitAck();
WriteByte(0x12); // 寄存器低字节
WaitAck();
WriteByte(0x10); // 数据
WaitAck();
Stop();
4.2.2 时序参数与上拉电阻
I2C 是开漏输出,必须外接上拉电阻。电阻值选不对,波形就会变形。
| 参数 | 标准模式 (100kHz) | 快速模式 (400kHz) |
|---|---|---|
| 上拉电阻 | 4.7kΩ ~ 10kΩ | 1.5kΩ ~ 4.7kΩ |
| 上升时间 (tr) | ≤ 1000ns | ≤ 300ns |
| 下降时间 (tf) | ≤ 300ns | ≤ 300ns |
嗯,这里要注意:如果总线电容太大(比如走线过长),上升时间会变慢。我建议用示波器实测一下 SCL 的上升沿,如果太缓,就减小上拉电阻。但别小于 1kΩ,否则驱动能力不够,反而拉不低。
4.3 Linux 下 I2C 设备驱动编写
在 Linux 里写 Sensor 驱动,本质上就是注册一个 I2C 客户端,然后通过 i2c_transfer 或 smbus 接口读写寄存器。
4.3.1 驱动框架
一个标准的 Sensor I2C 驱动,核心结构如下:
static const struct i2c_device_id sensor_id[] = {
{ "sensor_ov5640", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, sensor_id);
static const struct of_device_id sensor_of_match[] = {
{ .compatible = "ovti,ov5640" },
{ }
};
MODULE_DEVICE_TABLE(of, sensor_of_match);
static struct i2c_driver sensor_driver = {
.driver = {
.name = "sensor_ov5640",
.of_match_table = sensor_of_match,
},
.probe = sensor_probe,
.remove = sensor_remove,
.id_table = sensor_id,
};
module_i2c_driver(sensor_driver);
probe 函数里,你要做三件事:
- 分配并初始化 v4l2_subdev。
- 读取 Sensor 的 Chip ID,确认硬件连接正确。
- 注册 subdev 到媒体框架。
4.3.2 寄存器读写封装
我习惯把读写操作封装成两个函数,方便调用:
static int sensor_read_reg(struct i2c_client *client, u16 reg, u8 *val)
{
struct i2c_msg msg[2];
u8 reg_buf[2] = { reg >> 8, reg & 0xFF };
u8 val_buf;
msg[0].addr = client->addr;
msg[0].flags = 0; // 写
msg[0].len = 2;
msg[0].buf = reg_buf;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD; // 读
msg[1].len = 1;
msg[1].buf = &val_buf;
if (i2c_transfer(client->adapter, msg, 2) != 2)
return -EIO;
*val = val_buf;
return 0;
}
static int sensor_write_reg(struct i2c_client *client, u16 reg, u8 val)
{
u8 buf[3] = { reg >> 8, reg & 0xFF, val };
int ret;
ret = i2c_master_send(client, buf, 3);
if (ret != 3)
return -EIO;
return 0;
}
4.4 调试技巧:让问题无处遁形
驱动写好了,上电一试,Sensor 没反应。怎么办?别慌,按下面的步骤排查。
4.4.1 硬件层排查
- 测电压:用万用表量 Sensor 的 VDD、VDDIO、DVDD 是否正常。我遇到过 VDDIO 只有 1.6V,但 Sensor 要求 1.8V,结果 I2C 死活不通。
- 看波形:用示波器抓 SCL 和 SDA。看起始条件、地址、ACK 位是否完整。如果 SDA 一直高,说明 Sensor 没拉低 ACK。
- 检查地址:Sensor 的 I2C 地址通常由引脚电平决定。确认硬件上的 ADDR 引脚接法跟驱动里写的一致。
4.4.2 软件层调试
- 使用 i2c-tools:在用户态用 i2cdetect 扫描总线,看 Sensor 是否在预期地址上响应。再用 i2cget 和 i2cset 手动读写寄存器,验证驱动逻辑。
- 打印调试信息:在 probe 函数里加 dev_info,打印 Chip ID。如果读回来的 ID 不对,大概率是 I2C 通信有问题。
- 检查 DTS:确保设备树里 i2c 节点的时钟频率、pinctrl 配置正确。我曾经因为 pinctrl 没配好,I2C 引脚被复用成 GPIO,导致通信失败。
4.5 知识体系总览
下面这张图,把本章的核心逻辑串起来了。从总线选型,到时序分析,再到驱动编写和调试,每一步都环环相扣。
这张图把 I2C 和 SPI 的选型、时序、驱动、调试串在了一起。你写驱动时,脑子里要有这张图:先选总线,再抠时序,然后写代码,最后用示波器和 i2c-tools 验证。每一步都走扎实了,Sensor 控制就稳了。