☰
FPGA驱动OV5640图像采集:SCCB配置与DVP接口实战指南
2026/9/29 17:55:26 网站建设 项目流程

FPGA开发里凡是跟图像沾边的项目,大概率绕不开OV5640这颗传感器。不管你是做工业视觉、边缘检测演示,还是给实验室平台加一个视觉输入模块,这颗500万像素、自带DVP并行接口的CMOS都算得上最顺手的起点。我最早是被“FPGA驱动OV5640”这个任务逼上梁山的,查资料查到吐,发现真正把SCCB配置和DVP采集串起来讲的教程特别少:要么只讲协议,要么直接甩一份代码没有解释。这篇博文我把整个链路拆开,SCCB怎么配、DVP怎么采、FIFO怎么接、上板出问题怎么查,按照实际跑通项目的顺序讲一遍,适合刚接触FPGA图像采集、想自己动手做摄像头采集模块的朋友参考。

真正动手之后你会发现,OV5640本身并不难驱动,难的是把“配置通道”和“数据通道”两条线理清楚。配置通道走SCCB,管的是传感器内部几百个寄存器;数据通道走DVP,管的是像素时钟和同步信号。这两条线一旦在心里分开了,整个项目就成功了一半。

1. 项目全貌:为什么是FPGA搭配OV5640

1.1 OV5640这颗传感器凭什么还在服役

OV5640是OmniVision推出的一颗1/4英寸500万像素CMOS图像传感器,最高支持QSXGA分辨率,也就是2592x1944@15fps。放到今天看参数不算顶,但它有一个非常难得的特性:同时提供DVP并行接口和MIPI接口,而且这两种接口都有海量参考设计。对FPGA开发者来说,DVP版本只需要接一根像素时钟、一根行同步、一根帧同步,再配上8~10根数据线,逻辑上非常直观,不像MIPI还要处理差分对和高速协议层。

这颗芯片另一个优势是功耗和成本控制得不错,PCB上甚至不需要专门做高速走线,普通的2层板也能把DVP信号拉起来。这点对硬件平台很友好,很多FPGA开发板、摄像头扩展板都默认焊一颗OV5640,就是因为它的上手门槛足够低、资料足够多。我见过不少团队在项目初期先用OV5640打通图像采集链路,等算法验证完再换成更高端的传感器,本质上就是看中了它的“可替换性”。

1.2 数据通路全景:配置通道和数据通道缺一不可

一颗OV5640正常出图需要同时满足两个条件:寄存器配置正确,DVP数据通路正确。分别对应两条物理链路:

  • 配置通道:FPGA通过两线SCCB接口(SIO_C时钟线、SIO_D数据线)往OV5640内部寄存器写入分辨率、输出格式、曝光、增益等参数。这条通道速率不高,工作在几千到几百kHz,但对时序的正确性要求极严。
  • 数据通道:传感器配置好之后,会持续输出PCLK像素时钟、VSYNC帧同步、HREF行同步,以及D[9:0]像素数据。FPGA在PCLK的驱动下采样数据,拼成像素后写入FIFO或者缓存,供后续图像处理模块使用。

这两条通道一慢一快,一控制一流速,必须当成两个独立模块来设计。建议的工程划分是:SCCB控制器做成一个可被上层状态机调用的IP或者子模块,初始化序列单独写成寄存器表;DVP采集模块做成另一个子模块,只负责采样和拼接,不掺和任何配置逻辑。这样分工之后,后续换分辨率、换传感器、加图像算法,都只动其中一个模块,不会互相拖累。

2. SCCB协议:Ov家的“私有I2C”

2.1 SCCB与I2C的血缘关系

SCCB全称Serial Camera Control Bus,是OmniVision定义的串行控制总线,本质上是I2C的变体。它同样是两线制,SCL对应I2C的SCL,SDA对应I2C的SDA,传输格式也是起始条件、从机地址、寄存器地址、数据、应答位、停止条件这一套。实际开发中,绝大部分FPGA工程师写SCCB控制器时,都是直接按I2C主机的逻辑来写,因为OV5640对SCCB时序的容错能力足够强,只要满足“SDA在SCL高电平期间保持稳定、低电平期间才允许变化”这条基本规则,通信成功率就非常高。

当然两者也有细微差异。SCCB规范里把一次完整传输拆成“phase”来表述,比如写一个16位寄存器地址需要三个phase:第一个phase发从机地址,第二个phase发高8位寄存器地址,第三个phase发低8位寄存器地址,最后再跟一个数据phase。但落实到FPGA代码层面,我们只需要记住一个字节一个字节地按I2C格式发,中间不释放总线,最终效果完全一样。我测了好几块板子,从没遇到过严格意义上的SCCB和I2C兼容性问题,所以别被名词唬住。

2.2 一次完整写操作:起始、地址、寄存器号、数据、停止

OV5640的SCCB从机地址是0x3C(7位地址),左移一位拼上读写方向位后,写地址是0x78,读地址是0x79。这个细节经常有人搞混,一定要记清楚,FPGA里发的是8位地址,0x78开头,不是0x3C。

写寄存器的完整时序如下:

  1. 起始条件:SCL保持高电平,SDA从高拉低。
  2. 发送8位从机地址0x78,每发完一个字节等待第9个时钟周期的应答位。OV5640在应答周期会把SDA拉低。
  3. 发送寄存器地址高8位。
  4. 发送寄存器地址低8位。
  5. 发送要写入的数据。
  6. 停止条件:SCL保持高电平,SDA从低拉高。

注意这里连续发送地址和数据的多个字节之间没有起始条件,总线一直持有,OV5640内部会自动把后续字节组合成一次内部写操作。实际操作中还要注意一点:SDA数据切换必须发生在SCL低电平期间。很多新手写状态机,SCL拉高后SDA还在变,会直接导致从机采样到错误数据,表现就是寄存器写进去读回来不对,或者整个SCCB总线“卡死”。

SCL的频率建议控制在400kHz以内,我用200kHz居多,一个是够用,另一个是抗干扰能力强。FPGA侧通过分频计数产生SCL,主时钟50MHz时分频到200kHz,一个SCL周期需要250个系统时钟,高低电平各125个。

2.3 读操作:为什么必须“重启”一次

读操作比写操作多一个步骤,因为SCCB是半双工总线,数据方向要先从“主机写”切换到“从机写”。OV5640支持随机读和连续读,但无论哪种,第一步都是先按写操作的格式把寄存器地址发进去,然后再来一次起始条件,把从机地址改成0x79,之后才可以读取数据。

具体流程:

  1. 起始条件。
  2. 发送0x78(写方向)。
  3. 发送寄存器地址高8位。
  4. 发送寄存器地址低8位。
  5. 重复起始条件:SCL高,SDA从高拉低,相当于结束上一段传输再重新开始。
  6. 发送0x79(读方向)。
  7. 读取数据字节,主机在应答周期发送ACK,表示“我还要继续读”;最后一个字节前主机要发送NACK,表示“读完了”。
  8. 停止条件。

这个“重复起始”非常重要。我见过有的实现写成先执行一遍完整的写停止,再发起新的读传输,虽然从波形上看也包含了起始和停止,但严格来说不是同一笔事务,部分传感器会因此读不出数据。OV5640实测两种方式都能行,但我建议按重复起始来写,因为这是数据手册推荐的做法,兼容性最好。调试的时候可以在读操作后特意回读同一个寄存器两次,对比结果是不是一致,用这个方法来验证读时序对不对。

2.4 上电时序与SCCB前置条件

很多朋友SCCB代码写得完全正确,上板却读不到芯片ID,十有八九是上电时序没满足。OV5640不是上电就能立刻通信的,它有几个硬性前置条件:

  • 电源域要先到位。DOVDD数字IO电源、AVDD模拟电源、DVDD数字核心电源要按手册要求的顺序上电,硬件上如果这些电源是同一路LDO同时给到的,通常问题不大,但要确认电压值在规格范围内。
  • PWDN引脚必须拉低,让传感器退出掉电模式;RESETB引脚必须从低拉高,释放硬件复位。
  • XCLK必须有稳定的输入时钟。OV5640需要外部提供时钟才能工作,常见值是24MHz,也有用12MHz的。这个时钟一定要先稳定输出,再开始SCCB配置。
  • 软件复位流程:上电稳定后,通常先往0x3008写入软件复位命令,然后延时10ms以上,让内部逻辑复位完成,再去逐条配置其他寄存器。

我自己习惯的上电顺序是:先给XCLK,再拉高RESETB并拉低PWDN,延时5ms,然后开始SCCB配置。如果这里电源纹波大或者XCLK没起振,SCCB写的所有寄存器都会“石沉大海”,而且很难排查。建议硬件设计时在SCCB的SDA、SCL上加4.7k上拉电阻,FPGA内部的上拉不可靠,尤其是板子连接线比较长的时候。

3. FPGA里的SCCB主控制器:从状态机到初始化序列

3.1 接口定义先于逻辑编写

写SCCB控制器之前,先想清楚对外接口,这决定了整个模块好不好用。我推荐把控制器设计成一个“请求-响应”式的从端口模块,上层只需要告诉它“写哪个寄存器、写什么值”,它完成之后回一个完成信号。接口信号可以这样定:

  • clk:系统时钟,这里用50MHz或者100MHz都行。
  • rst_n:异步复位,低有效。
  • i_req:写请求脉冲,拉高一个周期表示发起一次写事务。
  • i_reg_addr:16位寄存器地址。
  • i_reg_data:8位写入数据。
  • i_rd_ena:读写方向选择,1表示读,0表示写。
  • o_done:完成脉冲,一次事务结束后拉高一个周期。
  • o_rd_data:读回来的数据,只有读事务完成时有效。
  • sccb_scl:输出到传感器的时钟线。
  • sccb_sda:双向数据线,用inout声明。

设计成这种接口有一个明显的好处:上层初始化状态机可以逐条发送寄存器配置,每发一条等待o_done,再发下一条。SCCB控制器内部不关心时序表,让配置和时序实现完全解耦,日后换传感器、换配置表,只需要改上层,底层控制器不用动。

3.2 SCCB控制器状态机的核心写法

控制器的核心状态机可以拆成几个大状态:空闲、起始、发送字节、等待应答、停止。发送字节状态内部再用一个计数器把8个bit位逐位移出,第9个周期释放SDA并采样应答位。

状态机跳转逻辑大致如下:

  • 空闲:检测到i_req后,生成起始条件,进入发送字节状态。
  • 发送字节:每个SCL时钟周期从最高位开始移位输出SDA。这里的关键是SDA在SCL低电平时变化,SCL高电平时要保持数据稳定。
  • 等待应答:第9个时钟周期释放SDA,由从机拉低表示应答。如果从机没有应答,可以设置一个错误标志,同时跳到停止状态。
  • 停止:生成停止条件,o_done拉高一个周期,回到空闲。

以写一次字节为例,代码骨架可以这样写:

// 一次发送8bit数据,sda在scl低电平期间变化 always @(posedge clk or negedge rst_n) begin if (!rst_n) bit_cnt <= 3'd0; else if (state == SEND_BYTE && clk_div_cnt == 1) bit_cnt <= bit_cnt + 1'b1; end assign scl = (state == IDLE) ? 1'b1 : scl_gen; // 这里scl_gen由分频计数器生成,高半周期和低半周期均匀分配

没有把完整代码全部贴出来,是因为完整的兼容代码要考虑分频、读写方向切换和应答,篇幅太长。建议的做法是把这个状态机当成I2C主控制器来写,网上成熟的I2C主机Verilog代码非常多,直接用I2C主机的状态机结构,把地址位宽改成8位,数据位宽改成8位,寄存器地址拆成两次发送,就是可用的SCCB控制器。

写状态机时最容易踩的坑是SDA的驱动时机。FPGA的inout引脚在输出模式下让SDA跟随数据,在读取应答位和读数据时必须把引脚置为高阻态,让从机能够驱动SDA。这个“方向盘交出去”的瞬间最容易被忽略,如果忘了切换方向,SDA会被FPGA持续拉着,从机根本无法应答,表现为一直读不到数据、波形上SDA没有毛刺一般的ACK脉冲。

3.3 寄存器配置序列怎么组织才不容易错

OV5640的寄存器多达几百个,全部手写不现实,正确做法是整理成一张配置表,由上层状态机逐条写入。根据我使用的经验,配置表通常分成几个功能块:

  • 复位与时钟:包含软件复位寄存器、PLL配置寄存器。软件复位后必须延时。
  • 格式控制:配置输出格式是RGB565、YUV422还是JPEG,配置数据位宽是8位还是10位。
  • 窗口裁剪与缩放:矩阵从哪一行哪一列开始输出,输出多少行多少列,决定了最终分辨率。
  • 镜像翻转:水平镜像、垂直翻转的控制位,如果图像方向反了,改这里比移植代码快得多。
  • 曝光与增益:自动曝光、手动曝光、增益值。调试阶段建议先固定一组参数,避免自动曝光引起的亮度波动干扰判断。

我常用的一张表格长这样:

功能模块涉及寄存器范围说明
复位与时钟0x3008附近软件复位,写入后延时10ms以上
输出格式0x4300附近RGB565 / YUV422 / JPEG选择
窗口裁剪0x3800-0x3814输出分辨率核心控制
镜像翻转0x3820-0x3821水平/垂直镜像控制
曝光增益0x3500-0x350B调试期建议先固定参数
芯片ID0x300A-0x300B只读,回读0x5640

配置表的具体取值不能照搬别人的工程,因为不同分辨率的配置依赖PLL和窗口裁剪的组合,网上流传的“全套配置表”很多都是针对720p@30fps的,直接拿来做640x480也能出图,但帧率、时序可能不符合预期。我的做法是:先跑通一套参考设计的配置表,确认图像基本正常之后,再按手册逐项调整分辨率和输出格式。

初始化状态机也很简单,本质就是“发送一条、等待完成、再发送下一条”,从头到尾把表格遍历一遍。注意在软件复位那一条之后要单独加一个延时状态,不要马上接着写后面的寄存器。

3.4 第一个里程碑:读回芯片ID

SCCB控制器写好后,第一个要验证的不是出图,而是能不能读回芯片ID。OV5640的ID寄存器是0x300A和0x300B,正常会读回0x56和0x40,拼在一起就是“0x5640”。

我的调试建议是:先写一个最简单的测试状态机,只做一件事——读芯片ID然后回读两次,把结果通过串口、LED或者FPGA片上逻辑分析仪显示出来。如果两次读回的值都是0x5640,说明SCCB物理层、时序、控制器状态机全部OK,可以放心去跑完整配置表。如果读不到ID,按优先级排查:

  1. SDA和SCL有没有接反、有没有上拉电阻。
  2. XCLK有没有输出,示波器直接测引脚。
  3. PWDN和RESETB电平对不对。
  4. 用逻辑分析仪抓SCCB波形,看地址是不是0x78/0x79,看应答位有没有出现。如果波形上只有地址没有应答,说明传感器没在正常工作状态,问题多半在电源或复位。
  5. 核对时钟频率,SCL不能太快,某些传感器对400kHz以上时序比较敏感。

读不到ID的问题,百分之八十出在这五条上。花半天把上电时序和物理连接查清楚,比反复改代码有效得多。

4. DVP图像采集:把一行行像素稳稳接住

4.1 DVP接口上有哪些信号,各自什么脾气

配置好寄存器后,OV5640会开始输出图像数据。DVP接口的信号不算多,但每个信号的时序关系一定要搞明白:

  • XCLK:从FPGA给传感器的输入时钟,不是采集信号。
  • PCLK:传感器输出的像素时钟,每个有效时钟沿对应一次像素数据采样。不同分辨率、帧率下PCLK频率不同,常见范围在20MHz到80MHz,720p@30fps时普遍在三四十MHz量级。
  • VSYNC:帧同步信号。一帧图像开始前会有一个脉冲,默认高电平有效,也就是VSYNC拉高表示开始新一帧。这个信号用来复位帧内的行计数器和写地址。
  • HREF:行同步信号,有的叫HSYNC。HREF为高电平期间,PCLK上升沿上的数据才是有效像素。VGA模式默认高有效。
  • D[9:0]:像素数据总线。如果配置成8位模式,只使用D[9:2]这8根线,D[1:0]置低。

调试DVP时第一件事就是拿示波器或者逻辑分析仪看这四组信号的相对关系。PCLK必须持续翻转,VSYNC要周期性出现,HREF在每一行都会拉高一段。如果PCLK有、VSYNC没有,多半是传感器配置不正确或者没有成功出图;如果PCLK和VSYNC都有、HREF一直在低,那就要查输出格式和窗口配置。

4.2 RGB565的像素拼接:两个时钟拼一个点

OV5640输出RGB565格式时,如果选择了8位数据线模式,每个像素需要两个PCLK周期才能传完。第一个周期传高字节,低5位是红色R[4:0],高3位是绿色高3位G[5:3];第二个周期传低字节,低3位是绿色低3位G[2:0],高5位是蓝色B[4:0]。把两个字节拼起来,就是完整的16位RGB565像素。

拼接逻辑用一个小状态机或计数器实现:

always @(posedge pclk or negedge rst_n) begin if (!rst_n) byte_cnt <= 1'b0; else if (href) byte_cnt <= byte_cnt + 1'b1; else byte_cnt <= 1'b0; end always @(posedge pclk or negedge rst_n) begin if (!rst_n) pixel_data <= 16'd0; else if (href && byte_cnt[0] == 1'b0) pixel_high <= din; // 第一个字节 else if (href && byte_cnt[0] == 1'b1) begin pixel_data <= {pixel_high, din}; // 两个字节拼成完整像素 pixel_valid <= 1'b1; end end

需要注意几点:

第一,byte_cnt的计数条件必须严格绑定HREF。HREF拉低期间不计数,否则每行起始位置会错位。第二,拼接后的pixel_valid信号只拉高一个PCLK周期,避免写FIFO时重复写同一个像素。第三,如果图像左右颜色不对,大概率是RGB顺序反了,可以在寄存器里调整输出顺序,或者在FPGA拼像素时换一下字节顺序。

4.3 帧缓存与跨时钟域处理

采集到的像素PCLK时钟域是传感器的,而后续图像处理模块可能跑在系统时钟域,两者频率不同,直接连一定会出问题。通用的做法是插入一个异步FIFO做跨时钟域缓冲。

FIFO的写端接PCLK,写使能接HREF与pixel_valid的组合,写数据是拼好的16位RGB565。读端接系统时钟,读使能由后面的处理模块控制。深度怎么选,关键取决于下游消费速度:

  • 如果下游是VGA显示器,读速率和写速率接近,缓存几行就够,深度2048足够。
  • 如果下游是DDR3等存储,突发写入对数据连续性要求高,建议深度至少4096。
  • 如果做实时图像处理,比如3x3卷积,除了FIFO之外还要考虑行缓存,那属于后续处理架构的范畴。

帧同步的处理同样重要。VSYNC有效沿到来时,说明新一帧开始了,这时应该把FIFO清空或者至少重置写指针,避免上一帧残留的数据混入新帧。我习惯在VSYNC上升沿产生一个帧起始脉冲,用它置位一个“帧内有效”标志,直到下一帧的VSYNC到来才清除。只有帧内有效标志为高时,HREF采到的数据才允许写入FIFO。这样能保证每一帧数据都是从帧头开始,不会出现首行错位。

4.4 采集模块的可复用结构

把DVP采集模块设计成通用结构,对后续项目复用很有帮助。推荐接口如下:

  • 输入:pclk、vsync、href、din[7:0]。
  • 输出:fifo_wr_clk、fifo_wr_en、fifo_wr_data[15:0]、frame_start。
  • 可选参数:像素格式、数据位宽、行有效计数。

内部逻辑包括同步信号边沿检测、行内字节计数、像素拼接、帧标志生成四部分。这样设计的好处是,将来把OV5640换成OV2640或者其他DVP接口传感器,只需要核对同步信号极性和像素格式,采集模块主体完全不用改。我不少项目里的DVP采集模块就是从一个最初给OV5640写的版本改过来的,改动量不超过半小时。

另外,如果FPGA资源允许,建议在采集模块后面挂一个简单的直通显示链路,先把图像显示到屏幕上。这一步看着多花了时间,但能立刻验证采集模块的正确性,后面调任何算法都有了一个可信的数据源。

5. 上板调试实战:从花屏到图像的排查路径

5.1 先看波形,再谈优化

上板调试阶段,我最推崇的做法是“信号优先”。不要一上来就怀疑代码逻辑,先看传感器输出的PCLK、VSYNC、HREF三条关键波形,能直接判断问题出在传感器还是FPGA。

如果手头有逻辑分析仪,优先抓这几个信号:SCCB的SDA和SCL、PCLK、VSYNC、HREF、FIFO写使能。抓SCCB可以确认地址和ACK;抓PCLK和VSYNC可以确认传感器是否正常工作;抓FIFO写使能可以确认数据有没有进入缓存。我见过一个经典案例,图像花屏,查了很久,最后发现是FIFO读时钟没有约束,导致读写频率不匹配,读指针追上了写指针,数据错位。这种问题仅靠看代码很难发现,但抓住FIFO读写指针的波形,一眼就能看出端倪。

如果使用Xilinx开发环境,可以在工程里加ILA调试核;使用Intel平台就加SignalTap。调试核的采样时钟最好用系统时钟,信号用跨时钟域后同步过的版本,否则采样本身可能不满足建立时间。

5.2 常见问题排查速查表

实际项目里遇到的问题来来回回就那么几类,我整理了一张速查表,方便对照:

现象排查方向解决办法
读不到芯片ID上电时序、SCCB物理连接查XCLK、复位电平、上拉、SCL频率
有PCLK无VSYNC传感器配置错误确认寄存器写入无误,重新上电配置
有VSYNC无HREF输出格式、窗口配置检查分辨率配置和输出使能位
图像全黑曝光、增益、输出格式提高曝光或增益,确认RGB/YUV格式
颜色偏色RGB字节顺序、数据线接线调整拼接顺序,检查D[9:2]接线
图像花屏FIFO跨时钟域、读写速率检查FIFO深度,确保读速率不低于写速率
图像左右颠倒镜像寄存器配置水平镜像/垂直翻转位
图像有横条纹供电噪声、FIFO溢出检查电源纹波,增加FIFO深度或暂停帧写入
亮度时亮时暗自动曝光调试阶段改手动曝光,固定增益

每个现象对应的排查路径都不是单一的。比如图像花屏,可能的原因有FIFO深度不够、VSYNC清零逻辑不对、下游读时序不规范。排查时要保持“一个变量一次修改”的原则,不要同时改三个寄存器,否则出了问题都不知道是谁的锅。

5.3 几条让调试少走弯路的建议

第一,工程里新建一个顶层测试模块,只做一件事情:上电初始化SCCB配置→回读芯片ID→把DVP数据通过FIFO送到片上RAM。这个链路不要加入任何图像处理逻辑,全链路越短越容易定位问题。等这条链路稳定了,再逐步加后续功能。

第二,PCLK的上升沿采样不是绝对的。OV5640的PCLK数据变化时刻在硬件手册里有说明,个别板子走线参数不同,可能导致上升沿采样出现亚稳态。如果采集的数据偶尔跳变,可以尝试用PCLK的反相沿采样,改一行代码就能生效,不需要动硬件。

第三,VSYNC和HREF的极性在寄存器里可以配置。拿到一块新板子时,先读配置表确认当前的极性设置,再写采集逻辑。我见过有人默认VSYNC高有效,结果参考设计里配置成低有效,导致整帧图像拼接错位,排查了整整一天。

第四,FIFO溢出是图像采集最常见的隐藏问题。显示模块处理不过来时,FIFO会满,满时再写就会丢数据。比较稳妥的做法是:FIFO快满时直接把当前帧作废,等下一帧VSYNC到来重新开始,这样最坏情况是丢一帧,不会出现半帧错乱。

最后分享一个小技巧:调试阶段在FPGA内部加一个“测试图案发生器”,产生彩条或者棋盘格信号,送到显示通路。画面显示正常,就说明显示和缓存链路没问题,图像数据是传感器那边来的;画面不正常,说明问题在显示链路。这个简单的二分法能帮你快速圈定问题范围,省下大量盲目调试的时间。

我第一次调试OV5640时,卡在SCCB读ID上,整整一个下午,最后发现是SDA上拉电阻虚焊,跟代码没有任何关系。从那以后我养成了一个习惯:任何摄像头项目启动,先用万用表确认I2C上拉、XCLK时钟、复位引脚电平,电气OK再谈逻辑。其实大多数看似难解的图像采集问题,都是基础环节的疏忽。先把基础抓稳,剩下的就是按部就班地搬寄存器、看波形、调参数。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询