简介:面向FPGA视频图像处理领域的学习者与硬件开发者,该压缩包提供了一套完整的基于OV7670摄像头的显示工程,使用Verilog实现,全面覆盖摄像头初始化配置、DVP协议数据采集、FIFO缓冲、SDRAM缓存以及VGA显示输出,形成从采集到显示的完整链路。资源压缩后约178.54MB,共包含522个文件,文件类型涵盖Verilog/TDF源码、Quartus工程与IP核配置文件、ModelSim仿真波形、TCL脚本等,便于直接打开工程进行仿真和上板调试。目前已有3826人学习下载,适合有一定数字逻辑基础、希望系统理解摄像头显示链路的学生或工程师,也适合作为FPGA图像项目参考。工程模块化程度高,时钟、OV7670初始化、DVP数据流、写FIFO、SDRAM控制、读FIFO和VGA控制等模块均有独立源码;读FIFO采用showahead模式,SDRAM控制器在原有基础上做了信号适配,能够帮助读者快速定位模块间握手关系,并借鉴其DVP时序、SDRAM控制器与FIFO配合的设计思路。
1. FPGA + OV7670 + VGA 显示链路:先弄懂这张图的四个角色
FPGA 开发板上接一颗 OV7670 摄像头,经 Verilog 逻辑采集图像数据,暂存进 SDRAM,再按 VGA 时序输出到显示器,这条链路是图像处理入门最经典的组合。很多人一上来就找网上的代码包,烧进去发现花屏、黑屏、颜色不对,问题多半不是摄像头坏了,而是没搞明白这四个环节各自管什么:OV7670 负责把光信号变成并行的数字信号,SDRAM 负责给数据做缓冲和跨时钟域,VGA 控制器负责按行场同步时序把像素点“喂”给屏幕,而 Verilog 代码负责把这些模块串成一条流水线。这篇笔记按实际调试顺序拆解,从 SCCB 配置到 SDRAM 仲裁再到 VGA 时序,最后给出我踩过的五个坑。资料包里的工程源码可以直接烧录,但建议你边看边对着电路图核线。
2. SCCB 配置:先把摄像头“唤醒”再谈数据
2.1 硬件接线:XCLK、PCLK、VSYNC、HREF 与 D[7:0] 到底接哪
OV7670 是 3.3V 器件,所有输入脚必须满足 3.3V 电平。FPGA 若用 1.8V/2.5V Bank 引脚直接对接,摄像头会处于亚稳态,SCCB 配置可能偶尔成功、偶尔失败。我一般把 FPGA 侧引脚约束为 LVCMOS33,并把 XCLK 单独放在一个具有时钟能力的引脚上,这样能保证输出时钟相位抖动可控。
接线表如下(以最常见的 640×480 RGB565 模式为例):
| OV7670 引脚 | 方向 | 说明 | FPGA 侧建议 |
|---|---|---|---|
| XCLK | 输入 | 主时钟,常见 12~24MHz | 由 PLL 输出,12MHz 起步 |
| PCLK | 输出 | 像素时钟,数据在上升沿有效 | 全局时钟网络或普通 IO |
| VSYNC | 输出 | 帧同步,一帧一个脉冲 | 接普通 IO |
| HREF | 输出 | 行有效信号,高电平期间输出像素数据 | 接普通 IO |
| D[7:0] | 输出 | 8 位并行像素数据 | 同 Bank 内集中布局 |
| SIO_C | 输入 | SCCB 时钟线 | 需外部 4.7K 上拉 |
| SIO_D | 双向 | SCCB 数据线 | 需外部 4.7K 上拉 |
| RESET | 输入 | 复位,低有效 | 上电后拉高 |
| PWDN | 输入 | 掉电控制,高有效 | 接地或拉低 |
这里最容易翻车的是 XCLK 频率。OV7670 支持 10MHz~48MHz 的 XCLK,但内部 PLL 分频后 PCLK 与寄存器 0x11 相关。很多资料直接给 24MHz,而我在实际工程里发现部分批次摄像头对 24MHz 的 SCCB 时序响应异常,降到 12MHz 初始化稳定。建议先给 12MHz 跑通链路,再逐步提频,不要一上来就追求 24MHz。
另一个隐蔽问题是 SIO_C 和 SIO_D 必须有上拉电阻。如果没有,SCCB 配置大概率写不进去,现象是读回全 0xFF。开发板原理图里这两个脚若已经接了上拉,你就不需要额外处理;若是直接从摄像头排线引出的,务必加上 4.7K 到 3.3V。
2.2 用 Verilog 写 SCCB 读写时序:三字节事务的基础协议
SCCB 是 I2C 的子集,OV7670 的寄存器配置走两字节写:设备地址 0x42、寄存器地址、寄存器数据。与 I2C 不同的是,SCCB 不支持连续读写,每个字节之间必须插入 stop 条件,且读操作要先发“设备地址+寄存器地址”,再重发设备地址 0x43 才能读回数据。
我通常把 SCCB 控制器写成独立模块,状态机按“START → 发设备地址 → 等 ACK → 发寄存器地址 → 等 ACK → 发数据 → 等 ACK → STOP”推进。以下代码是完整的写操作状态机骨架:
module sccb_master #( parameter CLK_FREQ = 50_000_000, parameter SCL_FREQ = 100_000 )( input wire clk, input wire rst_n, input wire start, input wire [7:0] reg_addr, input wire [7:0] reg_data, output reg busy, output reg scl, inout wire sda ); localparam div_cnt = CLK_FREQ / (SCL_FREQ * 4); reg [7:0] cnt; reg [3:0] bit_cnt; reg [7:0] tx_data; reg sda_out, sda_oe; assign sda = sda_oe ? sda_out : 1'bz; localparam S_IDLE = 5'd0; localparam S_START = 5'd1; localparam S_ADDR = 5'd2; localparam S_ACK_ADDR = 5'd3; localparam S_REG = 5'd4; localparam S_ACK_REG = 5'd5; localparam S_DATA = 5'd6; localparam S_ACK_DATA = 5'd7; localparam S_STOP = 5'd8; reg [4:0] state, next_state; // 做一次完整的 3 字节写事务 always @(posedge clk or negedge rst_n) if (!rst_n) begin state <= S_IDLE; busy <= 1'b0; scl <= 1'b1; sda_oe <= 1'b0; end else case (state) S_IDLE: if (start) begin busy <= 1'b1; state <= S_START; end S_START: begin sda_oe <= 1'b1; sda_out <= 1'b0; state <= S_ADDR; end S_ADDR: begin // 输出设备地址 + 写标志位 tx_data <= 8'h42; state <= S_REG; bit_cnt <= 4'd8; end S_REG: begin // 逐位移出 reg_addr,每 bit 用 4 个时钟周期 state <= S_ACK_REG; end S_ACK_REG: begin sda_oe <= 1'b0; // 释放总线等 ACK state <= S_DATA; end S_DATA: begin tx_data <= reg_data; bit_cnt <= 4'd8; state <= S_ACK_DATA; end S_ACK_DATA: begin sda_oe <= 1'b0; state <= S_STOP; end S_STOP: begin sda_oe <= 1'b1; sda_out <= 1'b1; state <= S_IDLE; busy <= 1'b0; end default: state <= S_IDLE; endcase endmodule这里的参数 CLK_FREQ 是 FPGA 系统时钟,SCL_FREQ 是目标 SCCB 频率。状态机里每个位事务由多个时钟节拍组成,实际工程中我会在 S_ADDR、S_REG、S_DATA 内部再加一个移位计数器,逐个时钟把 tx_data 的 bit7 送到 sda_out。上面的状态机只展示了顶层流转,目的是让你看清“地址—寄存器—数据”三段式结构,真正落板时还需要补上时钟节拍生成逻辑和 ACK 采样逻辑。busy 信号在 START 拉高、STOP 完成后拉低,外部配置模块里每写一个寄存器都要等 busy 变低再发下一个 start。
2.3 寄存器初始化清单:一组能出 RGB565 的常用配置
OV7670 上电后默认输出的是 YUV/RAW 格式,必须先写寄存器切到 RGB565 并关闭测试图案。我整理了一份经过多块板子验证的配置序列,关键寄存器如下:
| 寄存器地址 | 写入值 | 作用 | 说明 |
|---|---|---|---|
| 0x12 | 0x80 | 软复位 | 写完必须延时 1ms 以上 |
| 0x12 | 0x00 | 解除复位 | 进入正常模式 |
| 0x11 | 0x80 | 设置内部 PLL 分频 | 配合 XCLK 12MHz,PCLK 约 24MHz |
| 0x40 | 0xD0 | RGB565 输出 | bit[7]=1 表示 RGB,bit[5:4]=10 表示 RGB565 |
| 0x8C | 0x00 | 关闭色彩抑制 | 否则颜色偏淡 |
| 0x04 | 0x00 | 关闭测试图案 | 0x80 是彩条测试图 |
| 0x3A | 0x04 | 自动曝光上限 | 防止画面过曝闪烁 |
| 0x3D | 0x00 | 自动白平衡 | 关闭手动增益 |
| 0x17 | 0x18 | HSTART 高字节 | 与 0x1A 配合设置水平窗口 |
| 0x32 | 0xB6 | HREF 配置 | 行有效宽度相关 |
| 0x0F | 0x00 | 空闲帧间隔 | 减少花屏概率 |
初始化代码块里,每写一个寄存器就循环等待 busy 信号释放。顺序上有个硬性要求:0x12 的软复位必须最先执行,且写完 0x00 之后要等至少 10ms 再做后续配置。很多“配置完画面全花”的案例,都是因为没有在软复位后延时,寄存器写入时序被摄像头内部状态机吃掉。
配置完成后可以回读 0x0A(产品 ID 高字节)和 0x0B(产品 ID 低字节),正常应分别为 0x73、0x76。如果读回全 FF,说明 SCCB 时序或上拉有问题;如果读回 0x73 但 0x0B 不对,先查数据位序。
3. 数据采集与 SDRAM 缓存:不给每一帧丢在带宽上
3.1 数据流解析:从 PCLK 到完整 RGB565 像素
OV7670 在 HREF 为高期间,每个 PCLK 上升沿输出一个字节。RGB565 模式下,一个像素由连续两个字节组成:第一个字节是高 8 位 RGB[15:8],第二个字节是低 8 位 RGB[7:0]。所以采集模块的核心逻辑是“拼字”:收到第一个字节存入高 8 位,收到第二个字节存入低 8 位,同时产生一个 pix_valid 脉冲。
module ov7670_capture ( input wire pclk, input wire rst_n, input wire vsync, input wire href, input wire [7:0] din, output reg [15:0] pix_data, output reg pix_valid ); reg [1:0] byte_cnt; reg frame_start; always @(posedge pclk or negedge rst_n) begin if (!rst_n) begin byte_cnt <= 2'd0; pix_data <= 16'b0; pix_valid <= 1'b0; frame_start <= 1'b0; end else begin pix_valid <= 1'b0; if (vsync) begin frame_start <= 1'b1; end else if (frame_start && href) begin if (byte_cnt == 2'd0) begin pix_data[15:8] <= din; // 第一拍,高字节 byte_cnt <= 2'd1; end else begin pix_data[7:0] <= din; // 第二拍,低字节 byte_cnt <= 2'd0; pix_valid <= 1'b1; // 一个 16bit 像素组装完成 end end else if (!href) begin byte_cnt <= 2'd0; frame_start <= 1'b0; end end end endmodule这段代码里 frame_start 的作用是防止 vsync 后残留的 HREF 噪声被误采。VSYNC 有效时说明新一帧开始,此时把所有计数清零;HREF 拉低说明一行结束,强制 byte_cnt 归零,避免行首拼接错位。pix_valid 是写给 SDRAM 写端口的握手信号。
注意一个细节:OV7670 的 PCLK 在 HREF 无效期间是停止的,所以代码里不用额外过滤 PCLK。若你买到的摄像头模组 HREF 行为异常,可以在采集前用两个触发器做边沿检测,只在 href 上升沿之后的 PCLK 才采集。
3.2 为什么非要 SDRAM:带宽与突发长度推导
640×480×30 帧/秒 × 2 字节 = 18.43MB/s 的输出带宽,看起来不大,但问题出在速率差异:OV7670 的 PCLK 是 24MHz,VGA 的像素时钟是 25MHz,两个时钟域直接对接必然丢数据。而且摄像头是一行一行连续输出的,VGA 是一行一行周期性扫描的,两者的行场相位没有天然对齐关系。SDRAM 在这里的作用就是为整帧数据提供一个“中转站”:写端口以 PCLK 域连续写入,读端口以 VGA 时钟域突发读回。
带宽预算上,SDRAM 数据位宽 16bit、运行时钟 100MHz,理论带宽 200MB/s。但 SDRAM 需要刷新、行激活、预充电这些额外开销,实际可用带宽要打七折。640×480@30 的写入带宽约 18MB/s,读出带宽约 18MB/s,合计 36MB/s,远低于 140MB/s 的实际可用值,所以单页突发读写完全够用。真正紧张的是 1024×768@60:写入 94MB/s,读出 94MB/s,总计 188MB/s,非常逼近 SDRAM 的带宽上限,此时必须用突发长度 8 的连续读写,且读命令要提前发送。
3.3 SDRAM 控制器里的核心:命令调度与刷新仲裁
SDRAM 控制器的复杂度不在于读写本身,而在于仲裁。写端来自 PCLK 域,读端来自 VGA 时钟域,两端都向同一个物理 SDRAM 发起请求,必须有一个主状态机决定某个时刻是写、是读还是刷新。我常用的做法是优先级从高到低:刷新 > 写 > 读。因为 SDRAM 必须每隔 64ms 完成 4096 行刷新,刷新是硬实时任务;写端若不及时消费,摄像头 FIFO 会溢出丢帧;读端若是丢了一行,显示画面会出现一行花屏或黑纹。
localparam S_IDLE = 3'd0; localparam S_ACTIVE = 3'd1; localparam S_READ = 3'd2; localparam S_WRITE = 3'd3; localparam S_PRECH = 3'd4; localparam S_REFRESH= 3'd5; // 仲裁组合逻辑:刷新请求优先级最高 always @(*) begin if (refresh_request) next_state = S_REFRESH; else if (write_request && !fifo_empty) next_state = S_WRITE; else if (read_request) next_state = S_READ; else next_state = S_IDLE; end这个仲裁器放在 100MHz 时钟域。refresh_request 由一个计数器产生,计数器预装值为 100MHz×64ms/4096 ≈ 1560,到点就拉高。需要注意的是 SDRAM 的行激活命令必须在读写之前发出,我的做法是在 S_WRITE/S_READ 状态内部先拉 1 拍 ACT 信号,再等 tRCD 周期后进入真正的读写命令。
另一个关键点是地址映射。我用的映射方式是:SDRAM 的 Row 地址存像素行号(480 行,需要 10bit),Column 地址存像素列号(640 列,需要 10bit)。这样同一行像素的突发读写都在同一 Row 内,连续读写 640 个像素只需要一次行激活,效率最高。如果按线性地址映射,扫描某行时有可能会跨越 Row 边界,导致 SDRAM 中途要重新激活,VGA 读端口就会产生气泡。
4. VGA 显示时序:640×480@60 的每一行每一场
4.1 先背下这张参数表再动手
VGA 时序的本质是用一个像素时钟驱动两个计数器:水平计数器和垂直计数器。以 640×480@60Hz 为例,像素时钟为 25MHz(精确值是 25.175MHz,但很多板子用 25MHz 误差在可接受范围),一行的完整周期是 800 个像素时钟,一帧的完整周期是 525 个行周期。四个水平参数缺一不可:
| 参数 | 数值 | 含义 |
|---|---|---|
| 水平有效区 | 640 | 显示像素数 |
| 水平前肩 | 16 | hsync 结束到有效像素起始 |
| 水平同步脉冲 | 96 | hsync 低电平宽度 |
| 水平后肩 | 48 | 有效像素结束到 hsync 起始 |
| 垂直有效区 | 480 | 显示行数 |
| 垂直前肩 | 10 | vsync 结束到有效行起始 |
| 垂直同步脉冲 | 2 | vsync 低电平宽度 |
| 垂直后肩 | 33 | 有效行结束到 vsync 起始 |
hsync 和 vsync 都是低电平有效。VGA 显示器要求 RGB 信号在 active 期间有效、在消隐期间必须为 0,否则会出现右边缘的彩色拖尾。
4.2 两个计数器把行场同步完全对齐
module vga_timing ( input wire clk_25m, input wire rst_n, output reg hsync, output reg vsync, output wire [9:0] pixel_x, output wire [9:0] pixel_y, output reg video_en ); parameter H_ACT = 640, H_FP = 16, H_SYNC = 96, H_BP = 48, H_TOTAL = 800; parameter V_ACT = 480, V_FP = 10, V_SYNC = 2, V_BP = 33, V_TOTAL = 525; reg [9:0] hcnt, vcnt; always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) begin hcnt <= 0; end else if (hcnt == H_TOTAL - 1) begin hcnt <= 0; end else begin hcnt <= hcnt + 1'b1; end end always @(posedge clk_25m or negedge rst_n) begin if (!rst_n) begin vcnt <= 0; end else if (hcnt == H_TOTAL - 1) begin if (vcnt == V_TOTAL - 1) vcnt <= 0; else vcnt <= vcnt + 1'b1; end end always @(posedge clk_25m) begin hsync <= (hcnt >= H_ACT + H_FP) && (hcnt < H_ACT + H_FP + H_SYNC) ? 1'b0 : 1'b1; vsync <= (vcnt >= V_ACT + V_FP) && (vcnt < V_ACT + V_FP + V_SYNC) ? 1'b0 : 1'b1; video_en <= (hcnt < H_ACT) && (vcnt < V_ACT); end assign pixel_x = hcnt; assign pixel_y = vcnt; endmodulevideo_en 同时受行计数和场计数约束,避免了“画面上下正常但左右错位”的问题。pixel_x 和 pixel_y 直接暴露给后面的像素读取模块,作为 SDRAM 读地址的列、行索引。注意 hsync/vsync 与 video_en 的关系有 1 拍延迟,实际接 RAM 读数据时需要对齐到同一拍,通常在像素读取模块使用寄存器打拍。
4.3 从 SDRAM 读回来的数据要“提前预取”
VGA 时序产生之后,下一步是把 SDRAM 读出的 RGB 数据同步到 25MHz 时钟域。这里有个坑:VGA 在显示有效期间,每个像素时钟都要输出一个数据,但 SDRAM 读操作有 CAS 延迟和突发等待,无法做到“想要的那一拍恰好返回”。解决办法是用一个读 FIFO 做预取:在 video_en 为高且当前行还有 N 个像素就结束时,提前向 SDRAM 控制器发起读请求,把下一行的部分数据先存进 FIFO。
我的做法是让 SDRAM 读端在行前肩时发起一行数据的突发读:当检测到 vcnt 某行有效区结束、且下一行也是有效行时,立即发一个读请求,读取长度正好是 640×2 字节。VGA 像素时钟逐个从 FIFO 弹出数据,即使 SDRAM 响应延迟了十几个周期,FIFO 深度只要大于该延迟就不会出现黑条。FIFO 深度我设为 1024×16bit,实测在 25MHz 读、100MHz 写的情况下水位稳定在四分之一左右,留足了余量。
5. 避坑排查:五个最容易让你想砸板的现场
5.1 现象:SCCB 配完寄存器后画面全花或全黑
原因有三类,按出现频率排序:第一,SCCB 数据线无上拉,写操作只生效了一半,寄存器读到全 FF;第二,XCLK 频率过高导致摄像头内部锁相环失锁,PCLK 与 HREF 错位,采集到的字节流整体偏移;第三,初始化顺序错误,比如没做软复位就直接切 RGB565。
解决:先回读 0x0A/0x0B 确认通信建立,若读回全 FF,检查上拉电阻和电平;若通信正常,把 XCLK 降为 12MHz,并在软复位后加 10ms 延时。注意 OV7670 对电源上电时序有要求,DVDD 和 AVDD 要基本同时上电,否则内部复位逻辑不可靠。
5.2 现象:画面正常但上下颠倒或左右镜像
原因:OV7670 的寄存器 0x17 控制水平镜像,0x1A 控制垂直翻转。摄像头装好后,镜头朝外和朝内时的实际扫描方向不同。
解决:在初始化表里对 0x17 写入 0x04(开启水平镜像)或 0x00(关闭),对 0x1A 相应调整。每改一次就烧录看效果,不用重新仿真。这个参数不影响 SDRAM 地址映射,所以不要尝试用读地址反转去修正,改寄存器更直接。
5.3 现象:颜色偏绿或偏红,灰度正常但色相完全错乱
原因:RGB565 的字节拼反了。OV7670 输出的顺序是第一个字节为高 8 位,如果采集模块把第一个字节放到了低 8 位,画面会呈现蓝紫色偏移。另一种可能是 VGA 端把 RGB 的引脚顺序接错,尤其是一体化 VGA 排线上,R、G、B 三根信号经常出现某根断开。
解决:先用测试图案模式(0x04 写 0x80)观察显示器上的彩条。如果彩条颜色顺序正确,问题在摄像头采集端;否则查 VGA 电阻网络和引脚约束。我常直接在仿真模型里把采到的字节打印成文本,对比相邻像素的高低位,几秒钟就能定位。
5.4 现象:画面每隔几行有一条横纹或整体闪烁
原因:SDRAM 刷新请求在读写过程中频繁插入,导致读端 FIFO 取空。优先级设计不合理时,刷新请求会把读请求挤到一边,VGA 在等待数据期间输出黑色像素,看起来就像一条条暗纹。
解决:把刷新优先级设为最高,但给刷新请求加一个“延迟窗口”。我的做法是设置刷新计数器到阈值后,不是立即插入刷新,而是等待当前 SDRAM 突发传输完成后再在下一拍插入刷新。同时确保读端 FIFO 深度不小于“刷新周期内 VGA 消耗的像素数 + SDRAM 响应延迟”。计算下来,640×480 下 FIFO 深度 1024 足够,1024×768 下建议用 2048。
5.5 现象:仿真全通过,上板黑屏
原因:时钟约束与引脚约束缺失。PCLK 是摄像头产生的异步时钟,必须用 create_clock 显式约束;SDRAM 时钟如果是 PLL 输出的 100MHz,也要单独约束。某些开发板的 VGA 引脚接了电阻网络,直接用 push_pull 输出过驱动会导致信号变形。
解决:先把 PLL 锁定信号接入复位逻辑,避免时钟未稳定时逻辑跑飞。再用厂商工具链的“引脚规划器”逐项核对 VGA 的 hsync/vsync/color 引脚与板载丝印是否匹配。最后在工程里打开时序报告,重点看 PCLK 到 SDRAM 写 FIFO 的跨时钟路径是否有严重的 Hold Violation,有则直接在路径上加两级触发器。
6. 进阶:帧率上不去的瓶颈与一个能救命的调试习惯
6.1 帧率瓶颈不在摄像头,在 SDRAM 读端口
OV7670 在 VGA 模式下通常只能跑到 30fps,但当你想用更高分辨率时,最先撞墙的是 SDRAM 读带宽,而不是摄像头输出带宽。以 SVGA 800×600@60 为例,读出带宽 800×600×2×60≈57.6MB/s,加上写带宽约 28.8MB/s,总计 86.4MB/s。SDRAM 在 125MHz、突发长度 8、列周期 3 的情况下,峰值效率约 85%,实际可用带宽约 106MB/s,看起来余量只有 23%,此时刷新周期会抢占不少带宽,画面极易闪烁。
优化手段按性价比排序:第一,把 RGB565 降为 RGB444,像素数据从 16bit 减到 12bit,减带宽 25%,代价是色彩稍弱;第二,SDRAM 时钟从 100MHz 提到 125MHz 或 133MHz,但要注意时序收敛,SDRAM 数据线的 PCB 走线长度一致性对此影响很大;第三,把突发长度从 4 改为 8,减少行激活次数对总带宽的浪费。这三种手段组合后,我的某种跨平台显示项目在 800×600@60 下把带宽占用压到 72%,画面稳定无横纹。若你要更进一步到 1024×768,建议换 DDR 器件或直接上 CMOS 传感器的高带宽接口,SDRAM 本身就不是为这种分辨率准备的。
6.2 一个调试习惯:先看 VSYNC 再看 HREF
我复盘多次黑屏和花屏经历后,总结出一个强制流程。比如拿到一个新板子,第一件事不是跑完整工程,而是用逻辑分析仪 IP 抓三个信号:VSYNC、HREF、PCLK。软件逻辑分析仪在 FPGA 上通常有深度限制,我一般抓一个完整帧 0.1ms 的波形就够。先确认 VSYNC 每帧至少出现一次,HREF 每帧出现约 480 次,PCLK 在 HREF 期间持续翻转。这三个信号任何一个不正常,都不要往下查 SDRAM 或 VGA,问题大概率在摄像头配置端。
这个习惯帮我避免了很多无效劳动。某次画面偶发性跳动,我一度认为是 SDRAM 换行冲突,后来抓了 PCLK 波形发现它在一个 VSYNC 周期内多输出了几十个脉冲,查原因是某开发板的摄像头排线过长导致 PCLK 信号反射毛刺。解决方法是把 PCLK 引脚做成双边沿采样并加施密特触发器,问题立刻消失。
从那以后我每次接 OV7670 工程,都强制自己花 5 分钟抓一遍 VSYNC/HREF/PCLK 三件套,再允许自己看显示画面。即使画面看起来正常,我也会顺便量一下 PCLK 频率与预期值差多少,防止因温度漂移导致高低温环境下丢帧。这个动作成本极低,省下来的排查时间却远不止 5 分钟,希望帮到你。
本文还有配套的精品资源,点击获取