ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA驱动OV5640图像采集与RGB-LCD显示链路设计

FPGA驱动OV5640图像采集与RGB-LCD显示链路设计 简介在嵌入式视觉与实时图像处理中FPGA凭借并行时序控制能力成为连接摄像头与显示器的理想桥梁。图像数据从传感器到屏幕往往需要处理高速像素流与跨时钟域传输而行缓存直通技术正是解决资源受限场景下帧缓存不足的关键方法。其原理是利用异步FIFO缓存少量行数据配合帧同步策略实现低延迟的像素搬运从而避免整帧存储带来的存储器开销。该技术不仅适用于工业检测、车载影像等实时显示场景也为FPGA图像开发提供了一条从接口协议到时序生成的完整实践路径。围绕OV5640传感器与RGB-LCD显示屏文章基于EP4CE10器件详细阐述了DVP采集、SCCB配置、行缓存设计及帧同步策略帮助工程师快速搭建可靠的图像链路。1. 用 EP4CE10 跑通 OV5640 到 RGB-LCD 的完整图像链路EP4CE10 内部一共只有 414Kbit 块 RAM约 51KB而一帧 480×272 的 RGB565 画面要占 255KB。这个差距决定了项目不可能像 ARM 平台那样“先存一帧、再刷屏”它必须靠行缓存直通和帧同步把图像一路“流”过去。这正是 FPGA 做图像采集最值得理解的部分数据不是被缓存而是在时序控制下被搬运。这条链路覆盖了 OV5640 的 SCCB 初始化、DVP 并行口像素采集、跨时钟域 FIFO 缓存以及 RGB-LCD 的行场时序生成四段逻辑全部用 Verilog HDL 写进一颗 Cyclone IV 芯片里。对想入门 FPGA 图像开发的工程师来说它是比点灯、串口收发完整得多的训练场有低速配置接口、有高速像素流、有异步时钟域还有屏幕上肉眼可见的反馈。下面按我自己做这类项目时的顺序展开先盘资源定架构再写配置然后打通像素流最后讲排错。2. EP4CE10 资源盘点与整体架构设计时钟、接口与缓存选型2.1 为什么 51KB 内嵌 RAM 决定了不能用整帧方案EP4CE10 属于 Cyclone IV E 系列10K 逻辑单元、两个 PLL、46 个 M9K 块逻辑资源做这套链路足够但存储非常紧张。先算一笔账480×272RGB565 一帧是 480×272×2 261120 字节约 255KB哪怕缩到 QVGA 320×240也要 150KB照样超过内嵌 RAM。所以“OV5640 输出一帧FPGA 存下整帧再按 LCD 节奏读出”这条路在纯 EP4CE10 上走不通。常见的兜底做法有两种。一种是外挂 SDRAM把帧存到 16Mbit 的 SDRAM 里代价是工程复杂度明显上升要写 SDRAM 控制器、刷新逻辑、多端口仲裁另一种是用 AL422B 这类外部大容量 FIFO 芯片接口简单但容量只有 3Mbit勉强够 480×272 一帧且只适合帧缓存这一个用途。开发板上最常见的组合其实是第三种分辨率配成和 LCD 一致不做整帧缓存只缓存一行或几行让 sensor 的行数据在 LCD 消隐期里被逐行“转储”过去。这也是本文采用的主方案第 4 章给具体实现。2.2 顶层模块划分与信号连接整个工程的模块划分很固定下面这张表基本就是这类项目的骨架模块职责关键信号pll产生 XVCLK、系统时钟、LCD 像素时钟inclk0板载 24MHzc024MHzc150MHzc29~33MHzsccb_cfg用 SCCB 协议初始化 OV5640 寄存器sccb_sda、sccb_scl、上电完成标志ov5640_capture解析 DVP 时序拼接 RGB565 像素cam_pclk、cam_vsync、cam_href、cam_d[7:0]line_fifo跨时钟域行缓存写侧 cam_pclk读侧 lcd_clk1024×16bitlcd_timing产生 RGB-LCD 行场同步和数据使能lcd_hsync、lcd_vsync、lcd_de、lcd_rgbframe_sync帧消隐期握手决定丢弃还是放行当前帧cam_vsync、lcd_vsync、fifo 空标志顶层例化的核心就是把四个模块的线拉直摄像头配置完成后产生 xvclk 和初始化完成脉冲像素采集模块在 cam_href 有效时把 8bit 数据拼成 RGB565 写入 FIFOLCD 时序模块产生读使能帧同步模块负责阻止“写到一半被读走”的撕裂画面。// top_vga_camera.v 顶层例化示意 wire pll_lck; wire clk_xvclk; // 24MHz - OV5640 XVCLK wire clk_sys; // 50MHz 逻辑时钟 wire clk_lcd; // LCD 像素时钟9.2MHz 左右480x272 wire cam_init_ok; wire [15:0] fifo_q; wire fifo_rd_en; pll u_pll( .inclk0 (clk_24m), .c0 (clk_xvclk), .c1 (clk_sys), .c2 (clk_lcd), .locked (pll_lck) ); ov5640_capture u_cap( .pclk (cam_pclk), .vsync (cam_vsync), .href (cam_href), .din (cam_d[7:0]), .wr_en (fifo_wr_en), .wr_data (fifo_wr_data), .frame_valid(frame_valid) ); line_fifo u_fifo( .wrclk (cam_pclk), .wrreq (fifo_wr_en), .data (fifo_wr_data), .rdclk (clk_lcd), .rdreq (fifo_rd_en), .q (fifo_q), .rdempty(fifo_empty) );这个例化片段说明了三件事PLL 的三个输出必须分清楚用途FIFO 的写时钟是摄像头输出的 PCLK不是系统时钟这是跨时钟域的核心读时钟来自 LCD 像素时钟所以 FIFO 一定是异步 FIFO不能图省事用同步 RAM。常见错误是有人为了规避异步 FIFO 的 IP 配置把读侧也挂在 cam_pclk 下结果屏幕要么撕裂要么滚屏。2.3 PLL 时钟划分与选型理由板载晶振一般是 24MHz 或 50MHz。OV5640 的 XVCLK 推荐 24MHz所以我习惯把晶振选 24MHz 的板子省一级分频。PLL 三路输出里c0 的 24MHz 直接给摄像头做外部时钟OV5640 内部有自己的 PLL会把它倍频成 PCLK这个 PCLK 是芯片输出的FPGA 无法直接控制频率只能通过寄存器配置分频系数去间接调。c1 给逻辑和状态机用c2 给 LCD 的像素时钟。像素时钟按分辨率估算480×27260Hz 大约需要 9MHzVGA 640×48060 是 25.175MHz800×480 要 33MHz 左右。c2 具体设多少取决于你屏幕 datasheet 里的 clock frequency 参数。用 Quartus Prime Lite 开发 Altera 器件时PLL 就例化这个 IP 核不需要额外授权费Cyclone IV 全系列都支持。3. OV5640 初始化SCCB 时序状态机与关键寄存器表3.1 SCCB 和 I2C 的差异16 位寄存器地址的写序列OV5640 的配置接口叫 SCCB兼容 I2C 物理层但有个关键差异OV5640 的寄存器地址是 16 位所以一次写操作要传三个字节数据——设备地址、寄存器高字节、寄存器低字节、数据字节而不是 I2C 常见的“设备地址 寄存器地址 数据”。很多第一次写的人套 I2C 的驱动模板只传一个字节的寄存器地址结果摄像头完全不工作。SCCB 总线是开漏结构靠外部上拉电阻拉高。FPGA 的 IO 要配置成开漏输出或三态门不能推挽强拉。我用三态门实现的最简模型是输出数据位时sda_out 数据sda_oe 1释放总线时 sda_oe 0让上拉拉高。这和 I2C 从机的 GPIO 模拟方式相同但记住不要开内部上拉之后还用推挽输出否则两个器件会打架。SCCB 的从机地址在不同开发板上见过 0x42 和 0x78 两种写法实际是 7 位地址左移一位后的差异。与其纠结地址不如直接读 PID 验证OV5640 的 0x300A 读出 0x56、0x300B 读出 0x40初始化代码里第一步就轮询这个寄存器比任何纸上谈兵都可靠。3.2 可复用的 SCCB 写状态机Verilog下面是一段写寄存器状态机的核心骨架包含起始、发送、ACK 检测和停止。这里只贴主状态转移完整的位计数和移位逻辑按标准 I2C 发送补充。// sccb_write_fsm.v 核心状态定义 localparam IDLE 4d0; localparam START 4d1; localparam SEND_ADDR 4d2; // 设备地址 8bit localparam ACK_ADDR 4d3; // 设备地址 ACK localparam SEND_REG_H 4d4; // 寄存器高字节 localparam ACK_REG_H 4d5; localparam SEND_REG_L 4d6; // 寄存器低字节 localparam ACK_REG_L 4d7; localparam SEND_DATA 4d8; // 要写的值 localparam ACK_DATA 4d9; localparam STOP 4d10; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin state IDLE; end else begin case (state) IDLE: if (cfg_start) begin bit_cnt 3d7; scl 1b1; sda_oe 1b1; sda_out 1b0; state START; end START: // scl 高电平期间 sda 拉低产生起始条件 state SEND_ADDR; SEND_ADDR: // 每拍移出一位先 MSB state (bit_cnt 0) ? ACK_ADDR : SEND_ADDR; ACK_ADDR: // 释放 sda检测从机 ACK state SEND_REG_H; // ack_err sda_in; 为低才是正确 ACK ... endcase end end这段状态机的逻辑顺序对初学者是“送地址—求 ACK—送寄存器高字节—求 ACK—送寄存器低字节—求 ACK—送数据—求 ACK—停止”每一步都有对应的等待状态。写的时候最容易漏的是 ACK 等待SCCB 从机拉低 SDA 表示应答但它是开漏的释放后 SDA 并不会立刻变高总线电容会让边沿变缓。所以 ACK 检测必须在 SCL 高电平的中点采样而不是边沿采样。采样点设错能通信但偶发失败这在快时钟下尤其明显。3.3 必须写对的寄存器输出尺寸、格式与时钟完整初始化表一般有几百条寄存器开发板的驱动包里都有不建议从零对着数据手册翻译。真正需要理解的是下面几类寄存器地址作用常用值示例说明0x3008软件复位0x82写 1 后延时 5ms 以上再继续配置0x3103系统时钟 PLL 使能0x11部分驱动表通过它打开 PLL0x3808 / 0x3809输出宽度0x02 / 0x80组合起来是 0x0280即 6400x380A / 0x380B输出高度0x01 / 0xE00x01E0即 4800x4300输出格式0x6FRGB565 输出不同表写法略有差异0x300A / 0x300BPID / VID0x56 / 0x40只读用于验证链路通没通输出尺寸和格式这两条决定后续模块怎么搭。如果 LCD 是 480×272就把 0x3808~0x380B 配成 480×272让 OV5640 内部的 ISP 缩放去对分辨率FPGA 这边不做缩放处理。在 EP4CE10 上做任意比例缩放不划算逻辑资源和 9bit 乘法器都紧张能用 sensor 自带功能解决的绝不动手写算法。初始化流程的先后顺序比具体值更影响成功率上电后先把 PWDN 引脚拉高复位等电源稳定再拉低然后等 XVCLK 稳定接着写 0x3008 软复位延时后再开始整表写入。最后读 PID 确认链路。第一次调板我建议状态机里留一个调试信号把当前写的寄存器地址引到信号分析仪或 LED 上卡在哪条一目了然。4. DVP 像素采集与 RGB-LCD 显示行缓存直通方案4.1 DVP 接口时序解析与像素拼接OV5640 的 DVP 并行口有四根关键信号线PCLK 像素时钟、VSYNC 帧同步、HREF 行有效、D[7:0] 数据线。在 RGB565 模式下一个像素占两个 PCLK第一个节拍传高字节第二个节拍传低字节HREF 拉高期间PCLK 的上升沿连续送数据HREF 内部还包含行消隐所以不能简单等价于“HREF 高就是有效像素”。// ov5640_capture.v8bit 拼接为 RGB565 reg byte_odd; reg [7:0] byte_hi; reg [15:0] pixel_r; always (posedge cam_pclk or negedge rst_n) begin if (!rst_n) begin byte_odd 1b0; end else if (cam_href) begin byte_odd ~byte_odd; // 每个 PCLK 翻转一次 if (byte_odd) byte_hi cam_d; // 第一个字节拼接用的高 8 位 else begin pixel_r {byte_hi, cam_d}; // 第二个字节到达拼出一个像素 // 此处产生 fifo_wr_en 和 fifo_wr_data pixel_r end end else begin byte_odd 1b0; end end这段代码的逻辑说明byte_odd是节拍标志HREF 拉高时每个 PCLK 翻转一次奇数拍存高字节偶数拍拼像素。拼出来的pixel_r就是 RGB565 格式低 5 位是蓝色中间 6 位绿色高 5 位红色。数据手册里 PCLK 的采样沿推荐是上升沿但如果上板后画面出现横向错位或颜色噪点可以试试改用下降沿采样或者用 PLL 把采样时钟移相 90 度。HREF 内部的消隐区需要额外处理。上面代码只对 HREF 为高时的数据计数如果行有效像素是 480 个那么拼出的像素数到 480 就要停下来忽略 HREF 剩余高电平时间。判断方法是对拼接完成后的像素计数计数到H_ACTIVE - 1后清 0同时不再产生写使能。否则 FIFO 里会混入消隐区的垃圾数据LCD 上表现为每行尾部出现随机彩点。4.2 行 FIFO 逐行转储用 M9K 换帧不整帧缓存帧缓存放不下行缓存则完全没有压力。一行 480 像素 × 16bit 960 字节EP4CE10 的一个 M9K 块是 9Kbit约 1KB两个块就能做 2048×16bit 的异步 FIFO不仅够存一行还能吸收 PCLK 与 LCDCLK 之间的短时速率差。FIFO 的 IP 配置要点是读侧选 show-ahead预读模式可以让 LCD 时序模块在产生读使能之前就看到当前数据写侧用 cam_pclk读侧用 clk_lcd两个时钟完全异步深度选 1024 就够选 2048 更稳M9K 资源完全承担得起。读写指针和水位不用自己写IP 核内部管理但要注意把rdempty和wrfull都引出来看它们是排查花屏的第一手信号。直通方案的本质是“写到哪一行读就追到哪一行”。行与行之间的交接过在消隐期完成写侧检测到 HREF 拉低说明一行结束读侧由 LCD 时序模块在行消隐时暂停读 FIFO。真正实现时有一个细节容易漏FIFO 的写使能不能跨行连续拉高必须在 HREF 拉低后同步拉低否则下一行开头会接着上一行的数据继续写错位沿会整屏传递。4.3 LCD 时序发生器参数表与实现RGB-LCD 的接口一般是 RGB 并行数据加行同步 HSYNC、场同步 VSYNC、数据使能 DE。很多 4.3 寸屏默认 DE 模式时序参数以 datasheet 为准下面是两组合适的典型值可以起板后再细调分辨率H_ACTIVEH_FPH_SYNCH_BPV_ACTIVEV_FPV_SYNCV_BP480×27248024122722102640×48064016964848010233时序发生器就是两个计数器一个数像素、一个数行。像素计到H_TOTAL-1时回绕并令行计数加一行计数到V_TOTAL-1时回绕表示一帧结束。DE 信号用比较器产生保证数据只在有效区间出去。// lcd_timing.v产生 HSYNC / VSYNC / DE always (posedge clk_lcd or negedge rst_n) begin if (!rst_n) begin h_cnt 0; end else if (h_cnt H_TOTAL - 1) begin h_cnt 0; v_cnt (v_cnt V_TOTAL - 1) ? 0 : v_cnt 1b1; end else begin h_cnt h_cnt 1b1; end end assign lcd_hsync (h_cnt H_SYNC) ? 1b0 : 1b1; assign lcd_vsync (v_cnt V_SYNC) ? 1b0 : 1b1; assign lcd_de (h_cnt H_SYNC H_FP) (h_cnt H_SYNC H_FP H_ACTIVE) (v_cnt V_SYNC V_FP) (v_cnt V_SYNC V_FP V_ACTIVE);这个模块里最容易踩的坑是 DE 和像素数据的对齐。FIFO 读数据需要一拍寄存器输出又打一拍所以 RGB 输出会比 DE 晚两个周期。解决办法是把 DE 也打两拍和 RGB 同步送出。画面整体往左或往右偏一格基本都是这个对齐差不是时序参数的问题。4.4 帧同步策略丢帧比撕裂好直通方案没有整帧缓存无法在帧边界完美切换画面。如果写侧正在写第 N 行、读侧却在读第 N-1 行的半截硬件上就会出现画面上下两部分错位这就是撕裂。避免撕裂的通用策略是丢帧检测到新的 VSYNC 到来时如果 LCD 正在显示有效区域就丢弃这帧数据直到 LCD 进入场消隐再开始接收。实现起来并不复杂用一个frame_en信号sensor 的 VSYNC 有效沿到来时检查 LCD 是否处于垂直消隐期是则拉高放行否则保持禁止等当前帧显示完再拉高。代价是帧率会略低于 sensor 输出可能从 60fps 降到 55fps 左右但对显示类应用无所谓。真正需要帧率稳定且提质感的场景只能回到 SDRAM 双缓冲方案这已经是另一个量级的工程了。5. 上板前的检查清单约束、花屏与初始化失败5.1 给 PCLK 加 input delay 约束OV5640 输出的 PCLK 和数据都是芯片自己的时钟域FPGA 里没有对应的时钟树节点所以需要在 SDC 里定义一个虚拟时钟再对数据线做 set_input_delay 约束。很多人不做这步也能跑起来但那是在 Quartus 默认的宽松时序分析下碰运气。分辨率提高、PCLK 超过 50MHz 后布线延迟和 IO 延迟的影响会被放大。# camera_input.sdc create_clock -name cam_pclk -period 40.0 set_input_delay -clock cam_pclk -max 5.0 \ [get_ports {cam_d[*] cam_href cam_vsync}] set_input_delay -clock cam_pclk -min 2.0 \ [get_ports {cam_d[*] cam_href cam_vsync}]period 40ns 对应 25MHz PCLK具体按你配置的输出分辨率算。max/min 延时的值先给经验区间然后用 SignalTap 或直接看画面微调。如果时序报告显示 setup 违例第一个手段不是改代码而是改 PLL 的输出相位把 PCLK 采样点往后或往前挪 90 度往往一调就好。5.2 三电平确认法初始化失败先读 PID 再看 0x3008初始化失败是最难定位的问题因为它发生在最前面。我的固定顺序是先测 SCCB 的 SCL/SDA 引脚电平空闲时都应该是高再写一个只读 PID 的代码版本确认 0x300A/0x300B 能读到 0x56/0x40最后才跑完整初始化表。三步中任何一步不过都别往下调显示。现象检查项常见原因SCL 为低上拉电阻、FPGA IO 配置IO 被配成推挽输出或漏焊SDA 一直为高接线、器件地址地址高位配错从机不应答PID 读出全 FXVCLK 没起振PLL c0 没输出或 cam_pclk 相位不对PID 读出但对画面黑0x3008 后没延时软复位后立刻写表寄存器被复位覆盖PID 能读出说明 SCOB 物理层没问题之后画面黑多半是寄存器表里 0x3808~0x380B 的尺寸配置和实际 PCLK 不匹配或者 sensor 还停在空闲模式。0x3008 写 0x82 后必须等足够时间再继续这个延时放在状态机里用计数器做别用阻塞式延时。5.3 花屏、偏色、滚屏的定向排障法花屏排障先分清是“整屏花”还是“局部花”。整屏花看 FIFO 配置两个时钟域是不是真的异步、读侧 show-ahead 是不是开启、DE 和 RGB 有没有对齐。局部花优先怀疑行有效计数HREF 高电平内的消隐区没滤掉导致每行首尾有杂质。滚屏基本和帧同步相关也就是第 4.4 节里的frame_en没生效或者 FIFO 的rdempty信号没参与读使能的产生读侧在空 FIFO 时仍然在读数把旧数据重复读出来。颜色不对则查字节序RGB565 的高低位反了画面会偏蓝偏紫如果只是红绿互换通常是高 5 位和中间 6 位错位。最后检查电源和上电顺序OV5640 对 AVDD、DOVDD、DVDD 的上电顺序有要求反了会偶发初始化失败。上电后先拉低 PWDN等电源稳定 10ms 再拉高这个时序在 Verilog 里用复位延迟计数器实现。一套链路里最费时间的从来不是代码本身而是这些“先用工具确认信号、再改代码”的定位习惯。本文还有配套的精品资源点击获取
返回列表