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 初始化流程

  1. 上电复位:先给PWDN引脚拉低,再拉高,保持至少1ms
  2. 时钟配置:设置PLL,得到目标像素时钟
  3. 分辨率设置:配置输出窗口大小和缩放比例
  4. 输出格式:选择YUV、RGB还是JPEG
  5. 接口模式:配置DVP或MIPI相关寄存器
  6. 启动输出:设置软复位,开始输出图像

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接口的时序关系:

DVP接口时序示意图 VSYNC 帧有效 (约33ms) HREF 行1 行2 行1080 PCLK ...... D[9:0] D0 D1 D2 每个PCLK上升沿,数据线上输出一个像素 HREF高电平时,数据有效 VSYNC高电平时,一帧开始

你看,VSYNC拉高表示一帧开始,HREF拉高表示一行有效数据。每个PCLK上升沿,数据线上就输出一个像素。这个时序搞清楚了,写驱动就顺了。

4.5 避坑指南

最后分享几个我踩过的坑:

  • 上电顺序:OV5640对电源上电顺序有要求,必须先上模拟电压,再上IO电压。我曾经因为顺序反了,芯片死活不工作。
  • 时钟抖动:MIPI接口对时钟抖动很敏感。我建议用Zynq的PLL生成时钟,别用普通的晶振直连。
  • 寄存器配置:不要一次性把所有寄存器都配完。我习惯分步配置,每配一步就读回来验证一下。
  • 帧同步:DVP接口的VSYNC极性可以配置,有的模块是高有效,有的是低有效。一定要看数据手册确认。

我的经验:调摄像头驱动,最有效的方法是先让摄像头输出测试图案。OV5640内部有测试图案生成器,设置寄存器0x503D的bit7为1就能开启。这样不用管镜头和光线,先确认数据通路是通的。

好了,OV5640的驱动和接口对比就聊到这儿。这些内容都是我在实际项目中反复验证过的,希望能帮你少走弯路。

可可精品资料库