FPGA 摄像头采集处理显示指南:从 OV5640 到 HDMI 实时显示
概述
在视频监控、工业检测、医疗成像等领域,实时图像采集和显示是核心功能。FPGA 凭借高并行处理能力和低延迟特性,成为实现高性能视频处理系统的首选方案。
本文将详细介绍如何使用 FPGA 实现完整的摄像头采集、处理、显示系统,重点讲解 OV5640 摄像头的驱动、图像数据采集、缓存管理以及 HDMI 显示输出的全流程。
详细阐述了基于 FPGA 的摄像头采集、处理与显示系统设计方案。内容涵盖 OV5640 摄像头的基础知识、SCCB 通信协议、上电时序及寄存器配置;深入解析了 DVP 接口的信号定义与采集模块 Verilog 实现;探讨了图像缓存架构(如双端口 RAM、SDRAM)及 YUV 到 RGB 的色彩转换算法;最后介绍了 VGA/HDMI 时序生成、TMDS 编码原理及系统集成调试方法。文章提供了完整的代码框架与性能优化建议,适用于嵌入式视频处理系统设计参考。
在视频监控、工业检测、医疗成像等领域,实时图像采集和显示是核心功能。FPGA 凭借高并行处理能力和低延迟特性,成为实现高性能视频处理系统的首选方案。
本文将详细介绍如何使用 FPGA 实现完整的摄像头采集、处理、显示系统,重点讲解 OV5640 摄像头的驱动、图像数据采集、缓存管理以及 HDMI 显示输出的全流程。
一个典型的 FPGA 摄像头采集处理显示系统由以下几个主要部分组成:
FPGA 摄像头采集处理显示系统架构
├─ 1️⃣ 摄像头采集模块
│ ├─ OV5640 摄像头驱动
│ ├─ SCCB 配置接口
│ └─ DVP 数据采集
├─ 2️⃣ 图像处理模块
│ ├─ 数据格式转换
│ ├─ 图像缩放/裁剪
│ └─ 色彩空间转换
├─ 3️⃣ 存储缓存模块
│ ├─ SDRAM/DDR3
│ ├─ 双端口 RAM
│ └─ 帧缓冲管理
├─ 4️⃣ 显示输出模块
│ ├─ VGA 时序生成
│ ├─ HDMI 驱动
│ └─ TMDS 编码
└─ 5️⃣ 时钟管理模块
├─ PLL 时钟生成
├─ 多时钟域同步
└─ 时序约束
| 模块名称 | 功能描述 | 关键参数 |
|---|---|---|
| OV5640 驱动 | 摄像头初始化、寄存器配置、数据采集 | 分辨率、帧率、数据格式 |
| SCCB 控制器 | I2C 兼容的摄像头配置接口 | 时钟频率、地址宽度 |
| DVP 采集 | 并行数据采集、时序同步 | 像素时钟、行列同步 |
| 图像缓存 | 帧数据存储、读写管理 | 缓存大小、带宽 |
| VGA 驱动 | 显示时序生成、数据输出 | 分辨率、刷新率 |
| HDMI 驱动 | HDMI 信号编码、差分输出 | 分辨率、色深 |
摄像头 (OV5640) ↓ [DVP 接口:PCLK, HREF, VSYNC, Y[7:0]]
FPGA 采集模块 ↓ [16 位 RGB565 或 YUV422]
图像处理模块 ↓ [处理后的图像数据]
SDRAM/DDR3 缓存 ↓ [读取请求]
VGA/HDMI 驱动 ↓ [HDMI 差分信号]
显示器
1. 视频监控系统
2. 工业检测系统
3. 医疗成像系统
| 分辨率 | 帧率 | 像素时钟 | 缓存大小 | 适用场景 |
|---|---|---|---|---|
| 640×480 | 60fps | 25MHz | 600KB | 低端监控、测试 |
| 800×600 | 60fps | 40MHz | 960KB | 通用应用 |
| 1024×768 | 60fps | 65MHz | 1.5MB | 工业检测 |
| 1280×720 | 60fps | 74.25MHz | 1.8MB | 高清监控 |
| 1920×1080 | 60fps | 148.5MHz | 4.1MB | 全高清应用 |
1. 时钟管理
// 关键时钟信号
// 摄像头输入时钟 (XCLK): 24MHz
// 像素时钟 (PCLK): 根据分辨率变化
// HDMI 像素时钟:148.5MHz(1080p@60Hz)
// HDMI 串行时钟:5 倍像素时钟
// 时钟约束示例
create_clock -period 10.000 -name clk_sys [get_ports sys_clk]
create_generated_clock -name clk_hdmi \
-source [get_pins pll_inst/CLKIN1] \
-multiply_by 3 \
[get_pins pll_inst/CLKOUT0]
2. 跨时钟域同步
摄像头时钟域 (PCLK) ←→ 系统时钟域 (sys_clk) ↓ CDC 同步器 (双触发器) ↓ HDMI 时钟域 (clk_hdmi)
关键:使用格雷码或双触发器进行同步
3. 内存带宽管理
写入带宽:PCLK × 16bit (像素时钟 × 数据宽度)
读取带宽:clk_hdmi × 16bit (显示时钟 × 数据宽度)
例如 1080p@60Hz:
- 写入:148.5MHz × 16bit = 2.376Gbps
- 读取:148.5MHz × 16bit = 2.376Gbps
- 需要 DDR3-1600 以上支持
| 挑战 | 原因 | 解决方案 |
|---|---|---|
| 时序收敛困难 | 多时钟域、高频率 | 合理的时钟约束、流水线设计 |
| 缓存不足 | 高分辨率、高帧率 | 使用 DDR3、优化数据格式 |
| 图像撕裂 | 读写不同步 | 双缓冲、帧同步控制 |
| 色彩失真 | 格式转换错误 | 正确的色彩空间转换 |
| 延迟过高 | 处理流程复杂 | 流水线并行处理 |
OV5640 是豪威 (OmniVision) 公司推出的一款高性能 CMOS 图像传感器,广泛应用于监控、手机、平板等消费类电子产品。
主要特性:
| 特性 | 参数 |
|---|---|
| 最大分辨率 | 2592×1944(500 万像素) |
| 输出格式 | YUV422/420、RGB565、JPEG |
| 帧率 | 15-60fps(可配置) |
| 工作时钟 | 6-54MHz(推荐 24MHz) |
| 功耗 | 150-200mW |
| 接口 | DVP(并行)、SCCB(I2C 兼容) |
| 自动功能 | 自动对焦、自动曝光、自动白平衡 |
优势:
劣势:
OV5640 采用 CSP(芯片级封装),共有 60 个引脚。主要引脚如下:
┌─────────────────────────────────────┐
│ OV5640 CSP60 引脚分布
├─────────────────────────────────────┤
│ 电源引脚:
│ DVDD(1.8V) - 数字电源
│ AVDD(2.8V) - 模拟电源
│ DOVDD(1.8V) - 输出驱动电源
│
│ 时钟引脚:
│ XCLK - 外部时钟输入 (24MHz)
│ PCLK - 像素时钟输出
│
│ 同步信号:
│ VSYNC - 帧同步信号 (行场同步)
│ HREF - 行同步信号
│
│ 数据引脚:
│ Y[9:0] - 10 位像素数据
│ (通常使用 Y[9:2] 作为 8 位数据)
│
│ 控制引脚:
│ RESETB - 复位信号 (低有效)
│ PWDN - 掉电/省电 (高有效)
│
│ 通信引脚:
│ SIO_C - SCCB 时钟线
│ SIO_D - SCCB 数据线
└─────────────────────────────────────┘
1. 电源引脚
DVDD (1.8V ±5%)
├─ 数字核心电源
├─ 需要 100nF+10μF 滤波
└─ 对噪声敏感,需要单独的电源层
AVDD (2.8V ±5%)
├─ 模拟电源
├─ 需要 100nF+10μF 滤波
└─ 必须与 DVDD 同时上电
DOVDD (1.8V ±5%)
├─ 输出驱动电源
├─ 驱动 Y[9:0]、PCLK、VSYNC、HREF
└─ 需要 100nF 滤波
2. 时钟引脚
XCLK (外部时钟输入)
├─ 频率范围:6-54MHz
├─ 推荐频率:24MHz
├─ 占空比:45%-55%
├─ 可来自晶振或 FPGA 输出
└─ 必须稳定,抖动< 100ps
PCLK (像素时钟输出)
├─ 频率:根据分辨率和帧率变化
├─ 例如:1280×720@60Hz 时约 74.25MHz
├─ 由 OV5640 内部 PLL 生成
├─ 用于同步像素数据采集
└─ 需要在 FPGA 中采样
3. 同步信号
VSYNC (帧同步信号)
├─ 低电平表示帧有效
├─ 用于帧边界检测
├─ 周期 = 1/帧率
└─ 例如:60fps 时周期为 16.67ms
HREF (行同步信号)
├─ 高电平表示行有效
├─ 用于行边界检测
├─ 周期 = 1/(帧率×行数)
└─ 例如:1280×720@60Hz 时约 13.9μs
4. 数据引脚
Y[9:0] (10 位像素数据)
├─ 通常使用 Y[9:2] 作为 8 位数据
├─ 数据格式:YUV422、RGB565 等
├─ 在 PCLK 上升沿采样
├─ 需要与 HREF、VSYNC 同步
└─ 输出电平:DOVDD(1.8V)
5. 控制引脚
RESETB (复位信号,低有效)
├─ 低电平时复位芯片
├─ 需要保持低电平至少 1ms
├─ 上升沿后需要等待 20ms 才能配置
└─ 通常由 FPGA GPIO 驱动
PWDN (掉电/省电,高有效)
├─ 高电平时进入低功耗模式
├─ 低电平时正常工作
├─ 需要在 RESETB 之前拉低
└─ 通常由 FPGA GPIO 驱动
┌──────────────────────────────────────────────────┐
│ OV5640 内部结构框图
├──────────────────────────────────────────────────┤
│
│ XCLK(24MHz)
│ ↓
│ ┌─────────────────────────────────────────┐
│ │ PLL/时钟生成 │
│ │ (生成各种内部时钟) │
│ └─────────────────────────────────────────┘
│ ↓
│ ┌─────────────────────────────────────────┐
│ │ 感光矩阵 (2592×1944) │
│ │ (CMOS 传感器,光电转换) │
│ └─────────────────────────────────────────┘
│ ↓
│ ┌─────────────────────────────────────────┐
│ │ 模拟前端 (AFE) │
│ │ (放大、滤波、模数转换) │
│ └─────────────────────────────────────────┘
│ ↓
│ ┌─────────────────────────────────────────┐
│ │ ISP(图像信号处理) │
│ │ ├─ 自动曝光 (AE) │
│ │ ├─ 自动白平衡 (AWB) │
│ │ ├─ 自动对焦 (AF) │
│ │ ├─ 伽玛校正 │
│ │ ├─ 色彩矩阵 │
│ │ └─ 降噪处理 │
│ └─────────────────────────────────────────┘
│ ↓
│ ┌─────────────────────────────────────────┐
│ │ 格式转换 │
│ │ (YUV422/420、RGB565、JPEG 等) │
│ └─────────────────────────────────────────┘
│ ↓
│ ┌─────────────────────────────────────────┐
│ │ 输出接口 │
│ │ ├─ DVP(并行): Y[9:0]、PCLK、HREF、VSYNC│
│ │ └─ SCCB(I2C): 寄存器配置 │
│ └─────────────────────────────────────────┘
│
└──────────────────────────────────────────────────┘
SCCB(Serial Camera Control Bus) 是豪威公司定义的摄像头控制总线,与 I2C 协议类似,但有细微差别。
SCCB 特点:
写操作时序:
START | SLAVE_ADDR | ACK | H_ADDR | ACK | L_ADDR
| (8bit) | | (8bit) | | (8bit)
↓
ACK | DATA | ACK | STOP
|(8bit)| | |
读操作时序:
START | SLAVE_ADDR | ACK | H_ADDR | ACK | L_ADDR
| (8bit) | | (8bit) | | (8bit)
↓
ACK | START | SLAVE_ADDR | ACK | DATA | NACK | STOP
| | (8bit) | |(8bit)| |
| 特性 | SCCB | I2C |
|---|---|---|
| 寻址方式 | 16 位地址 | 7/10 位地址 |
| 时钟频率 | 100-400kHz | 100-400kHz |
| 从机应答 | 可选 | 必须 |
| 读操作 | 需要重复 START | 支持 |
| 兼容性 | 不完全兼容 I2C | 标准协议 |
实际应用中:FPGA 通常使用 I2C 控制器驱动 SCCB,因为两者在时序上基本兼容。
寄存器地址:16 位 (高字节 + 低字节)
寄存器数据:8 位
写寄存器示例:
地址:0x3008 (高字节 0x30, 低字节 0x08)
数据:0x82
SCCB 写操作:START → SLAVE_ADDR(0x78) → ACK → 0x30 → ACK → 0x08 → ACK → 0x82 → ACK → STOP
读寄存器示例:
地址:0x300A
SCCB 读操作:START → SLAVE_ADDR(0x78) → ACK → 0x30 → ACK → 0x0A → ACK → START → SLAVE_ADDR(0x79) → ACK → DATA → NACK → STOP
OV5640 的正确上电时序对系统稳定性至关重要。不正确的上电时序可能导致芯片无法正常工作。
上电时序流程:
时间轴:0ms
├─ 电源上电 (DVDD、AVDD、DOVDD)
│ └─ 所有电源应同时上电或按规定顺序上电
│ 5ms
├─ PWDN 拉低 (退出低功耗模式)
│ └─ 保持低电平
│ 10ms
├─ RESETB 拉低 (复位芯片)
│ └─ 保持低电平至少 1ms
│ 15ms
├─ RESETB 拉高 (释放复位)
│ └─ 芯片开始初始化
│ 35ms
└─ 等待 PLL 稳定 (约 20ms)
└─ 可以开始配置寄存器
1. 电源上电
推荐上电顺序:
1️⃣ AVDD (2.8V) 先上电
2️⃣ DVDD (1.8V) 后上电
3️⃣ DOVDD (1.8V) 最后上电
或者同时上电 (更常见)
电源稳定要求:
├─ 上升时间:< 100ms
├─ 纹波:< 100mV
├─ 稳定度:±5%
└─ 需要充分的滤波电容
2. PWDN 信号控制
PWDN (掉电/省电控制)
├─ 低电平:正常工作
├─ 高电平:低功耗模式
│ 上电时序:
├─ 电源上电后,PWDN 应拉低
├─ 保持低电平至少 5ms
└─ 然后进行 RESETB 复位
FPGA 实现示例:
always @(posedge clk)
begin
if (power_on_counter < 32'd5_000_000)
begin
pwdn <= 1'b1; // 保持低功耗
power_on_counter <= power_on_counter + 1;
end
else
begin
pwdn <= 1'b0; // 退出低功耗
end
end
3. RESETB 复位信号
RESETB (复位信号,低有效)
├─ 低电平:芯片复位
├─ 高电平:芯片工作
│ 复位时序:
├─ PWDN 拉低后,等待 5ms
├─ RESETB 拉低,保持至少 1ms
├─ RESETB 拉高,释放复位
└─ 等待 20ms,PLL 稳定
FPGA 实现示例:
always @(posedge clk)
begin
if (reset_counter < 32'd1_000_000)
begin
resetb <= 1'b0; // 保持复位
reset_counter <= reset_counter + 1;
end
else if (reset_counter < 32'd21_000_000)
begin
resetb <= 1'b1; // 释放复位
reset_counter <= reset_counter + 1;
end
else
begin
init_done <= 1'b1; // 初始化完成
end
end
OV5640 有数百个寄存器,这里介绍最常用的几个:
| 寄存器地址 | 寄存器名称 | 功能 | 典型值 |
|---|---|---|---|
| 0x3008 | SYSTEM_CTRL0 | 系统控制 | 0x82 |
| 0x3009 | SYSTEM_CTRL1 | 系统控制 | 0x02 |
| 0x3103 | CLOCK_CTRL | 时钟控制 | 0x11 |
| 0x3017 | MIPI_CTRL00 | MIPI 控制 | 0x00 |
| 0x3018 | MIPI_CTRL01 | MIPI 控制 | 0x00 |
| 0x3034 | PLL_CTRL0 | PLL 控制 | 0x18 |
| 0x3035 | PLL_CTRL1 | PLL 控制 | 0x11 |
| 0x3036 | PLL_CTRL2 | PLL 控制 | 0x54 |
| 0x3037 | PLL_CTRL3 | PLL 控制 | 0x13 |
| 0x3108 | SCCB_CTRL | SCCB 控制 | 0x01 |
| 0x3630 | ANALOG_CTRL | 模拟控制 | 0x2e |
| 0x3a00 | AE_CTRL | 自动曝光 | 0x78 |
| 0x3a02 | AE_CTRL02 | 自动曝光 | 0x00 |
| 0x3a03 | AE_CTRL03 | 自动曝光 | 0x9c |
设置分辨率的关键寄存器:
1️⃣ 输出大小控制 (0x3808-0x380B)
├─ 0x3808: 输出宽度高字节
├─ 0x3809: 输出宽度低字节
├─ 0x380A: 输出高度高字节
└─ 0x380B: 输出高度低字节
2️⃣ 时序控制 (0x3800-0x3807)
├─ 0x3800: 水平起始位置高字节
├─ 0x3801: 水平起始位置低字节
├─ 0x3802: 垂直起始位置高字节
├─ 0x3803: 垂直起始位置低字节
├─ 0x3804: 水平结束位置高字节
├─ 0x3805: 水平结束位置低字节
├─ 0x3806: 垂直结束位置高字节
└─ 0x3807: 垂直结束位置低字节
3️⃣ 格式控制 (0x4300)
├─ 0x4300: 输出格式选择
│ ├─ 0x00: YUV422
│ ├─ 0x10: RGB565
│ └─ 0x20: RGB444
640×480@30fps 配置:
寄存器配置表:
┌──────────┬──────────┬─────────────────────┐
│ 地址 │ 数据 │ 说明 │
├──────────┼──────────┼─────────────────────┤
│ 0x3008 │ 0x82 │ 系统复位 │
│ 0x3034 │ 0x18 │ PLL 倍频系数 │
│ 0x3035 │ 0x11 │ PLL 分频系数 │
│ 0x3036 │ 0x54 │ PLL 分频系数 │
│ 0x3037 │ 0x13 │ PLL 分频系数 │
│ 0x3800 │ 0x00 │ 水平起始位置 │
│ 0x3801 │ 0x00 │ │
│ 0x3802 │ 0x00 │ 垂直起始位置 │
│ 0x3803 │ 0x00 │ │
│ 0x3804 │ 0x0a │ 水平结束位置 │
│ 0x3805 │ 0x3f │ (2592) │
│ 0x3806 │ 0x07 │ 垂直结束位置 │
│ 0x3807 │ 0x9f │ (1944) │
│ 0x3808 │ 0x02 │ 输出宽度 │
│ 0x3809 │ 0x80 │ (640) │
│ 0x380a │ 0x01 │ 输出高度 │
│ 0x380b │ 0xe0 │ (480) │
│ 0x4300 │ 0x30 │ YUV422 输出 │
└──────────┴──────────┴─────────────────────┘
1280×720@60fps 配置:
关键寄存器配置:
0x3808 = 0x05 (宽度高字节)
0x3809 = 0x00 (宽度低字节) → 1280
0x380a = 0x02 (高度高字节)
0x380b = 0xd0 (高度低字节) → 720
PLL 配置:
0x3034 = 0x18
0x3035 = 0x21
0x3036 = 0x69
0x3037 = 0x13
SCCB 控制器是 FPGA 中用于与 OV5640 通信的模块,负责:
┌─────────────────────────────────────────┐
│ SCCB 控制器框图
├─────────────────────────────────────────┤
│
│ ┌──────────────────────────────────┐
│ │ 状态机 │
│ │ ├─ IDLE │
│ │ ├─ START │
│ │ ├─ ADDR_H │
│ │ ├─ ADDR_L │
│ │ ├─ DATA │
│ │ ├─ ACK │
│ │ └─ STOP │
│ └──────────────────────────────────┘
│ ↓
│ ┌──────────────────────────────────┐
│ │ 时钟分频器 │
│ │ (生成 SCCB 时钟) │
│ └──────────────────────────────────┘
│ ↓
│ ┌──────────────────────────────────┐
│ │ I/O 驱动 │
│ │ ├─ SIO_C (时钟线) │
│ │ └─ SIO_D (数据线) │
│ └──────────────────────────────────┘
│
└─────────────────────────────────────────┘
module sccb_ctrl (
input clk,
input rst_n, // 控制接口
input [15:0] reg_addr, // 寄存器地址
input [7:0] reg_data_w, // 写数据
output [7:0] reg_data_r,// 读数据
input reg_wr, // 写使能
input reg_rd, // 读使能
output reg_done, // 操作完成
// SCCB 接口
inout sio_c, // 时钟线 (开漏)
inout sio_d // 数据线 (开漏)
);
// 状态定义
localparam IDLE = 4'd0;
localparam START = 4'd1;
localparam ADDR_H = 4'd2;
localparam ADDR_L = 4'd3;
localparam DATA = 4'd4;
localparam ACK = 4'd5;
localparam STOP = 4'd6;
reg [3:0] state, next_state;
reg [7:0] bit_counter;
reg [7:0] data_buffer;
// 时钟分频 (100kHz SCCB 时钟)
// 假设系统时钟为 50MHz
// 分频系数 = 50MHz / (100kHz × 4) = 125
localparam CLK_DIV = 7'd125;
reg [6:0] clk_counter;
reg sccb_clk;
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
clk_counter <= 0;
sccb_clk <= 0;
end
else if (clk_counter == CLK_DIV - 1)
begin
clk_counter <= 0;
sccb_clk <= ~sccb_clk;
end
else
begin
clk_counter <= clk_counter + 1;
end
end
// 状态机
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
always @(*)
begin
case (state)
IDLE: begin
if (reg_wr || reg_rd)
next_state = START;
else
next_state = IDLE;
end
START: next_state = ADDR_H;
ADDR_H: next_state = ADDR_L;
ADDR_L: next_state = (reg_wr) ? DATA : ACK;
DATA: next_state = ACK;
ACK: next_state = STOP;
STOP: next_state = IDLE;
default: next_state = IDLE;
endcase
end
// I/O 驱动 (开漏输出)
assign sio_c = (state == IDLE) ? 1'bz : 1'b0;
assign sio_d = (state == IDLE) ? 1'bz : 1'b0;
endmodule
写操作时序示例:
CLK: ─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
SIO_C:─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─
SIO_D: ─┐ ┌─────────────────────────────────────────────────────────────
│ │ START └─┘ ┌─────────────────────────────────────────────────────────────┐
SIO_D:─┤ SLAVE_ADDR(0x78) │ ACK │ ADDR_H │ ACK │ ADDR_L │ ACK │ DATA │ ACK │ STOP └─────────────────────────────────────────────────────────────┘
DVP(Digital Video Port) 是 OV5640 的并行数据输出接口,用于传输图像数据。
DVP 接口特点:
DVP 接口信号:
┌─────────────────────────────────────────┐
│ DVP 接口信号定义
├─────────────────────────────────────────┤
│
│ 1️⃣ 时钟信号:
│ PCLK - 像素时钟 (采样时钟)
│ 频率:6-148.5MHz
│ 用于同步数据采样
│
│ 2️⃣ 同步信号:
│ VSYNC - 帧同步 (低电平表示帧有效)
│ HREF - 行同步 (高电平表示行有效)
│
│ 3️⃣ 数据信号:
│ Y[9:0] - 10 位像素数据
│ 通常使用 Y[9:2] 作为 8 位数据
│
│ 4️⃣ 其他:
│ XCLK - 输入时钟 (24MHz)
│ RESETB - 复位信号
│ PWDN - 掉电控制
│
└─────────────────────────────────────────┘
一帧图像的采集时序:
VSYNC: ─┐ ┌─ │ (低电平表示帧有效)
│ └────────────────────────────────────┘
HREF: ─┬─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─
│ │ │ │ │ │ │ │ │ └─────┘ └─────┘ └─────┘ └─────┘ └─ (高电平表示行有效)
PCLK: ─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─
Y[9:0]: ┌─────┬─────┬─────┬─────┬─────┬─────┐
│ P0 │ P1 │ P2 │ P3 │ P4 │ P5 │
└─────┴─────┴─────┴─────┴─────┴─────┘
↑ ↑ ↑ ↑ ↑ ↑
在 PCLK 上升沿采样
时序说明:
┌──────────────────────────────────────────────┐
│ 图像采集模块框图
├──────────────────────────────────────────────┤
│
│ OV5640 摄像头
│ ├─ PCLK
│ ├─ VSYNC
│ ├─ HREF
│ └─ Y[9:0]
│ ↓
│ ┌──────────────────────────────────────┐
│ │ 采集控制模块
│ │ ├─ 帧同步检测 (VSYNC)
│ │ ├─ 行同步检测 (HREF)
│ │ ├─ 像素计数
│ │ └─ 行列计数
│ └──────────────────────────────────────┘
│ ↓
│ ┌──────────────────────────────────────┐
│ │ 数据缓冲
│ │ ├─ 像素数据缓存
│ │ ├─ 行缓冲
│ │ └─ 帧缓冲
│ └──────────────────────────────────────┘
│ ↓
│ ┌──────────────────────────────────────┐
│ │ 输出接口
│ │ ├─ 像素数据输出
│ │ ├─ 行有效信号
│ │ ├─ 帧有效信号
│ │ └─ 数据有效信号
│ └──────────────────────────────────────┘
│
└──────────────────────────────────────────────┘
module dvp_capture (
input clk,
input rst_n,
// DVP 接口输入
input pclk,
input vsync,
input href,
input [9:0] data_in,
// 输出接口
output reg [7:0] data_out,
output reg data_valid,
output reg line_valid,
output reg frame_valid,
output reg [11:0] pixel_x,
output reg [11:0] pixel_y
);
// 状态定义
localparam IDLE = 2'd0;
localparam FRAME_ACTIVE = 2'd1;
localparam LINE_ACTIVE = 2'd2;
reg [1:0] state;
reg vsync_r1, vsync_r2;
reg href_r1, href_r2;
// 同步 VSYNC 和 HREF 到系统时钟
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
vsync_r1 <= 1'b1;
vsync_r2 <= 1'b1;
href_r1 <= 1'b0;
href_r2 <= 1'b0;
end
else
begin
vsync_r1 <= vsync;
vsync_r2 <= vsync_r1;
href_r1 <= href;
href_r2 <= href_r1;
end
end
// 采集控制状态机
always @(posedge pclk or negedge rst_n)
begin
if (!rst_n)
begin
state <= IDLE;
frame_valid <= 1'b0;
line_valid <= 1'b0;
pixel_x <= 12'd0;
pixel_y <= 12'd0;
end
else
begin
case (state)
IDLE: begin
if (!vsync)
begin
state <= FRAME_ACTIVE;
frame_valid <= 1'b1;
pixel_y <= 12'd0;
end
end
FRAME_ACTIVE: begin
if (vsync)
begin
state <= IDLE;
frame_valid <= 1'b0;
pixel_y <= 12'd0;
end
else if (href)
begin
line_valid <= 1'b1;
pixel_x <= pixel_x + 1;
end
else
begin
line_valid <= 1'b0;
if (pixel_x > 0)
begin
pixel_y <= pixel_y + 1;
pixel_x <= 12'd0;
end
end
end
default: state <= IDLE;
endcase
end
end
// 数据采样
always @(posedge pclk or negedge rst_n)
begin
if (!rst_n)
begin
data_out <= 8'd0;
data_valid <= 1'b0;
end
else
begin
if (href && frame_valid)
begin
data_out <= data_in[9:2]; // 使用高 8 位
data_valid <= 1'b1;
end
else
begin
data_valid <= 1'b0;
end
end
end
endmodule
1. 时钟域处理
PCLK 时钟域 (摄像头时钟) ↓ 采集数据 ↓ 系统时钟域 (FPGA 系统时钟) ↓ 处理数据
关键:使用 CDC(Clock Domain Crossing) 同步
2. 像素计数
像素计数逻辑:
├─ 在 HREF 高电平时计数像素
├─ HREF 下降沿时,行计数 +1
├─ VSYNC 下降沿时,帧计数 +1
└─ 用于生成行列坐标
3. 数据有效性判断
数据有效条件:
├─ VSYNC 低电平 (帧有效)
├─ HREF 高电平 (行有效)
├─ 在 PCLK 上升沿采样
└─ 三个条件同时满足时,数据有效
帧同步时序:
VSYNC: ─┐ ┌─ │ 帧有效 (低电平)
│ └────────────────────────────────────┘
frame_valid: ─┐ ┌─ │ 帧有效标志
│ └────────────────────────────────────┘
pixel_y: ─┬─────────────────────────────┬─ │ 0→1→2→...→(height-1)→0
└─────────────────────────────┘
行同步时序:
HREF: ─┬─────┐ ┬─────┐ ┬─────┐ │ │ │ │ │ │ └─────┘ └─────┘ └─────┘
line_valid: ─┬─────┐ ┬─────┐ ┬─────┐ │ │ │ │ │ │ └─────┘ └─────┘ └─────┘
pixel_x: ─┬─────────────┬─────────────┬─ │ 0→1→...→639 │ 0→1→...→639
└─────────────┴─────────────┘
测试场景:
1️⃣ 单帧采集测试
├─ 采集一帧完整图像
├─ 验证像素数据正确性
└─ 检查行列坐标
2️⃣ 连续采集测试
├─ 采集多帧图像
├─ 验证帧同步
└─ 检查数据连续性
3️⃣ 时序验证
├─ 使用逻辑分析仪
├─ 验证 PCLK、HREF、VSYNC 时序
└─ 检查数据采样点
在 FPGA 摄像头系统中,缓存的作用是:
缓存大小计算:
缓存大小 = 分辨率 × 像素深度 × 帧数
例如 1280×720@60fps:
├─ 单帧大小 = 1280 × 720 × 2 字节 (RGB565) = 1.8MB
├─ 双缓冲 = 1.8MB × 2 = 3.6MB
└─ 三缓冲 = 1.8MB × 3 = 5.4MB
常见缓存方案:
├─ 单缓冲:最小化延迟,但易出现撕裂
├─ 双缓冲:平衡延迟和稳定性 (推荐)
└─ 三缓冲:最大稳定性,但延迟最大
1. 片内 RAM 缓存
优点:✅ 速度快 (单周期访问) ✅ 延迟低 ✅ 易于集成
缺点:❌ 容量有限 (通常< 1MB) ❌ 只适合低分辨率
适用场景:
├─ 640×480 以下分辨率
├─ 行缓冲
└─ 临时数据存储
2. SDRAM 缓存
优点:✅ 容量大 (通常 128MB-512MB) ✅ 成本低 ✅ 易于扩展
缺点:❌ 延迟高 (10-20 个周期) ❌ 需要复杂的控制器 ❌ 需要刷新
适用场景:
├─ 中等分辨率 (720p)
├─ 成本敏感应用
└─ 需要大容量存储
3. DDR3 缓存
优点:✅ 容量大 (通常 512MB-2GB) ✅ 带宽高 (可达 25.6GB/s) ✅ 性能好
缺点:❌ 成本高 ❌ 功耗高 ❌ 控制复杂
适用场景:
├─ 高分辨率 (1080p 及以上)
├─ 高帧率 (60fps 及以上)
└─ 需要高性能
双端口 RAM 允许同时进行读写操作,是 FPGA 图像处理的关键组件。
双端口 RAM 结构:
┌──────────────────────────────────────┐
│ 双端口 RAM
├──────────────────────────────────────┤
│
│ 端口 A (写端口) 端口 B (读端口)
│ ├─ 地址:addr_a ├─ 地址:addr_b
│ ├─ 数据:data_in ├─ 数据:data_out
│ ├─ 写使能:we_a └─ 读使能:re_b
│ └─ 时钟:clk_a 时钟:clk_b
│
│ 可以同时:
│ ├─ 在地址 A 写入数据
│ ├─ 在地址 B 读出数据
│ └─ 两个操作独立进行
│
└──────────────────────────────────────┘
module dual_port_ram #(
parameter ADDR_WIDTH = 12,
parameter DATA_WIDTH = 16,
parameter DEPTH = 4096
) (
// 写端口 (采集端)
input clk_a,
input [ADDR_WIDTH-1:0] addr_a,
input [DATA_WIDTH-1:0] data_in,
input we_a,
// 读端口 (显示端)
input clk_b,
input [ADDR_WIDTH-1:0] addr_b,
output reg [DATA_WIDTH-1:0] data_out,
input re_b
);
reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
// 写操作 (采集端)
always @(posedge clk_a)
begin
if (we_a)
mem[addr_a] <= data_in;
end
// 读操作 (显示端)
always @(posedge clk_b)
begin
if (re_b)
data_out <= mem[addr_b];
end
endmodule
双缓冲帧管理:
┌─────────────────────────────────────────┐
│ 帧缓冲管理
├─────────────────────────────────────────┤
│
│ 缓冲区 A (写入缓冲)
│ ├─ 采集模块写入新帧
│ ├─ 显示模块不读取
│ └─ 写入完成后切换
│
│ 缓冲区 B (显示缓冲)
│ ├─ 显示模块读取显示
│ ├─ 采集模块不写入
│ └─ 显示完成后切换
│
│ 切换机制:
│ ├─ 采集完成 → 切换写入缓冲
│ ├─ 显示完成 → 切换显示缓冲
│ └─ 防止撕裂
│
└─────────────────────────────────────────┘
SDRAM(Synchronous Dynamic RAM) 是常用的外部存储器。
SDRAM 特点:
SDRAM 时序:
SDRAM 读操作时序:
CLK: ─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─
CMD: ─┬─────────┬─────────┬─────────┬─
│ ACTIVE │ READ │ PRECHARGE
└─────────┴─────────┴─────────┴─
ADDR: ─┬─────────┬─────────┬─────────┬─
│ ROW │ COL │ (X)
└─────────┴─────────┴─────────┴─
DATA: ─────────────────────┬─────────┬─
│ DATA_OUT│ └─────────┘ (延迟:CAS Latency)
┌──────────────────────────────────────────┐
│ SDRAM 控制器框图
├──────────────────────────────────────────┤
│
│ ┌────────────────────────────────────┐
│ │ 命令生成器
│ │ ├─ ACTIVE 命令
│ │ ├─ READ/WRITE 命令
│ │ └─ PRECHARGE 命令
│ └────────────────────────────────────┘
│ ↓
│ ┌────────────────────────────────────┐
│ │ 地址复用器
│ │ ├─ 行地址 (ROW)
│ │ └─ 列地址 (COL)
│ └────────────────────────────────────┘
│ ↓
│ ┌────────────────────────────────────┐
│ │ 数据通路
│ │ ├─ 写数据缓冲
│ │ └─ 读数据缓冲
│ └────────────────────────────────────┘
│ ↓
│ ┌────────────────────────────────────┐
│ │ SDRAM 接口
│ │ ├─ 地址线 (A[12:0])
│ │ ├─ 数据线 (DQ[15:0])
│ │ ├─ 控制线 (CS, RAS, CAS, WE)
│ │ └─ 时钟 (CLK)
│ └────────────────────────────────────┘
│
└──────────────────────────────────────────┘
module sdram_ctrl (
input clk,
input rst_n,
// 用户接口
input [23:0] addr, // 24 位地址 (支持 16MB)
input [15:0] data_in,
output [15:0] data_out,
input we, // 写使能
input re, // 读使能
output busy, // SDRAM 接口
// SDRAM 接口
output [12:0] sdram_addr,
output [1:0] sdram_ba, // Bank 地址
inout [15:0] sdram_dq,
output sdram_cs_n,
output sdram_ras_n,
output sdram_cas_n,
output sdram_we_n,
output sdram_clk
);
// 状态定义
localparam IDLE = 3'd0;
localparam ACTIVE = 3'd1;
localparam READ = 3'd2;
localparam WRITE = 3'd3;
localparam PRECHARGE = 3'd4;
reg [2:0] state, next_state;
reg [3:0] cmd_counter;
// 地址分解
wire [1:0] bank = addr[23:22];
wire [12:0] row = addr[21:9];
wire [8:0] col = addr[8:0];
// 状态机
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
state <= IDLE;
cmd_counter <= 0;
end
else
begin
state <= next_state;
if (state != next_state)
cmd_counter <= 0;
else
cmd_counter <= cmd_counter + 1;
end
end
always @(*)
begin
case (state)
IDLE: begin
if (we || re)
next_state = ACTIVE;
else
next_state = IDLE;
end
ACTIVE: begin
if (cmd_counter >= 2)
next_state = (we) ? WRITE : READ;
else
next_state = ACTIVE;
end
READ, WRITE: begin
if (cmd_counter >= 3)
next_state = PRECHARGE;
else
next_state = state;
end
PRECHARGE: begin
if (cmd_counter >= 2)
next_state = IDLE;
else
next_state = PRECHARGE;
end
default: next_state = IDLE;
endcase
end
// 命令输出
assign sdram_cs_n = 1'b0;
assign sdram_clk = clk;
always @(*)
begin
case (state)
ACTIVE: begin
sdram_addr = row;
sdram_ba = bank;
sdram_ras_n = 1'b0;
sdram_cas_n = 1'b1;
sdram_we_n = 1'b1;
end
READ: begin
sdram_addr = {4'b0, col};
sdram_ba = bank;
sdram_ras_n = 1'b1;
sdram_cas_n = 1'b0;
sdram_we_n = 1'b1;
end
WRITE: begin
sdram_addr = {4'b0, col};
sdram_ba = bank;
sdram_ras_n = 1'b1;
sdram_cas_n = 1'b0;
sdram_we_n = 1'b0;
end
PRECHARGE: begin
sdram_addr = 13'b0;
sdram_ba = 2'b0;
sdram_ras_n = 1'b0;
sdram_cas_n = 1'b1;
sdram_we_n = 1'b0;
end
default: begin
sdram_addr = 13'b0;
sdram_ba = 2'b0;
sdram_ras_n = 1'b1;
sdram_cas_n = 1'b1;
sdram_we_n = 1'b1;
end
endcase
end
assign busy = (state != IDLE);
endmodule
1️⃣ 格式转换
├─ YUV422 → RGB565
├─ YUV422 → YUV420
└─ RGB565 → RGB888
2️⃣ 图像缩放
├─ 最近邻插值 (快速)
├─ 双线性插值 (质量好)
└─ 双三次插值 (质量最好)
3️⃣ 图像滤波
├─ 高斯滤波 (平滑)
├─ 中值滤波 (去噪)
└─ Sobel 滤波 (边缘检测)
4️⃣ 色彩调整
├─ 亮度调整
├─ 对比度调整
└─ 饱和度调整
module yuv422_to_rgb565 (
input clk,
input [7:0] y,
input [7:0] u,
input [7:0] v,
output [15:0] rgb565
);
wire [15:0] r, g, b;
// YUV 到 RGB 转换公式
// R = Y + 1.402 * (V - 128)
// G = Y - 0.344 * (U - 128) - 0.714 * (V - 128)
// B = Y + 1.772 * (U - 128)
assign r = (y + ((v - 8'd128) * 9'd179) >> 8);
assign g = (y - (((u - 8'd128) * 9'd44) >> 8) - (((v - 8'd128) * 9'd91) >> 8));
assign b = (y + ((u - 8'd128) * 9'd227) >> 8);
// 限幅到 0-255
wire [7:0] r_clamp = (r > 255) ? 8'd255 : (r < 0) ? 8'd0 : r[7:0];
wire [7:0] g_clamp = (g > 255) ? 8'd255 : (g < 0) ? 8'd0 : g[7:0];
wire [7:0] b_clamp = (b > 255) ? 8'd255 : (b < 0) ? 8'd0 : b[7:0];
// 转换为 RGB565
assign rgb565 = {r_clamp[7:3], g_clamp[7:2], b_clamp[7:3]};
endmodule
VGA(Video Graphics Array) 是显示器的标准接口,HDMI 兼容 VGA 时序。
VGA 时序参数:
VGA 时序包含:
├─ 水平同步 (HSYNC): 行同步信号
├─ 垂直同步 (VSYNC): 帧同步信号
├─ 像素时钟 (PCLK): 采样时钟
└─ RGB 数据:颜色数据
VGA 时序关键参数:
├─ 分辨率:宽度×高度
├─ 刷新率:通常 60Hz
├─ 像素时钟:根据分辨率计算
└─ 同步脉宽:HSYNC 和 VSYNC 的宽度
1280×720@60Hz 时序参数:
┌──────────────────────────────────────────┐
│ 1280×720@60Hz VGA 时序参数
├──────────────────────────────────────────┤
│ 像素时钟 (PCLK) │ 74.25MHz │
│ 水平总像素 │ 1650 │
│ 水平有效像素 │ 1280 │
│ 水平前沿 (HFP) │ 110 │
│ 水平同步宽度 (HSW) │ 40 │
│ 水平后沿 (HBP) │ 220 │
│
│ 垂直总行数 │ 750 │
│ 垂直有效行数 │ 720 │
│ 垂直前沿 (VFP) │ 5 │
│ 垂直同步宽度 (VSW) │ 5 │
│ 垂直后沿 (VBP) │ 20 │
│
│ 刷新率 │ 60Hz │
│ 帧周期 │ 16.67ms │
└──────────────────────────────────────────┘
1920×1080@60Hz 时序参数:
像素时钟 (PCLK) │ 148.5MHz
水平总像素 │ 2200
水平有效像素 │ 1920
水平前沿 (HFP) │ 88
水平同步宽度 (HSW) │ 44
水平后沿 (HBP) │ 148
垂直总行数 │ 1125
垂直有效行数 │ 1080
垂直前沿 (VFP) │ 4
垂直同步宽度 (VSW) │ 5
垂直后沿 (VBP) │ 36
一行的 VGA 时序:
PCLK: ─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─
HSYNC: ─┐ ┌─ │ (低电平表示同步)
│ └────────────────────────────────────┘
RGB: ─┬─────────────────────┬─────────────┬─
│ 有效数据 (1280 像素) │ 无效数据 │
└─────────────────────┴─────────────┘
↑ ↑ ↑
HBP HFP HSW
HDMI(High-Definition Multimedia Interface) 是高清多媒体接口。
HDMI 特点:
HDMI 信号:
HDMI 接口信号:
┌──────────────────────────────────────┐
│ HDMI 接口信号定义
├──────────────────────────────────────┤
│
│ 1️⃣ 视频信号 (差分):
│ ├─ TMDS_CLK+ / TMDS_CLK-
│ ├─ TMDS_D0+ / TMDS_D0-
│ ├─ TMDS_D1+ / TMDS_D1-
│ └─ TMDS_D2+ / TMDS_D2-
│
│ 2️⃣ 音频信号 (差分):
│ ├─ TMDS_D3+ / TMDS_D3-
│ └─ (与视频复用)
│
│ 3️⃣ 控制信号:
│ ├─ DDC_SDA (I2C 数据)
│ ├─ DDC_SCL (I2C 时钟)
│ ├─ CEC (消费电子控制)
│ └─ HPD (热插拔检测)
│
│ 4️⃣ 电源:
│ ├─ +5V (电源)
│ └─ GND (地)
│
└──────────────────────────────────────┘
┌──────────────────────────────────────────┐
│ HDMI 驱动框图
├──────────────────────────────────────────┤
│
│ VGA 时序生成器
│ ├─ HSYNC、VSYNC 生成
│ ├─ 像素计数
│ └─ 行列计数
│ ↓
│ ┌──────────────────────────────────┐
│ │ TMDS 编码器
│ │ ├─ 8bit RGB → 10bit TMDS
│ │ ├─ 差分编码
│ │ └─ 时钟编码
│ └──────────────────────────────────┘
│ ↓
│ ┌──────────────────────────────────┐
│ │ 差分驱动器
│ │ ├─ TMDS_CLK+/-
│ │ ├─ TMDS_D0+/-
│ │ ├─ TMDS_D1+/-
│ │ └─ TMDS_D2+/-
│ └──────────────────────────────────┘
│ ↓
│ HDMI 连接器
│
└──────────────────────────────────────────┘
module vga_timing_gen #(
parameter H_TOTAL = 1650,
parameter H_ACTIVE = 1280,
parameter H_FP = 110,
parameter H_SYNC = 40,
parameter V_TOTAL = 750,
parameter V_ACTIVE = 720,
parameter V_FP = 5,
parameter V_SYNC = 5
) (
input clk,
input rst_n,
output reg hsync,
output reg vsync,
output reg [11:0] pixel_x,
output reg [11:0] pixel_y,
output reg data_valid
);
reg [11:0] h_counter, v_counter;
// 水平计数器
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
h_counter <= 0;
else if (h_counter == H_TOTAL - 1)
h_counter <= 0;
else
h_counter <= h_counter + 1;
end
// 垂直计数器
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
v_counter <= 0;
else if (h_counter == H_TOTAL - 1)
begin
if (v_counter == V_TOTAL - 1)
v_counter <= 0;
else
v_counter <= v_counter + 1;
end
end
// HSYNC 生成
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
hsync <= 1'b1;
else
begin
if (h_counter >= (H_ACTIVE + H_FP) && h_counter < (H_ACTIVE + H_FP + H_SYNC))
hsync <= 1'b0;
else
hsync <= 1'b1;
end
end
// VSYNC 生成
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
vsync <= 1'b1;
else
begin
if (v_counter >= (V_ACTIVE + V_FP) && v_counter < (V_ACTIVE + V_FP + V_SYNC))
vsync <= 1'b0;
else
vsync <= 1'b1;
end
end
// 像素坐标和数据有效信号
always @(posedge clk or negedge rst_n)
begin
if (!rst_n)
begin
pixel_x <= 0;
pixel_y <= 0;
data_valid <= 1'b0;
end
else
begin
pixel_x <= h_counter;
pixel_y <= v_counter;
if (h_counter < H_ACTIVE && v_counter < V_ACTIVE)
data_valid <= 1'b1;
else
data_valid <= 1'b0;
end
end
endmodule
TMDS(Transition Minimized Differential Signaling) 是 HDMI 的编码方式。
TMDS 编码特点:
TMDS 编码流程:
8bit RGB 数据 ↓
第一阶段:XOR 编码
├─ 计算转换数
└─ 选择编码方式 ↓
第二阶段:直流平衡
├─ 调整输出
└─ 保持直流平衡 ↓
10bit TMDS 数据 ↓
差分驱动
├─ TMDS+
└─ TMDS-
module tmds_encoder (
input clk,
input [7:0] data_in,
input ctrl_in,
output reg [9:0] tmds_out
);
wire [8:0] xor_result;
wire [3:0] ones_count;
// 第一阶段:XOR 编码
assign xor_result[0] = data_in[0];
assign xor_result[1] = xor_result[0] ^ data_in[1];
assign xor_result[2] = xor_result[1] ^ data_in[2];
assign xor_result[3] = xor_result[2] ^ data_in[3];
assign xor_result[4] = xor_result[3] ^ data_in[4];
assign xor_result[5] = xor_result[4] ^ data_in[5];
assign xor_result[6] = xor_result[5] ^ data_in[6];
assign xor_result[7] = xor_result[6] ^ data_in[7];
assign xor_result[8] = 1'b0;
// 计算 1 的个数
assign ones_count = xor_result[0] + xor_result[1] + xor_result[2] + xor_result[3] + xor_result[4] + xor_result[5] + xor_result[6] + xor_result[7];
// 第二阶段:直流平衡
always @(posedge clk)
begin
if (ctrl_in)
begin
// 控制字符编码
tmds_out <= 10'b1010101100; // 简化处理
end
else
begin
// 数据编码
if (ones_count > 4 || (ones_count == 4 && xor_result[0] == 0))
tmds_out <= {~xor_result[8], xor_result[7:0], 1'b0};
else
tmds_out <= {xor_result[8], xor_result[7:0], 1'b1};
end
end
endmodule
┌──────────────────────────────────────────────┐
│ HDMI 显示系统完整框图
├──────────────────────────────────────────────┤
│
│ 图像缓存 (SDRAM/DDR3)
│ ↓
│ ┌──────────────────────────────────────┐
│ │ 读取控制器
│ │ ├─ 地址生成
│ │ ├─ 数据读取
│ │ └─ 缓冲管理
│ └──────────────────────────────────────┘
│ ↓
│ ┌──────────────────────────────────────┐
│ │ VGA 时序生成器
│ │ ├─ HSYNC、VSYNC
│ │ ├─ 像素坐标
│ │ └─ 数据有效信号
│ └──────────────────────────────────────┘
│ ↓
│ ┌──────────────────────────────────────┐
│ │ TMDS 编码器
│ │ ├─ RGB → TMDS 编码
│ │ ├─ 时钟编码
│ │ └─ 控制信号编码
│ └──────────────────────────────────────┘
│ ↓
│ ┌──────────────────────────────────────┐
│ │ 差分驱动器
│ │ ├─ TMDS_CLK+/-
│ │ ├─ TMDS_D0+/-
│ │ ├─ TMDS_D1+/-
│ │ └─ TMDS_D2+/-
│ └──────────────────────────────────────┘
│ ↓
│ HDMI 连接器 → 显示器
│
└──────────────────────────────────────────────┘
一帧显示的完整时序:
VSYNC: ─┐ ┌─ │ (低电平表示帧有效)
│ └────────────────────────────────────┘
HSYNC: ─┬─────┐ ┬─────┐ ┬─────┐ ┬─────┐ ┬─
│ │ │ │ │ │ │ │ │ └─────┘ └─────┘ └─────┘ └─────┘ └─
RGB: ─┬─────────────────────┬─────────────┬─
│ 有效数据 │ 无效数据 │
└─────────────────────┴─────────────┘
TMDS: ─┬─────────────────────┬─────────────┬─
│ 编码后的数据 │ 编码后的数据│
└─────────────────────┴─────────────┘
module camera_hdmi_system (
input sys_clk,
input rst_n,
// 摄像头接口
output camera_xclk,
output camera_resetb,
output camera_pwdn,
inout camera_sio_c,
inout camera_sio_d,
input camera_pclk,
input camera_vsync,
input camera_href,
input [9:0] camera_data,
// HDMI 接口
output hdmi_clk_p,
output hdmi_clk_n,
output [2:0] hdmi_d_p,
output [2:0] hdmi_d_n,
// 调试接口
output [7:0] led,
input [3:0] btn
);
// 时钟生成
wire clk_100m, clk_74m25, clk_148m5;
pll_clk_gen pll_inst (
.clk_in(sys_clk),
.clk_100m(clk_100m),
.clk_74m25(clk_74m25),
.clk_148m5(clk_148m5)
);
// 摄像头初始化
wire camera_init_done;
camera_init camera_init_inst (
.clk(clk_100m),
.rst_n(rst_n),
.sio_c(camera_sio_c),
.sio_d(camera_sio_d),
.resetb(camera_resetb),
.pwdn(camera_pwdn),
.init_done(camera_init_done)
);
// 图像采集
wire [7:0] pixel_data;
wire pixel_valid;
wire frame_valid;
dvp_capture capture_inst (
.pclk(camera_pclk),
.vsync(camera_vsync),
.href(camera_href),
.data_in(camera_data),
.data_out(pixel_data),
.data_valid(pixel_valid),
.frame_valid(frame_valid)
);
// 图像缓存 (SDRAM)
wire [23:0] sdram_addr;
wire [15:0] sdram_data_w, sdram_data_r;
wire sdram_we, sdram_re;
sdram_ctrl sdram_inst (
.clk(clk_100m),
.rst_n(rst_n),
.addr(sdram_addr),
.data_in(sdram_data_w),
.data_out(sdram_data_r),
.we(sdram_we),
.re(sdram_re)
);
// VGA 时序生成
wire [11:0] pixel_x, pixel_y;
wire hsync, vsync;
wire vga_data_valid;
vga_timing_gen vga_inst (
.clk(clk_74m25),
.rst_n(rst_n),
.hsync(hsync),
.vsync(vsync),
.pixel_x(pixel_x),
.pixel_y(pixel_y),
.data_valid(vga_data_valid)
);
// HDMI 输出
hdmi_tx hdmi_inst (
.clk_pixel(clk_74m25),
.clk_tmds(clk_148m5),
.rst_n(rst_n),
.hsync(hsync),
.vsync(vsync),
.rgb_data(sdram_data_r),
.data_valid(vga_data_valid),
.hdmi_clk_p(hdmi_clk_p),
.hdmi_clk_n(hdmi_clk_n),
.hdmi_d_p(hdmi_d_p),
.hdmi_d_n(hdmi_d_n)
);
// 状态指示
assign led[0] = camera_init_done;
assign led[1] = frame_valid;
assign led[2] = vga_data_valid;
assign led[7:3] = 5'b0;
endmodule
采集模块 → 缓存模块 → 显示模块
采集模块输出:
├─ pixel_data[7:0]: 像素数据
├─ pixel_valid: 数据有效
├─ frame_valid: 帧有效
└─ pixel_x, pixel_y: 像素坐标
缓存模块:
├─ 输入:采集数据
├─ 输出:显示数据
└─ 管理:双缓冲切换
显示模块输入:
├─ rgb_data[15:0]: RGB565 数据
├─ hsync, vsync: 同步信号
└─ data_valid: 数据有效
问题 1: 摄像头初始化失败 排查步骤:
问题 2: 图像采集无数据 排查步骤:
问题 3: HDMI 无显示 排查步骤:
推荐工具:
优化策略:
优化策略:
优化策略:
本文详细介绍了 FPGA 摄像头采集、处理、显示系统的完整设计流程,包括系统架构、OV5640 驱动、DVP 采集、图像缓存、HDMI 输出及调试技巧。通过模块化设计和合理的时序管理,可以实现稳定的实时视频处理系统。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online