阶段目标:I2C 配置摄像头寄存器,DVP视频流接收,将捕获的视频帧写入 DDR2,通过 VDMA 显示实时摄像头画面

输出结果:VGA 显示器实时显示 OV5640 摄像头采集的画面

设备:NEXYS A7开发板

架构梳理

  • 使用 I2C (SCCB 协议) 访问 0x3C 器件地址,读取 0x300A/B 寄存器获取 0x5640 ID。
  • 自定义 DVP to AXI-Stream IP:编写精简 Verilog,利用 PCLK 将 8 位数据 (D[7:0]) 拼接为 16 位 RGB565,并转换 VSYNC/HREF 为 tuser/tvalid/tlast 握手信号。
  • VDMA S2MM 闭环:重新开启 VDMA 写通道,将摄像头数据源源不断地写入 DDR 显存的首地址。

系统总体架构

00.png

硬件模块配置

1. OV5640 摄像头接口

作用:提供视频流信号

手写模块暴露接口

`timescale 1ns / 1ps

module ov5640_dvp_to_axis #(
    parameter integer H_ACTIVE = 640,
    parameter integer V_ACTIVE = 480,
    parameter integer FIFO_ADDR_WIDTH = 12,
    parameter         VSYNC_ACTIVE_HIGH = 1'b1,
    parameter         HREF_ACTIVE_HIGH  = 1'b1,
    parameter         RGB565_BYTE_ORDER = 1'b0,
    parameter         SWAP_RB = 1'b0
) (
    input  wire        pclk,
    input  wire        rstn,

    input  wire        cam_vsync,
    input  wire        cam_href,
    input  wire [7:0]  cam_data,

    output wire        m_axis_video_tvalid,
    input  wire        m_axis_video_tready,
    output wire [23:0] m_axis_video_tdata,
    output wire [0:0]  m_axis_video_tuser,
    output wire [0:0]  m_axis_video_tlast,

    output wire        probe_pixel_valid,
    output wire [15:0] probe_rgb565,
    output wire [15:0] probe_x,
    output wire [15:0] probe_y,
    output reg         probe_overflow
);

    reg        vsync_d;
    reg        href_d;
    reg [7:0]  data_d;

    reg        byte_phase;
    reg [7:0]  first_byte;
    reg [15:0] rgb565;

    reg [15:0] x_cnt;
    reg [15:0] y_cnt;
    reg        line_has_pixel;
    reg        sof_pending;
    reg        frame_active;

    localparam integer FIFO_DEPTH = (1 << FIFO_ADDR_WIDTH);
    localparam integer FIFO_COUNT_WIDTH = FIFO_ADDR_WIDTH + 1;
    localparam [FIFO_COUNT_WIDTH-1:0] FIFO_DEPTH_COUNT = (1 << FIFO_ADDR_WIDTH);

    reg [25:0] fifo_mem [0:FIFO_DEPTH-1];
    reg [FIFO_ADDR_WIDTH-1:0] fifo_wr_ptr;
    reg [FIFO_ADDR_WIDTH-1:0] fifo_rd_ptr;
    reg [FIFO_COUNT_WIDTH-1:0] fifo_count;

    reg        pixel_push;
    reg [23:0] pixel_data;
    reg        pixel_user;
    reg        pixel_last;

    wire fifo_empty = (fifo_count == {FIFO_COUNT_WIDTH{1'b0}});
    wire fifo_full  = (fifo_count == FIFO_DEPTH_COUNT);
    wire fifo_pop   = m_axis_video_tready && !fifo_empty;
    wire fifo_push  = pixel_push && !fifo_full;

    wire vsync_active = VSYNC_ACTIVE_HIGH ? cam_vsync : ~cam_vsync;
    wire href_active  = HREF_ACTIVE_HIGH  ? cam_href  : ~cam_href;

    wire vsync_active_d = VSYNC_ACTIVE_HIGH ? vsync_d : ~vsync_d;
    wire href_active_d  = HREF_ACTIVE_HIGH  ? href_d  : ~href_d;

    wire vsync_start =  vsync_active && !vsync_active_d;
    wire vsync_end   = !vsync_active &&  vsync_active_d;
    wire href_end    = !href_active  &&  href_active_d;

    wire [4:0] pix_r5 = RGB565_BYTE_ORDER ? cam_data[7:3] : first_byte[7:3];
    wire [5:0] pix_g6 = RGB565_BYTE_ORDER ? {cam_data[2:0], first_byte[7:5]} :
                                            {first_byte[2:0], cam_data[7:5]};
    wire [4:0] pix_b5 = RGB565_BYTE_ORDER ? first_byte[4:0] : cam_data[4:0];

    wire [7:0] pix_r = {pix_r5, pix_r5[4:2]};
    wire [7:0] pix_g = {pix_g6, pix_g6[5:4]};
    wire [7:0] pix_b = {pix_b5, pix_b5[4:2]};
    wire [23:0] pixel_rgb888 = SWAP_RB ? {pix_b, pix_g, pix_r} : {pix_r, pix_g, pix_b};

    always @(posedge pclk) begin
        if (!rstn) begin
            vsync_d        <= 1'b0;
            href_d         <= 1'b0;
            data_d         <= 8'h00;
            byte_phase     <= 1'b0;
            first_byte     <= 8'h00;
            rgb565         <= 16'h0000;
            x_cnt          <= 16'd0;
            y_cnt          <= 16'd0;
            line_has_pixel <= 1'b0;
            sof_pending    <= 1'b0;
            frame_active   <= 1'b0;
            fifo_wr_ptr    <= {FIFO_ADDR_WIDTH{1'b0}};
            fifo_rd_ptr    <= {FIFO_ADDR_WIDTH{1'b0}};
            fifo_count     <= {FIFO_COUNT_WIDTH{1'b0}};
            pixel_push     <= 1'b0;
            pixel_data     <= 24'h000000;
            pixel_user     <= 1'b0;
            pixel_last     <= 1'b0;
            probe_overflow <= 1'b0;
        end else begin
            vsync_d     <= cam_vsync;
            href_d      <= cam_href;
            data_d      <= cam_data;
            pixel_push  <= 1'b0;

            if (fifo_pop) begin
                fifo_rd_ptr <= fifo_rd_ptr + 1'b1;
            end

            if (vsync_start) begin
                x_cnt          <= 16'd0;
                y_cnt          <= 16'd0;
                byte_phase     <= 1'b0;
                line_has_pixel <= 1'b0;
                sof_pending    <= 1'b0;
                frame_active   <= 1'b0;
                fifo_wr_ptr    <= {FIFO_ADDR_WIDTH{1'b0}};
                fifo_rd_ptr    <= {FIFO_ADDR_WIDTH{1'b0}};
                fifo_count     <= {FIFO_COUNT_WIDTH{1'b0}};
            end else if (vsync_end) begin
                x_cnt          <= 16'd0;
                y_cnt          <= 16'd0;
                byte_phase     <= 1'b0;
                line_has_pixel <= 1'b0;
                sof_pending    <= 1'b1;
                frame_active   <= 1'b1;
                fifo_wr_ptr    <= {FIFO_ADDR_WIDTH{1'b0}};
                fifo_rd_ptr    <= {FIFO_ADDR_WIDTH{1'b0}};
                fifo_count     <= {FIFO_COUNT_WIDTH{1'b0}};
            end else if (href_end) begin
                x_cnt          <= 16'd0;
                byte_phase     <= 1'b0;
                line_has_pixel <= 1'b0;
                if (line_has_pixel && (y_cnt < V_ACTIVE)) begin
                    y_cnt <= y_cnt + 1'b1;
                end
                if (line_has_pixel && (y_cnt == V_ACTIVE - 1)) begin
                    frame_active <= 1'b0;
                end
            end else if (frame_active && href_active && !vsync_active) begin
                if (!byte_phase) begin
                    first_byte <= cam_data;
                    byte_phase <= 1'b1;
                end else begin
                    byte_phase <= 1'b0;
                    rgb565 <= RGB565_BYTE_ORDER ? {cam_data, first_byte} : {first_byte, cam_data};

                    if ((x_cnt < H_ACTIVE) && (y_cnt < V_ACTIVE)) begin
                        pixel_push     <= 1'b1;
                        pixel_data     <= pixel_rgb888;
                        pixel_user     <= sof_pending;
                        pixel_last     <= (x_cnt == H_ACTIVE - 1);
                        sof_pending    <= 1'b0;
                        line_has_pixel <= 1'b1;
                        x_cnt          <= x_cnt + 1'b1;

                        if (fifo_full && !fifo_pop) begin
                            probe_overflow <= 1'b1;
                        end
                    end
                end
            end else begin
                byte_phase <= 1'b0;
            end

            if (fifo_push) begin
                fifo_mem[fifo_wr_ptr] <= {pixel_user, pixel_last, pixel_data};
                fifo_wr_ptr <= fifo_wr_ptr + 1'b1;
            end

            case ({fifo_push, fifo_pop})
                2'b10: fifo_count <= fifo_count + 1'b1;
                2'b01: fifo_count <= fifo_count - 1'b1;
                default: fifo_count <= fifo_count;
            endcase
        end
    end

    assign m_axis_video_tvalid = !fifo_empty;
    assign m_axis_video_tdata  = fifo_mem[fifo_rd_ptr][23:0];
    assign m_axis_video_tuser  = fifo_mem[fifo_rd_ptr][25];
    assign m_axis_video_tlast  = fifo_mem[fifo_rd_ptr][24];

    assign probe_pixel_valid = pixel_push;
    assign probe_rgb565      = rgb565;
    assign probe_x           = x_cnt;
    assign probe_y           = y_cnt;

endmodule

2. I2C 控制器(SCCB 协议)

作用:简化I2C通信

频率和系统主频保持一致,启动参考
5.png

3. VDMA 双通道配置

启用 S2MM(写通道),配置和读通道保持一致即可
6.png

时钟方案

Clocking Wizard 配置

注意这块要看手上摄像头模块有没有自己的晶振,有的话需要引入这个pclk,没有的话需要自己在Clocking Wizard里分出来一个24M。

pclk供给vdma的读通道时钟和手写ov5640_dvp_to_axis_0的时钟

接线

Run Connection Automation

摄像头输入链路 :OV5640->ov5640_dvp_to_axis_0->VDMA S2MM->AXI SmartConnect->MIG DDR2

显示输出链路:VDMA MM2S->AXI SmartConnect->AXI4-Stream to Video Out

尾声

Generate Output Products

Create HDL Wrapper

分配引脚生成.xdc

综合实现生成比特流

关于OV5640的初始化表

图像质量不足的可以参考一下这一套,调校了几版效果还可以

static const ov5640_reg_t ov5640_isp_regs[] = {
    {0x3A0F, 0x30}, {0x3A10, 0x28}, {0x3A1B, 0x30}, {0x3A1E, 0x26},
    {0x3A11, 0x60}, {0x3A1F, 0x14}, {0x3A18, 0x00}, {0x3A19, 0x7C},
    {0x5000, 0xA7}, {0x5001, 0xA3},
    /* LENC lens shading correction */
    {0x5800, 0x23}, {0x5801, 0x14}, {0x5802, 0x0F}, {0x5803, 0x0F},
    {0x5804, 0x12}, {0x5805, 0x26}, {0x5806, 0x0C}, {0x5807, 0x08},
    {0x5808, 0x05}, {0x5809, 0x05}, {0x580A, 0x08}, {0x580B, 0x0D},
    {0x580C, 0x08}, {0x580D, 0x03}, {0x580E, 0x00}, {0x580F, 0x00},
    {0x5810, 0x03}, {0x5811, 0x09}, {0x5812, 0x07}, {0x5813, 0x03},
    {0x5814, 0x00}, {0x5815, 0x01}, {0x5816, 0x03}, {0x5817, 0x08},
    {0x5818, 0x0D}, {0x5819, 0x08}, {0x581A, 0x05}, {0x581B, 0x06},
    {0x581C, 0x08}, {0x581D, 0x0E}, {0x581E, 0x29}, {0x581F, 0x17},
    {0x5820, 0x11}, {0x5821, 0x11}, {0x5822, 0x15}, {0x5823, 0x28},
    {0x5824, 0x46}, {0x5825, 0x26}, {0x5826, 0x08}, {0x5827, 0x26},
    {0x5828, 0x64}, {0x5829, 0x26}, {0x582A, 0x24}, {0x582B, 0x22},
    {0x582C, 0x24}, {0x582D, 0x24}, {0x582E, 0x06}, {0x582F, 0x22},
    {0x5830, 0x40}, {0x5831, 0x42}, {0x5832, 0x24}, {0x5833, 0x26},
    {0x5834, 0x24}, {0x5835, 0x22}, {0x5836, 0x22}, {0x5837, 0x26},
    {0x5838, 0x44}, {0x5839, 0x24}, {0x583A, 0x26}, {0x583B, 0x28},
    {0x583C, 0x42}, {0x583D, 0xCE},
    {0x5180, 0xFF}, {0x5181, 0xF2}, {0x5182, 0x00}, {0x5183, 0x14},
    {0x5184, 0x25}, {0x5185, 0x24}, {0x5186, 0x09}, {0x5187, 0x09},
    {0x5188, 0x09}, {0x5189, 0x75}, {0x518A, 0x54}, {0x518B, 0xE0},
    {0x518C, 0xB2}, {0x518D, 0x42}, {0x518E, 0x3D}, {0x518F, 0x56},
    {0x5190, 0x46}, {0x5191, 0xF8}, {0x5192, 0x04}, {0x5193, 0x70},
    {0x5194, 0xF0}, {0x5195, 0xF0}, {0x5196, 0x03}, {0x5197, 0x01},
    {0x5198, 0x04}, {0x5199, 0x12}, {0x519A, 0x04}, {0x519B, 0x00},
    {0x519C, 0x06}, {0x519D, 0x82}, {0x519E, 0x38},
    {0x5381, 0x1E}, {0x5382, 0x5B}, {0x5383, 0x08}, {0x5384, 0x0A},
    {0x5385, 0x7E}, {0x5386, 0x88}, {0x5387, 0x7C}, {0x5388, 0x6C},
    {0x5389, 0x10}, {0x538A, 0x01}, {0x538B, 0x98},
    {0x5480, 0x01}, {0x5481, 0x08}, {0x5482, 0x14}, {0x5483, 0x28},
    {0x5484, 0x51}, {0x5485, 0x65}, {0x5486, 0x71}, {0x5487, 0x7D},
    {0x5488, 0x87}, {0x5489, 0x91}, {0x548A, 0x9A}, {0x548B, 0xAA},
    {0x548C, 0xB8}, {0x548D, 0xCD}, {0x548E, 0xDD}, {0x548F, 0xEA},
    {0x5490, 0x1D},
    {0x5580, 0x06}, {0x5583, 0x40}, {0x5584, 0x10}, {0x5589, 0x10},
    {0x558A, 0x00}, {0x558B, 0xF8}, {0x501D, 0x40},
    {0x5300, 0x08}, {0x5301, 0x30}, {0x5302, 0x10}, {0x5303, 0x00},
    {0x5304, 0x08}, {0x5305, 0x30}, {0x5306, 0x08}, {0x5307, 0x16},
    {0x5309, 0x08}, {0x530A, 0x30}, {0x530B, 0x04}, {0x530C, 0x06},
    {0x5025, 0x00},
    {OV5640_REG_END, 0x00}
};