跳到主要内容
FPGA 摄像头采集处理显示指南:OV5640 到 HDMI 实时显示 | 极客日志
编程语言 算法
FPGA 摄像头采集处理显示指南:OV5640 到 HDMI 实时显示 综述由AI生成 FPGA 实现摄像头采集与 HDMI 显示系统涉及 OV5640 驱动、DVP 接口时序、图像缓存管理及 TMDS 编码。了上电初始化流程、SCCB 寄存器配置、双端口 RAM 及 SDRAM 控制器设计,以及 VGA/HDMI 时序生成方法。通过 Verilog 代码示例展示了数据采集、格式转换(YUV 转 RGB)和显示输出的完整链路,适用于视频监控与工业检测场景。
kaikai 发布于 2026/3/21 更新于 2026/4/25 4 浏览FPGA 摄像头采集处理显示指南:从 OV5640 到 HDMI 实时显示
概述
在当今的视频监控、工业检测、医疗成像等领域,实时图像采集和显示已成为必不可少的功能。FPGA 因其高并行处理能力和低延迟特性,成为实现高性能视频处理系统的首选方案。
本文将详细介绍如何使用 FPGA 实现一个完整的摄像头采集、处理、显示系统,重点讲解 OV5640 摄像头的驱动、图像数据采集、缓存管理以及 HDMI 显示输出的全流程。
一、摄像头采集处理显示系统概述
1.1 系统架构与核心模块
1.1.1 完整系统架构
一个典型的 FPGA 摄像头采集处理显示系统由以下几个主要部分组成:
FPGA 摄像头采集处理显示系统架构
├─ 1️⃣ 摄像头采集模块
│ ├─ OV5640 摄像头驱动
│ ├─ SCCB 配置接口
│ └─ DVP 数据采集
├─ 2️⃣ 图像处理模块
│ ├─ 数据格式转换
│ ├─ 图像缩放/裁剪
│ └─ 色彩空间转换
├─ 3️⃣ 存储缓存模块
│ ├─ SDRAM/DDR3
│ ├─ 双端口 RAM
│ └─ 帧缓冲管理
├─ 4️⃣ 显示输出模块
│ ├─ VGA 时序生成
│ ├─ HDMI 驱动
│ └─ TMDS 编码
└─ 5️⃣ 时钟管理模块
├─ PLL 时钟生成
├─ 多时钟域同步
└─ 时序约束
1.1.2 核心模块功能说明
模块名称 功能描述 关键参数 OV5640 驱动 摄像头初始化、寄存器配置、数据采集 分辨率、帧率、数据格式 SCCB 控制器 I2C 兼容的摄像头配置接口 时钟频率、地址宽度 DVP 采集 并行数据采集、时序同步 像素时钟、行列同步 图像缓存 帧数据存储、读写管理 缓存大小、带宽 VGA 驱动 显示时序生成、数据输出 分辨率、刷新率 HDMI 驱动 HDMI 信号编码、差分输出 分辨率、色深
1.1.3 数据流向
摄像头 (OV5640) ↓ [DVP 接口:PCLK, HREF, VSYNC, Y[7:0] ]
FPGA 采集模块 ↓ [16 位 RGB565 或 YUV422]
图像处理模块 ↓ [处理后的图像数据]
SDRAM/DDR3 缓存 ↓ [读取请求]
VGA/HDMI 驱动 ↓ [HDMI 差分信号]
显示器
1.2 应用场景与实现方案
1.2.1 典型应用场景
1. 视频监控系统
特点:需要实时处理多路视频流,对延迟要求严格 (< 100ms),需要支持多种分辨率 (720p, 1080p),可能需要视频编码 (H.264/H.265)。
FPGA 优势:低延迟 (毫秒级),支持并行处理,灵活的数据流管理,可扩展性强。
2. 工业检测系统
特点:需要高帧率采集 (60fps 以上),对图像质量要求高,需要实时图像处理 (滤波、边缘检测等),可能需要多摄像头同步。
FPGA 优势:支持高帧率处理,可实现复杂的图像算法,多摄像头同步容易,处理延迟可预测。
3. 医疗成像系统
特点:对图像质量要求极高,需要实时显示和存储,可能需要图像增强处理,需要高可靠性。
FPGA 优势:可实现高质量图像处理,支持多种数据格式,可靠性高,易于集成。
1.2.2 不同分辨率的实现方案 分辨率 帧率 像素时钟 缓存大小 适用场景 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.3 设计流程与关键技术点
1.3.1 设计流程 1️⃣ 需求分析
├─ 确定分辨率和帧率
├─ 选择摄像头型号
└─ 评估 FPGA 资源
2️⃣ 硬件设计
├─ 摄像头接口设计
├─ 电源管理设计
├─ 时钟分配设计
└─ 显示接口设计
3️⃣ 软件设计
├─ 摄像头驱动开发
├─ 图像采集模块
├─ 缓存管理
└─ 显示驱动
4️⃣ 集成与调试
├─ 模块集成
├─ 时序验证
├─ 功能测试
└─ 性能优化
5️⃣ 部署与维护
├─ 系统集成
├─ 可靠性测试
└─ 文档完善
1.3.2 关键技术点 // 关键时钟信号
- 摄像头输入时钟 (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]
摄像头时钟域 (PCLK) ←→ 系统时钟域 (sys_clk)
↓ CDC 同步器 (双触发器)
↓ HDMI 时钟域 (clk_hdmi)
关键:使用格雷码或双触发器进行同步
写入带宽:PCLK × 16 bit (像素时钟 × 数据宽度)
读取带宽:clk_hdmi × 16 bit (显示时钟 × 数据宽度)
例如 1080 p@60 Hz:
- 写入:148.5 MHz × 16 bit = 2.376 Gbps
- 读取:148.5 MHz × 16 bit = 2.376 Gbps
- 需要 DDR3-1600 以上支持
1.3.3 常见设计挑战 挑战 原因 解决方案 时序收敛困难 多时钟域、高频率 合理的时钟约束、流水线设计 缓存不足 高分辨率、高帧率 使用 DDR3、优化数据格式 图像撕裂 读写不同步 双缓冲、帧同步控制 色彩失真 格式转换错误 正确的色彩空间转换 延迟过高 处理流程复杂 流水线并行处理
二、OV5640 摄像头基础知识
2.1 OV5640 摄像头概述
2.1.1 OV5640 的基本特性 OV5640 是豪威 (OmniVision) 公司推出的一款高性能 CMOS 图像传感器,广泛应用于监控、手机、平板等消费类电子产品。
特性 参数 最大分辨率 2592×1944(500 万像素) 输出格式 YUV422/420、RGB565、JPEG 帧率 15-60fps(可配置) 工作时钟 6-54MHz(推荐 24MHz) 功耗 150-200mW 接口 DVP(并行)、SCCB(I2C 兼容) 自动功能 自动对焦、自动曝光、自动白平衡
2.1.2 OV5640 的优势与劣势
分辨率高 (500 万像素)
功能完整 (自动对焦、自动曝光等)
支持多种输出格式
成本相对较低
应用广泛,资料丰富
寄存器众多,配置复杂
初始化时间较长 (20ms 以上)
对电源噪声敏感
需要外部晶振
2.2 OV5640 引脚定义与功能
2.2.1 引脚分布 OV5640 采用 CSP(芯片级封装),共有 60 个引脚。主要引脚如下:
┌─────────────────────────────────────┐
│ OV5640 CSP60 引脚分布 │
├─────────────────────────────────────┤
│ 电源引脚: │
│ DVDD (1.8 V) - 数字电源 │
│ AVDD (2.8 V) - 模拟电源 │
│ DOVDD (1.8 V) - 输出驱动电源 │
│ │
│ 时钟引脚: │
│ XCLK - 外部时钟输入 (24 MHz) │
│ PCLK - 像素时钟输出 │
│ │
│ 同步信号: │
│ VSYNC - 帧同步信号 (行场同步) │
│ HREF - 行同步信号 │
│ │
│ 数据引脚: │
│ Y [9:0] - 10 位像素数据 │
│ (通常使用 Y[9 :2 ] 作为 8 位数据) │
│ │
│ 控制引脚: │
│ RESETB - 复位信号 (低有效) │
│ PWDN - 掉电/省电 (高有效) │
│ │
│ 通信引脚: │
│ SIO_C - SCCB 时钟线 │
│ SIO_D - SCCB 数据线 │
└─────────────────────────────────────┘
2.2.2 关键引脚详解 DVDD (1.8 V ±5% )
├─ 数字核心电源
├─ 需要 100 nF+10 μF 滤波
└─ 对噪声敏感,需要单独的电源层
AVDD (2.8 V ±5% )
├─ 模拟电源
├─ 需要 100 nF+10 μF 滤波
└─ 必须与 DVDD 同时上电
DOVDD (1.8 V ±5% )
├─ 输出驱动电源
├─ 驱动 Y [9:0] 、PCLK、VSYNC、HREF
└─ 需要 100 nF 滤波
XCLK (外部时钟输入)
├─ 频率范围:6 -54 MHz
├─ 推荐频率:24 MHz
├─ 占空比:45% -55%
├─ 可来自晶振或 FPGA 输出
└─ 必须稳定,抖动< 100 ps
PCLK (像素时钟输出)
├─ 频率:根据分辨率和帧率变化
├─ 例如:1280 ×720 @60Hz 时约 74.25 MHz
├─ 由 OV5640 内部 PLL 生成
├─ 用于同步像素数据采集
└─ 需要在 FPGA 中采样
VSYNC (帧同步信号)
├─ 低电平表示帧有效
├─ 用于帧边界检测
├─ 周期 = 1 /帧率
└─ 例如:60 fps 时周期为 16.67ms
HREF (行同步信号)
├─ 高电平表示行有效
├─ 用于行边界检测
├─ 周期 = 1 /(帧率×行数)
└─ 例如:1280 ×720 @60Hz 时约 13.9 μs
Y [9:0] (10 位像素数据)
├─ 通常使用 Y [9:2] 作为 8 位数据
├─ 数据格式:YUV422、RGB565 等
├─ 在 PCLK 上升沿采样
├─ 需要与 HREF、VSYNC 同步
└─ 输出电平:DOVDD (1.8 V)
RESETB (复位信号,低有效)
├─ 低电平时复位芯片
├─ 需要保持低电平至少 1ms
├─ 上升沿后需要等待 20ms 才能配置
└─ 通常由 FPGA GPIO 驱动
PWDN (掉电/省电,高有效)
├─ 高电平时进入低功耗模式
├─ 低电平时正常工作
├─ 需要在 RESETB 之前拉低
└─ 通常由 FPGA GPIO 驱动
2.3 OV5640 工作原理
2.3.1 内部结构框图 ┌──────────────────────────────────────────────────┐
│ OV5640 内部结构框图 │
├──────────────────────────────────────────────────┤
│ │
│ XCLK (24 MHz) │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ PLL/时钟生成 │ │
│ │ (生成各种内部时钟) │ │
│ └─────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ 感光矩阵 (2592 ×1944 ) │ │
│ │ (CMOS 传感器,光电转换) │ │
│ └─────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ 模拟前端 (AFE) │ │
│ │ (放大、滤波、模数转换) │ │
│ └─────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ ISP (图像信号处理) │ │
│ │ ├─ 自动曝光 (AE) │ │
│ │ ├─ 自动白平衡 (AWB) │ │
│ │ ├─ 自动对焦 (AF) │ │
│ │ ├─ 伽玛校正 │ │
│ │ ├─ 色彩矩阵 │ │
│ │ └─ 降噪处理 │ │
│ └─────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ 格式转换 │ │
│ │ (YUV422/420 、RGB565、JPEG 等) │ │
│ └─────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────┐ │
│ │ 输出接口 │ │
│ │ ├─ DVP (并行): Y[9 :0 ]、PCLK、HREF、VSYNC │ │
│ │ └─ SCCB (I2C): 寄存器配置 │ │
│ └─────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────┘
2.3.2 工作流程 1 ️⃣ 上电初始化
├─ RESETB 拉低,复位芯片
├─ PWDN 拉低,退出低功耗
├─ 等待 20ms,PLL 稳定
└─ 芯片就绪
2 ️⃣ 寄存器配置
├─ 通过 SCCB 写入配置参数
├─ 设置分辨率、帧率、输出格式
├─ 配置自动曝光、白平衡等
└─ 配置完成
3 ️⃣ 图像采集
├─ 感光矩阵采集光信号
├─ ISP 处理图像
├─ 格式转换
└─ 通过 DVP 接口输出
4 ️⃣ 数据输出
├─ PCLK: 像素时钟 (采样时钟)
├─ VSYNC: 帧同步 (低电平表示帧有效)
├─ HREF: 行同步 (高电平表示行有效)
├─ Y[9:0]: 像素数据
└─ 循环输出每一帧
2.4 SCCB 通信协议
2.4.1 SCCB 协议概述 SCCB(Serial Camera Control Bus) 是豪威公司定义的摄像头控制总线,与 I2C 协议类似,但有细微差别。
基于 I2C,但不完全兼容
两线制:SIO_C(时钟)、SIO_D(数据)
时钟频率:100-400kHz(推荐 100kHz)
支持单字节和双字节寻址
OV5640 使用 16 位地址
2.4.2 SCCB 时序 写操作时序:
┌─────────────────────────────────────────────────┐
│ START | SLAVE_ADDR | ACK | H_ADDR | ACK | L_ADDR│
│ | (8 bit) | | (8 bit) | | (8 bit) │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ ACK | DATA | ACK | STOP |
│ |(8 bit)| | |
└─────────────────────────────────────────────────┘
读操作时序:
┌─────────────────────────────────────────────────┐
│ START | SLAVE_ADDR | ACK | H_ADDR | ACK | L_ADDR│
│ | (8 bit) | | (8 bit) | | (8 bit) │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ ACK | START | SLAVE_ADDR | ACK | DATA | NACK | STOP│
│ | | (8 bit) | |(8 bit)| |
└─────────────────────────────────────────────────┘
2.4.3 SCCB 与 I2C 的区别 特性 SCCB I2C 寻址方式 16 位地址 7/10 位地址 时钟频率 100-400kHz 100-400kHz 从机应答 可选 必须 读操作 需要重复 START 支持 兼容性 不完全兼容 I2C 标准协议
实际应用中: FPGA 通常使用 I2C 控制器驱动 SCCB,因为两者在时序上基本兼容。
2.4.4 OV5640 寄存器访问 寄存器地址:16 位 (高字节 + 低字节)
寄存器数据:8 位
写寄存器示例:
地址:0 x3008 (高字节 0 x30, 低字节 0 x08)
数据:0 x82
SCCB 写操作:START → SLAVE_ADDR (0 x78) → ACK → 0 x30 → ACK → 0 x08 → ACK → 0 x82 → ACK → STOP
读寄存器示例:
地址:0 x300A
SCCB 读操作:START → SLAVE_ADDR (0 x78) → ACK → 0 x30 → ACK → 0 x0A → ACK → START → SLAVE_ADDR (0 x79) → ACK → DATA → NACK → STOP
三、摄像头初始化与配置
3.1 OV5640 上电时序
3.1.1 上电时序流程 OV5640 的正确上电时序对系统稳定性至关重要。不正确的上电时序可能导致芯片无法正常工作。
上电时序流程:
时间轴:0ms
├─ 电源上电 (DVDD、AVDD、DOVDD)
│ └─ 所有电源应同时上电或按规定顺序上电
│ 5ms
├─ PWDN 拉低 (退出低功耗模式)
│ └─ 保持低电平
│ 10ms
├─ RESETB 拉低 (复位芯片)
│ └─ 保持低电平至少 1ms
│ 15ms
├─ RESETB 拉高 (释放复位)
│ └─ 芯片开始初始化
│ 35ms
└─ 等待 PLL 稳定 (约 20ms)
└─ 可以开始配置寄存器
3.1.2 上电时序详细说明 推荐上电顺序:
1️⃣ AVDD (2.8V) 先上电
2️⃣ DVDD (1.8V) 后上电
3️⃣ DOVDD (1.8V) 最后上电
或者同时上电 (更常见)
电源稳定要求:
├─ 上升时间:< 100ms
├─ 纹波:< 100mV
├─ 稳定度:±5%
└─ 需要充分的滤波电容
PWDN (掉电/省电控制)
├─ 低电平:正常工作
├─ 高电平:低功耗模式
│ 上电时序:
├─ 电源上电后,PWDN 应拉低
├─ 保持低电平至少 5 ms
└─ 然后进行 RESETB 复位
FPGA 实现示例:
always @(posedge clk) begin
if (power_on_counter < 32
pwdn <= 1
power_on_counter <= power_on_counter + 1 ;
end else begin
pwdn <= 1
end
end
RESETB (复位信号,低有效)
├─ 低电平:芯片复位
├─ 高电平:芯片工作
│ 复位时序:
├─ PWDN 拉低后,等待 5 ms
├─ RESETB 拉低,保持至少 1 ms
├─ RESETB 拉高,释放复位
└─ 等待 20 ms,PLL 稳定
FPGA 实现示例:
always @(posedge clk) begin
if (reset_counter < 32
resetb <= 1
reset_counter <= reset_counter + 1 ;
end else if (reset_counter < 32
resetb <= 1
reset_counter <= reset_counter + 1 ;
end else begin
init_done <= 1
end
end
3.2 OV5640 关键寄存器配置
3.2.1 常用寄存器说明 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
3.2.2 分辨率配置寄存器 设置分辨率的关键寄存器:
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
3.2.3 常见分辨率配置示例 寄存器配置表:
┌──────────┬──────────┬─────────────────────┐
│ 地址 │ 数据 │ 说明 │
├──────────┼──────────┼─────────────────────┤
│ 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 输出 │
└──────────┴──────────┴─────────────────────┘
关键寄存器配置:
0x3808 = 0 x05 (宽度高字节)
0x3809 = 0 x00 (宽度低字节) → 1280
0x380a = 0 x02 (高度高字节)
0x380b = 0 xd0 (高度低字节) → 720
PLL 配置:
0x3034 = 0 x18
0x3035 = 0 x21
0x3036 = 0 x69
0x3037 = 0 x13
3.3 SCCB 控制器设计
3.3.1 SCCB 控制器功能 SCCB 控制器是 FPGA 中用于与 OV5640 通信的模块,负责:
生成 SCCB 时序信号
实现寄存器读写操作
处理应答信号
3.3.2 SCCB 控制器框图 ┌─────────────────────────────────────────┐
│ SCCB 控制器框图 │
├─────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────┐ │
│ │ 状态机 │ │
│ │ ├─ IDLE │ │
│ │ ├─ START │ │
│ │ ├─ ADDR_H │ │
│ │ ├─ ADDR_L │ │
│ │ ├─ DATA │ │
│ │ ├─ ACK │ │
│ │ └─ STOP │ │
│ └──────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────┐ │
│ │ 时钟分频器 │ │
│ │ (生成 SCCB 时钟) │ │
│ └──────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────┐ │
│ │ I /O 驱动 │ │
│ │ ├─ SIO_C (时钟线) │ │
│ │ └─ SIO_D (数据线) │ │
│ └──────────────────────────────────┘ │
│ │
└─────────────────────────────────────────┘
3.3.3 SCCB 控制器 Verilog 实现框架 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) begin
state <= IDLE;
end else begin
state <= next_state;
end
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
3.3.4 SCCB 控制器时序 写操作时序示例:
CLK: ─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
SIO_C:─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─
SIO_D: ─┐ ┌─────────────────────────────────────────────────────────────
│ │ START └─┘ ┌─────────────────────────────────────────────────────────────┐
SIO_D:─┤ SLAVE_ADDR(0x78) │ ACK │ ADDR_H │ ACK │ ADDR_L │ ACK │ DATA │ ACK │ STOP └─────────────────────────────────────────────────────────────┘
四、图像采集模块设计
4.1 DVP 接口详解
4.1.1 DVP 接口概述 DVP(Digital Video Port) 是 OV5640 的并行数据输出接口,用于传输图像数据。
并行数据传输 (8/10 位)
同步时序信号 (PCLK、HREF、VSYNC)
高速数据率 (可达 148.5MHz)
简单易用,易于 FPGA 集成
4.1.2 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 - 掉电控制 │
│ │
└─────────────────────────────────────────┘
4.1.3 DVP 时序关系 一帧图像的采集时序:
VSYNC: ─┐ ┌─
│ (低电平表示帧有效)
│ └────────────────────────────────────┘
HREF: ─┬─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─
│ │ │ │ │ │ │ │ │ └─────┘ └─────┘ └─────┘ └─────┘ └─ (高电平表示行有效)
PCLK: ─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─
Y[9:0]: ┌─────┬─────┬─────┬─────┬─────┬─────┐
│ P0 │ P1 │ P2 │ P3 │ P4 │ P5 │
└─────┴─────┴─────┴─────┴─────┴─────┘
↑ ↑ ↑ ↑ ↑ ↑
在 PCLK 上升沿采样
在 PCLK 上升沿采样 Y[9:0] 数据
HREF 高电平时,数据有效
VSYNC 低电平时,帧有效
一帧包含多行,一行包含多个像素
4.2 图像采集模块设计
4.2.1 采集模块框图 ┌──────────────────────────────────────────────┐
│ 图像采集模块框图 │
├──────────────────────────────────────────────┤
│ │
│ OV5640 摄像头 │
│ ├─ PCLK │
│ ├─ VSYNC │
│ ├─ HREF │
│ └─ Y [9:0] │
│ ↓ │
│ ┌──────────────────────────────────────┐ │
│ │ 采集控制模块 │ │
│ │ ├─ 帧同步检测 (VSYNC) │ │
│ │ ├─ 行同步检测 (HREF) │ │
│ │ ├─ 像素计数 │ │
│ │ └─ 行列计数 │ │
│ └──────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────┐ │
│ │ 数据缓冲 │ │
│ │ ├─ 像素数据缓存 │ │
│ │ ├─ 行缓冲 │ │
│ │ └─ 帧缓冲 │ │
│ └───────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────┐ │
│ │ 输出接口 │ │
│ │ ├─ 像素数据输出 │ │
│ │ ├─ 行有效信号 │ │
│ │ ├─ 帧有效信号 │ │
│ │ └─ 数据有效信号 │ │
│ └──────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────┘
4.2.2 采集模块 Verilog 实现 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
4.2.3 采集模块关键设计点 PCLK 时钟域 (摄像头时钟)
↓ 采集数据
↓ 系统时钟域 (FPGA 系统时钟)
↓ 处理数据
关键:使用 CDC (Clock Domain Crossing) 同步
像素计数逻辑:
├─ 在 HREF 高电平时计数像素
├─ HREF 下降沿时,行计数 +1
├─ VSYNC 下降沿时,帧计数 +1
└─ 用于生成行列坐标
数据有效条件:
├─ VSYNC 低电平 (帧有效)
├─ HREF 高电平 (行有效)
├─ 在 PCLK 上升沿采样
└─ 三个条件同时满足时,数据有效
4.3 采集模块时序控制
4.3.1 帧同步控制 帧同步时序:
VSYNC: ─┐ ┌─
│ 帧有效 (低电平)
│ └────────────────────────────────────┘
frame_valid: ─┐ ┌─
│ 帧有效标志
│ └────────────────────────────────────┘
pixel_y: ─┬─────────────────────────────┬─
│ 0→1→2→...→(height-1)→0
│ └─────────────────────────────┘
4.3.2 行同步控制 行同步时序:
HREF: ─┬─────┐ ┬─────┐ ┬─────┐
│ │ │ │ │ │ │ │ │ └─────┘ └─────┘ └─────┘
line_valid: ─┬─────┐ ┬─────┐ ┬─────┐
│ │ │ │ │ │ │ │ │ └─────┘ └─────┘ └─────┘
pixel_x: ─┬─────────────┬─────────────┬─
│ 0→1→...→639 │ 0→1→...→639 │
└─────────────┴─────────────┘
4.3.3 采集模块测试 测试场景:
1️⃣ 单帧采集测试
├─ 采集一帧完整图像
├─ 验证像素数据正确性
└─ 检查行列坐标
2️⃣ 连续采集测试
├─ 采集多帧图像
├─ 验证帧同步
└─ 检查数据连续性
3️⃣ 时序验证
├─ 使用逻辑分析仪
├─ 验证 PCLK、HREF、VSYNC 时序
└─ 检查数据采样点
五、图像处理与缓存
5.1 图像缓存设计
5.1.1 缓存需求分析
缓冲采集数据,解决采集和显示速率不匹配
实现帧缓冲,支持双缓冲显示
存储处理后的图像数据
缓存大小 = 分辨率 × 像素深度 × 帧数
例如 1280×720@60fps:
├─ 单帧大小 = 1280 × 720 × 2 字节 (RGB565) = 1.8MB
├─ 双缓冲 = 1.8MB × 2 = 3.6 MB
└─ 三缓冲 = 1.8MB × 3 = 5.4 MB
常见缓存方案:
├─ 单缓冲:最小化延迟,但易出现撕裂
├─ 双缓冲:平衡延迟和稳定性 (推荐)
└─ 三缓冲:最大稳定性,但延迟最大
5.1.2 缓存架构选择 优点:✅ 速度快 (单周期访问) ✅ 延迟低 ✅ 易于集成
缺点:❌ 容量有限 (通常< 1 MB) ❌ 只适合低分辨率
适用场景:
├─ 640 ×480 以下分辨率
├─ 行缓冲
└─ 临时数据存储
优点:✅ 容量大 (通常 128 MB-512 MB) ✅ 成本低 ✅ 易于扩展
缺点:❌ 延迟高 (10 -20 个周期) ❌ 需要复杂的控制器 ❌ 需要刷新
适用场景:
├─ 中等分辨率 (720 p)
├─ 成本敏感应用
└─ 需要大容量存储
优点:✅ 容量大 (通常 512 MB-2 GB) ✅ 带宽高 (可达 25.6 GB/s) ✅ 性能好
缺点:❌ 成本高 ❌ 功耗高 ❌ 控制复杂
适用场景:
├─ 高分辨率 (1080 p 及以上)
├─ 高帧率 (60 fps 及以上)
└─ 需要高性能
5.2 双端口 RAM 设计
5.2.1 双端口 RAM 概述 双端口 RAM 允许同时进行读写操作,是 FPGA 图像处理的关键组件。
双端口 RAM 结构:
┌──────────────────────────────────────┐
│ 双端口 RAM │
├──────────────────────────────────────┤
│ │
│ 端口 A (写端口) 端口 B (读端口) │
│ ├─ 地址:addr_a │
│ ├─ 地址:addr_b │
│ ├─ 数据:data_in │
│ ├─ 数据:data_out │
│ ├─ 写使能:we_a │
│ └─ 读使能:re_b │
│ └─ 时钟:clk_a │
│ 时钟:clk_b │
│ │
│ 可以同时: │
│ ├─ 在地址 A 写入数据 │
│ ├─ 在地址 B 读出数据 │
│ └─ 两个操作独立进行 │
│ ────── │
└──────────────────────────────────────┘
5.2.2 双端口 RAM Verilog 实现 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) begin
mem[addr_a] <= data_in;
end
end
// 读操作 (显示端)
always @(posedge clk_b) begin
if (re_b) begin
data_out <= mem[addr_b];
end
end
endmodule
5.2.3 帧缓冲管理 双缓冲帧管理:
┌─────────────────────────────────────────┐
│ 帧缓冲管理 │
├─────────────────────────────────────────┤
│ │
│ 缓冲区 A (写入缓冲) │
│ ├─ 采集模块写入新帧 │
│ ├─ 显示模块不读取 │
│ └─ 写入完成后切换 │
│ │
│ 缓冲区 B (显示缓冲) │
│ ├─ 显示模块读取显示 │
│ ├─ 采集模块不写入 │
│ └─ 显示完成后切换 │
│ │
│ 切换机制: │
│ ├─ 采集完成 → 切换写入缓冲 │
│ ├─ 显示完成 → 切换显示缓冲 │
│ └─ 防止撕裂 │
│ │
└─────────────────────────────────────────┘
5.3 SDRAM 控制器设计
5.3.1 SDRAM 基础知识 SDRAM(Synchronous Dynamic RAM) 是常用的外部存储器。
容量大 (128MB-512MB)
成本低
需要定期刷新
访问延迟较高
SDRAM 读操作时序:
CLK: ─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─
CMD: ─┬─────────┬─────────┬─────────┬─
│ ACTIVE │ READ │ PRECHARGE └─────────┴─────────┴─────────┴─
ADDR: ─┬─────────┬─────────┬─────────┬─
│ ROW │ COL │ (X) └─────────┴─────────┴─────────┴─
DATA: ─────────────────────┬─────────┬─
│ DATA_OUT│ └─────────┘ (延迟:CAS Latency)
5.3.2 SDRAM 控制器框图 ┌──────────────────────────────────────────┐
│ SDRAM 控制器框图 │
├──────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────┐ │
│ │ 命令生成器 │ │
│ │ ├─ ACTIVE 命令 │ │
│ │ ├─ READ/WRITE 命令 │ │
│ │ └─ PRECHARGE 命令 │ │
│ └────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────┐ │
│ │ 地址复用器 │ │
│ │ ├─ 行地址 (ROW) │ │
│ │ └─ 列地址 (COL) │ │
│ └────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────┐ │
│ │ 数据通路 │ │
│ │ ├─ 写数据缓冲 │ │
│ │ └─ 读数据缓冲 │ │
│ └────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────┐ │
│ │ SDRAM 接口 │ │
│ │ ├─ 地址线 (A[12 :0 ]) │ │
│ │ ├─ 数据线 (DQ[15 :0 ]) │ │
│ │ ├─ 控制线 (CS, RAS, CAS, WE) │ │
│ │ └─ 时钟 (CLK) │ │
│ └────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────┘
5.3.3 SDRAM 控制器简化实现 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 接口
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
5.4 图像处理基础
5.4.1 常见图像处理操作 1 ️⃣ 格式转换
├─ YUV422 → RGB565
├─ YUV422 → YUV420
└─ RGB565 → RGB888
2 ️⃣ 图像缩放
├─ 最近邻插值 (快速)
├─ 双线性插值 (质量好)
└─ 双三次插值 (质量最好)
3 ️⃣ 图像滤波
├─ 高斯滤波 (平滑)
├─ 中值滤波 (去噪)
└─ Sobel 滤波 (边缘检测)
4 ️⃣ 色彩调整
├─ 亮度调整
├─ 对比度调整
└─ 饱和度调整
5.4.2 YUV422 到 RGB565 转换 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
六、HDMI 显示输出
6.1 VGA 时序基础
6.1.1 VGA 时序概述 VGA(Video Graphics Array) 是显示器的标准接口,HDMI 兼容 VGA 时序。
VGA 时序包含:
├─ 水平同步 (HSYNC): 行同步信号
├─ 垂直同步 (VSYNC): 帧同步信号
├─ 像素时钟 (PCLK): 采样时钟
└─ RGB 数据:颜色数据
VGA 时序关键参数:
├─ 分辨率:宽度×高度
├─ 刷新率:通常 60Hz
├─ 像素时钟:根据分辨率计算
└─ 同步脉宽:HSYNC 和 VSYNC 的宽度
6.1.2 常见分辨率的 VGA 时序 ┌──────────────────────────────────────────┐
│ 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 │
└──────────────────────────────────────────┘
像素时钟 (PCLK) │ 148.5 MHz
水平总像素 │ 2200
水平有效像素 │ 1920
水平前沿 (HFP) │ 88
水平同步宽度 (HSW) │ 44
水平后沿 (HBP) │ 148
垂直总行数 │ 1125
垂直有效行数 │ 1080
垂直前沿 (VFP) │ 4
垂直同步宽度 (VSW) │ 5
垂直后沿 (VBP) │ 36
6.1.3 VGA 时序图 一行的 VGA 时序:
PCLK: ─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─
HSYNC: ─┐ ┌─
│ (低电平表示同步)
│ └────────────────────────────────────┘
RGB: ─┬─────────────────────┬─────────────┬─
│ 有效数据 (1280 像素) │ 无效数据 │
└─────────────────────┴─────────────┘
↑ ↑ ↑
HBP HFP HSW
6.2 HDMI 驱动设计
6.2.1 HDMI 接口概述 HDMI(High-Definition Multimedia Interface) 是高清多媒体接口。
支持高分辨率 (最高 4K)
支持音频传输
支持 HDCP 加密
向后兼容 DVI
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 (地) │
│ │
└──────────────────────────────────────┘
6.2.2 HDMI 驱动框图 ┌──────────────────────────────────────────┐
│ HDMI 驱动框图 │
├──────────────────────────────────────────┤
│ │
│ VGA 时序生成器 │
│ ├─ HSYNC、VSYNC 生成 │
│ ├─ 像素计数 │
│ └─ 行列计数 │
│ ↓ │
│ ┌──────────────────────────────────┐ │
│ │ TMDS 编码器 │ │
│ │ ├─ 8bit RGB → 10bit TMDS │ │
│ │ ├─ 差分编码 │ │
│ │ └─ 时钟编码 │ │
│ └──────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────┐ │
│ │ 差分驱动器 │ │
│ │ ├─ TMDS_CLK+/- │ │
│ │ ├─ TMDS_D0+/- │ │
│ │ ├─ TMDS_D1+/- │ │
│ │ └─ TMDS_D2+/- │ │
│ └──────────────────────────────────┘ │
│ ↓ │
│ HDMI 连接器 │
│ │
└──────────────────────────────────────────┘
6.3 VGA 时序生成器
6.3.1 VGA 时序生成器 Verilog 实现 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) begin
h_counter <= 0;
end else if (h_counter == H_TOTAL - 1) begin
h_counter <= 0;
end else begin
h_counter <= h_counter + 1;
end
end
// 垂直计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
v_counter <= 0;
end else if (h_counter == H_TOTAL - 1) begin
if (v_counter == V_TOTAL - 1) begin
v_counter <= 0;
end else begin
v_counter <= v_counter + 1;
end
end
end
// HSYNC 生成
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
hsync <= 1'b1;
end else begin
if (h_counter >= (H_ACTIVE + H_FP) && h_counter < (H_ACTIVE + H_FP + H_SYNC)) begin
hsync <= 1'b0;
end else begin
hsync <= 1'b1;
end
end
end
// VSYNC 生成
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
vsync <= 1'b1;
end else begin
if (v_counter >= (V_ACTIVE + V_FP) && v_counter < (V_ACTIVE + V_FP + V_SYNC)) begin
vsync <= 1'b0;
end else begin
vsync <= 1'b1;
end
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) begin
data_valid <= 1'b1;
end else begin
data_valid <= 1'b0;
end
end
end
endmodule
6.4 TMDS 编码
6.4.1 TMDS 编码概述 TMDS(Transition Minimized Differential Signaling) 是 HDMI 的编码方式。
8bit 数据编码为 10bit
最小化信号转换
支持差分传输
抗干扰能力强
6.4.2 TMDS 编码原理 TMDS 编码流程:
8 bit RGB 数据
↓
第一阶段:XOR 编码
├─ 计算转换数
└─ 选择编码方式
↓
第二阶段:直流平衡
├─ 调整输出
└─ 保持直流平衡
↓
10 bit TMDS 数据
↓
差分驱动
├─ TMDS+
└─ TMDS-
6.4.3 简化的 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)) begin
tmds_out <= {~xor_result[8], xor_result[7:0], 1'b0};
end else begin
tmds_out <= {xor_result[8], xor_result[7:0], 1'b1};
end
end
end
endmodule
6.5 HDMI 显示完整流程
6.5.1 HDMI 显示系统框图 ┌──────────────────────────────────────────────┐
│ HDMI 显示系统完整框图 │
├──────────────────────────────────────────────┤
│ │
│ 图像缓存 (SDRAM/DDR3) │
│ ↓ │
│ ┌──────────────────────────────────────┐ │
│ │ 读取控制器 │ │
│ │ ├─ 地址生成 │ │
│ │ ├─ 数据读取 │ │
│ │ └─ 缓冲管理 │ │
│ └──────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────┐ │
│ │ VGA 时序生成器 │ │
│ │ ├─ HSYNC、VSYNC │ │
│ │ ├─ 像素坐标 │ │
│ │ └─ 数据有效信号 │ │
│ └──────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────┐ │
│ │ TMDS 编码器 │ │
│ │ ├─ RGB → TMDS 编码 │ │
│ │ ├─ 时钟编码 │ │
│ │ └─ 控制信号编码 │ │
│ └──────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────┐ │
│ │ 差分驱动器 │ │
│ │ ├─ TMDS_CLK+/- │ │
│ │ ├─ TMDS_D0+/- │ │
│ │ ├─ TMDS_D1+/- │ │
│ │ └─ TMDS_D2+/- │ │
│ └──────────────────────────────────────┘ │
│ ↓ │
│ HDMI 连接器 → 显示器 │
│ │
└──────────────────────────────────────────────┘
6.5.2 HDMI 显示时序 一帧显示的完整时序:
VSYNC: ─┐ ┌─
│ (低电平表示帧有效)
│ └────────────────────────────────────┘
HSYNC: ─┬─────┐ ┬─────┐ ┬─────┐ ┬─────┐ ┬─
│ │ │ │ │ │ │ │ │ └─────┘ └─────┘ └─────┘ └─────┘ └─
RGB: ─┬─────────────────────┬─────────────┬─
│ 有效数据 │ 无效数据 │
└─────────────────────┴─────────────┘
TMDS: ─┬─────────────────────┬─────────────┬─
│ 编码后的数据 │ 编码后的数据│
└─────────────────────┴─────────────┘
七、完整实战案例与最佳实践
7.1 系统集成
7.1.1 顶层模块设计 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
7.1.2 模块间接口 采集模块 → 缓存模块 → 显示模块
采集模块输出:
├─ pixel_data[7:0]: 像素数据
├─ pixel_valid: 数据有效
├─ frame_valid: 帧有效
└─ pixel_x, pixel_y: 像素坐标
缓存模块:
├─ 输入:采集数据
├─ 输出:显示数据
└─ 管理:双缓冲切换
显示模块输入:
├─ rgb_data[15:0]: RGB565 数据
├─ hsync, vsync: 同步信号
└─ data_valid: 数据有效
7.2 调试技巧
7.2.1 常见问题排查 问题 1: 摄像头初始化失败
排查步骤:
1️⃣ 检查电源
├─ 测量 DVDD、AVDD、DOVDD 电压
├─ 检查电源纹波
└─ 确保电源稳定
2️⃣ 检查时钟
├─ 测量 XCLK 频率
├─ 检查时钟占空比
└─ 确保时钟稳定
3️⃣ 检查复位时序
├─ 使用逻辑分析仪
├─ 验证 RESETB、PWDN 时序
└─ 确保时序正确
4️⃣ 检查 SCCB 通信
├─ 使用逻辑分析仪
├─ 验证 SIO_C、SIO_D 波形
└─ 检查从机应答
问题 2: 图像采集无数据
排查步骤:
1️⃣ 检查 DVP 接口
├─ 测量 PCLK 频率
├─ 检查 HREF、VSYNC 波形
└─ 验证数据线连接
2️⃣ 检查采集模块
├─ 仿真验证时序
├─ 检查状态机
└─ 添加调试信号
3️⃣ 使用逻辑分析仪
├─ 捕获完整帧数据
├─ 验证数据有效性
└─ 检查时序关系
问题 3: HDMI 无显示
排查步骤:
1️⃣ 检查 HDMI 连接
├─ 确保连接牢固
├─ 尝试不同显示器
└─ 检查 HDMI 线质量
2️⃣ 检查 VGA 时序
├─ 仿真验证时序
├─ 检查 HSYNC、VSYNC
└─ 验证像素时钟
3️⃣ 检查 TMDS 编码
├─ 使用示波器
├─ 测量差分信号
└─ 检查信号幅度
4️⃣ 检查显示数据
├─ 验证 RGB 数据
├─ 检查数据有效信号
└─ 确保数据连续
7.2.2 调试工具 推荐工具:
├─ 逻辑分析仪
│ ├─ 捕获时序信号
│ ├─ 验证协议
│ └─ 分析数据
│
├─ 示波器
│ ├─ 测量模拟信号
│ ├─ 检查信号质量
│ └─ 验证时序
│
├─ 万用表
│ ├─ 测量电压
│ ├─ 检查连接
│ └─ 验证电源
│
└─ 仿真工具
├─ ModelSim
├─ VCS
└─ Vivado 仿真
7.3 性能优化
7.3.1 带宽优化 优化策略:
1️⃣ 减少不必要的访问
├─ 使用行缓冲
├─ 批量读写
└─ 避免随机访问
2️⃣ 提高缓存命中率
├─ 合理分配缓存
├─ 预取数据
└─ 优化访问模式
3️⃣ 使用高速存储
├─ 优先使用片内 RAM
├─ 使用 DDR3 代替 SDRAM
└─ 增加缓存大小
7.3.2 延迟优化 优化策略:
1️⃣ 流水线设计
├─ 采集、处理、显示并行
├─ 减少阻塞
└─ 提高吞吐量
2️⃣ 时钟优化
├─ 提高时钟频率
├─ 使用多时钟域
└─ 优化时序约束
3️⃣ 算法优化
├─ 简化处理算法
├─ 使用查表法
└─ 并行处理
7.3.3 功耗优化 优化策略:
1️⃣ 时钟门控
├─ 关闭未使用模块
├─ 动态调整频率
└─ 减少时钟树
2️⃣ 电压优化
├─ 降低工作电压
├─ 使用低功耗工艺
└─ 优化电源管理
3️⃣ 存储优化
├─ 减少存储访问
├─ 使用压缩格式
└─ 优化存储结构
总结 本文详细介绍了 FPGA 摄像头采集、处理、显示系统的完整设计流程:
系统架构和核心模块
应用场景和实现方案
设计流程和关键技术点
OV5640 摄像头基础知识
引脚定义和工作原理
SCCB 通信协议
上电时序和复位流程
寄存器配置和参数设置
SCCB 控制器设计
缓存架构选择
双端口 RAM 设计
SDRAM 控制器实现
图像处理基础
VGA 时序基础
HDMI 驱动设计
TMDS 编码原理
显示系统集成
时序是关键:正确的时序是系统稳定运行的基础
模块化设计:独立开发和测试各模块
充分仿真:在 RTL 仿真阶段发现问题
逐步优化:先保证功能,再优化性能
文档完善:记录设计决策和调试经验
多摄像头同步采集
实时图像处理 (滤波、边缘检测等)
视频编码 (H.264/H.265)
网络传输 (RTP/RTSP)
机器学习推理 (目标检测、人脸识别等)
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online