news 2026/10/19 5:02:59

FPGA图像采集实战:OV7670+SDRAM+VGA全链路设计与中值滤波

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA图像采集实战:OV7670+SDRAM+VGA全链路设计与中值滤波

简介:这是一套面向FPGA与数字图像处理学习者的完整工程源码,围绕OV7670摄像头采集、SDRAM缓存与VGA显示展开,并集成中值滤波算法,适合电子、集成电路及嵌入式方向的学生与工程师做课程设计、毕业设计或项目练手。资源包共576个文件,约11.74MB,以Verilog源码(.v)、VHDL文件(.vhd)、Quartus工程配置(.qpf、.qsf、.qip)、综合与仿真报告(.rpt、.summary)、引脚与时序约束(.pin、.sdc)以及大量编译缓存与数据库文件为主,完整保留了从RTL设计到综合实现的工程痕迹。项目覆盖IIC寄存器配置、SDRAM双端口控制器、VGA时序驱动与中值滤波去噪等关键模块,读者可据此理解图像数据流的缓存调度与显示同步机制,并参考工程约束与报告排查时序问题。目前已有399人学习下载,适合希望打通摄像头采集到屏幕显示全链路的进阶实践者。

1. 从一块摄像头到显示器:OV7670 + SDRAM + VGA 这条链路到底能干什么

如果你手头正好有一块带 OV7670 摄像头接口和 VGA 输出口的 FPGA 开发板,又不想只停留在点灯和跑马灯阶段,那这个项目大概率就是你要找的东西。它做的事情很纯粹:OV7670 采集图像,数据先写进 SDRAM 缓存,再由 FPGA 读出并通过 VGA 接口送到显示器上,中间还留了中值滤波的处理位置。整条链路跑通之后,你就能在屏幕上看到摄像头拍到的实时画面,而不是一堆雪花点或者花屏。

这套东西适合两类人:一类是刚学完 Verilog 基础语法、想找一个完整项目把时序、跨时钟域、存储器控制串起来的新手;另一类是做图像采集前端、想拿一个最小系统验证自己算法模块的熟手。它不依赖软核,不需要操作系统,纯硬件逻辑实现,资源占用可控,CPLD 也能勉强塞进去。下面我就按实际拆项目的顺序,把这条链路从头到尾讲一遍。

2. 系统架构拆解:四个模块怎么串成一条图像流水线

2.1 整体数据流与时钟域划分

先看数据怎么走。OV7670 输出的是 8 位并行数据,配合 PCLK、VSYNC、HREF 三个同步信号。SDRAM 是单端口或者伪双端口,读写共用一套地址和数据总线。VGA 输出需要 25.175MHz 的像素时钟(640x480@60Hz),而 OV7670 的 PCLK 通常在 12MHz 到 24MHz 之间,取决于你配置的输出格式和分辨率。

这就带来一个核心问题:摄像头写入和 VGA 读取是两个不同的时钟域,而且 SDRAM 同一时刻只能服务一个方向。常见做法是用一个异步 FIFO 或者乒乓缓冲来隔离,但更省资源的方案是让 SDRAM 控制器做时分复用——写优先,读在写间隙插入。我一般会把 SDRAM 时钟设成 100MHz,然后给写和读各分配时间片,保证 VGA 不会断流。

整个系统我习惯拆成四个模块:

  • ov7670_capture:负责把摄像头原始数据打拍对齐,输出有效的像素数据和写使能。
  • sdram_ctrl:SDRAM 初始化、刷新、读写仲裁,对外提供简单的写端口和读端口。
  • vga_timing:产生行同步、场同步和像素坐标,从 SDRAM 读数据。
  • top:例化以上模块,连接引脚,处理复位和锁相环。

中值滤波如果要做,通常插在ov7670_capture和sdram_ctrl之间,或者放在 VGA 读取之后。放在写入前的好处是 SDRAM 里存的就是处理后的图,读取逻辑不用改;坏处是滤波模块要跟着 PCLK 跑,时序压力大。放在读取后则可以用 VGA 时钟处理,但需要额外的行缓冲。这个项目里我建议先跑通直通,再考虑加滤波。

2.2 OV7670 采集模块的关键时序

OV7670 的时序不算复杂,但有几个坑。VSYNC 下降沿表示一帧开始,HREF 高电平期间数据有效,PCLK 上升沿采样。注意 HREF 有效期间并不是每个 PCLK 都有有效数据,前几个像素是 dummy。我一般会在 HREF 拉高后延迟几个周期再开始写。

// ov7670_capture.v 片段 always @(posedge pclk or negedge rst_n) begin if (!rst_n) begin wr_en <= 1'b0; data_out <= 8'd0; end else begin // HREF 有效且延迟计数到位才写 if (href_d1 && href_d2 && href_d3) begin wr_en <= 1'b1; data_out <= cam_data; // cam_data 是摄像头原始 8 位 end else begin wr_en <= 1'b0; end end end

这里href_d1到href_d3是三级打拍,目的是滤掉 HREF 刚拉高时的抖动,同时跳过前几个无效像素。参数上,延迟级数根据你实际抓到的波形调,一般 2 到 4 级都行。cam_data直接接摄像头数据引脚,不需要额外缓存,因为 SDRAM 写端口会锁存。

2.3 SDRAM 控制器的读写仲裁策略

SDRAM 部分是这个项目的重头戏。很多人卡在初始化不成功或者读写冲突上。初始化流程是固定的:上电后等待 200us,然后发预充电、刷新、加载模式寄存器。这些命令的时序参数要严格按芯片手册来,比如 tRP、tRFC、tMRD。

读写仲裁我一般用状态机实现,优先级给写。因为摄像头数据是实时的,丢了就丢了;VGA 读取晚一点只是画面轻微撕裂,人眼不一定看得出来。

// sdram_ctrl.v 仲裁片段 localparam IDLE = 2'd0; localparam WRITE = 2'd1; localparam READ = 2'd2; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; end else begin case (state) IDLE: begin if (wr_req) state <= WRITE; else if (rd_req) state <= READ; end WRITE: begin // 写突发长度 1,写完回 IDLE if (wr_done) state <= IDLE; end READ: begin if (rd_done) state <= IDLE; end endcase end end

wr_req和rd_req分别来自采集模块和 VGA 模块。wr_done和rd_done由 SDRAM 命令序列产生。注意这里没有做读写同时请求的复杂仲裁,因为同一时刻只会有一个方向有效——VGA 读取是在消隐期做的,而消隐期摄像头可能还在写,所以实际项目中我会把读操作安排在 HREF 无效的时间段,这样天然避开了冲突。

2.4 VGA 时序生成与数据读出

VGA 的 640x480@60Hz 时序是标准化的:行同步 96 个像素,行消隐后沿 48 个像素,行有效 640 个像素,行消隐前沿 16 个像素,总共 800 个像素时钟一行。场同步 2 行,场消隐后沿 33 行,场有效 480 行,场消隐前沿 10 行,总共 525 行。

// vga_timing.v 片段 localparam H_ACTIVE = 640; localparam H_FP = 16; localparam H_SYNC = 96; localparam H_BP = 48; localparam V_ACTIVE = 480; localparam V_FP = 10; localparam V_SYNC = 2; localparam V_BP = 33; always @(posedge vga_clk or negedge rst_n) begin if (!rst_n) begin h_cnt <= 0; v_cnt <= 0; end else begin if (h_cnt == H_ACTIVE + H_FP + H_SYNC + H_BP - 1) begin h_cnt <= 0; if (v_cnt == V_ACTIVE + V_FP + V_SYNC + V_BP - 1) v_cnt <= 0; else v_cnt <= v_cnt + 1; end else begin h_cnt <= h_cnt + 1; end end end // 数据有效区域 assign data_valid = (h_cnt < H_ACTIVE) && (v_cnt < V_ACTIVE);

vga_clk就是 25.175MHz,通常用锁相环从 50MHz 晶振分频得到。data_valid拉高时,从 SDRAM 读出的像素数据直接送到 VGA 的 RGB 引脚。注意 SDRAM 读数据有 latency,一般 2 到 3 个时钟周期,所以地址要提前给。我一般会在data_valid前几个周期就开始预取。

3. 从零跑通:工程搭建、引脚约束与上板验证

3.1 工程目录与文件组织

拿到源码后,先别急着编译。我习惯先把目录理清楚,这样后面改起来不迷路。一个典型的工程结构是这样的:

ov7670_sdram_vga/ ├── rtl/ │ ├── top.v │ ├── ov7670_capture.v │ ├── sdram_ctrl.v │ ├── vga_timing.v │ └── pll.v ├── sim/ │ ├── tb_top.v │ └── sdram_model.v ├── constraint/ │ └── pin.tcl └── doc/ └── timing_note.txt

rtl放综合代码,sim放仿真测试平台,constraint放引脚和时序约束。如果你用的是某公司的开发环境,约束文件可能是.qsf或者.ucf,但逻辑一样:把cam_data[7:0]、pclk、vsync、href、vga_r[4:0]、vga_g[5:0]、vga_b[4:0]、vga_hs、vga_vs这些信号绑到实际引脚上。

3.2 锁相环配置与时钟约束

锁相环要产生至少两个时钟:一个给 SDRAM 控制器,通常 100MHz;一个给 VGA,25.175MHz。如果摄像头 PCLK 直接来自 OV7670 内部,那它就是一个异步时钟,需要做跨时钟域处理。

# pll 配置示例(某公司 IP 核) create_clock -name clk_50m -period 20.000 [get_ports clk] create_clock -name clk_vga -period 39.722 [get_ports vga_clk] create_clock -name clk_sdram -period 10.000 [get_ports sdram_clk]

clk_vga的周期是 1/25.175MHz ≈ 39.722ns。clk_sdram是 100MHz,周期 10ns。注意 SDRAM 时钟输出到芯片时会有相位偏移,一般用锁相环的相移功能调 90 度或者 180 度,保证数据在时钟中间采样。这个参数需要根据板子实际布线调,没有万能值。

3.3 上板调试步骤与现象判断

编译通过后,下载到 FPGA。先别接摄像头,用仿真或者内部产生的测试图案验证 VGA 通路。我一般会在top里加一个测试模式,用一个计数器产生彩条,直接送到 VGA 引脚。如果屏幕上出现稳定的彩条,说明 VGA 时序和锁相环没问题。

然后接上摄像头,观察现象:

  • 屏幕全黑:检查 OV7670 的复位和电源,确认 SCCB 配置是否成功。很多模块默认输出 RGB565,但如果你没配置,它可能输出 YUV,颜色会不对。
  • 屏幕花屏或者滚动:SDRAM 读写冲突或者时序不满足。用逻辑分析仪抓 SDRAM 的 CAS、RAS、WE 信号,看命令间隔是否满足 tRC、tRAS。
  • 图像偏色或者错位:HREF 延迟没调对,或者 SDRAM 地址映射搞错了。检查写入地址和读出地址是否一致。

提示:调试 SDRAM 时,先把时钟降到 50MHz 跑通,再逐步升到 100MHz。高频下时序余量小,容易翻车。

4. 中值滤波插入位置与资源权衡

4.1 3x3 窗口生成与排序网络

中值滤波的核心是取一个 3x3 窗口的 9 个像素,排序后取中间值。在 FPGA 里做排序,最直接的是排序网络,用比较器搭。9 个数的中值可以用 19 个比较器实现,比冒泡排序省资源。

// median_filter.v 片段:三级流水线排序 // 第一级:两两比较 wire [7:0] a1, a2, a3, a4, a5, a6, a7, a8, a9; // ... 窗口数据赋值 // 第二级:分组排序 // 第三级:取中值 assign median = /* 排序网络输出 */;

实际写的时候,我会用三个行缓冲(line buffer)来缓存前两行,加上当前行组成 3x3 窗口。行缓冲用双端口块 RAM 实现,深度 640,位宽 8 位。每来一个像素,窗口右移一列,同时更新行缓冲。

4.2 滤波前后资源占用对比

不加滤波时,整个工程在中等规模 FPGA 上大概占用 800 到 1200 个逻辑单元,SDRAM 控制器占大头。加上中值滤波后,逻辑单元增加约 300 到 500 个,块 RAM 增加 2 到 3 个。如果 CPLD 资源紧张,可以把滤波关掉,用宏定义或者参数控制。

模块逻辑单元(约)块 RAM(约)
采集 + SDRAM + VGA10001
加中值滤波14003

这个数字不是绝对的,取决于综合工具和优化选项。但趋势是明确的:滤波的代价主要在行缓冲和排序网络。

4.3 滤波位置对时序的影响

前面说过,滤波放在写入前还是读取后,时序压力不同。放在写入前,滤波模块要跟着 PCLK 跑,如果 PCLK 是 24MHz,那 3x3 窗口的更新和排序必须在一个 PCLK 周期内完成,或者用流水线。放在读取后,可以用 VGA 时钟,25.175MHz 和 PCLK 差不多,但读取后的数据是已经缓存的,不存在丢帧问题。

我一般会放在写入前,因为这样 SDRAM 里存的就是干净图,VGA 端不用改。但如果你发现时序不收敛,就挪到读取后,加一个 FIFO 做缓冲。

5. 避坑与常见问题排查

5.1 现象:VGA 显示正常但摄像头图像全黑

原因:OV7670 没有正确初始化。很多模块上电后默认输出是关闭的,需要通过 SCCB 写寄存器配置输出格式、分辨率、时钟分频等。如果你只接了数据线和同步线,没接 SCCB,那摄像头根本不工作。

解决:确认 SCCB 的 SCL 和 SDA 是否接到 FPGA,并在上电后执行一段配置序列。常见配置包括设置 COM7 为 RGB565、COM3 的时钟分频、CLKRC 的内部分频等。配置序列可以存在 ROM 里,上电后依次读出。

5.2 现象:图像有横向条纹或者抖动

原因:SDRAM 读写冲突或者刷新不及时。SDRAM 需要定期刷新,一般 64ms 内刷新 8192 次,平均 7.8us 一次。如果刷新被读写长时间打断,数据会丢失。

解决:在 SDRAM 控制器里加一个刷新计数器,优先级高于读写。刷新命令插入时,当前读写操作要能暂停。我一般会把刷新周期设成 7.5us,留一点余量。

5.3 现象:图像颜色偏绿或者偏紫

原因:RGB 数据位序搞反了。OV7670 输出 RGB565 时,第一个字节是 R[4:0] 和 G[5:3],第二个字节是 G[2:0] 和 B[4:0]。如果你直接按字节拼接,颜色肯定不对。

解决:在采集模块里做位拼接,把两个字节合成 16 位 RGB565,再送到 VGA 的 RGB 引脚。VGA 如果是 5-6-5 接口,直接对应;如果是 4-4-4,还要做截断。

5.4 现象:屏幕上有雪花点或者随机噪点

原因:跨时钟域没有同步,或者 SDRAM 读数据时采样点不对。PCLK 和 SDRAM 时钟是异步的,写使能和数据直接跨域会亚稳态。

解决:在写端口加一个异步 FIFO,或者至少用两级触发器同步写使能。SDRAM 读数据时,用锁相环把 SDRAM 时钟相移 90 度,在数据稳定窗口采样。

5.5 现象:编译通过但下载后无反应

原因:引脚约束错了,或者锁相环没有锁定。有些开发板的 VGA 接口是电阻网络 DAC,引脚电平标准要设成 3.3V LVTTL,不能设成 LVDS。

解决:对照原理图逐个检查引脚号,确认锁相环的 locked 信号拉高后再释放复位。可以在 top 里加一个 LED 指示 locked 状态,方便判断。

6. 二次开发:把中值滤波换成其他算法,以及验证方法

跑通直通之后,你可能会想换算法。这个项目的结构对二次开发很友好,因为图像数据在 SDRAM 里是线性存放的,你只需要改写入前的处理模块,或者改读取后的处理模块。

我一般会先做一个「算法旁路」开关,用一个参数或者拨码开关控制是否启用滤波。这样调试时可以先关掉算法,确认通路没问题,再打开算法看效果。具体做法是在top里加一个多路选择器:

// top.v 片段:算法旁路 wire [7:0] raw_data; wire [7:0] filtered_data; wire [7:0] final_data; assign final_data = bypass ? raw_data : filtered_data;

bypass可以接一个拨码开关,也可以接一个寄存器,通过串口或者按键控制。这样你上板后可以实时切换,对比效果。

换成其他算法时,比如均值滤波或者高斯滤波,接口是一样的:输入 3x3 窗口,输出 8 位像素。你只需要替换median_filter模块的内部实现,外部的行缓冲和窗口生成不用动。均值滤波更简单,把 9 个数相加除以 9 就行,但除法在 FPGA 里占资源,可以用移位近似。高斯滤波需要加权,权重系数可以存在 ROM 里。

验证方法上,我习惯分三步走:

  1. 仿真验证:用测试平台产生已知图案,比如全白、全黑、渐变,看输出是否符合预期。
  2. 在线抓图:用逻辑分析仪或者 FPGA 厂商的在线调试工具,抓取 SDRAM 写入和读出的数据,对比滤波前后的值。
  3. 上板对比:用同一场景,切换旁路开关,肉眼观察噪声是否减少,边缘是否保留。

有一个细节容易被忽略:中值滤波对椒盐噪声效果好,但对高斯噪声一般。如果你发现滤波后图像变模糊,可能是窗口太大或者排序网络有 bug。我一般会先用 MATLAB 或者 Python 模拟一遍算法,确认系数和窗口没问题,再移植到 Verilog。

从那以后我每次做图像处理项目,都会先写一个行为级模型跑一遍,再动手写 RTL。这样能省下大量上板调试的时间。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 5:02:28

机器学习导论习题解答:题型拆解、推导方法与避坑清单

很多同学学机器学习导论&#xff0c;最大的错觉是把“听懂”当成了“学会”。老师在台上讲线性回归、决策树、支持向量机&#xff0c;台下听得热血沸腾&#xff0c;可真拿到一份习题册&#xff0c;面对一堆公式、数据集和“请证明”三个字&#xff0c;瞬间卡壳。我自己最初也是…

作者头像 李华
网站建设 2026/10/19 5:02:15

BTS7960大功率电机驱动模块:原理图与PCB设计实战指南

简介&#xff1a;这是一套面向智能车竞赛、机器人控制及大功率直流电机应用场景的BTS7960驱动模块完整设计资料&#xff0c;适合电子设计爱好者、嵌入式开发者与飞思卡尔智能车参赛者参考。资源包共11个文件&#xff0c;约5.08MB&#xff0c;包含Altium Designer工程文件&#…

作者头像 李华
网站建设 2026/10/19 5:01:38

六轴机械臂标定全解析:从零点到DH参数的工程实战指南

1. 先把"标定"这件事说透&#xff1a;它解决的真不是精度问题干了这些年机器人应用&#xff0c;我见过太多人对"六轴机械臂标定"理解跑偏。很多人一听标定&#xff0c;第一反应就是"我示教器上按个零点校准不就完事了吗"。说实话&#xff0c;我刚…

作者头像 李华
网站建设 2026/10/19 5:01:08

开放式代码审查落地指南:从流程设计到工具配置

代码审查真的不能走过场&#xff0c;我决定把"open-code-review"落实到团队日常代码审查这件事&#xff0c;很多人以为就是拉个群让同事帮忙看看代码&#xff0c;或者走个流程在合并按钮上点个"同意"。我在经历了好几次线上事故、几次"这代码谁写的&q…

作者头像 李华
网站建设 2026/10/19 5:00:19

宝塔面板快速部署Django项目实战指南

1. 项目概述&#xff1a;为什么“宝塔快速部署Django项目”成了高频刚需最近在几个技术交流群里&#xff0c;几乎每天都有人问&#xff1a;“Django本地跑得好好的&#xff0c;一上服务器就502”“Nginx配置改了八遍还是静态文件404”“uwsgi启动报错找不到模块&#xff0c;但v…

作者头像 李华