4. MIPI摄像头模组配置:I2C控制接口与寄存器读写、摄像头上电时序
做MIPI摄像头驱动,说白了就是跟传感器芯片“对话”。你得告诉它怎么工作,它再把图像数据传回来。这个对话的桥梁,就是I2C总线。我个人习惯把摄像头模组看作一个“黑盒子”,我们通过I2C往里面写寄存器,控制它的行为。
4.1 I2C控制接口与寄存器读写
MIPI摄像头模组内部通常集成了一个传感器芯片(比如索尼的IMX系列、豪威的OV系列)。这个芯片内部有一堆寄存器,每个寄存器都有地址和值。我们通过I2C总线,按照芯片手册规定的协议,去读写这些寄存器。
I2C读写的基本流程:
- 起始信号:SCL高电平时,SDA从高变低。
- 发送设备地址:7位地址 + 读写位(0写,1读)。
- 等待应答:从设备拉低SDA表示应答。
- 发送寄存器地址:通常是16位地址,高字节在前。
- 发送/接收数据:写操作发送数据,读操作接收数据。
- 停止信号:SCL高电平时,SDA从低变高。
避坑指南:我曾经在一个项目里,I2C读写总是超时。查了半天,发现是寄存器地址长度搞错了。有的传感器用8位地址,有的用16位。一定要仔细看数据手册的“Register Map”章节。
代码示例:I2C写寄存器函数(伪代码)
// 写16位寄存器地址,8位数据
int i2c_write_reg(uint8_t dev_addr, uint16_t reg_addr, uint8_t data) {
uint8_t buf[3];
buf[0] = (reg_addr >> 8) & 0xFF; // 高字节
buf[1] = reg_addr & 0xFF; // 低字节
buf[2] = data;
// 调用底层I2C驱动发送数据
return i2c_master_write(dev_addr, buf, 3);
}
代码示例:I2C读寄存器函数(伪代码)
// 读16位寄存器地址,返回8位数据
uint8_t i2c_read_reg(uint8_t dev_addr, uint16_t reg_addr) {
uint8_t buf[2];
buf[0] = (reg_addr >> 8) & 0xFF;
buf[1] = reg_addr & 0xFF;
// 先写寄存器地址
i2c_master_write(dev_addr, buf, 2);
// 再读数据
uint8_t data;
i2c_master_read(dev_addr, &data, 1);
return data;
}
4.2 摄像头上电时序
摄像头模组不是插上电就能用的。它需要按照严格的时序上电,否则可能无法正常工作,甚至损坏芯片。我见过不少新手直接给所有电源同时上电,结果图像全是雪花。
典型的上电时序要求:
- 先供模拟电源(AVDD):通常2.8V或3.3V。
- 再供数字电源(DVDD):通常1.2V或1.8V。
- 最后供IO电源(IOVDD):通常1.8V或2.8V。
- 等待电源稳定:至少1ms。
- 释放XSHUTDOWN引脚:拉高,让传感器退出复位状态。
- 提供MCLK时钟:通常是24MHz或27MHz。
- 等待内部初始化:约10ms~100ms,具体看芯片。
注意:不同厂家的传感器,上电时序可能完全不同。有的要求先供DVDD,有的要求XSHUTDOWN必须在MCLK之前拉高。一定要以数据手册的“Power-Up Sequence”章节为准。
上电时序的FPGA实现思路:
在FPGA里,我通常用一个状态机来控制上电流程。每个状态对应一个步骤,状态之间用计数器延时。这样既灵活又可靠。
// 上电状态机(伪代码)
typedef enum {
POWER_OFF,
AVDD_ON,
DVDD_ON,
IOVDD_ON,
WAIT_STABLE,
XSHUTDOWN_HIGH,
MCLK_START,
WAIT_INIT,
POWER_READY
} power_state_t;
void power_sequence_ctrl() {
switch(state) {
case POWER_OFF:
// 关闭所有电源和时钟
break;
case AVDD_ON:
// 使能AVDD电源
break;
// ... 其他状态类似
case POWER_READY:
// 可以开始I2C配置
break;
}
}
4.3 常用传感器配置
传感器上电完成后,就可以通过I2C配置它的工作模式了。常用的配置项包括分辨率、帧率、曝光时间和增益。
分辨率配置:
传感器内部有一个“窗口”概念。你可以通过设置窗口的起始位置和大小,来输出不同分辨率的图像。比如一个200万像素的传感器,你可以让它输出1920x1080,也可以输出640x480。
帧率配置:
帧率由行时间(HTS)和帧时间(VTS)决定。公式很简单:帧率 = MCLK / (HTS * VTS)。HTS和VTS都可以通过寄存器设置。
曝光与增益配置:
曝光时间决定了传感器收集光子的时间。增益则是对收集到的信号进行放大。两者配合使用,可以适应不同的光照环境。
我的经验:在调试曝光时,我习惯先固定增益,调整曝光时间。如果曝光时间已经很长了(比如超过30ms),图像还是暗,再增加增益。这样可以避免增益太大引入噪声。
常用寄存器配置示例(以OV5640为例):
| 功能 | 寄存器地址 | 值 | 说明 |
|---|---|---|---|
| 分辨率 | 0x3808, 0x3809 | 0x07, 0x80 | 输出宽度1920 |
| 分辨率 | 0x380A, 0x380B | 0x04, 0x38 | 输出高度1080 |
| 帧率 | 0x380C, 0x380D | 0x0B, 0xB8 | HTS = 3000 |
| 帧率 | 0x380E, 0x380F | 0x04, 0x62 | VTS = 1122 |
| 曝光 | 0x3500, 0x3501, 0x3502 | 0x00, 0x2E, 0xE0 | 曝光时间12000行 |
| 增益 | 0x350A | 0x10 | 全局增益16x |
4.4 RAW数据输出格式配置
MIPI摄像头输出的原始数据叫RAW格式。它直接来自传感器的像素阵列,没有经过任何处理。常见的RAW格式有RAW8、RAW10、RAW12等,分别表示每个像素用8位、10位、12位数据表示。
配置RAW格式的关键寄存器:
- 输出格式选择:设置传感器输出RAW8还是RAW10。
- 数据打包方式:RAW10和RAW12通常需要打包,比如两个10位像素打包成20位,或者四个12位像素打包成48位。
- MIPI数据通道配置:设置使用几个MIPI lane,每个lane的数据速率。
避坑指南:我曾经在配置RAW10输出时,发现图像颜色不对。后来发现是数据打包的字节序搞反了。传感器默认是高位在前,但我的FPGA解析代码是按低位在前处理的。嗯,这个问题花了我整整一个下午。
RAW10数据打包示例:
// 两个10位像素打包成20位(2.5字节)
// 像素0: 0x3A1 (10位)
// 像素1: 0x1C7 (10位)
// 打包后: 0x1C7 0x3A1 (实际存储顺序看配置)
// 通常存储为: [0x3A] [0x1C] [0x71] (高4位+低4位+中间4位)
4.5 OTP与校准数据读取
每个摄像头模组在出厂时,都会在内部OTP(One-Time Programmable)存储器里烧录一些校准数据。这些数据包括镜头阴影校正参数、坏点位置、白平衡增益等。读取这些数据,可以让你的图像质量更好。
OTP读取流程:
- 使能OTP读取模式:写特定寄存器,让传感器进入OTP读取状态。
- 读取OTP页数据:通过I2C读取OTP存储器的内容。
- 解析数据:根据芯片手册的OTP映射表,解析出各个校准参数。
- 应用参数:将解析出的参数写入对应的寄存器。
我的经验:OTP数据通常有校验和(Checksum)。读取后一定要验证校验和,否则用了错误的数据,图像反而会更差。我曾经遇到过一批模组OTP数据损坏,幸好校验和帮我发现了问题。
OTP数据解析示例:
// 假设OTP页0的数据结构如下:
// 字节0: 校验和
// 字节1-2: 镜头阴影校正系数A
// 字节3-4: 镜头阴影校正系数B
// 字节5: 坏点数量
// 字节6-...: 坏点坐标列表
uint8_t otp_page[32];
read_otp_page(0, otp_page); // 读取第0页
uint8_t checksum = otp_page[0];
uint16_t coeff_a = (otp_page[1] << 8) | otp_page[2];
uint16_t coeff_b = (otp_page[3] << 8) | otp_page[4];
uint8_t bad_pixel_count = otp_page[5];
// 验证校验和
if (verify_checksum(otp_page, 32) == 0) {
// 应用校准参数
write_reg(0x1234, coeff_a);
write_reg(0x1235, coeff_b);
}
4.6 本章知识体系
下面这张图,是我梳理的本章核心逻辑。你可以看到,从硬件上电到I2C配置,再到数据输出和校准,每一步都环环相扣。
好了,这一章的内容就到这里。配置摄像头模组,说白了就是跟传感器“打交道”。上电时序要严格,I2C读写要细心,参数配置要合理。把这些基础打牢了,后面的图像采集和卷积加速才能顺利进行。
可可精品资料库