第一次接触FPGA和OV7670摄像头时,我完全被这两个硬件的组合给难住了。FPGA(现场可编程门阵列)就像一块神奇的电子积木,而OV7670则是那个能"看见"世界的眼睛。让我用最直白的方式解释它们的特性:
FPGA本质上是一块可以通过编程来定义硬件功能的芯片。想象你有一盒乐高积木,可以根据需要随时搭建不同的结构——FPGA就是电子世界的乐高。它的最大优势是并行处理能力,这意味着它可以同时处理多个任务,不像普通单片机那样只能串行工作。
OV7670摄像头模块则是性价比极高的图像传感器,最高支持VGA分辨率(640x480)的输出。我实测下来,这款摄像头在室内光线充足的环境下表现相当稳定,但弱光环境下噪点会比较明显。它通过DVP(Digital Video Port)接口输出数据,这个我们后面会详细讲。
为什么选择这个组合? 在图像处理领域,FPGA的并行特性让它特别适合处理摄像头产生的高速数据流。我用STM32尝试过同样的任务,结果发现单片机根本处理不过来这么大量的实时数据。而FPGA可以轻松应对,还能保持很低的延迟。
在开始编程前,你需要准备好这些硬件(这是我实际用过的配置):
连接时最容易出错的就是引脚对应关系。我踩过的坑是:OV7670的PCLK(像素时钟)必须连接到FPGA的专用时钟输入引脚,否则会出现数据不同步的问题。具体连接方式:
code复制OV7670 FPGA
-------------------
3.3V -> 3.3V
GND -> GND
SCL -> 普通IO
SDA -> 普通IO
VSYNC -> 专用时钟引脚
HREF -> 普通IO
PCLK -> 专用时钟引脚
D0-D7 -> 普通IO
特别注意:XCLK需要由FPGA提供25MHz时钟信号。我第一次调试时忘记配置这个时钟,结果摄像头完全不工作。
OV7670使用SCCB(Serial Camera Control Bus)协议进行配置,这个协议和I2C很像但有些关键区别。我用逻辑分析仪抓取的波形显示:
特别注意:SCCB的ACK信号和I2C不同。在写周期结束时,主机必须产生NA(Not Acknowledged)信号,即保持SDA为高电平。
OV7670有201个寄存器,但实际需要配置的约160个。经过多次测试,我发现这几个关键寄存器必须正确配置:
| 寄存器地址 | 配置值 | 功能说明 |
|---|---|---|
| 0x12 | 0x80 | 复位所有寄存器 |
| 0x3A | 0x04 | 设置输出格式为RGB565 |
| 0x40 | 0xD0 | 开启色彩处理管道 |
| 0x11 | 0x0F | 设置内部时钟分频 |
这是我使用的初始化代码片段:
verilog复制// SCCB写操作
task sccb_write;
input [7:0] dev_addr;
input [7:0] reg_addr;
input [7:0] reg_data;
begin
// 启动条件
sda = 1'b1;
scl = 1'b1;
#100 sda = 1'b0;
#100 scl = 1'b0;
// 发送设备地址
send_byte(dev_addr);
// 发送寄存器地址
send_byte(reg_addr);
// 发送数据
send_byte(reg_data);
// 停止条件
scl = 1'b1;
#100 sda = 1'b1;
end
endtask
DVP接口的时序让我调试了整整两天。关键信号包括:
实测波形显示,一帧图像的传输过程是这样的:
OV7670输出的是8位数据,但我们需要16位的RGB565格式。这是我的转换代码:
verilog复制always@(posedge pclk or negedge rst_n) begin
if(!rst_n) begin
byte_cnt <= 0;
rgb_data <= 16'd0;
end else if(href && !vsync) begin
byte_cnt <= byte_cnt + 1;
case(byte_cnt)
0: rgb_data[15:8] <= cam_data;
1: begin
rgb_data[7:0] <= cam_data;
rgb_valid <= 1'b1;
end
default: rgb_valid <= 1'b0;
endcase
end else begin
byte_cnt <= 0;
rgb_valid <= 1'b0;
end
end
常见问题:如果发现图像颜色异常,很可能是字节顺序错了。我遇到过红色和蓝色通道反相的情况,就是因为高低字节顺序弄反了。
SDRAM的时序控制是系统中最复杂的部分。我采用的方案是:
关键状态机设计:
verilog复制case(state)
IDLE: if(wr_req) state <= ACTIVE;
ACTIVE: state <= WRITE;
WRITE: if(bl_cnt == BL_MAX) state <= PRECHARGE;
PRECHARGE: state <= IDLE;
endcase
为了避免图像撕裂,我实现了双缓冲机制:
实际测试中,这个设计可以将显示延迟控制在1帧以内(约33ms)。
VGA的标准时序参数:
我的VGA控制器核心代码:
verilog复制// 行计数器
always@(posedge vga_clk) begin
if(h_cnt == H_TOTAL-1) begin
h_cnt <= 0;
if(v_cnt == V_TOTAL-1)
v_cnt <= 0;
else
v_cnt <= v_cnt + 1;
end else
h_cnt <= h_cnt + 1;
end
// 同步信号生成
assign hsync = (h_cnt < H_SYNC) ? 0 : 1;
assign vsync = (v_cnt < V_SYNC) ? 0 : 1;
assign valid = (h_cnt >= H_BACK) && (h_cnt < H_BACK+H_DISP) &&
(v_cnt >= V_BACK) && (v_cnt < V_BACK+V_DISP);
RGB565格式解析:
在显示时需要注意gamma校正。我通过查找表实现了简单的校正:
verilog复制// Gamma校正LUT
wire [4:0] r_gamma = gamma_lut[rgb_in[15:11]];
wire [5:0] g_gamma = gamma_lut[rgb_in[10:5]];
wire [4:0] b_gamma = gamma_lut[rgb_in[4:0]];
整个系统的数据流是这样的:
顶层模块的主要接口:
verilog复制module top(
input clk_50m,
// 摄像头接口
input cam_vsync,
input cam_href,
input cam_pclk,
input [7:0] cam_data,
// VGA接口
output vga_hsync,
output vga_vsync,
output [15:0] vga_data,
// SDRAM接口
output sdram_clk,
output sdram_cke,
output sdram_cs_n,
output sdram_ras_n,
output sdram_cas_n,
output sdram_we_n,
output [1:0] sdram_ba,
output [12:0] sdram_addr,
inout [15:0] sdram_data
);
我在调试过程中遇到的主要问题及解决方法:
经过多次迭代,我发现这些优化能显著提升系统性能:
实测优化后的系统性能:
这个项目让我深刻体会到FPGA在实时图像处理中的优势。虽然初期调试比较困难,但一旦调通,系统的稳定性和性能都令人满意。建议初学者可以从降低分辨率开始(比如QVGA),等基本功能实现后再提升到VGA分辨率。