Décodage du flux vidéo de la caméra OV5640 pour FPGA

L'intégration d'un module caméra OV5640 nécessite de traiter le flux de données brut qu'il émet. Après son initialisation via une interface de type I2C, ce capteur CMOS délivre des données au format RGB565. Cependant, les premières images capturées après la mise sous tension sont souvent instables. Il est donc courant d'ignorer un certain nombre de trames initiales avant de traiter le signal vidéo utile.

Un défi technique majeur réside dans la gestion des domaines d'horloge distincts. Le pixel clock (pclk) fourni par la caméra à l'interface FPGA est asynchrone par rapport à l'horloge système principale. Un copmosant de type FIFO asynchrone est indispensable pour assurer un transfert de données robuste entre ces deux domaines horaires.

Le module de premier niveau (CameraDataBridge) orchestre cette opération. Il instancie un décodeur vidéo et une FIFO pour produire un flux de données aligné sur l'horlogee système. Le format de sortie combine les données pixel et des marqueurs de trame/ligne.

`timescale 1ns / 1ps
module CameraDataBridge #(
    parameter DISCARD_FRAMES = 15,
    parameter FIFO_DEPTH = 2048,
    parameter IMAGE_WIDTH = 1280,
    parameter IMAGE_HEIGHT = 720
)(
    input wire sys_clk,
    input wire sys_rst_n,
    input wire cam_pixel_clk,
    input wire cam_data_clk,
    input wire cam_vsync,
    input wire cam_href,
    input wire [7:0] cam_din,
    output wire cam_mclk,
    input wire fifo_rd_ready,
    output wire [31:0] stream_out_data,
    output wire stream_out_valid,
    output wire frame_reset,
    output wire vsync_out
);

    wire decoded_vsync;
    wire decoded_href;
    wire decoded_de;
    wire [15:0] rgb_pixel;
    wire frame_start;
    wire line_end;

    // Reset synchronizer
    reg [5:0] sys_rst_sync;
    always @(posedge cam_data_clk) begin
        sys_rst_sync <= {sys_rst_sync[4:0], sys_rst_n};
    end

    // Video Decoder Instance
    VideoSignalProcessor #(
        .DISCARD_FRAMES(DISCARD_FRAMES),
        .IMAGE_WIDTH(IMAGE_WIDTH),
        .IMAGE_HEIGHT(IMAGE_HEIGHT)
    ) u_video_decoder (
        .clk(cam_data_clk),
        .rst_n(sys_rst_n),
        .vsync_in(cam_vsync),
        .href_in(cam_href),
        .data_in(cam_din),
        .vsync_out(decoded_vsync),
        .href_out(decoded_href),
        .data_enable(decoded_de),
        .rgb_out(rgb_pixel),
        .frame_start(frame_start),
        .line_end(line_end)
    );

    // Vsync edge detector for FIFO reset
    reg [3:0] vsync_pipe;
    always @(posedge sys_clk) begin
        vsync_pipe <= {vsync_pipe[2:0], cam_vsync};
    end
    wire fifo_reset = (~vsync_pipe[2] & vsync_pipe[3]) | (~sys_rst_sync[5]);

    // FIFO input assembly
    wire [17:0] fifo_wr_data = {frame_start, line_end, rgb_pixel};
    wire fifo_empty;

    // Async FIFO for clock domain crossing
    xpm_fifo_async #(
        .FIFO_MEMORY_TYPE("block"),
        .ECC_MODE("no_ecc"),
        .RELATED_CLOCKS(0),
        .FIFO_WRITE_DEPTH(FIFO_DEPTH),
        .WRITE_DATA_WIDTH(18),
        .READ_MODE("fwft"),
        .FIFO_READ_LATENCY(0),
        .READ_DATA_WIDTH(18),
        .CDC_SYNC_STAGES(2)
    ) u_async_fifo (
        .rst(fifo_reset),
        .wr_clk(cam_data_clk),
        .wr_en(decoded_de),
        .din(fifo_wr_data),
        .rd_clk(sys_clk),
        .rd_en(fifo_rd_ready),
        .dout(fifo_rd_data),
        .empty(fifo_empty),
        // Unused ports omitted for brevity
        .full(),
        .overflow(),
        .underflow()
    );

    // Output assignments
    assign cam_mclk = cam_pixel_clk;
    assign stream_out_valid = ~fifo_empty & fifo_rd_ready;
    assign vsync_out = decoded_vsync;
    assign frame_reset = fifo_reset;
    // Expand 18-bit FIFO data to 32-bit AXI-Stream format
    assign stream_out_data = {fifo_rd_data[17:16], 6'b0,
                              fifo_rd_data[15:11], 3'b0,
                              fifo_rd_data[10:5], 2'b0,
                              fifo_rd_data[4:0], 3'b0};

endmodule

Le module de décodage (VideoSignalProcessor) se concentre sur le traitemant du signal brut émis par le capteur. Sa tâche principale est de synchroniser les signaux de contrôle et d'assembler les octets consécutifs en pixels RGB de 16 bits. Il génère également les indicateurs de début et de fin nécessaires au flux de sortie.

`timescale 1ns / 1ps
module VideoSignalProcessor #(
    parameter DISCARD_FRAMES = 15,
    parameter IMAGE_WIDTH = 640,
    parameter IMAGE_HEIGHT = 480
)(
    input wire clk,
    input wire rst_n,
    input wire vsync_in,
    input wire href_in,
    input wire [7:0] data_in,
    output reg vsync_out,
    output reg href_out,
    output wire data_enable,
    output reg [15:0] rgb_out,
    output wire frame_start,
    output wire line_end
);

    // Input synchronization
    reg [2:0] vsync_reg;
    reg [2:0] href_reg;
    reg [7:0] data_reg;
    always @(posedge clk) begin
        vsync_reg <= {vsync_reg[1:0], vsync_in};
        href_reg <= {href_reg[1:0], href_in};
        data_reg <= data_in;
    end

    // Edge detection
    wire vsync_rising = ~vsync_reg[2] & vsync_reg[1];
    wire vsync_falling = ~vsync_reg[1] & vsync_reg[2];
    wire href_falling = ~href_reg[1] & href_reg[2];

    // Frame discard logic
    reg [6:0] frame_counter;
    reg processing_active;
    always @(posedge clk) begin
        if (~rst_n) begin
            frame_counter <= 0;
            processing_active <= 1'b0;
        end else begin
            if (vsync_rising && frame_counter < DISCARD_FRAMES) begin
                frame_counter <= frame_counter + 1;
            end
            if (frame_counter >= DISCARD_FRAMES) begin
                processing_active <= 1'b1;
            end
        end
    end

    // Pixel assembly (8-bit to 16-bit RGB565)
    reg byte_select; // 0: High byte, 1: Low byte
    reg [7:0] pixel_byte;
    always @(posedge clk) begin
        if (~rst_n) begin
            byte_select <= 1'b0;
            pixel_byte <= 8'b0;
            rgb_out <= 16'b0;
        end else begin
            if (href_reg[1]) begin
                byte_select <= ~byte_select;
                pixel_byte <= data_reg;
                // Latch assembled pixel on the second byte
                if (byte_select) begin
                    rgb_out <= {pixel_byte, data_reg};
                end
            end else begin
                byte_select <= 1'b0;
                rgb_out <= 16'b0;
            end
        end
    end

    // Pixel counter for active line
    reg [11:0] pixel_x_counter;
    always @(posedge clk) begin
        if (~rst_n || ~href_reg[2]) begin
            pixel_x_counter <= 0;
        end else if (byte_select) begin // Increment once per pixel
            pixel_x_counter <= pixel_x_counter + 1;
        end
    end

    // Output control signals
    assign data_enable = processing_active & href_reg[2] & (pixel_x_counter < IMAGE_WIDTH);
    assign frame_start = processing_active & href_reg[2] & (pixel_x_counter == 0);
    assign line_end = processing_active & (pixel_x_counter == IMAGE_WIDTH - 1);

    // Delayed output of control signals
    always @(posedge clk) begin
        vsync_out <= processing_active ? vsync_reg[2] : 1'b0;
        href_out <= processing_active ? href_reg[2] : 1'b0;
    end

endmodule

Cette architecture sépare clairement les fonctions de décodage et de synchronisation inter-horloges. Les signaux de contrôle (frame_start, line_end) encapsulés dans le flux permettent au traitement aval (comme des opérations de filtrage ou de détection de contours) de reconnaître la structure de l'image sans dépendre directement des signaux physiques de la caméra.

Étiquettes: OV5640 FPGA Verilog traitement vidéo Domaine d'horloge

Publié le 11 juin à 22h11