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虽然快,但没有硬件应答,你得靠读回验证,调试起来多一步。
2.2 Sensor寄存器读写流程
你想想看,Sensor内部有几百个寄存器,控制着曝光、增益、帧率、输出格式等等。你要做的,就是通过I2C/SPI往这些地址写值。
标准的读写流程是这样的:
- 发送设备地址 + 写标志(I2C从设备地址左移1位,最低位为0)
- 等待ACK(如果没收到,说明Sensor没准备好或地址错了)
- 发送寄存器高8位地址(很多Sensor寄存器地址是16位的)
- 发送寄存器低8位地址
- 发送要写入的数据
- 等待ACK(确认写入成功)
- 发送停止条件
读操作稍微复杂一点,需要先写地址,再重新发送起始条件+读标志,然后读取数据。我贴一段实际项目中用过的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;
}
2.3 PLL配置与时钟树分析
Sensor内部有一个PLL(锁相环),它负责把外部输入的参考时钟(比如24MHz)倍频成Sensor内部各个模块需要的工作时钟。你想想看,一个Sensor内部有:像素阵列、行读出电路、ADC、MIPI接口……每个模块对时钟的要求都不一样。
典型的时钟树长这样:
配置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)
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,你得提高像素时钟,或者降低分辨率。
配置帧率和分辨率,通常需要操作以下寄存器:
| 寄存器名称 | 作用 | 典型值 |
|---|---|---|
| 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) |
好了,这一章的内容就到这里。I2C/SPI是Sensor驱动的基础,PLL和时钟树是性能的保障,帧率和分辨率是最终呈现的结果。这三块搞明白了,Sensor驱动的大框架你就拿下了。