4、图像传感器接口:OV5640摄像头驱动、MIPI与DVP接口对比、Sensor寄存器配置实战
做图像处理这么多年,我接触过不少摄像头传感器。OV5640算是用得最多的一款。为什么?因为它性价比高,500万像素,还能输出多种格式。今天咱们就聊聊怎么把它用起来。
4.1 OV5640是个什么来头?
OV5640是OmniVision公司的一款CMOS图像传感器。它最大支持2592x1944分辨率,也就是500万像素。帧率方面,在1080p下能跑到30fps,VGA下能到90fps。
它支持两种输出接口:DVP和MIPI。嗯,这里要注意,不是所有版本的OV5640都同时支持两种接口。买模块的时候一定要看清楚。
核心参数一览:
- 有效像素:2592 x 1944 (500万)
- 光学尺寸:1/4英寸
- 像素大小:1.4μm x 1.4μm
- 输出格式:RAW RGB、RGB565、YUV422、JPEG
- 接口类型:DVP (并行) 或 MIPI (2-lane)
- 工作电压:核心1.5V,模拟2.8V,IO 1.8-3.3V
4.2 MIPI vs DVP:到底选哪个?
这个问题我经常被问到。说白了,就是选速度还是选简单。
| 对比项 | DVP (并行) | MIPI (串行) |
|---|---|---|
| 信号线数量 | 10-12根 (数据+时钟+同步) | 4-6根 (差分对) |
| 最高速率 | 约100MHz (并行) | 每lane可达1Gbps |
| 抗干扰能力 | 一般,线多了容易串扰 | 强,差分信号天生抗干扰 |
| 布线难度 | 简单,但占IO多 | 需要阻抗匹配,但IO少 |
| 适用场景 | 低分辨率、低速、简单系统 | 高分辨率、高速、复杂系统 |
我在项目中遇到过这样的情况:用DVP接口做1080p 30fps,结果因为PCB布线太长,数据线之间产生了串扰,图像上出现了条纹。后来换成MIPI接口,问题就解决了。所以我的建议是:
- 分辨率低于VGA,帧率不高 → DVP够用,还省事
- 要跑1080p以上,或者帧率要求高 → 老老实实用MIPI
小提示:Zynq的PL端IO可以接DVP,但MIPI需要专用的硬核或者外接电平转换芯片。我一般用TI的DS90UB913/914系列做MIPI转LVDS,或者直接用Zynq的硬核MIPI CSI-2接收器。
4.3 寄存器配置实战:让OV5640动起来
配置OV5640,说白了就是通过SCCB接口(兼容I2C)往寄存器里写值。我习惯先把摄像头初始化流程理清楚,再动手写代码。
4.3.1 初始化流程
- 上电复位:先给PWDN引脚拉低,再拉高,保持至少1ms
- 时钟配置:设置PLL,得到目标像素时钟
- 分辨率设置:配置输出窗口大小和缩放比例
- 输出格式:选择YUV、RGB还是JPEG
- 接口模式:配置DVP或MIPI相关寄存器
- 启动输出:设置软复位,开始输出图像
4.3.2 关键寄存器示例
这里我挑几个最常用的寄存器,你配置的时候肯定用得上:
// 芯片ID寄存器 (只读)
#define OV5640_CHIP_ID_H 0x300A
#define OV5640_CHIP_ID_L 0x300B
// 系统控制
#define OV5640_SYSTEM_CTRL0 0x3008 // 软复位: bit7=1
#define OV5640_SYSTEM_CTRL1 0x3009 // 时钟使能
// PLL配置
#define OV5640_PLL_CTRL0 0x3034 // PLL分频
#define OV5640_PLL_CTRL1 0x3035 // PLL倍频
#define OV5640_PLL_CTRL2 0x3036 // PLL输出分频
// 输出格式
#define OV5640_FORMAT_CTRL 0x4300 // 0x00: YUV, 0x04: RGB, 0x30: JPEG
// 窗口设置
#define OV5640_WIN_START_X 0x3800 // 窗口起始X (高8位)
#define OV5640_WIN_START_Y 0x3802 // 窗口起始Y (高8位)
#define OV5640_WIN_WIDTH 0x3804 // 窗口宽度 (高8位)
#define OV5640_WIN_HEIGHT 0x3806 // 窗口高度 (高8位)
4.3.3 实战:配置1080p 30fps输出
我记得第一次调OV5640的时候,花了整整两天才把图像调出来。后来总结了一套固定的配置序列,分享给你:
// 1. 软复位
sccb_write(0x3008, 0x82);
delay_ms(10);
// 2. 设置PLL,得到84MHz像素时钟 (1080p 30fps需要)
sccb_write(0x3034, 0x1A); // 分频系数
sccb_write(0x3035, 0x21); // 倍频系数
sccb_write(0x3036, 0x46); // 输出分频
sccb_write(0x3037, 0x12); // 系统分频
// 3. 设置输出窗口为1920x1080
sccb_write(0x3800, 0x00); // X起始高
sccb_write(0x3801, 0x00); // X起始低
sccb_write(0x3802, 0x00); // Y起始高
sccb_write(0x3803, 0x00); // Y起始低
sccb_write(0x3804, 0x0A); // X结束高 (1920)
sccb_write(0x3805, 0x80); // X结束低
sccb_write(0x3806, 0x04); // Y结束高 (1080)
sccb_write(0x3807, 0x38); // Y结束低
// 4. 设置输出格式为YUV422
sccb_write(0x4300, 0x00); // YUV格式
// 5. 启动输出
sccb_write(0x3008, 0x02); // 清除软复位,开始输出
注意:不同版本的OV5640,寄存器地址可能略有差异。我遇到过一批模块,0x3034的默认值不一样,导致PLL锁不住。建议上电后先读一下芯片ID(0x300A和0x300B),确认通信正常再往下配。
4.4 时序与同步信号
不管用DVP还是MIPI,理解时序都很重要。DVP接口有VSYNC(帧同步)、HREF(行同步)和PCLK(像素时钟)。MIPI则用数据包的头尾来标识。
我画了一张图,帮你理清DVP接口的时序关系:
你看,VSYNC拉高表示一帧开始,HREF拉高表示一行有效数据。每个PCLK上升沿,数据线上就输出一个像素。这个时序搞清楚了,写驱动就顺了。
4.5 避坑指南
最后分享几个我踩过的坑:
- 上电顺序:OV5640对电源上电顺序有要求,必须先上模拟电压,再上IO电压。我曾经因为顺序反了,芯片死活不工作。
- 时钟抖动:MIPI接口对时钟抖动很敏感。我建议用Zynq的PLL生成时钟,别用普通的晶振直连。
- 寄存器配置:不要一次性把所有寄存器都配完。我习惯分步配置,每配一步就读回来验证一下。
- 帧同步:DVP接口的VSYNC极性可以配置,有的模块是高有效,有的是低有效。一定要看数据手册确认。
我的经验:调摄像头驱动,最有效的方法是先让摄像头输出测试图案。OV5640内部有测试图案生成器,设置寄存器0x503D的bit7为1就能开启。这样不用管镜头和光线,先确认数据通路是通的。
好了,OV5640的驱动和接口对比就聊到这儿。这些内容都是我在实际项目中反复验证过的,希望能帮你少走弯路。