跳到主要内容FPGA 实现 OV5640 摄像头视频图像显示 | 极客日志编程语言
FPGA 实现 OV5640 摄像头视频图像显示
基于 FPGA 使用 Verilog 实现 OV5640 摄像头视频采集与 VGA 显示的系统设计。核心模块包含 SCCB 控制器用于寄存器初始化,DVP 接口负责数据采集,RAM 进行帧缓存,VGA 控制器驱动显示屏。系统支持 640x480 分辨率输入及 RGB565 格式输出,通过状态机控制初始化流程。实际显示区域为 350x218,存在轻微画面撕裂问题,建议采用乒乓缓存优化。
云间漫步0 浏览 一、工程介绍
OV5640 摄像头通过 DVP 接口输出视频图像数据,并通过 VGA 接口输出给显示器。FPGA 需要完成的功能包括:OV5640 初始化、DVP 接口数据采集、图像数据缓存、VGA 数据输出。模块设计也相应按照这四个部分进行划分。
二、Verilog 实现
(1)OV5640 初始化
(1.1)SCCB 控制器
ov5640 摄像头初始化需要向其内部配置寄存器写入数据进行配置,实现对图像数据格式、图像大小、图像反转镜像、曝光、补偿等设置。对 ov5640 寄存器的读写操作需要遵循 SCCB 通信协议,不过 SCCB 与 I2C 协议很相似,SCCB 可以兼容 I2C,只是时序细节有区别,不过我工程中还是单独设计了一个 SCCB 控制器。
`timescale 1ns / 1ps // 适用于 ov5640 SCCB 通信 寄存器(字节)地址 16 位,数据 8 位
module SCCB_ctrl(
input wire clk, //系统时钟 100MHz
input wire rst_n, //复位
inout wire sda, //双向数据线(inout)
output wire scl, //输出时钟线
input wire rw_ctrl, //读写使能信号(0 写 1 读)
input wire work_start, //SCCB 启动信号
input wire [6:0] slave_addr,//7bit 从设备地址
input wire [15:0] byte_addr,//16bit 字地址
input wire [7:0] w_data, //8bit 待写数据
output reg [7:0] r_data, //8bit 读取数据
output reg work_done //SCCB 读写完成信号
);
//sda 传输方向控制
reg sda_oe; //sda 输出使能,为 1 表示 sda 作输出
reg sda_out; //sda 输出信号线
wire sda_in; //sda 输入寄存器
assign sda_in = sda;
assign sda = sda_oe ? (sda_out ? 1'bz : 1'b0) : 1'bz;
//状态机参数
reg [4:0] state;
localparam IDLE = 5'd0,
START = 5'd1,
W_SLAVE_ADDR = 5'd2,
ACK1 = 5'd3,
W_H_BYTE_ADDR = 5'd4,
ACK2 = 5'd5,
W_L_BYTE_ADDR = 5'd6,
ACK3 = 5'd7,
STOP = 5'd8,
W_DATA = 5'd9,
W_ACK = 5'd10,
STOP2 = 5'd11,
START2 = 5'd12,
R_SLAVE_ADDR = 5'd13,
R_ACK = 5'd14,
R_DATA = 5'd15,
N_ACK = 5'd16;
//计数器及参数
reg clk_div;
reg [7:0] cnt_clk;
reg [3:0] cnt_bit;
localparam cnt_max_400khz = 8'd125;
wire scl_half_1;
wire scl_half_0;
wire scl_ack_jump;
assign scl_half_1 = (cnt_clk == cnt_max_400khz >> 1 && clk_div==1'b1);
assign scl_half_0 = (cnt_clk == cnt_max_400khz >> 1 && clk_div==1'b0);
assign scl_ack_jump=((cnt_clk ==(cnt_max_400khz >> 1)-5) && clk_div==1'b0);
//数据寄存器
reg [7:0] w_data_buf;
reg [7:0] r_data_buf;
reg [7:0] w_slave_addr_buf;
reg [7:0] r_slave_addr_buf;
reg [7:0] H_byte_addr_buf;
reg [7:0] L_byte_addr_buf;
reg work_en;
//*************************************** MAIN CODE ***************************************//
always @(posedge clk or negedge rst_n)
if (!rst_n) begin
w_slave_addr_buf <= 8'b0000_0000;
r_slave_addr_buf <= 8'b0000_0001;
H_byte_addr_buf <= 8'b0;
L_byte_addr_buf <= 8'b0;
w_data_buf <= 8'b0;
end else if (work_start) begin
w_slave_addr_buf [7:1] <= slave_addr;
r_slave_addr_buf [7:1] <= slave_addr;
w_data_buf <= w_data;
H_byte_addr_buf <= byte_addr[15:8];
L_byte_addr_buf <= byte_addr[7:0];
end
always @(posedge clk or negedge rst_n)
if (!work_en || !rst_n) begin
cnt_clk <= 8'd1;
clk_div <= 1'b1;
end else if (cnt_clk == cnt_max_400khz) begin
cnt_clk <= 8'd1;
clk_div <= ~clk_div;
end else cnt_clk <= cnt_clk + 8'd1;
assign scl = clk_div;
always @(posedge clk or negedge rst_n)
if (!rst_n) begin
state <= IDLE;
sda_oe <= 1'b0;
sda_out <= 1'b1;
work_en <= 1'b0;
work_done <= 1'b0;
cnt_bit <= 4'd0;
end else case(state)
IDLE: begin
sda_oe <= 1'b0;
sda_out <= 1'b1;
work_done <= 1'b0;
if (work_start) begin
work_en <= 1'b1;
state <= START;
end
end
START: begin
sda_oe <= 1'b1;
if (scl_half_1) begin
sda_out <= 1'b0;
state <= W_SLAVE_ADDR;
end
end
W_SLAVE_ADDR: begin
sda_oe <= 1'b1;
if (scl_half_0) begin
if (cnt_bit != 4'd8) begin
sda_out <= w_slave_addr_buf[7-cnt_bit];
cnt_bit <= cnt_bit + 4'd1;
end else begin
state <= ACK1;
cnt_bit <= 4'd0;
end
end
end
ACK1: begin
sda_oe <= 1'b0;
if (scl_ack_jump) state <= W_H_BYTE_ADDR;
end
W_H_BYTE_ADDR: begin
sda_oe <= 1'b1;
if (scl_half_0) begin
if (cnt_bit != 4'd8) begin
sda_out <= H_byte_addr_buf[7-cnt_bit];
cnt_bit <= cnt_bit + 4'd1;
end else begin
state <= ACK2;
cnt_bit <= 4'd0;
end
end
end
ACK2: begin
sda_oe <= 1'b0;
if (scl_ack_jump) state <= W_L_BYTE_ADDR;
end
W_L_BYTE_ADDR: begin
sda_oe <= 1'b1;
if (scl_half_0) begin
if (cnt_bit != 4'd8) begin
sda_out <= L_byte_addr_buf[7-cnt_bit];
cnt_bit <= cnt_bit + 4'd1;
end else begin
state <= ACK3;
cnt_bit <= 4'd0;
end
end
end
ACK3: begin
sda_oe <= 1'b0;
if (scl_ack_jump) begin
state <= rw_ctrl ? STOP2 : W_DATA;
sda_out <= rw_ctrl ? 1'b0 : sda_out;
end
end
STOP: begin
sda_oe <= 1'b1;
if (scl_half_1) begin
sda_out <= 1'b1;
work_done <= 1'b1;
work_en <= 1'b0;
state <= IDLE;
end
end
W_DATA: begin
sda_oe <= 1'b1;
if (scl_half_0) begin
if (cnt_bit != 4'd8) begin
sda_out <= w_data_buf[7-cnt_bit];
cnt_bit <= cnt_bit + 4'd1;
end else begin
state <= W_ACK;
cnt_bit <= 4'd0;
end
end
end
W_ACK: begin
sda_oe <= 1'b0;
if (scl_ack_jump) begin
sda_out <= 1'b0;
state <= STOP;
end
end
STOP2: begin
sda_oe <= 1'b1;
if (scl_half_1) begin
sda_out <= 1'b1;
state <= START2;
end
end
START2: begin
sda_oe <= 1'b1;
if (scl_half_1) begin
sda_out <= 1'b0;
state <= R_SLAVE_ADDR;
end
end
R_SLAVE_ADDR: begin
sda_oe <= 1'b1;
if (scl_half_0) begin
if (cnt_bit != 4'd8) begin
sda_out <= r_slave_addr_buf[7-cnt_bit];
cnt_bit <= cnt_bit + 4'd1;
end else begin
state <= R_ACK;
cnt_bit <= 4'd0;
end
end
end
R_ACK: begin
sda_oe <= 1'b0;
if (scl_ack_jump) state <= R_DATA;
end
R_DATA: begin
sda_oe <= 1'b0;
if (scl_half_1 && cnt_bit!=4'd8) begin
r_data_buf[7-cnt_bit] <= sda_in;
cnt_bit <= cnt_bit + 4'd1;
end
if (scl_ack_jump && cnt_bit==4'd8) begin
state <= N_ACK;
cnt_bit <= 4'd0;
r_data <= r_data_buf;
end
end
N_ACK: begin
sda_oe <= 1'b1;
if (scl_half_0) sda_out <= 1'b1;
if (scl_ack_jump) begin
sda_out <= 1'b0;
state <= STOP;
end
end
default: state <= IDLE;
endcase
end
endmodule
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- 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
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online
(1.2)ov5640 初始化数据表
有了 SCCB 控制器后,可以实现对 ov5640 寄存器的读写操作了,接下来需要关心如何配置的问题,也就是具体向什么寄存器写什么数据,具体参考 ov5640 的用户手册。
下面展示的为 ov5640 摄像头初始化模代码块,用于配置摄像头寄存器以实现分辨率 640x480 30fps(支持图像大小参数设置)、翻转镜像(支持参数控制)、60fps、RGB565 格式的图像输出。模块通过状态机控制初始化流程,依次完成复位、延时、寄存器配置操作。初始化数据存储在 data 数组中,该模块配合 SCCB 控制器模块可以依次完成 data 中所有数据的写入。初始化完成后输出 initial_done 信号。
//ov5640 寄存器初始化配置参数 : 分辨率 640*480 30fps RGB565 格式
`timescale 1ns / 1ps
module ov5640_initial_table#(
parameter IMAGE_WIDTH = 16'd640,
parameter IMAGE_HEIGHT = 16'd480,
parameter IMAGE_FLIP_EN = 1'b0,
parameter IMAGE_MIRROR_EN = 1'b0
)(
input wire clk,
input wire rst_n,
input wire initial_start,
input wire work_done,
output reg work_start,
output reg [23:0] initial_data,
output reg initial_done
);
localparam DATA_SIZE = 9'd257;
localparam DELAY_5ms = 20'd500_000;
localparam IMAGE_FLIP_DAT = IMAGE_FLIP_EN ? 8'h47 : 8'h41,
IMAGE_MIRROR_DAT = IMAGE_MIRROR_EN ? 8'h01 : 8'h07;
localparam IDLE = 2'd0,
RESET = 2'd1,
DELAY = 2'd2,
DATA = 2'd3;
reg [23:0] data [DATA_SIZE-1:0];
reg [19:0] delay_cnt;
reg [8:0] cnt;
reg [1:0] state;
always @(posedge clk or negedge rst_n)
if (!rst_n) begin
state <= IDLE;
initial_data <= data[0];
initial_done <= 1'b0;
work_start <= 1'b0;
cnt <= 9'd0;
delay_cnt <= 20'd0;
end else case (state)
IDLE: begin
if (initial_start) begin
initial_done <= 1'b0;
initial_data <= data[cnt];
work_start <= 1'b1;
cnt <= cnt + 9'd1;
end else if (work_done) begin
state <= RESET;
initial_data <= data[cnt];
work_start <= 1'b1;
cnt <= cnt + 9'd1;
end else work_start <= 1'b0;
end
RESET: begin
if (work_done) state <= DELAY;
else work_start <= 1'b0;
end
DELAY: begin
if (delay_cnt == DELAY_5ms) begin
delay_cnt <= 20'd0;
state <= DATA;
initial_data <= data[cnt];
work_start <= 1'b1;
cnt <= cnt + 9'd1;
end else begin
delay_cnt <= delay_cnt + 20'd1;
work_start <= 1'b0;
end
end
DATA: begin
if (work_done) begin
if (cnt <= DATA_SIZE-1) begin
initial_data <= data[cnt];
work_start <= 1'b1;
cnt <= cnt + 9'd1;
end else begin
initial_done <= 1'b1;
initial_data <= data[0];
cnt <= 9'd0;
state <= IDLE;
end
end else work_start <= 1'b0;
end
default:;
endcase
end
initial begin
data[0] = 24'h3103_11;
data[1] = 24'h3008_82;
data[2] = 24'h3008_42;
data[3] = 24'h3103_03;
data[4] = 24'h3017_ff;
data[5] = 24'h3018_ff;
data[6] = 24'h3034_1a;
data[7] = 24'h3037_13;
data[8] = 24'h3108_01;
data[9] = 24'h3630_36;
data[10] = 24'h3631_0e;
data[11] = 24'h3632_e2;
data[12] = 24'h3633_12;
data[13] = 24'h3621_e0;
data[14] = 24'h3704_a0;
data[15] = 24'h3703_5a;
data[16] = 24'h3715_78;
data[17] = 24'h3717_01;
data[18] = 24'h370b_60;
data[19] = 24'h3705_1a;
data[20] = 24'h3905_02;
data[21] = 24'h3906_10;
data[22] = 24'h3901_0a;
data[23] = 24'h3731_12;
data[24] = 24'h3600_08;
data[25] = 24'h3601_33;
data[26] = 24'h302d_60;
data[27] = 24'h3620_52;
data[28] = 24'h371b_20;
data[29] = 24'h471c_50;
data[30] = 24'h3a13_43;
data[31] = 24'h3a18_00;
data[32] = 24'h3a19_f8;
data[33] = 24'h3635_13;
data[34] = 24'h3636_03;
data[35] = 24'h3634_40;
data[36] = 24'h3622_01;
data[37] = 24'h3c01_34;
data[38] = 24'h3c04_28;
data[39] = 24'h3c05_98;
data[40] = 24'h3c06_00;
data[41] = 24'h3c07_08;
data[42] = 24'h3c08_00;
data[43] = 24'h3c09_1c;
data[44] = 24'h3c0a_9c;
data[45] = 24'h3c0b_40;
data[46] = 24'h3810_00;
data[47] = 24'h3811_10;
data[48] = 24'h3812_00;
data[49] = 24'h3708_64;
data[50] = 24'h4001_02;
data[51] = 24'h4005_1a;
data[52] = 24'h3000_00;
data[53] = 24'h3004_ff;
data[54] = 24'h300e_58;
data[55] = 24'h302e_00;
data[56] = 24'h4300_61;
data[57] = 24'h501f_01;
data[58] = 24'h440e_00;
data[59] = 24'h5000_a7;
data[60] = 24'h3a0f_30;
data[61] = 24'h3a10_28;
data[62] = 24'h3a1b_30;
data[63] = 24'h3a1e_26;
data[64] = 24'h3a11_60;
data[65] = 24'h3a1f_14;
data[66] = 24'h5800_23;
data[67] = 24'h5801_14;
data[68] = 24'h5802_0f;
data[69] = 24'h5803_0f;
data[70] = 24'h5804_12;
data[71] = 24'h5805_26;
data[72] = 24'h5806_0c;
data[73] = 24'h5807_08;
data[74] = 24'h5808_05;
data[75] = 24'h5809_05;
data[76] = 24'h580a_08;
data[77] = 24'h580b_0d;
data[78] = 24'h580c_08;
data[79] = 24'h580d_03;
data[80] = 24'h580e_00;
data[81] = 24'h580f_00;
data[82] = 24'h5810_03;
data[83] = 24'h5811_09;
data[84] = 24'h5812_07;
data[85] = 24'h5813_03;
data[86] = 24'h5814_00;
data[87] = 24'h5815_01;
data[88] = 24'h5816_03;
data[89] = 24'h5817_08;
data[90] = 24'h5818_0d;
data[91] = 24'h5819_08;
data[92] = 24'h581a_05;
data[93] = 24'h581b_06;
data[94] = 24'h581c_08;
data[95] = 24'h581d_0e;
data[96] = 24'h581e_29;
data[97] = 24'h581f_17;
data[98] = 24'h5820_11;
data[99] = 24'h5821_11;
data[100] = 24'h5822_15;
data[101] = 24'h5823_28;
data[102] = 24'h5824_46;
data[103] = 24'h5825_26;
data[104] = 24'h5826_08;
data[105] = 24'h5827_26;
data[106] = 24'h5828_64;
data[107] = 24'h5829_26;
data[108] = 24'h582a_24;
data[109] = 24'h582b_22;
data[110] = 24'h582c_24;
data[111] = 24'h582d_24;
data[112] = 24'h582e_06;
data[113] = 24'h582f_22;
data[114] = 24'h5830_40;
data[115] = 24'h5831_42;
data[116] = 24'h5832_24;
data[117] = 24'h5833_26;
data[118] = 24'h5834_24;
data[119] = 24'h5835_22;
data[120] = 24'h5836_22;
data[121] = 24'h5837_26;
data[122] = 24'h5838_44;
data[123] = 24'h5839_24;
data[124] = 24'h583a_26;
data[125] = 24'h583b_28;
data[126] = 24'h583c_42;
data[127] = 24'h583d_ce;
data[128] = 24'h5180_ff;
data[129] = 24'h5181_f2;
data[130] = 24'h5182_00;
data[131] = 24'h5183_14;
data[132] = 24'h5184_25;
data[133] = 24'h5185_24;
data[134] = 24'h5186_09;
data[135] = 24'h5187_09;
data[136] = 24'h5188_09;
data[137] = 24'h5189_75;
data[138] = 24'h518a_54;
data[139] = 24'h518b_e0;
data[140] = 24'h518c_b2;
data[141] = 24'h518d_42;
data[142] = 24'h518e_3d;
data[143] = 24'h518f_56;
data[144] = 24'h5190_46;
data[145] = 24'h5191_f8;
data[146] = 24'h5192_04;
data[147] = 24'h5193_70;
data[148] = 24'h5194_f0;
data[149] = 24'h5195_f0;
data[150] = 24'h5196_03;
data[151] = 24'h5197_01;
data[152] = 24'h5198_04;
data[153] = 24'h5199_12;
data[154] = 24'h519a_04;
data[155] = 24'h519b_00;
data[156] = 24'h519c_06;
data[157] = 24'h519d_82;
data[158] = 24'h519e_38;
data[159] = 24'h5480_01;
data[160] = 24'h5481_08;
data[161] = 24'h5482_14;
data[162] = 24'h5483_28;
data[163] = 24'h5484_51;
data[164] = 24'h5485_65;
data[165] = 24'h5486_71;
data[166] = 24'h5487_7d;
data[167] = 24'h5488_87;
data[168] = 24'h5489_91;
data[169] = 24'h548a_9a;
data[170] = 24'h548b_aa;
data[171] = 24'h548c_b8;
data[172] = 24'h548d_cd;
data[173] = 24'h548e_dd;
data[174] = 24'h548f_ea;
data[175] = 24'h5490_1d;
data[176] = 24'h5381_1e;
data[177] = 24'h5382_5b;
data[178] = 24'h5383_08;
data[179] = 24'h5384_0a;
data[180] = 24'h5385_7e;
data[181] = 24'h5386_88;
data[182] = 24'h5387_7c;
data[183] = 24'h5388_6c;
data[184] = 24'h5389_10;
data[185] = 24'h538a_01;
data[186] = 24'h538b_98;
data[187] = 24'h5580_06;
data[188] = 24'h5583_40;
data[189] = 24'h5584_10;
data[190] = 24'h5589_10;
data[191] = 24'h558a_00;
data[192] = 24'h558b_f8;
data[193] = 24'h501d_40;
data[194] = 24'h5300_08;
data[195] = 24'h5301_30;
data[196] = 24'h5302_10;
data[197] = 24'h5303_00;
data[198] = 24'h5304_08;
data[199] = 24'h5305_30;
data[200] = 24'h5306_08;
data[201] = 24'h5307_16;
data[202] = 24'h5309_08;
data[203] = 24'h530a_30;
data[204] = 24'h530b_04;
data[205] = 24'h530c_06;
data[206] = 24'h5025_00;
data[207] = 24'h3008_02;
data[208] = 24'h3035_41;
data[209] = 24'h3036_72;
data[210] = 24'h3c07_08;
data[211] = {16'h3820, IMAGE_FLIP_DAT};
data[212] = {16'h3821, IMAGE_MIRROR_DAT};
data[213] = 24'h3814_31;
data[214] = 24'h3815_31;
data[215] = 24'h3800_00;
data[216] = 24'h3801_00;
data[217] = 24'h3802_00;
data[218] = 24'h3803_be;
data[219] = 24'h3804_0a;
data[220] = 24'h3805_3f;
data[221] = 24'h3806_06;
data[222] = 24'h3807_e4;
data[223] = {16'h3808, IMAGE_WIDTH [15:8]};
data[224] = {16'h3809, IMAGE_WIDTH [7:0] };
data[225] = {16'h380a, IMAGE_HEIGHT[15:8]};
data[226] = {16'h380b, IMAGE_HEIGHT[7:0] };
data[227] = 24'h380c_07;
data[228] = 24'h380d_69;
data[229] = 24'h380e_03;
data[230] = 24'h380f_21;
data[231] = 24'h3813_06;
data[232] = 24'h3618_00;
data[233] = 24'h3612_29;
data[234] = 24'h3709_52;
data[235] = 24'h370c_03;
data[236] = 24'h3a02_09;
data[237] = 24'h3a03_63;
data[238] = 24'h3a14_09;
data[239] = 24'h3a15_63;
data[240] = 24'h4004_02;
data[241] = 24'h3002_1c;
data[242] = 24'h3006_c3;
data[243] = 24'h4713_03;
data[244] = 24'h4407_04;
data[245] = 24'h460b_35;
data[246] = 24'h460c_22;
data[247] = 24'h4837_22;
data[248] = 24'h3824_02;
data[249] = 24'h5001_a3;
data[250] = 24'h3503_00;
data[251] = {16'h3821, IMAGE_MIRROR_DAT};
data[252] = 24'h3035_21;
data[253] = 24'h3a02_12;
data[254] = 24'h3a03_c6;
data[255] = 24'h3a14_12;
data[256] = 24'h3a15_c6;
end
endmodule
(2)DVP 数据采集
配置完寄存器后,摄像头会开始输出图像数据,对于 ov5640 来说一般是通过 DVP 接口进行数据接收采集。这一部分需要添加一个地址输出端口,因为图像数据需要依次存入 RAM。
`timescale 1ns / 1ps
module DVP_ctrl#(
parameter PIC_CNT_MAX = 8'd10
)(
input wire rst_n,
input wire ov5640_pclk,
input wire ov5640_href,
input wire ov5640_vsync,
input wire [7:0] ov5640_data,
output reg [15:0] RGB565_data,
output wire data_valid,
output reg [16:0] w_addr
);
reg pix_flag;
wire pic_flag;
reg pic_valid;
reg [7:0] pic_cnt;
reg [7:0] r_ov5640_data;
reg ov5640_vsync_delay;
reg pix_flag_delay;
always@(posedge ov5640_pclk or negedge rst_n)
if(rst_n == 1'b0) ov5640_vsync_delay <= 1'b0;
else ov5640_vsync_delay <= ov5640_vsync;
assign pic_flag = ((ov5640_vsync_delay == 1'b0) && (ov5640_vsync == 1'b1)) ? 1'b1 : 1'b0;
always @(posedge ov5640_pclk or negedge rst_n)
if (!rst_n) begin
pic_cnt <= 8'd0;
pic_valid <= 1'b0;
end else if (pic_flag) begin
if (pic_cnt == PIC_CNT_MAX) begin
pic_cnt <= 8'd0;
pic_valid <= 1'b1;
end else pic_cnt <= pic_cnt + 8'd1;
end
always @(posedge ov5640_pclk or negedge rst_n)
if (!rst_n) begin
pix_flag <= 1'b0;
r_ov5640_data <= 8'b0;
RGB565_data <= 8'b0;
end else if (ov5640_href) begin
if (!pix_flag) begin
r_ov5640_data <= ov5640_data;
pix_flag <= 1'b1;
end else begin
RGB565_data <= {r_ov5640_data , ov5640_data};
pix_flag <= 1'b0;
end
end
always @(posedge ov5640_pclk or negedge rst_n)
if (!rst_n) w_addr <= 17'h0;
else if (pic_flag) w_addr <= 17'h0;
else if (ov5640_href && pic_valid) if (pix_flag) w_addr <= w_addr + 17'h1;
always@(posedge ov5640_pclk or negedge rst_n)
if(rst_n == 1'b0) pix_flag_delay <= 1'b0;
else pix_flag_delay <= pix_flag;
assign data_valid = pic_valid & pix_flag_delay;
endmodule
(3)RAM 数据缓存
- 速率匹配:摄像头输出速率与显示或处理速率可能不一致,缓存可平衡两者间的速度差异。
- 数据完整性:缓存确保数据在传输过程中不会丢失或损坏,尤其是在异步系统中。
- 帧同步:缓存可以实现帧的完整存储,避免显示或处理时出现撕裂或不完整帧。
- 提高效率:还可以通过乒乓操作或双缓冲技术,缓存可以实现读写并行,提高系统吞吐量。
该工程使用 FPGA 上的 block ram 资源进行缓存。最终要在屏幕上显示的图像的像素大小为 350218,数据格式为 RGB565(16 位),因此需要 RAM 数据位宽 16 位,深度至少为 350218 = 76,300 才能存下一帧数据保证画面完全显示。
(4)VGA 控制器
完成数据采集和缓存后只需要将数据从 RAM 读出来,通过 VGA 控制器输出给显示屏即可。
`timescale 1ns / 1ps
module VGA_ctrl#(
parameter DISP_WIDTH = 12'd256,
parameter DISP_HEIGTH = 12'd160
)(
input wire clk,
input wire rst_n,
input wire [15:0]data_in,
output wire hsync,
output wire vsync,
output reg [15:0]RGB,
output reg [16:0]data_addr
);
reg [11:0] h_cnt;
reg [11:0] v_cnt;
wire data_valid;
wire [11:0] pix_x;
wire [11:0] pix_y;
wire disp_area;
localparam H_SYNC = 12'd96,
H_BACK = 12'd40,
H_LEFT = 12'd8,
H_DATA = 12'd640,
H_RIGHT = 12'd8,
H_FRONT = 12'd8,
H_TOTAL = H_SYNC + H_BACK + H_LEFT + H_DATA + H_RIGHT + H_FRONT,
V_SYNC = 12'd2,
V_BACK = 12'd25,
V_LEFT = 12'd8,
V_DATA = 12'd480,
V_RIGHT = 12'd8,
V_FRONT = 12'd2,
V_TOTAL = V_SYNC + V_BACK + V_LEFT + V_DATA + V_RIGHT + V_FRONT;
localparam RED = 16'hF800,
GREEN = 16'h07E0,
BLUE = 16'h001F,
BLACK = 16'h0000,
WHITE = 16'hFFFF;
always @(posedge clk or negedge rst_n)
if (!rst_n) h_cnt <= 12'd0;
else if (h_cnt == H_TOTAL-1) h_cnt <= 12'd0;
else h_cnt <= h_cnt + 12'd1;
always @(posedge clk or negedge rst_n)
if (!rst_n) v_cnt <= 12'd0;
else if (v_cnt == V_TOTAL-1) v_cnt <= 12'd0;
else if (h_cnt == H_TOTAL-1) v_cnt <= v_cnt + 12'd1;
assign hsync = (h_cnt < H_SYNC) ? 1'b0 : 1'b1;
assign vsync = (v_cnt < V_SYNC) ? 1'b0 : 1'b1;
assign data_valid = (h_cnt >= H_SYNC + H_BACK + H_LEFT -1) && (v_cnt >= V_SYNC + V_BACK + V_LEFT -1) && (h_cnt <= H_SYNC + H_BACK + H_LEFT + H_DATA -1) && (v_cnt <= V_SYNC + V_BACK + V_LEFT + V_DATA -1);
assign pix_x = (data_valid) ? (h_cnt - (H_SYNC + H_BACK + H_LEFT -1)) : 12'hFFF;
assign pix_y = (data_valid) ? (v_cnt - (V_SYNC + V_BACK + V_LEFT -1)) : 12'hFFF;
assign disp_area = (pix_x >= (H_DATA - DISP_WIDTH )/2 && pix_x < (H_DATA + DISP_WIDTH )/2 && pix_y >= (V_DATA - DISP_HEIGTH)/2 && pix_y < (V_DATA + DISP_HEIGTH)/2 );
always @(posedge clk or negedge rst_n)
if (!rst_n) RGB <= BLACK;
else if (disp_area) RGB <= data_in;
else RGB <= BLACK;
always @(posedge clk or negedge rst_n)
if (!rst_n) data_addr <= 17'h0;
else if (v_cnt == V_TOTAL-1) data_addr <= 17'h0;
else if (disp_area) data_addr <= data_addr + 17'h1;
endmodule
(5)顶层模块
最后是顶层模块,除去上述介绍到的模块,还有一个按键消抖模块,用于按钮控制摄像头初始化,以及一个时钟 IP 模块,用于生成准确的 VGA 驱动时钟。
`timescale 1ns / 1ps
module TOP(
input wire sys_clk,
input wire ov5640_pclk,
input wire ov5640_vsync,
input wire ov5640_href,
input wire [7:0] ov5640_data,
input wire rst_n,
input wire start,
inout wire sda,
output wire scl,
output wire initial_done,
output wire hsync,
output wire vsync,
output wire[3:0] red,
output wire[3:0] green,
output wire[3:0] blue
);
wire initial_start;
wire work_start;
wire work_done;
wire [23:0] initial_data;
wire VGA_clk;
wire ram_w_en;
wire [16:0] ram_w_addr;
wire [15:0] ram_w_data;
wire [16:0] ram_r_addr;
wire [15:0] ram_r_data;
wire [15:0] RGB;
assign red = RGB[15:12];
assign green = RGB[10:7];
assign blue = RGB[4:1];
ov5640_initial_table#(
.IMAGE_WIDTH (16'd350),
.IMAGE_HEIGHT (16'd218)
)ov5640_initial_table(
.clk (sys_clk),
.rst_n (rst_n),
.initial_start (initial_start),
.work_done (work_done),
.work_start (work_start),
.initial_data (initial_data),
.initial_done (initial_done)
);
SCCB_ctrl SCCB_ctrl(
.clk (sys_clk),
.rst_n (rst_n),
.sda (sda),
.scl (scl),
.work_start (work_start),
.rw_ctrl (1'b0),
.slave_addr (7'b0111_100),
.byte_addr (initial_data[23:8]),
.w_data (initial_data[7:0]),
.r_data (),
.work_done (work_done)
);
DVP_ctrl #(
.PIC_CNT_MAX (8'd10)
) DVP_ctrl (
.ov5640_pclk (ov5640_pclk),
.rst_n (rst_n),
.ov5640_vsync (ov5640_vsync),
.ov5640_href (ov5640_href),
.ov5640_data (ov5640_data),
.data_valid (ram_w_en),
.w_addr (ram_w_addr),
.RGB565_data (ram_w_data)
);
VGA_ctrl#(
.DISP_WIDTH (12'd350),
.DISP_HEIGTH (12'd218)
) VGA_ctrl(
.clk (VGA_clk),
.rst_n (rst_n),
.data_addr (ram_r_addr),
.data_in (ram_r_data),
.hsync (hsync),
.vsync (vsync),
.RGB (RGB)
);
key_filter key_filter(
.i_clk (sys_clk),
.i_rstn (rst_n),
.i_key (start),
.ok (initial_start)
);
VGA_clk_gen VGA_clk_gen(
.sys_clk (sys_clk),
.VGA_clk (VGA_clk)
);
RAM RAM (
.clka (ov5640_pclk),
.wea (ram_w_en),
.addra (ram_w_addr),
.dina (ram_w_data),
.clkb (VGA_clk),
.addrb (ram_r_addr),
.doutb (ram_r_data)
);
endmodule
三、效果演示
VGA 以 640480@60Hz 驱动显示屏,在中心 350218 区域进行视频显示。实测降低 ov5640 输出视频帧数也能解决画面问题。此外右侧画面被部分切割到左侧,估计是帧同步没做好,所以仍有不足,有时间再继续优化。