免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

FPGA驱动OV5640图像采集:SCCB配置与DVP接口实战指南

FPGA驱动OV5640图像采集:SCCB配置与DVP接口实战指南 FPGA开发里凡是跟图像沾边的项目大概率绕不开OV5640这颗传感器。不管你是做工业视觉、边缘检测演示还是给实验室平台加一个视觉输入模块这颗500万像素、自带DVP并行接口的CMOS都算得上最顺手的起点。我最早是被“FPGA驱动OV5640”这个任务逼上梁山的查资料查到吐发现真正把SCCB配置和DVP采集串起来讲的教程特别少要么只讲协议要么直接甩一份代码没有解释。这篇博文我把整个链路拆开SCCB怎么配、DVP怎么采、FIFO怎么接、上板出问题怎么查按照实际跑通项目的顺序讲一遍适合刚接触FPGA图像采集、想自己动手做摄像头采集模块的朋友参考。真正动手之后你会发现OV5640本身并不难驱动难的是把“配置通道”和“数据通道”两条线理清楚。配置通道走SCCB管的是传感器内部几百个寄存器数据通道走DVP管的是像素时钟和同步信号。这两条线一旦在心里分开了整个项目就成功了一半。1. 项目全貌为什么是FPGA搭配OV56401.1 OV5640这颗传感器凭什么还在服役OV5640是OmniVision推出的一颗1/4英寸500万像素CMOS图像传感器最高支持QSXGA分辨率也就是2592x194415fps。放到今天看参数不算顶但它有一个非常难得的特性同时提供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的SCLSDA对应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从机地址是0x3C7位地址左移一位拼上读写方向位后写地址是0x78读地址是0x79。这个细节经常有人搞混一定要记清楚FPGA里发的是8位地址0x78开头不是0x3C。写寄存器的完整时序如下起始条件SCL保持高电平SDA从高拉低。发送8位从机地址0x78每发完一个字节等待第9个时钟周期的应答位。OV5640在应答周期会把SDA拉低。发送寄存器地址高8位。发送寄存器地址低8位。发送要写入的数据。停止条件SCL保持高电平SDA从低拉高。注意这里连续发送地址和数据的多个字节之间没有起始条件总线一直持有OV5640内部会自动把后续字节组合成一次内部写操作。实际操作中还要注意一点SDA数据切换必须发生在SCL低电平期间。很多新手写状态机SCL拉高后SDA还在变会直接导致从机采样到错误数据表现就是寄存器写进去读回来不对或者整个SCCB总线“卡死”。SCL的频率建议控制在400kHz以内我用200kHz居多一个是够用另一个是抗干扰能力强。FPGA侧通过分频计数产生SCL主时钟50MHz时分频到200kHz一个SCL周期需要250个系统时钟高低电平各125个。2.3 读操作为什么必须“重启”一次读操作比写操作多一个步骤因为SCCB是半双工总线数据方向要先从“主机写”切换到“从机写”。OV5640支持随机读和连续读但无论哪种第一步都是先按写操作的格式把寄存器地址发进去然后再来一次起始条件把从机地址改成0x79之后才可以读取数据。具体流程起始条件。发送0x78写方向。发送寄存器地址高8位。发送寄存器地址低8位。重复起始条件SCL高SDA从高拉低相当于结束上一段传输再重新开始。发送0x79读方向。读取数据字节主机在应答周期发送ACK表示“我还要继续读”最后一个字节前主机要发送NACK表示“读完了”。停止条件。这个“重复起始”非常重要。我见过有的实现写成先执行一遍完整的写停止再发起新的读传输虽然从波形上看也包含了起始和停止但严格来说不是同一笔事务部分传感器会因此读不出数据。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_addr16位寄存器地址。i_reg_data8位写入数据。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 3d0; else if (state SEND_BYTE clk_div_cnt 1) bit_cnt bit_cnt 1b1; end assign scl (state IDLE) ? 1b1 : 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和窗口裁剪的组合网上流传的“全套配置表”很多都是针对720p30fps的直接拿来做640x480也能出图但帧率、时序可能不符合预期。我的做法是先跑通一套参考设计的配置表确认图像基本正常之后再按手册逐项调整分辨率和输出格式。初始化状态机也很简单本质就是“发送一条、等待完成、再发送下一条”从头到尾把表格遍历一遍。注意在软件复位那一条之后要单独加一个延时状态不要马上接着写后面的寄存器。3.4 第一个里程碑读回芯片IDSCCB控制器写好后第一个要验证的不是出图而是能不能读回芯片ID。OV5640的ID寄存器是0x300A和0x300B正常会读回0x56和0x40拼在一起就是“0x5640”。我的调试建议是先写一个最简单的测试状态机只做一件事——读芯片ID然后回读两次把结果通过串口、LED或者FPGA片上逻辑分析仪显示出来。如果两次读回的值都是0x5640说明SCCB物理层、时序、控制器状态机全部OK可以放心去跑完整配置表。如果读不到ID按优先级排查SDA和SCL有没有接反、有没有上拉电阻。XCLK有没有输出示波器直接测引脚。PWDN和RESETB电平对不对。用逻辑分析仪抓SCCB波形看地址是不是0x78/0x79看应答位有没有出现。如果波形上只有地址没有应答说明传感器没在正常工作状态问题多半在电源或复位。核对时钟频率SCL不能太快某些传感器对400kHz以上时序比较敏感。读不到ID的问题百分之八十出在这五条上。花半天把上电时序和物理连接查清楚比反复改代码有效得多。4. DVP图像采集把一行行像素稳稳接住4.1 DVP接口上有哪些信号各自什么脾气配置好寄存器后OV5640会开始输出图像数据。DVP接口的信号不算多但每个信号的时序关系一定要搞明白XCLK从FPGA给传感器的输入时钟不是采集信号。PCLK传感器输出的像素时钟每个有效时钟沿对应一次像素数据采样。不同分辨率、帧率下PCLK频率不同常见范围在20MHz到80MHz720p30fps时普遍在三四十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 1b0; else if (href) byte_cnt byte_cnt 1b1; else byte_cnt 1b0; end always (posedge pclk or negedge rst_n) begin if (!rst_n) pixel_data 16d0; else if (href byte_cnt[0] 1b0) pixel_high din; // 第一个字节 else if (href byte_cnt[0] 1b1) begin pixel_data {pixel_high, din}; // 两个字节拼成完整像素 pixel_valid 1b1; 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再谈逻辑。其实大多数看似难解的图像采集问题都是基础环节的疏忽。先把基础抓稳剩下的就是按部就班地搬寄存器、看波形、调参数。
返回列表