ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

FPGA驱动OV5640图像采集:从SCCB配置到DVP显示完整实战

FPGA驱动OV5640图像采集:从SCCB配置到DVP显示完整实战 接手一个摄像头驱动项目很多朋友第一时间会想到用ARM或者树莓派。但如果你做的是实时图像处理、高速采集或者想彻底搞懂图像数据从传感器到显示器的完整链路FPGA驱动OV5640几乎是绕不开的一课。这篇博文就围绕“FPGA OV5640”这套黄金组合从底层的SCCB配置协议到DVP接口的图像数据采集再到缓存与显示联动把整个实操链路完整过一遍。内容会尽量贴近实际调试现场把踩过的坑、验证过的参数、以及代码思路都摊开来讲适合已经能跑通LED流水灯、想往图像方向进阶的FPGA学习者也适合正在被摄像头时序折磨的工程师参考。1. 整体方案与系统设计思路1.1 为什么选OV5640为什么用FPGA而不是直接用ARM或树莓派OV5640这颗传感器在图像处理领域的使用频率极高它是一颗500万像素的CMOS图像传感器支持DVP和MIPI两种输出接口。在FPGA开发板上绝大多数都是通过DVP接口来接因为它引脚数少、时序相对直观非常适合用来学习图像采集的完整流程。那么问题来了既然树莓派或者STM32也都能驱动OV5640为什么还要用FPGA来做从我个人的实践体会来看有三个核心原因。第一FPGA是并行硬件架构采集和处理的时钟节拍完全可控。OV5640输出的像素时钟PCLK、行同步HREF、场同步VSYNC这些信号本质上都是硬件时序信号FPGA用硬件逻辑去做同步、采样、缓存天然就是匹配的。你用MCU靠中断去抓这些信号很容易被系统调度打断导致丢帧或者花屏。第二图像采集只是起点后面往往还跟着灰度转换、二值化、边缘检测、帧差法这些算法。FPGA做这些操作是流水线式的处理完一帧图像的时间基本是固定的不像CPU那样受主频和操作系统调度的影响。做实时图像处理的项目这个确定性太重要了。第三从学习的角度FPGA驱动OV5640能让你把“传感器配置—数据链路—缓存—显示”这条完整链路都亲手搭一遍尤其是SCCB寄存器配置和DVP时序对齐这两块属于图像硬件开发的基操搞懂之后再做MIPI、再做ISP会有非常扎实的基础。1.2 系统整体架构与模块划分在写代码之前我习惯先把整个系统的模块框图在纸上画出来。FPGA的开发一定要先定架构再动手写代码直接上来就写模块后面大概率会返工。这套OV5640采集显示系统整体可以划分为四个大模块。SCCB配置模块i2c_cfg负责上电后对OV5640的寄存器进行初始化配置比如输出分辨率、像素格式、时钟分频等。这个模块本质上是I2C主机控制器只不过遵循的协议是SCCB。图像采集模块cmos_capture负责接收OV5640通过DVP接口输出的PCLK、VSYNC、HREF、DATA[15:0]信号按照行场时序把有效像素数据解析出来。数据缓存模块frame_buffer因为采集端的写时钟是PCLK显示端的读时钟是VGA或HDMI的像素时钟两个时钟域不同需要FIFO或者双口RAM做跨时钟域缓存。显示驱动模块vga_driver / hdmi_driver把缓存图像数据按照显示器的时序刷出去。模块划分清楚之后你需要明确一个关键问题系统上电后FPGA必须先通过SCCB把OV5640配置好传感器才会开始输出图像数据。所以SCCB配置模块的优先级最高而且配置完成后需要留出一段稳定时间再让采集模块开始接收数据。提示很多新手会在上电后立刻开始采集结果图像全黑或者全是噪声。原因就是OV5640还没完成初始化传感器根本没开始输出有效数据。建议在配置模块完成后打一个标志信号比如cfg_done采集模块等这个信号拉高后再开始等待VSYNC。2. SCCB协议与寄存器配置实战2.1 SCCB协议本质和I2C的相似与不同SCCB全称是Serial Camera Control Bus是OmniVision豪威定义的串行控制总线协议。它的时序和I2C非常像但有几个细节差异如果不注意会出现一个很典型的故障寄存器写不进去读取出来的数据全是0xFF。SCCB的时序核心组成如下。起始条件SCCL为高时SDA从高变低表示传输开始。停止条件SCCL为高时SDA从低变高表示传输结束。数据有效性SDA必须在SCCL高电平期间保持稳定SCCL低电平期间允许变化。这点和I2C完全一致。应答位这是SCCB和I2C最大的区别。I2C在第9个时钟周期有ACK应答而SCCB在写操作时第9位是Dont Care位主机不需要检测从机应答在读操作时有NAK机制。换句话说SCCB的从机地址字节只有7位有效第8位固定为XDont Care第9位也是Dont Care。所以OV5640的SCCB地址通常写作0x787位地址0x3C左移一位或0x7A7位地址0x3D左移一位具体由OV5640的SCCB_ID引脚电平决定。读操作也有一点特殊SCCB读操作必须先发送写地址和寄存器地址再重新发送读地址在I2C里这叫重复起始条件。我之前调试时就是因为漏掉了这个先写后读的步骤读寄存器一直返回0xFF排查了很久。2.2 寄存器配置核心项与计算过程OV5640的寄存器数量非常多密密麻麻几百个。做FPGA驱动时不需要全部配置我们只需要把关键寄存器配好让传感器输出我们想要的格式。常用的配置项如下。功能寄存器地址典型值说明软件复位0x31030x03复位整个传感器输出格式0x43000x02配置为RGB565格式Y[9:2]输出输出尺寸-高字节0x38080x05输出图像宽度高8位输出尺寸-低字节0x38090x10输出图像宽度低8位即0x5101296输出尺寸-高字节0x380A0x02输出图像高度高8位输出尺寸-低字节0x380B0x58输出图像高度低8位即0x258600时钟分频配置0x31080x01PLL相关配置寄存器HTS0x380C0x0A行总长度高字节寄存器HTS0x380D0x20行总长度低字节0xA202592寄存器VTS0x380E0x07帧总长度高字节寄存器VTS0x380F0xD0帧总长度低字节0x7D02000这里拿时钟频率计算举个例子。OV5640的输入时钟XCLK通常给24MHz通过PLL倍频后可以得到传感器内部的工作时钟。PLL的倍频分频配置主要体现在寄存器0x3034、0x3035、0x3036、0x3037中。常用的一个组合是0x3035 0x11PLL倍频系数0x3036 0xC2PLL分频系数0x3037 0x0EPLL分频系数。PLL输出频率的计算公式大致是F XCLK × PLL倍频系数 / PLL分频系数举个实例XCLK24MHz倍频系数0x11对应17分频系数0x0E对应14那么F 24 × 17 / 14 ≈ 29.14MHz这个频率就是传感器内部的工作时钟再经过分频后产生PCLK。PCLK的具体值和HTS、VTS相关。PCLK F × 分频系数然后再根据HTS×VTS×帧率的关系计算最终的输出帧率。比如一帧总像素 HTS × VTS 2592 × 2000 5184000如果PCLK约等于42MHz那么帧率约等于42MHz / 5184000 ≈ 8fps实际上OV5640的输出帧率就是在这些参数之间互相制约的。我做720P30fps时配置的是0x380C0x0A200x380E0x07D0最终实测帧率约30fps。注意0x4300这个寄存器特别容易配错。配置成0x02是RGB565格式输出Y[9:2]配置成0x30是YUV422格式输出UYVY。如果显示出来颜色完全不对或者图像偏色怪异第一个检查项就应该是0x4300的值。2.3 SCCB读写时序的具体实现思路SCCB的底层代码本质上是状态机核心状态包括IDLE、START、SEND_ADDR、SEND_REG_ADDR、SEND_DATA、STOP等。对于读操作需要在SEND_REG_ADDR之后插入一个读地址发送状态实现“伪停止”或“重复起始”的效果。SCCL的频率建议控制在100KHz~400KHz之间。我的做法是在FPGA内部做一个分频计数器从系统时钟50MHz分频得到200KHz的SCCL。SDA引脚用三态门来控制输出时置为高阻态配合外部上拉电阻实现释放总线。SCCB写入寄存器的基本流程如下。发送起始条件。发送从机地址0x78写方向。发送寄存器地址高字节。发送寄存器地址低字节。发送要写入的数据。发送停止条件。SCCB读取寄存器的基本流程如下。发送起始条件。发送从机地址0x78写方向。发送寄存器地址高字节。发送寄存器地址低字节。再次发送起始条件或者伪停止。发送从机地址0x79读方向。读取从机返回的数据。主机发送NAK然后发送停止条件。在状态机里每个bit的切换都要严格卡在SCCL的边沿上。我习惯的做法是SCCL高电平期间SDA数据必须保持稳定SCCL低电平期间SDA才允许切换。这个时序细节建议用示波器量一下SCCL和SDA的波形来验证特别是起始和停止条件的建立时间一旦不满足传感器会完全不理你。3. DVP接口图像数据采集与信号解析3.1 DVP接口信号定义与关键时序OV5640通过DVP接口输出的信号主要有5组。PCLK像素时钟每个上升沿对应一个像素数据。VSYNC场同步信号一帧图像开始/结束的标志。HREF行同步信号高电平期间输出一行有效像素。DATA[15:0]16位并行像素数据在PCLK上升沿采样。XCLK外部输入时钟FPGA提供给传感器的工作时钟。这里面最容易让人迷糊的是RGB565格式下的数据排列。配置0x43000x02时OV5640输出的是RGB565但它是怎么排列到16位数据线上的呢经过实际抓取波形验证RGB565格式下16位数据线上直接对应RGB565但需要注意高低字节顺序可能是RGB高位在前或BGR低位在前这个由0x4300的其他位决定。而如果你配置成YUV422格式数据排列又不一样了。比如YUV422的UYVY格式第一个像素输出U第二个像素输出Y第三个像素输出V第四个像素输出Y以此类推。这个格式和RGB565的字节序完全不同调试时最容易头大。拿RGB565来说DVP接口输出的数据顺序是第一个PCLK上升沿DATA[15:0] R[4:0] 11 | G[5:0] 5 | B[4:0]对应第一个像素。第二个PCLK上升沿输出第二个像素。HREF信号在有效像素输出期间保持高电平行消隐期间为低电平。VSYNC信号标识一帧开始。这里要特别留意OV5640的VSYNC是高有效还是低有效可以通过寄存器0x3820的bit1、bit0来配置。复位默认值是低有效很多人在VGA显示时发现图像上下翻转或者同步错乱就是VSYNC极性没配对。3.2 图像采集模块的状态机设计采集模块的核心任务就是正确地抓取每一帧的每一行有效像素。我的设计思路是用一个状态机加上行、场计数器。状态机转移逻辑大致如下。IDLE状态等待VSYNC有效。VSYNC来后这里以低有效为例进入VSYNC_LOW状态。VSYNC_LOW状态等待VSYNC拉高表示新的一帧将要开始。进入LINE_WAIT状态。LINE_WAIT状态等待HREF拉高。HREF拉高后进入DATA_SAMPLE状态同时开始逐像素采样。DATA_SAMPLE状态在HREF高电平期间每个PCLK上升沿采样一次DATA[15:0]同时像素计数器累加。当HREF拉低说明一行结束行计数器加1回到LINE_WAIT状态等待下一行。当行计数器达到预设分辨率比如480行采样完毕之后回到IDLE状态等待下一帧VSYNC。在写这部分代码时有一个关键技巧PCLK和HREF、DATA在进入FPGA之后必须先经过两级寄存器打拍同步再用于状态机判断和采样。这背后的原因是亚稳态问题。OV5640和FPGA是异步器件PCLK虽然由传感器产生但相对于FPGA内部时钟是异步的。如果你直接用FPGA的系统时钟去采样PCLK上的数据或者把HREF直接用于状态机判断一旦HREF在数据变化边沿附近发生电平翻转就可能触发触发器亚稳态导致整个采集逻辑错乱图像出现随机花屏或者行错位。正确的做法是先用PCLK对HREF和DATA进行打拍采样保证和PCLK同源再用打拍后的信号做后续逻辑。或者反过来用系统时钟同步PCLK的上升沿确保在PCLK稳定后再采样数据。我在Xilinx的Artix-7和Altera的Cyclone IV上都试过这个同步步骤绝对不能省。3.3 像素数据的有效窗口与消隐处理很多新手搞不清楚HREF和有效像素的对应关系导致采集到的图像左边或者右边有一条黑边或者错位。这里说一个非常实用的经验。OV5640在输出一帧图像时并不是持续的像素流而是按照“行有效 行消隐”的方式输出。在一行有效数据开始前HREF有一个建立时间在有效数据结束后HREF拉低进入行消隐。帧头也有帧消隐。所以你在配置采集模块时通常要用HREF作为数据有效标志而不是简单地数PCLK脉冲。如果直接用PCLK总数除以图像的宽度来定位行会因为消隐期的存在而完全对不上。我曾经踩过一个很实在的坑在某个工程里我只用了VSYNC和PCLK去采集数据没有用HREF做行门控结果图像是一条一条错位的斜纹。后来加上HREF门控严格按照HREF高电平时才采样问题立刻消失。VGA显示时还有一个同步问题。OV5640输出的RGB565图像像素数据需要按照从左到右、从上到下的顺序写入显示缓存。而VGA输出也是同样的顺序。两者如果不同步画面会出现撕裂或者偏移。解决办法是采集端写入FIFO时行号、列号都从0开始计数和VGA显示端的行列计数保持一致。4. 数据缓存与显示联动设计4.1 FIFO还是双口RAM图像缓存方案怎么选采集端PCLK和显示端像素时钟不在同一个时钟域必须做跨时钟域处理。常见的方案有两种异步FIFO和双口RAM。如果只是做实时的图像透传也就是采一帧显示一帧用异步FIFO就够了。优点是不需要管理复杂的读写地址FIFO内部会处理读写指针。但FIFO的容量必须能覆盖一行有效像素的数据量因为显示端的读取是连续的如果FIFO深度不够显示端会读到空数据图像出现条纹。如果要做帧缓存也就是采集端在写一帧的同时显示端在读上一帧这需要至少两帧的存储空间也就是乒乓操作。这时候双口RAM更合适。因为你可以把RAM分成两个bank一个bank写新帧一个bank读旧帧每帧交替切换。当然如果分辨率高、帧率大存储资源消耗也很大。以720P的RGB565为例一帧数据 1280 × 720 × 16bit ≈ 1.84MB至少需要两帧就是3.68MB这已经超出了很多中低端FPGA的片上存储容量。所以实际工程中我们通常不会做完整的一帧缓存而是做行缓存。具体来说在显示端以行为单位设置一个足够容纳一行像素的FIFO。采集端持续往FIFO里写显示端持续从FIFO里读。在行与行的切换间隙利用消隐时间做同步。这种方案对资源的要求低很多但有个前提采集端的PCLK频率和显示端的像素时钟频率不能差太多否则FIFO会不断积累误差最终上溢或者下溢。解决方法是让两端的有效行长度保持完全相同并且在每一行开始前对FIFO进行一次清空复位保证写入和读取从同一个起点开始。提示如果你用的是Intel/Altera的FPGAQuartus自带FIFO IP核配置时务必选择“异步时钟”选项读写时钟分别为PCLK和显示像素时钟。不要图省事选同步模式那样根本解决不了跨时钟域问题。4.2 从采集到VGA显示的完整数据通路我用一个720P的RGB565项目来具体说明。系统时钟50MHzPCLK约为42MHzVGA像素时钟需要74.25MHz。因为两个时钟不同直接用一个FIFO做缓存。数据通路如下。OV5640通过DVP接口输出PCLK、HREF、DATA[15:0]。采集模块在HREF高电平期间每个PCLK上升沿把DATA[15:0]写入行FIFO。FIFO写时钟用PCLK。VGA驱动模块在显示有效区域每个像素时钟到来时从FIFO读出一个像素数据。FIFO读时钟用VGA像素时钟。VGA驱动模块把读出的RGB565数据拆分成R、G、B分量分别送给VGA接口的DAC转换。这里有一个很实在的问题VGA显示时如果显示器的扫描顺序和FIFO读出的顺序不一致画面就会撕裂。所以需要在VGA驱动模块中同样设置行计数和场计数只有当行计数值和场计数值都在有效区域时才从FIFO读取数据并输出到显示器。VGA时序的关键参数以常见的720P为例。参数数值行同步像素数16行后肩像素数62行有效像素数1280行前肩像素数62一行总像素数1420场同步行数5场后肩行数27场有效行数720场前肩行数28一帧总行数780像素时钟74.25MHz当然如果用HDMI则不需要DAC转换直接在FPGA内部做TMDS编码即可但要注意DDC通道和热插拔检测的处理。篇幅原因这里主要讲VGA通路HDMI编码的细节下次单独开一篇。4.3 乒乓缓存如何保证显示画面不撕裂前面我提到只要FIFO容量够大实时透传也可以显示。但透传有个天然的问题采集端和显示端如果没有做到严格的行同步对齐画面会出现水平撕裂也就是图像上半部分来自当前帧下半部分来自上一帧或者下一帧。要彻底消除撕裂最稳妥的办法还是做帧级别的乒乓缓存。乒乓操作的核心思想是准备两块存储区域标记为A和B。在帧N1写入A区期间显示端读取B区的帧N在帧N2写入B区期间显示端读取A区的帧N1。这样读写操作交替进行显示端永远看到的都是完整的一帧。实现乒乓的关键是一个帧切换标志。采集端在检测到新的一帧VSYNC到来时把写地址切换到另一块区域显示端在扫描到垂直消隐期间把读地址也切换到另一块区域。实际工程中需要注意写地址的切换和读地址的切换必须有足够的时序余量。最简单的实现方式是用一个1bit的ram_sel寄存器。采集端在VSYNC有效时翻转显示端在场消隐时翻转。为了保证两边看到的ram_sel是同步的建议在两端各自打两拍再做地址切换避免读端正在读A区的时候写端突然切换到了A区。4.4 显示效果调试颜色、位置、同步三个维度图像能上屏之后接下来的调试主要看三个维度颜色对不对、位置偏不偏、画面稳不稳。颜色不对先查OV5640的0x4300寄存器是否配置为RGB565再查DVP数据线的高低字节有没有接反。很多开发板的原理图上DATA端口顺序是反的导致红色和蓝色互换画面整体呈蓝紫色调。这种问题不用改代码可以通过调换数据线位置解决。位置偏移通常是HREF的有效窗口和VGA的有效窗口没对齐。如果你发现整个画面往左偏移或者往上偏移多半是采集模块少计了行消隐或者场消隐的周期。解决的办法是在采集模块里行数据从HREF拉高后的第一个PCLK开始采样而显示端从VGA行同步之后经过后肩、有效数据等标准时序再开始读FIFO这样两边的时钟节拍就对上了。画面不稳定优先检查FIFO的读写指针是否平衡。如果采集端写入快于显示端读取FIFO会溢出反过来会下溢。我的经验是在每帧开始或每行开始的时候对FIFO的读指针做一次重置把两端重新拉回同一个起点可以大幅降低不同步的概率。5. 常见问题与调试排查技巧实录5.1 图像全黑SCCB读寄存器全是0xFF这是出现频率最高的问题。排查方法按以下顺序来。第一步用I2C调试工具或者逻辑分析仪检查上电后SCCL和SDA线上有没有波形。如果完全没有波形说明SCCB模块没有启动检查FPGA代码里状态机的触发条件比如cfg_start信号有没有拉高。第二步有波形但读回来全是0xFF大概率是从机地址不对。OV5640的SCCB_ID引脚如果悬空或接高电平设备地址是0x78如果接地地址是0x7A。先把原理图确认清楚再看代码里的设备地址。第三步如果地址也对但通信不稳定检查SCCL频率。有些OV5640对SCCL的上限频率有要求超过400KHz会通信失败。建议把SCCL分频到200KHz左右稳妥。第四步确认PWDN引脚没有被拉高。PWDN是掉电引脚如果它被拉高了OV5640直接进入掉电模式SCCB肯定响应不了。注意OV5640的复位引脚也就是RESETB需要在上电后拉高。有些板子设计的是低电平复位如果你一直复位传感器也不会工作。上电时序里要先拉低复位再拉高复位同时保持PWDN低电平再开始执行SCCB配置这个顺序别搞反。5.2 图像有斜条纹或者整条错位之前说过多半是没用HREF做行门控或者HREF同步没做好。排查思路是这样的用逻辑分析仪抓HREF和PCLK确认HREF的周期是否等于一行有效像素的时间。如果HREF高电平期间PCLK的脉冲数和预期分辨率不一致说明OV5640输出配置有问题或者PCLK分频配置不对。另外DVP接口的DATA线在PCLK跳变沿附近有毛刺也可能导致采到错误数据。建议在PCB上给DVP数据线做等长处理同时在FPGA内部对每个数据位都打两拍。我实测过同样的代码在优化了PCB走线后花屏现象明显减少。5.3 图像颜色偏蓝或偏红颜色通道互换是最常见的原因。RGB565格式下如果硬件上把R和B通道接反了画面就会整体偏蓝。排查方法是不修改代码先通过抓取一张纯色画面的波形确认DATA[15:11]对应的是不是标准RGB565的R通道。如果确认接反了有两个修复办法。第一修改原理图把数据线调换过来第二在FPGA内部做数据位重新映射。比如采集到的data[15:11]本来是R但实际硬件接的是B就在代码里用assign r data[4:0]这种方式做重新映射。5.4 FIFO溢出画面出现横条纹滚动这个现象典型的表现为画面可以显示但有一条或者多条横条纹在垂直方向不断滚动。原因基本就是FIFO的读写速率不匹配。显示端读取速率大于采集端写入速率FIFO读空显示端输出了空数据反之亦然。解决思路有以下几种。调整VGA时序里的有效像素总数让显示端的像素消耗速度和采集端的写入速度尽量接近。在每帧开始或者每行开始的时候对FIFO做一次清空重新建立同步。如果使用了帧缓存可以锁存一帧完成后产生的write_done信号在写端完成整帧切换后再允许读端读取。5.5 图像分辨率错误画面只显示一部分如果画面只显示左上角一部分后续区域是黑屏大概率是采集模块里的行场计数器配置错了。比如你配置OV5640输出1280x720但采集模块里把最大行数设置成了480那么超过480行的数据直接就被状态机忽略了自然显示不出来。还有一个容易忽略的点OV5640在输出RGB565时可能默认配置为“输出尺寸小于最大尺寸”的模式。比如0x3808和0x3809配置的是输出宽度0x380C和0x380D配置的是行总长度HTS。如果输出尺寸和HTS不匹配也会导致数据错乱。建议对照OV5640的官方配置表确认在特定分辨率下0x3808/0x380A/0x380C/0x380E这几组寄存器的值是否配套。6. 扩展思考从DVP到MIPI以及ISP算法的切入点如果你把这套DVP接口的采集流程跑通了其实就已经具备了对图像数据做进一步处理的基础。从趋势来看越来越多的传感器开始转向MIPI接口OV5640本身也支持MIPI用FPGA接收MIPI信号需要做差分信号处理、通道对齐、字节解包等复杂度比DVP高不少但核心思路依然是一样的对齐同步信号解析有效数据缓存并显示。另外图像采集只是前菜真正的重头戏是采集之后的ISP处理。比如坏点校正、黑电平校正、去马赛克、白平衡、伽马校正这些算法在FPGA里基本都是逐像素的流水线处理。我之前在采集到RGB565之后紧接着加了一个灰度转换模块和一个Sobel边缘检测模块整条流水线的延迟只有几个像素时钟实时性非常好这就是CPU方案做不到的。写在最后的调试心得我最初调通这套OV5640系统前后花了两三天时间其中一半时间都耗在SCCB配置和DVP行场同步上。回头看最核心的经验有三条第一时序问题一定要用逻辑分析仪或者示波器去看波形靠猜是猜不出来的第二SCCB配置完成后必须留足够的时间让传感器稳定采集模块不要抢跑第三FIFO的读写时钟域隔离和帧同步标志宁可做得复杂一点也不要为了省资源留下隐患。这套系统调通之后后面再做MIPI接入、再做图像算法加速都会顺畅很多。
返回列表