2. Sensor驱动核心:I2C/SPI通信协议详解,Sensor寄存器读写流程,PLL配置与时钟树分析,帧率与分辨率设定

Sensor驱动,说白了就是让摄像头芯片“开口说话”。你给它供电、给它时钟,它就得乖乖告诉你它准备好了,然后你通过一根总线去配置它的内部寄存器。这根总线,绝大多数情况下就是I2C或SPI。

我刚开始调Sensor时,总觉得I2C很简单,两根线嘛。直到有一次,上电后Sensor就是不回ACK,查了三天,结果是上电时序差了0.5ms。嗯,从那以后,我对通信协议再也不敢马虎了。

2.1 I2C与SPI:选谁?怎么用?

先看一张对比表,帮你快速建立印象:

特性 I2C SPI
线数 2线(SCL, SDA) 4线(SCLK, MOSI, MISO, CS)
速率 标准100kHz~3.4MHz 通常10MHz~50MHz
寻址方式 7位/10位设备地址 片选信号(CS)
通信方式 半双工 全双工
典型场景 寄存器配置、参数读取 高帧率数据输出、RAW图传输

我个人习惯:配置寄存器用I2C,传图像数据用SPI。为什么?I2C有应答机制,你写一个寄存器,Sensor会回复ACK,这样你能确认它收到了。SPI虽然快,但没有硬件应答,你得靠读回验证,调试起来多一步。

小技巧: 很多Sensor同时支持I2C和SPI,通过一个引脚电平选择。上电前一定要确认这个引脚的电平,我见过有人焊板子时漏了上拉电阻,结果Sensor一直工作在SPI模式,I2C怎么都扫不到设备。

2.2 Sensor寄存器读写流程

你想想看,Sensor内部有几百个寄存器,控制着曝光、增益、帧率、输出格式等等。你要做的,就是通过I2C/SPI往这些地址写值。

标准的读写流程是这样的:

  1. 发送设备地址 + 写标志(I2C从设备地址左移1位,最低位为0)
  2. 等待ACK(如果没收到,说明Sensor没准备好或地址错了)
  3. 发送寄存器高8位地址(很多Sensor寄存器地址是16位的)
  4. 发送寄存器低8位地址
  5. 发送要写入的数据
  6. 等待ACK(确认写入成功)
  7. 发送停止条件

读操作稍微复杂一点,需要先写地址,再重新发送起始条件+读标志,然后读取数据。我贴一段实际项目中用过的I2C读写代码,你感受一下:

// I2C写寄存器示例(16位地址,8位数据)
int sensor_write_reg(uint8_t slave_addr, uint16_t reg_addr, uint8_t data) {
    uint8_t buf[3];
    buf[0] = (reg_addr >> 8) & 0xFF;  // 高8位地址
    buf[1] = reg_addr & 0xFF;         // 低8位地址
    buf[2] = data;                    // 要写入的数据

    // 发送设备地址+写标志,然后发送3字节数据
    int ret = i2c_master_write(slave_addr, buf, 3);
    if (ret != 0) {
        // 我曾经在这里吃过亏:没有加延时,连续写太快Sensor没反应过来
        printf("I2C write failed at addr 0x%04X\n", reg_addr);
        return -1;
    }
    return 0;
}
避坑指南: 我曾经遇到过一个问题:写寄存器时一切正常,但读回来的值总是0xFF。查了半天,发现是读操作前没有重新发送起始条件。I2C读操作的正确姿势是:写地址 → 写寄存器地址 → 重复起始条件 → 读地址 → 读数据。少一个重复起始,Sensor就不会把数据放到总线上。

2.3 PLL配置与时钟树分析

Sensor内部有一个PLL(锁相环),它负责把外部输入的参考时钟(比如24MHz)倍频成Sensor内部各个模块需要的工作时钟。你想想看,一个Sensor内部有:像素阵列、行读出电路、ADC、MIPI接口……每个模块对时钟的要求都不一样。

典型的时钟树长这样:

Sensor 时钟树结构图 外部晶振 (24MHz) PLL 锁相环 倍频系数: 100x → 2.4GHz 分频器 像素阵列时钟 ~48MHz ADC采样时钟 ~96MHz MIPI接口时钟 ~1.2Gbps 说明:PLL输出频率 = 输入频率 × 倍频系数 / 分频系数 配置时需确保每个分支的时钟在Sensor规格书规定的范围内

配置PLL时,你需要在Sensor的寄存器里设置三个关键参数:

  • 倍频系数(Multiplier):决定PLL输出频率的上限
  • 分频系数(Divider):把PLL输出降到各模块需要的频率
  • 预分频(Pre-divider):有时输入时钟太高,需要先分频再进PLL

举个例子,假设Sensor规格书要求像素时钟为48MHz,MIPI时钟为1.2Gbps。外部晶振24MHz,PLL倍频100倍得到2.4GHz,然后:

  • 像素时钟分支:2.4GHz ÷ 50 = 48MHz
  • MIPI时钟分支:2.4GHz ÷ 2 = 1.2GHz(对应DDR模式下1.2Gbps)
关键点: PLL配置错误是Sensor不工作的常见原因。我建议你先把PLL配置成Sensor的“默认模式”,确认能出图后,再根据你的帧率需求去调整。不要一上来就想跑最高帧率,容易翻车。

2.4 帧率与分辨率设定

帧率和分辨率,说白了就是一对冤家。你想跑高分辨率,帧率就得降;你想跑高帧率,分辨率就得砍。为什么?因为Sensor的像素时钟是有限的。

帧率的计算公式很简单:

帧率 = 像素时钟频率 / (水平总像素 × 垂直总像素)

其中:
- 水平总像素 = 有效水平像素 + HBlank(水平消隐)
- 垂直总像素 = 有效垂直像素 + VBlank(垂直消隐)

你想想看,如果你把分辨率从1080P降到720P,但保持像素时钟不变,帧率自然就上去了。反过来,如果你想在1080P下跑60fps,但像素时钟只有100MHz,那你就得算一算:

100,000,000 / (1920 + HBlank) / (1080 + VBlank) = 60
→ (1920 + HBlank) × (1080 + VBlank) ≈ 1,666,667

如果HBlank取200,VBlank取30,那么(1920+200)×(1080+30)=2120×1110=2,353,200,远大于1,666,667。说明100MHz的像素时钟跑不动1080P 60fps,你得提高像素时钟,或者降低分辨率。

实战经验: 我在调试一款500万像素Sensor时,客户要求30fps全分辨率输出。我算了一下,像素时钟需要跑到约150MHz。但Sensor规格书说最大像素时钟只有120MHz。怎么办?最后和客户协商,把帧率降到25fps,同时微调了HBlank和VBlank的值,刚好卡在120MHz以内。所以,帧率、分辨率、像素时钟三者必须一起算,缺一不可。

配置帧率和分辨率,通常需要操作以下寄存器:

寄存器名称 作用 典型值
PLL_MULTIPLIER PLL倍频系数 0x64 (100倍)
PLL_DIVIDER PLL分频系数 0x32 (50分频)
HTS (Horizontal Total Size) 水平总像素(含消隐) 0x0848 (2120)
VTS (Vertical Total Size) 垂直总像素(含消隐) 0x0456 (1110)
X_OUTPUT_SIZE 有效水平像素 0x0780 (1920)
Y_OUTPUT_SIZE 有效垂直像素 0x0438 (1080)
重要提醒: 修改HTS和VTS时,一定要先停止Sensor的流(通常通过写0x00到模式控制寄存器),配置完成后再重新开启。我曾经在Sensor还在出图时直接改VTS,结果画面出现撕裂,查了半天才发现是“热修改”导致的。Sensor不是所有寄存器都支持热修改,配置前先看规格书。

好了,这一章的内容就到这里。I2C/SPI是Sensor驱动的基础,PLL和时钟树是性能的保障,帧率和分辨率是最终呈现的结果。这三块搞明白了,Sensor驱动的大框架你就拿下了。