프로젝트 개요
OV5640 카메라 모듈은 DVP 인터페이스를 통해 비디오 데이터를 전송하며, 이 데이터를 FPGA에서 처리하여 VGA 인터페이스로 디스플레이에 출력하는 시스템을 구현한다. 주요 기능은 다음과 같다:
- OV5640 초기화 설정
- DVP 인터페이스를 통한 이미지 데이터 수집
- 수집된 이미지 데이터의 메모리 버퍼링
- VGA 인터페이스를 통한 디스플레이 출력
Verilog 구현
OV5640 초기화
SCCB 통신 컨트롤러
OV5640 카메라는 SCCB(Serial Camera Control Bus) 프로토콜을 사용하여 내부 레지스터를 구성한다. SCCB는 I2C와 유사하지만 약간의 타이밍 차이가 있으므로 별도의 컨트롤러를 설계하였다.
`timescale 1ns / 1ps
module SCCB_controller(
input wire clk,
input wire rst_n,
inout wire sda,
output wire scl,
input wire read_write,
input wire start_operation,
input wire [6:0] device_address,
input wire [15:0] register_address,
input wire [7:0] write_data,
output reg [7:0] read_data,
output reg operation_complete
);
reg sda_output_enable;
reg sda_output_value;
wire sda_input_value;
assign sda_input_value = sda;
assign sda = sda_output_enable ? (sda_output_value ? 1'bz : 1'b0) : 1'bz;
// 상태 머신 정의
reg [4:0] current_state;
localparam IDLE_STATE = 5'd0,
START_BIT = 5'd1,
WRITE_DEVICE_ADDR = 5'd2,
ACK_RECEIVE = 5'd3,
WRITE_HIGH_ADDR = 5'd4,
ACK2_RECEIVE = 5'd5,
WRITE_LOW_ADDR = 5'd6,
ACK3_RECEIVE = 5'd7,
STOP_BIT = 5'd8,
WRITE_DATA_STATE = 5'd9,
WRITE_ACK = 5'd10,
INTERMEDIATE_STOP = 5'd11,
RESTART_BIT = 5'd12,
READ_DEVICE_ADDR = 5'd13,
READ_ACK = 5'd14,
READ_DATA_STATE = 5'd15,
NO_ACK = 5'd16;
// 클럭 분주 및 카운터
reg clock_divider;
reg [7:0] clock_counter;
reg [3:0] bit_counter;
localparam CLOCK_DIV_VALUE = 8'd125;
wire half_clock_high = (clock_counter == CLOCK_DIV_VALUE >> 1 && clock_divider == 1'b1);
wire half_clock_low = (clock_counter == CLOCK_DIV_VALUE >> 1 && clock_divider == 1'b0);
wire ack_transition = ((clock_counter == (CLOCK_DIV_VALUE >> 1) - 5) && clock_divider == 1'b0);
// 데이터 레지스터
reg [7:0] tx_data_buffer;
reg [7:0] rx_data_buffer;
reg [7:0] device_addr_write;
reg [7:0] device_addr_read;
reg [7:0] high_byte_addr;
reg [7:0] low_byte_addr;
reg enable_operation;
// 데이터 준비
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
device_addr_write <= 8'b0;
device_addr_read <= 8'b1;
high_byte_addr <= 8'b0;
low_byte_addr <= 8'b0;
tx_data_buffer <= 8'b0;
end else if (start_operation) begin
device_addr_write[7:1] <= device_address;
device_addr_read[7:1] <= device_address;
tx_data_buffer <= write_data;
high_byte_addr <= register_address[15:8];
low_byte_addr <= register_address[7:0];
end
end
// 클럭 생성
always @(posedge clk or negedge rst_n) begin
if (!enable_operation || !rst_n) begin
clock_counter <= 8'd1;
clock_divider <= 1'b1;
end else if (clock_counter == CLOCK_DIV_VALUE) begin
clock_counter <= 8'd1;
clock_divider <= ~clock_divider;
end else
clock_counter <= clock_counter + 8'd1;
end
assign scl = clock_divider;
// 상태 머신 로직
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= IDLE_STATE;
sda_output_enable <= 1'b0;
sda_output_value <= 1'b1;
enable_operation <= 1'b0;
operation_complete <= 1'b0;
bit_counter <= 4'd0;
end else
case(current_state)
IDLE_STATE: begin
sda_output_enable <= 1'b0;
sda_output_value <= 1'b1;
operation_complete <= 1'b0;
if (start_operation) begin
enable_operation <= 1'b1;
current_state <= START_BIT;
end
end
START_BIT: begin
sda_output_enable <= 1'b1;
if (half_clock_high) begin
sda_output_value <= 1'b0;
current_state <= WRITE_DEVICE_ADDR;
end
end
WRITE_DEVICE_ADDR: begin
sda_output_enable <= 1'b1;
if (half_clock_low) begin
if (bit_counter != 4'd8) begin
sda_output_value <= device_addr_write[7-bit_counter];
bit_counter <= bit_counter + 4'd1;
end else begin
current_state <= ACK_RECEIVE;
bit_counter <= 4'd0;
end
end
end
ACK_RECEIVE: begin
sda_output_enable <= 1'b0;
if (ack_transition)
current_state <= WRITE_HIGH_ADDR;
end
WRITE_HIGH_ADDR: begin
sda_output_enable <= 1'b1;
if (half_clock_low) begin
if (bit_counter != 4'd8) begin
sda_output_value <= high_byte_addr[7-bit_counter];
bit_counter <= bit_counter + 4'd1;
end else begin
current_state <= ACK2_RECEIVE;
bit_counter <= 4'd0;
end
end
end
ACK2_RECEIVE: begin
sda_output_enable <= 1'b0;
if (ack_transition)
current_state <= WRITE_LOW_ADDR;
end
WRITE_LOW_ADDR: begin
sda_output_enable <= 1'b1;
if (half_clock_low) begin
if (bit_counter != 4'd8) begin
sda_output_value <= low_byte_addr[7-bit_counter];
bit_counter <= bit_counter + 4'd1;
end else begin
current_state <= ACK3_RECEIVE;
bit_counter <= 4'd0;
end
end
end
ACK3_RECEIVE: begin
sda_output_enable <= 1'b0;
if (ack_transition) begin
current_state <= read_write ? INTERMEDIATE_STOP : WRITE_DATA_STATE;
sda_output_value <= read_write ? 1'b0 : sda_output_value;
end
end
STOP_BIT: begin
sda_output_enable <= 1'b1;
if (half_clock_high) begin
sda_output_value <= 1'b1;
operation_complete <= 1'b1;
enable_operation <= 1'b0;
current_state <= IDLE_STATE;
end
end
WRITE_DATA_STATE: begin
sda_output_enable <= 1'b1;
if (half_clock_low) begin
if (bit_counter != 4'd8) begin
sda_output_value <= tx_data_buffer[7-bit_counter];
bit_counter <= bit_counter + 4'd1;
end else begin
current_state <= WRITE_ACK;
bit_counter <= 4'd0;
end
end
end
WRITE_ACK: begin
sda_output_enable <= 1'b0;
if (ack_transition) begin
sda_output_value <= 1'b0;
current_state <= STOP_BIT;
end
end
INTERMEDIATE_STOP: begin
sda_output_enable <= 1'b1;
if (half_clock_high) begin
sda_output_value <= 1'b1;
current_state <= RESTART_BIT;
end
end
RESTART_BIT: begin
sda_output_enable <= 1'b1;
if (half_clock_high) begin
sda_output_value <= 1'b0;
current_state <= READ_DEVICE_ADDR;
end
end
READ_DEVICE_ADDR: begin
sda_output_enable <= 1'b1;
if (half_clock_low) begin
if (bit_counter != 4'd8) begin
sda_output_value <= device_addr_read[7-bit_counter];
bit_counter <= bit_counter + 4'd1;
end else begin
current_state <= READ_ACK;
bit_counter <= 4'd0;
end
end
end
READ_ACK: begin
sda_output_enable <= 1'b0;
if (ack_transition)
current_state <= READ_DATA_STATE;
end
READ_DATA_STATE: begin
sda_output_enable <= 1'b0;
if (half_clock_high && bit_counter != 4'd8) begin
rx_data_buffer[7-bit_counter] <= sda_input_value;
bit_counter <= bit_counter + 4'd1;
end
if (ack_transition && bit_counter == 4'd8) begin
current_state <= NO_ACK;
bit_counter <= 4'd0;
read_data <= rx_data_buffer;
end
end
NO_ACK: begin
sda_output_enable <= 1'b1;
if (half_clock_low)
sda_output_value <= 1'b1;
if (ack_transition) begin
sda_output_value <= 1'b0;
current_state <= STOP_BIT;
end
end
default: current_state <= IDLE_STATE;
endcase
end
endmodule
OV5640 초기화 데이터 테이블
SCCB 컨트롤러를 통해 OV5640 레지스터를 구성하기 위한 초기화 데이터 테이블이다. 이 모듈은 640x480 해상도, 30fps, RGB565 형식으로 이미지를 출력하도록 설정된다.
// OV5640 레지스터 초기화 설정: 640*480 30fps RGB565 형식
`timescale 1ns / 1ps
module ov5640_config_table#(
parameter IMG_WIDTH = 16'd640,
parameter IMG_HEIGHT = 16'd480,
parameter FLIP_ENABLE = 1'b0,
parameter MIRROR_ENABLE = 1'b0
)(
input wire clk,
input wire rst_n,
input wire init_start,
input wire sccb_done,
output reg sccb_start,
output reg [23:0] config_data,
output reg init_complete
);
localparam DATA_COUNT = 9'd257;
localparam DELAY_5MS = 20'd500_000;
localparam FLIP_VALUE = FLIP_ENABLE ? 8'h47 : 8'h41,
MIRROR_VALUE = MIRROR_ENABLE ? 8'h01 : 8'h07;
localparam STATE_IDLE = 2'd0,
STATE_RESET = 2'd1,
STATE_DELAY = 2'd2,
STATE_CONFIG = 2'd3;
reg [23:0] config_array [DATA_COUNT-1:0];
reg [19:0] delay_counter;
reg [8:0] data_index;
reg [1:0] current_state;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= STATE_IDLE;
config_data <= config_array[0];
init_complete <= 1'b0;
sccb_start <= 1'b0;
data_index <= 9'd0;
delay_counter <= 20'd0;
end else case (current_state)
STATE_IDLE: begin
if (init_start) begin
init_complete <= 1'b0;
config_data <= config_array[data_index];
sccb_start <= 1'b1;
data_index <= data_index + 9'd1;
end else if (sccb_done) begin
current_state <= STATE_RESET;
config_data <= config_array[data_index];
sccb_start <= 1'b1;
data_index <= data_index + 9'd1;
end else
sccb_start <= 1'b0;
end
STATE_RESET: begin
if (sccb_done)
current_state <= STATE_DELAY;
else
sccb_start <= 1'b0;
end
STATE_DELAY: begin
if (delay_counter == DELAY_5MS) begin
delay_counter <= 20'd0;
current_state <= STATE_CONFIG;
config_data <= config_array[data_index];
sccb_start <= 1'b1;
data_index <= data_index + 9'd1;
end else begin
delay_counter <= delay_counter + 20'd1;
sccb_start <= 1'b0;
end
end
STATE_CONFIG: begin
if (sccb_done) begin
if (data_index <= DATA_COUNT-1) begin
config_data <= config_array[data_index];
sccb_start <= 1'b1;
data_index <= data_index + 9'd1;
end else begin
init_complete <= 1'b1;
config_data <= config_array[0];
data_index <= 9'd0;
current_state <= STATE_IDLE;
end
end else
sccb_start <= 1'b0;
end
default:;
endcase
end
initial begin
config_array[0] = 24'h3103_11;
config_array[1] = 24'h3008_82;
config_array[2] = 24'h3008_42;
config_array[3] = 24'h3103_03;
config_array[4] = 24'h3017_ff;
config_array[5] = 24'h3018_ff;
config_array[6] = 24'h3034_1a;
config_array[7] = 24'h3037_13;
config_array[8] = 24'h3108_01;
config_array[9] = 24'h3630_36;
config_array[10] = 24'h3631_0e;
config_array[11] = 24'h3632_e2;
config_array[12] = 24'h3633_12;
config_array[13] = 24'h3621_e0;
config_array[14] = 24'h3704_a0;
config_array[15] = 24'h3703_5a;
config_array[16] = 24'h3715_78;
config_array[17] = 24'h3717_01;
config_array[18] = 24'h370b_60;
config_array[19] = 24'h3705_1a;
config_array[20] = 24'h3905_02;
config_array[21] = 24'h3906_10;
config_array[22] = 24'h3901_0a;
config_array[23] = 24'h3731_12;
config_array[24] = 24'h3600_08;
config_array[25] = 24'h3601_33;
config_array[26] = 24'h302d_60;
config_array[27] = 24'h3620_52;
config_array[28] = 24'h371b_20;
config_array[29] = 24'h471c_50;
config_array[30] = 24'h3a13_43;
config_array[31] = 24'h3a18_00;
config_array[32] = 24'h3a19_f8;
config_array[33] = 24'h3635_13;
config_array[34] = 24'h3636_03;
config_array[35] = 24'h3634_40;
config_array[36] = 24'h3622_01;
config_array[37] = 24'h3c01_34;
config_array[38] = 24'h3c04_28;
config_array[39] = 24'h3c05_98;
config_array[40] = 24'h3c06_00;
config_array[41] = 24'h3c07_08;
config_array[42] = 24'h3c08_00;
config_array[43] = 24'h3c09_1c;
config_array[44] = 24'h3c0a_9c;
config_array[45] = 24'h3c0b_40;
config_array[46] = 24'h3810_00;
config_array[47] = 24'h3811_10;
config_array[48] = 24'h3812_00;
config_array[49] = 24'h3708_64;
config_array[50] = 24'h4001_02;
config_array[51] = 24'h4005_1a;
config_array[52] = 24'h3000_00;
config_array[53] = 24'h3004_ff;
config_array[54] = 24'h300e_58;
config_array[55] = 24'h302e_00;
config_array[56] = 24'h4300_61;
config_array[57] = 24'h501f_01;
config_array[58] = 24'h440e_00;
config_array[59] = 24'h5000_a7;
config_array[60] = 24'h3a0f_30;
config_array[61] = 24'h3a10_28;
config_array[62] = 24'h3a1b_30;
config_array[63] = 24'h3a1e_26;
config_array[64] = 24'h3a11_60;
config_array[65] = 24'h3a1f_14;
config_array[66] = 24'h5800_23;
config_array[67] = 24'h5801_14;
config_array[68] = 24'h5802_0f;
config_array[69] = 24'h5803_0f;
config_array[70] = 24'h5804_12;
config_array[71] = 24'h5805_26;
config_array[72] = 24'h5806_0c;
config_array[73] = 24'h5807_08;
config_array[74] = 24'h5808_05;
config_array[75] = 24'h5809_05;
config_array[76] = 24'h580a_08;
config_array[77] = 24'h580b_0d;
config_array[78] = 24'h580c_08;
config_array[79] = 24'h580d_03;
config_array[80] = 24'h580e_00;
config_array[81] = 24'h580f_00;
config_array[82] = 24'h5810_03;
config_array[83] = 24'h5811_09;
config_array[84] = 24'h5812_07;
config_array[85] = 24'h5813_03;
config_array[86] = 24'h5814_00;
config_array[87] = 24'h5815_01;
config_array[88] = 24'h5816_03;
config_array[89] = 24'h5817_08;
config_array[90] = 24'h5818_0d;
config_array[91] = 24'h5819_08;
config_array[92] = 24'h581a_05;
config_array[93] = 24'h581b_06;
config_array[94] = 24'h581c_08;
config_array[95] = 24'h581d_0e;
config_array[96] = 24'h581e_29;
config_array[97] = 24'h581f_17;
config_array[98] = 24'h5820_11;
config_array[99] = 24'h5821_11;
config_array[100] = 24'h5822_15;
config_array[101] = 24'h5823_28;
config_array[102] = 24'h5824_46;
config_array[103] = 24'h5825_26;
config_array[104] = 24'h5826_08;
config_array[105] = 24'h5827_26;
config_array[106] = 24'h5828_64;
config_array[107] = 24'h5829_26;
config_array[108] = 24'h582a_24;
config_array[109] = 24'h582b_22;
config_array[110] = 24'h582c_24;
config_array[111] = 24'h582d_24;
config_array[112] = 24'h582e_06;
config_array[113] = 24'h582f_22;
config_array[114] = 24'h5830_40;
config_array[115] = 24'h5831_42;
config_array[116] = 24'h5832_24;
config_array[117] = 24'h5833_26;
config_array[118] = 24'h5834_24;
config_array[119] = 24'h5835_22;
config_array[120] = 24'h5836_22;
config_array[121] = 24'h5837_26;
config_array[122] = 24'h5838_44;
config_array[123] = 24'h5839_24;
config_array[124] = 24'h583a_26;
config_array[125] = 24'h583b_28;
config_array[126] = 24'h583c_42;
config_array[127] = 24'h583d_ce;
config_array[128] = 24'h5180_ff;
config_array[129] = 24'h5181_f2;
config_array[130] = 24'h5182_00;
config_array[131] = 24'h5183_14;
config_array[132] = 24'h5184_25;
config_array[133] = 24'h5185_24;
config_array[134] = 24'h5186_09;
config_array[135] = 24'h5187_09;
config_array[136] = 24'h5188_09;
config_array[137] = 24'h5189_75;
config_array[138] = 24'h518a_54;
config_array[139] = 24'h518b_e0;
config_array[140] = 24'h518c_b2;
config_array[141] = 24'h518d_42;
config_array[142] = 24'h518e_3d;
config_array[143] = 24'h518f_56;
config_array[144] = 24'h5190_46;
config_array[145] = 24'h5191_f8;
config_array[146] = 24'h5192_04;
config_array[147] = 24'h5193_70;
config_array[148] = 24'h5194_f0;
config_array[149] = 24'h5195_f0;
config_array[150] = 24'h5196_03;
config_array[151] = 24'h5197_01;
config_array[152] = 24'h5198_04;
config_array[153] = 24'h5199_12;
config_array[154] = 24'h519a_04;
config_array[155] = 24'h519b_00;
config_array[156] = 24'h519c_06;
config_array[157] = 24'h519d_82;
config_array[158] = 24'h519e_38;
config_array[159] = 24'h5480_01;
config_array[160] = 24'h5481_08;
config_array[161] = 24'h5482_14;
config_array[162] = 24'h5483_28;
config_array[163] = 24'h5484_51;
config_array[164] = 24'h5485_65;
config_array[165] = 24'h5486_71;
config_array[166] = 24'h5487_7d;
config_array[167] = 24'h5488_87;
config_array[168] = 24'h5489_91;
config_array[169] = 24'h548a_9a;
config_array[170] = 24'h548b_aa;
config_array[171] = 24'h548c_b8;
config_array[172] = 24'h548d_cd;
config_array[173] = 24'h548e_dd;
config_array[174] = 24'h548f_ea;
config_array[175] = 24'h5490_1d;
config_array[176] = 24'h5381_1e;
config_array[177] = 24'h5382_5b;
config_array[178] = 24'h5383_08;
config_array[179] = 24'h5384_0a;
config_array[180] = 24'h5385_7e;
config_array[181] = 24'h5386_88;
config_array[182] = 24'h5387_7c;
config_array[183] = 24'h5388_6c;
config_array[184] = 24'h5389_10;
config_array[185] = 24'h538a_01;
config_array[186] = 24'h538b_98;
config_array[187] = 24'h5580_06;
config_array[188] = 24'h5583_40;
config_array[189] = 24'h5584_10;
config_array[190] = 24'h5589_10;
config_array[191] = 24'h558a_00;
config_array[192] = 24'h558b_f8;
config_array[193] = 24'h501d_40;
config_array[194] = 24'h5300_08;
config_array[195] = 24'h5301_30;
config_array[196] = 24'h5302_10;
config_array[197] = 24'h5303_00;
config_array[198] = 24'h5304_08;
config_array[199] = 24'h5305_30;
config_array[200] = 24'h5306_08;
config_array[201] = 24'h5307_16;
config_array[202] = 24'h5309_08;
config_array[203] = 24'h530a_30;
config_array[204] = 24'h530b_04;
config_array[205] = 24'h530c_06;
config_array[206] = 24'h5025_00;
config_array[207] = 24'h3008_02;
config_array[208] = 24'h3035_41;
config_array[209] = 24'h3036_72;
config_array[210] = 24'h3c07_08;
config_array[211] = {16'h3820, FLIP_VALUE};
config_array[212] = {16'h3821, MIRROR_VALUE};
config_array[213] = 24'h3814_31;
config_array[214] = 24'h3815_31;
config_array[215] = 24'h3800_00;
config_array[216] = 24'h3801_00;
config_array[217] = 24'h3802_00;
config_array[218] = 24'h3803_be;
config_array[219] = 24'h3804_0a;
config_array[220] = 24'h3805_3f;
config_array[221] = 24'h3806_06;
config_array[222] = 24'h3807_e4;
config_array[223] = {16'h3808, IMG_WIDTH[15:8]};
config_array[224] = {16'h3809, IMG_WIDTH[7:0]};
config_array[225] = {16'h380a, IMG_HEIGHT[15:8]};
config_array[226] = {16'h380b, IMG_HEIGHT[7:0]};
config_array[227] = 24'h380c_07;
config_array[228] = 24'h380d_69;
config_array[229] = 24'h380e_03;
config_array[230] = 24'h380f_21;
config_array[231] = 24'h3813_06;
config_array[232] = 24'h3618_00;
config_array[233] = 24'h3612_29;
config_array[234] = 24'h3709_52;
config_array[235] = 24'h370c_03;
config_array[236] = 24'h3a02_09;
config_array[237] = 24'h3a03_63;
config_array[238] = 24'h3a14_09;
config_array[239] = 24'h3a15_63;
config_array[240] = 24'h4004_02;
config_array[241] = 24'h3002_1c;
config_array[242] = 24'h3006_c3;
config_array[243] = 24'h4713_03;
config_array[244] = 24'h4407_04;
config_array[245] = 24'h460b_35;
config_array[246] = 24'h460c_22;
config_array[247] = 24'h4837_22;
config_array[248] = 24'h3824_02;
config_array[249] = 24'h5001_a3;
config_array[250] = 24'h3503_00;
config_array[251] = {16'h3821, MIRROR_VALUE};
config_array[252] = 24'h3035_21;
config_array[253] = 24'h3a02_12;
config_array[254] = 24'h3a03_c6;
config_array[255] = 24'h3a14_12;
config_array[256] = 24'h3a15_c6;
end
endmodule
DVP 데이터 수집
OV5640에서 출력되는 이미지 데이터를 DVP 인터페이스를 통해 수집하는 모듈이다. 수집된 데이터는 RGB565 형식으로 변환되어 메모리에 저장된다.
`timescale 1ns / 1ps
module dvp_data_capture#(
parameter FRAME_SKIP_COUNT = 8'd10
)(
input wire reset_n,
input wire pixel_clock,
input wire line_sync,
input wire frame_sync,
input wire [7:0] pixel_data,
output reg [15:0] rgb565_output,
output wire valid_data,
output reg [16:0] write_address
);
reg pixel_complete;
wire frame_complete;
reg frame_valid;
reg [7:0] frame_counter;
reg [7:0] delayed_pixel_data;
reg delayed_frame_sync;
reg delayed_pixel_complete;
always@(posedge pixel_clock or negedge reset_n)
if(reset_n == 1'b0)
delayed_frame_sync <= 1'b0;
else
delayed_frame_sync <= frame_sync;
assign frame_complete = ((delayed_frame_sync == 1'b0) &&
(frame_sync == 1'b1)) ? 1'b1 : 1'b0;
always @(posedge pixel_clock or negedge reset_n) begin
if (!reset_n) begin
frame_counter <= 8'd0;
frame_valid <= 1'b0;
end else if (frame_complete) begin
if (frame_counter == FRAME_SKIP_COUNT) begin
frame_counter <= 8'd0;
frame_valid <= 1'b1;
end else
frame_counter <= frame_counter + 8'd1;
end
end
always @(posedge pixel_clock or negedge reset_n) begin
if (!reset_n) begin
pixel_complete <= 1'b0;
delayed_pixel_data <= 8'b0;
rgb565_output <= 8'b0;
end else if (line_sync) begin
if (!pixel_complete) begin
delayed_pixel_data <= pixel_data;
pixel_complete <= 1'b1;
end else begin
rgb565_output <= {delayed_pixel_data, pixel_data};
pixel_complete <= 1'b0;
end
end
end
always @(posedge pixel_clock or negedge reset_n) begin
if (!reset_n)
write_address <= 17'h0;
else if (frame_complete)
write_address <= 17'h0;
else if (line_sync && frame_valid)
if (pixel_complete)
write_address <= write_address + 17'h1;
end
always@(posedge pixel_clock or negedge reset_n)
if(reset_n == 1'b0)
delayed_pixel_complete <= 1'b0;
else
delayed_pixel_complete <= pixel_complete;
assign valid_data = frame_valid & delayed_pixel_complete;
endmodule
RAM 데이터 버퍼
수집된 이미지 데이터를 임시로 저장하기 위한 RAM 버퍼이다. FPGA 내부 블록 RAM을 사용하여 구현되며, 최소한의 용량만 사용하여 전체 프레임을 저장할 수 있도록 설계되었다.
VGA 컨트롤러
버퍼에 저장된 이미지 데이터를 VGA 인터페이스를 통해 디스플레이에 출력하는 컨트롤러이다. 640x480 해상도, 60Hz 주사율로 동작하도록 설정된다.
`timescale 1ns / 1ps
module vga_controller#(
parameter DISPLAY_WIDTH = 12'd256,
parameter DISPLAY_HEIGHT = 12'd160
)(
input wire clock,
input wire reset_n,
input wire [15:0] video_data,
output wire horizontal_sync,
output wire vertical_sync,
output reg [15:0] rgb_output,
output reg [16:0] read_address
);
reg [11:0] horizontal_counter;
reg [11:0] vertical_counter;
wire data_valid_signal;
wire [11:0] pixel_x;
wire [11:0] pixel_y;
wire display_area;
localparam H_SYNC_PULSE = 12'd96,
H_BACK_PORCH = 12'd40,
H_LEFT_BORDER = 12'd8,
H_ACTIVE_PIXELS = 12'd640,
H_RIGHT_BORDER = 12'd8,
H_FRONT_PORCH = 12'd8,
H_TOTAL_CYCLES = H_SYNC_PULSE + H_BACK_PORCH + H_LEFT_BORDER + H_ACTIVE_PIXELS + H_RIGHT_BORDER + H_FRONT_PORCH,
V_SYNC_PULSE = 12'd2,
V_BACK_PORCH = 12'd25,
V_LEFT_BORDER = 12'd8,
V_ACTIVE_LINES = 12'd480,
V_RIGHT_BORDER = 12'd8,
V_FRONT_PORCH = 12'd2,
V_TOTAL_LINES = V_SYNC_PULSE + V_BACK_PORCH + V_LEFT_BORDER + V_ACTIVE_LINES + V_RIGHT_BORDER + V_FRONT_PORCH;
localparam RED_COLOR = 16'hF800,
GREEN_COLOR = 16'h07E0,
BLUE_COLOR = 16'h001F,
BLACK_COLOR = 16'h0000;
always @(posedge clock or negedge reset_n) begin
if (!reset_n)
horizontal_counter <= 12'd0;
else if (horizontal_counter == H_TOTAL_CYCLES-1)
horizontal_counter <= 12'd0;
else
horizontal_counter <= horizontal_counter + 12'd1;
end
always @(posedge clock or negedge reset_n) begin
if (!reset_n)
vertical_counter <= 12'd0;
else if (vertical_counter == V_TOTAL_LINES-1)
vertical_counter <= 12'd0;
else if (horizontal_counter == H_TOTAL_CYCLES-1)
vertical_counter <= vertical_counter + 12'd1;
end
assign horizontal_sync = (horizontal_counter < H_SYNC_PULSE) ? 1'b0 : 1'b1;
assign vertical_sync = (vertical_counter < V_SYNC_PULSE) ? 1'b0 : 1'b1;
assign data_valid_signal = (horizontal_counter >= H_SYNC_PULSE + H_BACK_PORCH + H_LEFT_BORDER -1) &&
(vertical_counter >= V_SYNC_PULSE + V_BACK_PORCH + V_LEFT_BORDER -1) &&
(horizontal_counter <= H_SYNC_PULSE + H_BACK_PORCH + H_LEFT_BORDER + H_ACTIVE_PIXELS -1) &&
(vertical_counter <= V_SYNC_PULSE + V_BACK_PORCH + V_LEFT_BORDER + V_ACTIVE_LINES -1);
assign pixel_x = (data_valid_signal) ? (horizontal_counter - (H_SYNC_PULSE + H_BACK_PORCH + H_LEFT_BORDER -1)) : 12'hFFF;
assign pixel_y = (data_valid_signal) ? (vertical_counter - (V_SYNC_PULSE + V_BACK_PORCH + V_LEFT_BORDER -1)) : 12'hFFF;
assign display_area = (pixel_x >= (H_ACTIVE_PIXELS - DISPLAY_WIDTH)/2 && pixel_x < (H_ACTIVE_PIXELS + DISPLAY_WIDTH)/2 &&
pixel_y >= (V_ACTIVE_LINES - DISPLAY_HEIGHT)/2 && pixel_y < (V_ACTIVE_LINES + DISPLAY_HEIGHT)/2);
always @(posedge clock or negedge reset_n) begin
if (!reset_n)
rgb_output <= BLACK_COLOR;
else if (display_area)
rgb_output <= video_data;
else
rgb_output <= BLACK_COLOR;
end
always @(posedge clock or negedge reset_n) begin
if (!reset_n)
read_address <= 17'h0;
else if (vertical_counter == V_TOTAL_LINES-1)
read_address <= 17'h0;
else if (display_area)
read_address <= read_address + 17'h1;
end
endmodule
탑 레벨 모듈
전체 시스템을 통합하는 탑 레벨 모듈이다. SCCB 컨트롤러, DVP 데이터 수집기, VGA 컨트롤러, RAM 버퍼 등 모든 서브 모듈을 연결하고 제어한다.
`timescale 1ns / 1ps
module top_level(
input wire system_clock,
input wire camera_pixel_clock,
input wire camera_frame_sync,
input wire camera_line_sync,
input wire [7:0] camera_data,
input wire reset_n,
input wire start_button,
inout wire sccb_data,
output wire sccb_clock,
output wire initialization_complete,
output wire horizontal_sync,
output wire vertical_sync,
output wire[3:0] red_channel,
output wire[3:0] green_channel,
output wire[3:0] blue_channel
);
wire init_start;
wire sccb_operation_start;
wire sccb_operation_done;
wire [23:0] init_data;
wire vga_clock;
wire ram_write_enable;
wire [16:0] ram_write_address;
wire [15:0] ram_write_data;
wire [16:0] ram_read_address;
wire [15:0] ram_read_data;
wire [15:0] rgb_data;
assign red_channel = rgb_data[15:12];
assign green_channel = rgb_data[10:7];
assign blue_channel = rgb_data[4:1];
ov5640_config_table#(
.IMG_WIDTH(16'd350),
.IMG_HEIGHT(16'd218)
) ov5640_init_table(
.clk(system_clock),
.rst_n(reset_n),
.init_start(init_start),
.sccb_done(sccb_operation_done),
.sccb_start(sccb_operation_start),
.config_data(init_data),
.init_complete(initialization_complete)
);
SCCB_controller sccb_ctrl(
.clk(system_clock),
.rst_n(reset_n),
.sda(sccb_data),
.scl(sccb_clock),
.read_write(1'b0),
.start_operation(sccb_operation_start),
.device_address(7'b0111_100),
.register_address(init_data[23:8]),
.write_data(init_data[7:0]),
.read_data(),
.operation_complete(sccb_operation_done)
);
dvp_data_capture #(
.FRAME_SKIP_COUNT(8'd10)
) dvp_capture(
.pixel_clock(camera_pixel_clock),
.reset_n(reset_n),
.line_sync(camera_line_sync),
.frame_sync(camera_frame_sync),
.pixel_data(camera_data),
.valid_data(ram_write_enable),
.write_address(ram_write_address),
.rgb565_output(ram_write_data)
);
vga_controller#(
.DISPLAY_WIDTH(12'd350),
.DISPLAY_HEIGHT(12'd218)
) vga_ctrl(
.clock(vga_clock),
.reset_n(reset_n),
.read_address(ram_read_address),
.video_data(ram_read_data),
.horizontal_sync(horizontal_sync),
.vertical_sync(vertical_sync),
.rgb_output(rgb_data)
);
button_debounce button_filter(
.i_clk(system_clock),
.i_rstn(reset_n),
.i_key(start_button),
.ok(init_start)
);
vga_clock_generator clock_gen(
.sys_clk(system_clock),
.vga_clk(vga_clock)
);
image_ram ram_instance(
.clka(camera_pixel_clock),
.wea(ram_write_enable),
.addra(ram_write_address),
.dina(ram_write_data),
.clkb(vga_clock),
.addrb(ram_read_address),
.doutb(ram_read_data)
);
endmodule
실행 결과
VGA 인터페이스를 통해 640x480@60Hz 해상도로 디스플레이에 실시간 영상을 출력하였다. 중심 영역 350x218 픽셀 크기로 표시되며, 약간의 화면 깨짐 현상이 발생하나 전반적으로 안정적인 동작을 확인할 수 있었다.