





The video capture module accepts data from the pixel_data camera in the YCbCr 4: 2: 2 or RGB: 565 format and the frame and line sweep control signals hsync, vsync, translates them into the clk domain (50 MHz), generates the out_pixel_valid control signal and out_vclk and passes them to the data format conversion module. Also, this module generates out_stat statistics on the number of received data for 1 frame. Statistics can be read through the UART. The module is controlled by an external capt_en data enable signal. This signal is set by the camera setup module upon completion of the setup. Verilog code:always @(posedge clk) begin hs_sync_1 <= hsync;hs_sync_2 <= hs_sync_1; vs_sync_1 <= vsync;vs_sync_2 <= vs_sync_1; vclk_sync_1 <= pclk;vclk_sync_2 <= vclk_sync_1; pixdata_sync_1 <= pixel_data;pixdata_sync_2 <= pixdata_sync_1; end reg vclk_old; always @(posedge clk)vclk_old <= vclk_sync_2; wire vclk_posedge = (vclk_old == 1'b0) && (vclk_sync_2 == 1'b1); reg sample_new,sample_hsync,sample_vsync; reg [7:0] sample_pixel; always @(posedge clk) begin sample_new <= vclk_posedge; if (vclk_posedge) begin sample_hsync <= hs_sync_2; sample_vsync <= vs_sync_2; sample_pixel <= pixdata_sync_2; end End reg last_vsync_sample,P2_vsync_triggered,P2_vsync_end_triggered; reg P2_sample_vsync,P2_sample_new,P2_sample_hsync; reg [7:0] P2_sample_pixel; reg P2_new_frame,capt_done,capt_enable; always @(posedge clk) begin if (capt_en == 1'b1 || P2_vsync_triggered == 1'b1) capt_enable <= 1'b1; else capt_enable <= 1'b0; end always @(posedge clk) if (!nRst) begin last_vsync_sample <= 1'b0,P2_vsync_triggered <= 1'b0; P2_vsync_end_triggered <= 1'b0,P2_new_frame <= 1'b0; capt_done <= 1'b0; end else begin if (capt_enable) begin if (sample_new) begin last_vsync_sample <= (sample_vsync/* && capt_en*/); P2_sample_pixel <= sample_pixel; P2_sample_hsync <= sample_hsync; P2_sample_vsync <= sample_vsync; end // Pipeline Step P2_sample_new <= sample_new; if (!P2_vsync_end_triggered) begin if ((last_vsync_sample == 1'b1) && (sample_vsync == 1'b0)) begin P2_vsync_triggered <= 1'b1; P2_new_frame <= 1'b1; end if (P2_vsync_triggered && sample_vsync) begin P2_vsync_end_triggered <= 1'b1; P2_vsync_triggered <= 1'b0; capt_done <= ~capt_done; end end else begin P2_vsync_end_triggered <= 1'b0; P2_vsync_triggered <= 1'b0; end if (P2_new_frame) P2_new_frame <= 1'b0; end else begin last_vsync_sample <= 1'b0;P2_vsync_triggered <= 1'b0; P2_vsync_end_triggered <= 1'b0;P2_new_frame <= 1'b0;capt_done <= 1'b0; end end
The YCbCr 4: 2: 2 format is not very convenient for further work, since the data follow in the following sequence: Y0 Cb0 Y1 Cr1 Y2 Cb2 Y3 Cr3 ... Therefore, we will convert it to the YCbCr 4: 4: 4 format. In fact, the entire conversion is reduced to issuing Y Cb Cr data per 1 clock of the data_strob signal. In Verilog it looks like this: always @(posedge clk) if (!nRst) pix_ctr <= 2'b0; else begin if (pixel_valid) begin if (vclk) pix_ctr <= pix_ctr + 1'b1; end else pix_ctr <= 2'd0; end always @(posedge clk) case (pix_ctr) 2'd0:begin YYY <= pixel_data; CCr <= Crr; CCb <= Cbb; Ypix_clock <= 1'b1;end 2'd1:begin Cbb <= pixel_data; YY <= YYY; end 2'd2:begin YYY <= pixel_data; CCr <= Crr; CCb <= Cbb; Ypix_clock <= 1'b1;end 2'd3:begin Crr <= pixel_data; YY <= YYY; end endcase assign data_strob = Ypix_clock; assign Y = YY; assign Cb = CCb; assign Cr = CCr;
In the end, we always work with data in the RGB format, so we need to get it from YCbCr. This is done according to the formula of datasheet on camera: parameter PRECISION = 11; parameter OUTPUT = 8; parameter INPUT = 8; parameter OUT_SIZE = PRECISION + OUTPUT; parameter BUS_MSB = OUT_SIZE + 2; always @ (posedge clk) if (!nRst) begin R_int <= 22'd0; G_int <= 22'd0; B_int <= 22'd0; end else begin if (istrb) begin //R = Y + 1.371(Cr - 128) R_int <= (Y_reg << PRECISION)+(C1*(Cr_reg-8'd128)); //G = Y - 0.698(Cr-128)-0.336(Cb-128) G_int <= (Y_reg << PRECISION)-(C2*(Cr_reg-8'd128))-(C3*(Cb_reg-8'd128)); //B = Y + 1.732(Cb-128) B_int <= (Y_reg << PRECISION)+(C4*(Cb_reg-8'd128)); end end assign R = (R_int[BUS_MSB]) ? 8'd16 : (R_int[OUT_SIZE+1:OUT_SIZE] == 2'b00) ? R_int[OUT_SIZE-1:PRECISION] : 8'd240; assign G = (G_int[BUS_MSB]) ? 8'd16 : (G_int[OUT_SIZE+1:OUT_SIZE] == 2'b00) ? G_int[OUT_SIZE-1:PRECISION] : 8'd240; assign B = (B_int[BUS_MSB]) ? 8'd16 : (B_int[OUT_SIZE+1:OUT_SIZE] == 2'b00) ? B_int[OUT_SIZE-1:PRECISION] : 8'd240;
This module makes us out of a 24-bit 16-bit RGB format. It is convenient for us because Takes up less space in memory, reduces bitrate, has color reproduction acceptable for our purposes and, most importantly, fits into one word of SDRAM data, which makes work much easier. The data strobe signal is simply transmitted from the previous module. assign oRGB = {iR[7:3], iG[7:2], iB[7:3]}; assign ostrb = istrb;
This module came to the project from the very beginning. Its goal is to convert an input stream of 640x480 pixels into a stream of 320x240, 160x120, 128x120, 80x60 and 320x480. These formats were needed to work with the LCD display from the Siemens S65, TFT display for the Arduino board and to implement image rotation in the FPGA and SDRAM block memory using the CORDIC algorithm. In other words, this is the legacy of other projects. In this project it is possible to change the screen resolution on-the-fly, and this module plays the first violin here. The module also generates statistics on the amount of data per frame for debugging. The module has been created for a long time and its code should be redeveloped, but while it works, we will not touch it. always @(posedge clk) if (!nRst) begin w_ctr <= 16'd0;h_ctr <= 16'd0;frame_start <= 1'b0; rsmp_w <= 8'd0;rsmp_h <= 8'd0; end else begin if (resampler_init) begin w_ctr <= 16'd0;h_ctr <= 16'd0;frame_start <= 1'b0; rsmp_w <= 8'd0;rsmp_h <= 8'd0; end else begin /* This case works ONLY if the input strobe is valid */ if (istrb) begin if (w_ctr == I_WIDTH-1'b1) begin w_ctr <= 16'd0; if (h_ctr == I_HEIGHT-1'b1) begin h_ctr <= 16'd0; frame_start <= 1'b1; end else begin h_ctr <= h_ctr + 1'b1;frame_start <= 1'b0; end if (rsmp_h == H_FACT-1'b1) begin rsmp_h <= 8'd0; end else begin rsmp_h <= rsmp_h + 1'b1; end end else begin w_ctr <= w_ctr + 1'b1; frame_start <= 1'b0; end if (rsmp_w == W_FACT-1'b1) begin rsmp_w <= 8'd0; end else begin rsmp_w <= rsmp_w + 1'b1; end end end end reg pix_valid; always @(rsmp_w or rsmp_h or wh_multiply or H_FACT) begin if (wh_multiply == 1'b1) begin pix_valid = ((rsmp_w == 8'd0) && (rsmp_h == 8'd0))?1'b1:1'b0; end else begin pix_valid = ((rsmp_w == 8'd0) && (rsmp_h != 8'd0 ))?1'b1:1'b0; end end assign pixel_valid = pix_valid; always @(posedge clk) if (!nRst) begin frame_enable <= 1'b0; end else begin if (resampler_init) begin frame_enable <= 1'b0; end else begin if (frame_start) begin if (!lcd_busy) frame_enable <= 1'b1; else frame_enable <= 1'b0; end end end reg local_frame_start = 1'b0; always @(posedge clk) if (!nRst) begin ostrb_port <= 1'b0; dout_port <= 17'd0; local_frame_start <= 1'b0; end else begin local_frame_start <= frame_start ? 1'b1: local_frame_start; if (istrb && !resampler_init && !lcd_busy) begin if (pixel_valid) begin // if our column and our row if (frame_enable && !dout_dis) begin dout_port[16:0] <= {local_frame_start, din[15:0]}; ostrb_port <= 1'b1; local_frame_start <= 1'b0; end else begin ostrb_port <= 1'b0; end end else ostrb_port <= 1'b0; end else ostrb_port <= 1'b0; end
This is a two-blade FIFO dcfifo mega-function Altera 256x17. The sixteenth bit - the frame_start signal is added for convenience of indicating the start of a new frame after rescaler.
This bulky module is a single writer who takes data from the FIFO IN module and writes it to SDRAM alternately in different memory areas for even and odd frames and two readers who read data from SDRAM, each from its own memory area and write it to the weekend FIFO. Priority is given to readers, since they work on an HDMI controller with a frequency of 25 MHz (640x480), and he does not tolerate delays, there must always be data in the FIFO for processing and output to the screen. The time left from filling the output FIFO is the time of the inactive screen area plus the emptying time of the FIFO, the writer works. // FIFO 1 wire out_fifo_almost_full = &fifo_wr_used[9:4]; wire out_fifo_almost_empty = !(|fifo_wr_used[10:8]); // FIFO 2 wire out_fifo_almost_full_2 = &fifo_wr_used_2[9:4]; wire out_fifo_almost_empty_2 = !(|fifo_wr_used_2[10:8]);
The module from the site fpga4fun.com was taken as a basis and was slightly modified for our type of SDRAM K4S561632 chip with the addition of chip initialization and additional delays to comply with the time frame:
As in the case of FIFO IN, these FIFOs are two-fold mega-functions of 1024x16 dcfifo.
So we got to the module, which is the salt of the earth of this project. As you can see, it receives data and control signals from both output FIFOs, a slice of the HDMI 25 MHz controller pixel_clock, pixel counters counter_x, counter_y and a signal from the active display area of the blank. RGB signals come out of it, ready for display on the display. // FIFO 1 wire in_fifo_data_avail = |fifo_rd_used[10:4]; wire in_fifo_almost_empty = !(|fifo_rd_used[10:4]); // FIFO 2 wire in_fifo_data_avail_2 = |fifo_rd_used_2[10:4]; wire in_fifo_almost_empty_2 = !(|fifo_rd_used_2[10:4]); wire fifos_available = in_fifo_data_avail & in_fifo_data_avail_2; wire fifos_almost_empty = in_fifo_almost_empty | in_fifo_almost_empty_2; wire in_frame = ((counter_x < RES_X) && (counter_y < RES_Y))?1'b1:1'b0; wire frame_start = ((counter_x == 0) && (counter_y == 0))?1'b1:1'b0; // Reader FIFO 1 & 2 always @(posedge pix_clk or negedge nRst) if (!nRst) begin fifo_rd_req <= 1'b0; fifo_rd_req_2 <= 1'b0; pixel_data <= 16'h0000; worker_state <= 2'h1; end else begin case (worker_state) 2'h0: begin if (in_frame) begin if (fifos_almost_empty) begin //worker_state <= 2'h1; fifo_rd_req <= 1'b0; fifo_rd_req_2 <= 1'b0; end else begin pixel_data <= fifo_data; pixel_data_2 <= fifo_data_2; fifo_rd_req <= 1'b1; fifo_rd_req_2 <= 1'b1; end end else begin fifo_rd_req <= 1'b0; fifo_rd_req_2 <= 1'b0; end end 2'h1: begin if (blank) begin worker_state <= 2'h2; end end 2'h2: begin // start reading if more than 16 words are already in the fifo if (fifos_available && frame_start) begin fifo_rd_req <= 1'b1; fifo_rd_req_2 <= 1'b1; worker_state <= 2'h0; nd end endcase end // Convert to grayscale frame 1 wire [7:0] R1 = {pixel_data[15 : 11], pixel_data[15 : 13]}; wire [7:0] G1 = {pixel_data[10 : 5], pixel_data[10 : 9]}; wire [7:0] B1 = {pixel_data[4 : 0], pixel_data[4 : 2]}; wire [7:0] GS1 = (R1 >> 2)+(R1 >> 5)+(G1 >> 1)+(G1 >> 4)+(B1 >> 4)+(B1 >> 5); // Convert to grayscale frame 2 wire [7:0] R2 = {pixel_data_2[15 : 11], pixel_data_2[15 : 13]}; wire [7:0] G2 = {pixel_data_2[10 : 5], pixel_data_2[10 : 9]}; wire [7:0] B2 = {pixel_data_2[4 : 0], pixel_data_2[4 : 2]}; wire [7:0] GS2 = (R2 >> 2)+(R2 >> 5)+(G2 >> 1)+(G2 >> 4)+(B2 >> 4)+(B2 >> 5); 
reg [7:0] difference = 0; wire [7:0] max_val = (GS1 > GS2) ? GS1 : GS2; wire [7:0] min_val = (GS1 < GS2) ? GS1 : GS2; always @(posedge pix_clk) begin if (in_frame) begin difference <= max_val - min_val; end else difference <= 8'h00; end wire [15:0] out_val = in_frame ? (difference > `BS_THRESHOLD) ? 16'hF1_00 : pixel_data_2 : in_frame2 ? pixel_data_diff : 16'h00_00; // VGA 24 bit assign R = {out_val[15 : 11], out_val[15 : 13]}; assign G = {out_val[10 : 5], out_val[10 : 9]}; assign B = {out_val[4 : 0], out_val[4 : 2]}; 



A third-party SCCB interface module is used to configure the camera. It is slightly modified for the needs of the project and is capable of recording camera register values on-the-fly upon a command from the UART interface.
The module consists of a receiver and transmitter UART and module io_controller
Source: https://habr.com/ru/post/323258/
All Articles