OV5640 摄像头图像显示

概述

image.png
  OV5640 是一款 1/4 英寸单芯片图像传感器,其感光阵列达到 25921944(即 500W 像素),能实现最快 15fps QSXVGA(25921944)或者 90fps VGA(640*480)分辨率的图像采集。传感器采用 OmniVision 推出的 OmniBSI(背面照度)技术,使传感器达到更高的性能,如高灵敏度、低串扰和低噪声。传感器内 部集成了图像处理的功能,包括自动曝光控制(AEC)、自动白平衡(AWB)等。同时该传感器支持 LED 补光、MIPI(移动产业处理器接口)输出接口和 DVP(数字视频并行)输出接口选择、ISP(图像信号处理) 以及 AFC(自动聚焦控制)等功能。输出最大为 500 万像素的图像 (2592x1944 分辨率),支持使用 VGA 时序输出图像数据,输出图像的数据格式支持 YUV(422/420)、YCbCr422、 RGB565 以及 JPEG 格式,若直接输出 JPEG 格式的图 像时可大大减少数据量,方便网络传输。
OV5640 功能框图
   (1) 控制寄存器标号1处的是 OV5640 的控制寄存器,它根据这些寄存器配置的参数来运行,而 这些参数是由外部控制器通过 SIO_C 和 SIO_D 引脚写入的, SIO_C 与 SIO_D 使用的通讯协议跟 I2C 十分类似。
  (2) 通信、控制信号及时钟标号2处包含了 OV5640 的通信、控制信号及外部时钟,其中 PCLK、 HREF 及 VSYNC 分别是像素同步时钟、行同步信号以及帧同步信号,这与液晶屏控制中的信号是很类似的。 RESETB 引脚为低电平时,用于复位整个传感器芯片, PWDN 用于控制芯片进入低功耗模式。注意最后的一个 XCLK 引脚,它跟 PCLK 是完全不同的, XCLK 是用于驱动整个传感器芯片的时钟信号,是外部输入到 OV5640 的信号;而 PCLK 是 OV5640 输出数据时的同步信号,它是由 OV5640 输 出的信号。 XCLK 可以外接晶振或由外部控制器提供。
  (3) 感光矩阵标号3处的是感光矩阵,光信号在这里转化成电信号,经过各种处理,这些信号存储成由一个个像素点表示的数字图像。
  (4) 数据输出信号标号4处包含了 DSP 处理单元,它会根据控制寄存器的配置做一些基本的图像处理运算。这部分还包含了图像格式转换单元及压缩单元,转换出的数据最终通过 Y0-Y9 引脚输出,OV5640 在 RGB565 模式下只有高 8 位数据是有效的即 D[9:2], 因此我们仅使用 Y2-Y9 引脚。
  (5) 数据输出信号标号5处为 VCM 处理单元,他会通过图像分析来实现图像的自动对焦功能。要实现自动对焦还需要下载自动对焦固件到模组,后面摄像头实验详细介绍这个功能。

SCCB 时序

外部控制器对 OV5640 寄存器的配置参数是通过 SCCB 总线传输过去的,而 SCCB 总线跟 I2C 十分类似。

SCCB 的起始、停止信号及数据有效性

起始信号: 在 SCL(图中为 SIO_C) 为高电平时, SDA(图中为 SIO_D)出现一 个下降沿,则 SCCB 开始传输。
   停止信号:在 SCL 为高电平时, SDA 出现一个上升沿,则 SCCB 停止传输。
   数据有效性:除了开始和停止状态, 在数据传输过程中,当 SCL 为高电平时, 必须保证 SDA 上的数据稳定,也就是说, SDA 上的电平变换只能发生在 SCL 为 低电平的时候,SDA 的信号在 SCL 为高电平时被采集。
SCCB 起始、终止信号
SCCB 的数据有效性

SCCB 数据读写过程

在 SCCB 协议中定义的读写操作与 I2C 也是一样的,只是换了一种说法。它定义了两种写操作,即三步写操作和两步写操作。三步写操作可向从设备的一个目的寄存器中写入 数据,见图 。在三步写操作中,第一阶段发送从设备的 ID 地址+W 标志(等于 I2C 的 设备地址: 7 位设备地址+读写方向标志),第二阶段发送从设备目标寄存器的 8 位地址, 第三阶段发送要写入寄存器的 8 位数据。图中的“X”数据位可写入 1 或 0,对通讯无影响。
SCCB 的三步写操作
   而两步写操作没有第三阶段,即只向从器件传输了设备 ID+W 标志和目的寄存器的地址,见图 。两步写操作是用来配合后面的读寄存器数据操作的,它与读操作一起使 用,实现 I2C 的复合过程。
SCCB 的两步写操作
  两步读操作,它用于读取从设备目的寄存器中的数据,见图 。在第一阶段中发送从设备的设备 ID+R 标志(设备地址+读方向标志)和自由位,在第二阶段中读取寄存器中的 8 位数据和写 NA 位(非应答信号)。 由于两步读操作没有确定目的寄存器的地址,所以在 读操作前,必需有一个两步写操作,以提供读操作中的寄存器地址。
SCCB 的两步读操作

OV5640 的寄存器

控制 OV5640 涉及到它很多的寄存器,可直接查询《ov5640datasheet》了解,通过这 些寄存器的配置,可以控制它输出图像的分辨率大小、图像格式及图像方向等。要注意的是 OV5640 寄存器地址为 16 位。

像素数据输出时序

对 OV5640 采用 SCCB 协议进行控制,而它输出图像时则使用 VGA 时序(还可用 SVGA、 UXGA,这些时序都差不多),这跟控制液晶屏输入图像时很类似。 OV5640 输出 图像时,一帧帧地输出,在帧内的数据一般从左到右,从上到下,一个像素一个像素地输 出(也可通过寄存器修改方向)。 若我们使用 Y2-Y9 数据线,图像格式设置为 RGB565,进行数据输出 时, Y2-Y9 数据线会在 1 个像素同步时钟 PCLK 的驱动下发送 1 字节的数据信号,所以 2 个 PCLK 时钟可发送 1 个 RGB565 格式的像素数据。像素数据依次传输,每传输完一行数据时,行同步信号 HREF 会输出一个电平跳变信号,每传输完一帧图像时, VSYNC 会输出一个电平跳变信号。
DVP 接口时序

主要模块代码
图像采集模块

图像采集模块的主要功能是接收并拼接 OV5640 摄像头传入的图像数据
图像采集模块框图
image.png
  在实际的使用过程中,为了避免采集到摄像头刚初始化完成时的不稳定数据,一般舍弃前10帧图像后,再采集数据。因此通过 摄像头输入的帧同步信号来进行帧数的判断ov5640_vsync。同时,因为我们仅仅使用摄像头的8跟数据线采集数据,因此两个时钟周期才可以传输一个完整RGB565像素数据,对此,我们需要将采集到的数据进行拼接。

`timescale 1ns / 1ns
//
// Company: 
// Engineer: 
// 
// Create Date: 2024/03/20 17:06:21
// Design Name: 
// Module Name: OV5640_data
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module  OV5640_data#(
    parameter PIC_CNT_MAX   = 4'd10            //舍弃前10帧不稳定图像数据
)
(
    input   wire            sys_rst_n       ,   //复位信号
// OV5640
    input   wire            ov5640_pclk     ,   //摄像头像素时钟
    input   wire            ov5640_href     ,   //摄像头行同步信号
    input   wire            ov5640_vsync    ,   //摄像头场同步信号
    input   wire    [ 7:0]  ov5640_data     ,   //摄像头图像数据
// 写FIFO
    output  wire            ov5640_wr_en    ,   //图像数据有效使能信号
    output  wire    [15:0]  ov5640_data_out     //图像数据
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//wire  define
wire            pic_flag    ;   //帧图像标志信号,每拉高一次,代表一帧完整图像

//reg   define
reg             ov5640_vsync_dly    ;   //摄像头输入场同步信号打拍
reg     [3:0]   cnt_pic             ;   //图像帧计数器
reg             pic_valid           ;   //帧有效标志信号
reg     [7:0]   pic_data_reg        ;   //输入8位图像数据缓存
reg     [15:0]  data_out_reg        ;   //输出16位图像数据缓存
reg             data_flag           ;   //输入8位图像数据缓存
reg             data_flag_dly1      ;   //图像数据拼接标志信号打拍

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//ov5640_vsync_dly:摄像头输入场同步信号打拍
always@(posedge ov5640_pclk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        ov5640_vsync_dly    <=  1'b0;
    else
        ov5640_vsync_dly    <=  ov5640_vsync;

//pic_flag:帧图像标志信号,每拉高一次,代表一帧完整图像
assign  pic_flag = ((ov5640_vsync_dly == 1'b0)
                    && (ov5640_vsync == 1'b1)) ? 1'b1 : 1'b0;

//cnt_pic:图像帧计数器
always@(posedge ov5640_pclk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_pic <=  4'd0;
    else    if(cnt_pic < PIC_CNT_MAX)
        cnt_pic <=  cnt_pic + 1'b1;
    else
        cnt_pic <=  cnt_pic;

//pic_valid:帧有效标志
always@(posedge ov5640_pclk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        pic_valid   <=  1'b0;
    else    if((cnt_pic == PIC_CNT_MAX) && (pic_flag == 1'b1))
        pic_valid   <=  1'b1;
    else
        pic_valid   <=  pic_valid;

//data_out_reg,pic_data_reg,data_flag:输出16位图像数据缓冲
//输入8位图像数据缓存输入8位,图像数据缓存
always@(posedge ov5640_pclk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
            data_out_reg    <=  16'd0;
            pic_data_reg    <=  8'd0;
            data_flag       <=  1'b0;
        end
    else    if(ov5640_href == 1'b1)
        begin
            data_flag       <=  ~data_flag;
            pic_data_reg    <=  ov5640_data;
            data_out_reg    <=  data_out_reg;
        if(data_flag == 1'b1)
            data_out_reg    <=  {pic_data_reg,ov5640_data};
        else
            data_out_reg    <=  data_out_reg;
        end
    else
        begin
            data_flag       <=  1'b0;
            pic_data_reg    <=  8'd0;
            data_out_reg    <=  data_out_reg;
        end

//data_flag_dly1:图像数据缓存打拍
always@(posedge ov5640_pclk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        data_flag_dly1  <=  1'b0;
    else
        data_flag_dly1  <=  data_flag;

//ov5640_data_out:输出16位图像数据
assign  ov5640_data_out = (pic_valid == 1'b1) ? data_out_reg : 16'b0;

//ov5640_wr_en:输出16位图像数据使能
assign  ov5640_wr_en = (pic_valid == 1'b1) ? data_flag_dly1 : 1'b0;

endmodule
寄存器配置模块

要想 OV5640 摄像头正常工作,需要先对摄像头进行寄存器配置,即向摄像头寄存器 写入对应指令。
寄存器配置模块框图
image.png
  此处需要注意的是,摄像头上电之后,需要等待一段时间之后,才可以开始寄存器的配置。配置则是通过iic总线向寄存器发送命令。

`timescale 1ns / 1ns
//
// Company: 
// Engineer: 
// 
// Create Date: 2024/03/20 20:44:00
// Design Name: 
// Module Name: ov5640_cfg
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module ov5640_cfg#(
    parameter   CFG_NUM      = 9'd250,        //需配置的寄存器个数
    parameter   CNT_WAIT_MAX = 15'd20000      //ov640配置寄存器等待时间
)
(
    input   wire        sys_clk     , //时钟,IIC时钟 
    input   wire        sys_rst_n   , //复位
    input   wire        cfg_one_end , //一个寄存器配置完成,IIC传来

    output  reg         cfg_start   , //单个寄存器配置信号
    output  reg [23:0] cfg_data     , //寄存器地址+数据
    output  reg         cfg_done      //寄存器配置完成
    );

//reg define
    reg [8:0]   cfg_num ;      //寄存器配置完成计数器
    reg [14:0]  wait_cnt;      //等待计数 



//main code
//cfg_num已配置寄存器计数
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n == 1'b0)
            cfg_num <= 1'b0;
        else if(cfg_one_end == 1'b1)
            cfg_num <= cfg_num + 1'b1;
        else
            cfg_num <= cfg_num;
    end
//wait_cnt ov5640上电后需要等待一段时间,才可以开始配置寄存器
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n == 1'b0)
            wait_cnt <= 1'b0;
        else if(wait_cnt < CNT_WAIT_MAX )
            wait_cnt <= wait_cnt + 1'b1;
        else
            wait_cnt <= wait_cnt;
    end

//cfg_start:单个寄存器配置触发信号
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n == 1'b0)
            cfg_start <= 1'b0;
        else if((cfg_one_end == 1'b1) && (cfg_num < CFG_NUM))
            cfg_start <= 1'b1;
        else if(wait_cnt == CNT_WAIT_MAX - 1'b1)
            cfg_start <= 1'b1;
        else
            cfg_start <= 1'b0;
    end

//cfg_done
    always @(posedge sys_clk or negedge sys_rst_n) begin
        if(sys_rst_n == 1'b0)
            cfg_done <= 1'b0;
        else if(cfg_num == CFG_NUM && cfg_one_end == 1'b1)
            cfg_done <= 1'b1;
        else
            cfg_done <= cfg_done;
    end

    //----------------------------------------------------
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n)
        cfg_data <= 24'b0;
    else if(cfg_done == 1'b0)begin
        case(cfg_num)
            //先对寄存器进行软件复位,使寄存器恢复初始值
            //寄存器软件复位后,需要延时1ms才能配置其它寄存器
            8'd0  : cfg_data <= {16'h300a,8'h0}; //
            8'd1  : cfg_data <= {16'h300b,8'h0}; //
            8'd2  : cfg_data <= {16'h3008,8'h82}; //Bit[7]:复位 Bit[6]:电源休眠
            8'd3  : cfg_data <= {16'h3008,8'h02}; //正常工作模式
            8'd4  : cfg_data <= {16'h3103,8'h02}; //Bit[1]:1 PLL Clock
            //引脚输入/输出控制 FREX/VSYNC/HREF/PCLK/D[9:6]
            8'd5  : cfg_data <= {8'h30,8'h17,8'hff};
            //引脚输入/输出控制 D[5:0]/GPIO1/GPIO0 
            8'd6  : cfg_data <= {16'h3018,8'hff};
            8'd7  : cfg_data <= {16'h3037,8'h13}; //PLL分频控制
            8'd8  : cfg_data <= {16'h3108,8'h01}; //系统根分频器
            8'd9  : cfg_data <= {16'h3630,8'h36};
            8'd10 : cfg_data <= {16'h3631,8'h0e};
            8'd11 : cfg_data <= {16'h3632,8'he2};
            8'd12 : cfg_data <= {16'h3633,8'h12};
            8'd13 : cfg_data <= {16'h3621,8'he0};
            8'd14 : cfg_data <= {16'h3704,8'ha0};
            8'd15 : cfg_data <= {16'h3703,8'h5a};
            8'd16 : cfg_data <= {16'h3715,8'h78};
            8'd17 : cfg_data <= {16'h3717,8'h01};
            8'd18 : cfg_data <= {16'h370b,8'h60};
            8'd19 : cfg_data <= {16'h3705,8'h1a};
            8'd20 : cfg_data <= {16'h3905,8'h02};
            8'd21 : cfg_data <= {16'h3906,8'h10};
            8'd22 : cfg_data <= {16'h3901,8'h0a};
            8'd23 : cfg_data <= {16'h3731,8'h12};
            8'd24 : cfg_data <= {16'h3600,8'h08}; //VCM控制,用于自动聚焦
            8'd25 : cfg_data <= {16'h3601,8'h33}; //VCM控制,用于自动聚焦
            8'd26 : cfg_data <= {16'h302d,8'h60}; //系统控制
            8'd27 : cfg_data <= {16'h3620,8'h52};
            8'd28 : cfg_data <= {16'h371b,8'h20};
            8'd29 : cfg_data <= {16'h471c,8'h50};
            8'd30 : cfg_data <= {16'h3a13,8'h43}; //AEC(自动曝光控制)
            8'd31 : cfg_data <= {16'h3a18,8'h00}; //AEC 增益上限
            8'd32 : cfg_data <= {16'h3a19,8'hf8}; //AEC 增益上限
            8'd33 : cfg_data <= {16'h3635,8'h13};
            8'd34 : cfg_data <= {16'h3636,8'h03};
            8'd35 : cfg_data <= {16'h3634,8'h40};
            8'd36 : cfg_data <= {16'h3622,8'h01};
            8'd37 : cfg_data <= {16'h3c01,8'h34};
            8'd38 : cfg_data <= {16'h3c04,8'h28};
            8'd39 : cfg_data <= {16'h3c05,8'h98};
            8'd40 : cfg_data <= {16'h3c06,8'h00}; //light meter 1 阈值[15:8]
            8'd41 : cfg_data <= {16'h3c07,8'h08}; //light meter 1 阈值[7:0]
            8'd42 : cfg_data <= {16'h3c08,8'h00}; //light meter 2 阈值[15:8]
            8'd43 : cfg_data <= {16'h3c09,8'h1c}; //light meter 2 阈值[7:0]
            8'd44 : cfg_data <= {16'h3c0a,8'h9c}; //sample number[15:8]
            8'd45 : cfg_data <= {16'h3c0b,8'h40}; //sample number[7:0]
            8'd46 : cfg_data <= {16'h3810,8'h00}; //Timing Hoffset[11:8]
            8'd47 : cfg_data <= {16'h3811,8'h10}; //Timing Hoffset[7:0]
            8'd48 : cfg_data <= {16'h3812,8'h00}; //Timing Voffset[10:8]
            8'd49 : cfg_data <= {16'h3708,8'h64};
            8'd50 : cfg_data <= {16'h4001,8'h02}; //BLC(黑电平校准)补偿起始行号
            8'd51 : cfg_data <= {16'h4005,8'h1a}; //BLC(黑电平校准)补偿始终更新
            8'd52 : cfg_data <= {16'h3000,8'h00}; //系统块复位控制
            8'd53 : cfg_data <= {16'h3004,8'hff}; //时钟使能控制
            8'd54 : cfg_data <= {16'h4300,8'h61}; //格式控制 RGB565
            8'd55 : cfg_data <= {16'h501f,8'h01}; //ISP RGB
            8'd56 : cfg_data <= {16'h440e,8'h00};
            8'd57 : cfg_data <= {16'h5000,8'ha7}; //ISP控制
            8'd58 : cfg_data <= {16'h3a0f,8'h30}; //AEC控制;stable range in high
            8'd59 : cfg_data <= {16'h3a10,8'h28}; //AEC控制;stable range in low
            8'd60 : cfg_data <= {16'h3a1b,8'h30}; //AEC控制;stable range out high
            8'd61 : cfg_data <= {16'h3a1e,8'h26}; //AEC控制;stable range out low
            8'd62 : cfg_data <= {16'h3a11,8'h60}; //AEC控制; fast zone high
            8'd63 : cfg_data <= {16'h3a1f,8'h14}; //AEC控制; fast zone low
            //LENC(镜头校正)控制 16'h5800~16'h583d
            8'd64 : cfg_data <= {16'h5800,8'h23}; 
            8'd65 : cfg_data <= {16'h5801,8'h14};
            8'd66 : cfg_data <= {16'h5802,8'h0f};
            8'd67 : cfg_data <= {16'h5803,8'h0f};
            8'd68 : cfg_data <= {16'h5804,8'h12};
            8'd69 : cfg_data <= {16'h5805,8'h26};
            8'd70 : cfg_data <= {16'h5806,8'h0c};
            8'd71 : cfg_data <= {16'h5807,8'h08};
            8'd72 : cfg_data <= {16'h5808,8'h05};
            8'd73 : cfg_data <= {16'h5809,8'h05};
            8'd74 : cfg_data <= {16'h580a,8'h08};
            8'd75 : cfg_data <= {16'h580b,8'h0d};
            8'd76 : cfg_data <= {16'h580c,8'h08};
            8'd77 : cfg_data <= {16'h580d,8'h03};
            8'd78 : cfg_data <= {16'h580e,8'h00};
            8'd79 : cfg_data <= {16'h580f,8'h00};
            8'd80 : cfg_data <= {16'h5810,8'h03};
            8'd81 : cfg_data <= {16'h5811,8'h09};
            8'd82 : cfg_data <= {16'h5812,8'h07};
            8'd83 : cfg_data <= {16'h5813,8'h03};
            8'd84 : cfg_data <= {16'h5814,8'h00};
            8'd85 : cfg_data <= {16'h5815,8'h01};
            8'd86 : cfg_data <= {16'h5816,8'h03};
            8'd87 : cfg_data <= {16'h5817,8'h08};
            8'd88 : cfg_data <= {16'h5818,8'h0d};
            8'd89 : cfg_data <= {16'h5819,8'h08};
            8'd90 : cfg_data <= {16'h581a,8'h05};
            8'd91 : cfg_data <= {16'h581b,8'h06};
            8'd92 : cfg_data <= {16'h581c,8'h08};
            8'd93 : cfg_data <= {16'h581d,8'h0e};
            8'd94 : cfg_data <= {16'h581e,8'h29};
            8'd95 : cfg_data <= {16'h581f,8'h17};
            8'd96 : cfg_data <= {16'h5820,8'h11};
            8'd97 : cfg_data <= {16'h5821,8'h11};
            8'd98 : cfg_data <= {16'h5822,8'h15};
            8'd99 : cfg_data <= {16'h5823,8'h28};
            8'd100: cfg_data <= {16'h5824,8'h46};
            8'd101: cfg_data <= {16'h5825,8'h26};
            8'd102: cfg_data <= {16'h5826,8'h08};
            8'd103: cfg_data <= {16'h5827,8'h26};
            8'd104: cfg_data <= {16'h5828,8'h64};
            8'd105: cfg_data <= {16'h5829,8'h26};
            8'd106: cfg_data <= {16'h582a,8'h24};
            8'd107: cfg_data <= {16'h582b,8'h22};
            8'd108: cfg_data <= {16'h582c,8'h24};
            8'd109: cfg_data <= {16'h582d,8'h24};
            8'd110: cfg_data <= {16'h582e,8'h06};
            8'd111: cfg_data <= {16'h582f,8'h22};
            8'd112: cfg_data <= {16'h5830,8'h40};
            8'd113: cfg_data <= {16'h5831,8'h42};
            8'd114: cfg_data <= {16'h5832,8'h24};
            8'd115: cfg_data <= {16'h5833,8'h26};
            8'd116: cfg_data <= {16'h5834,8'h24};
            8'd117: cfg_data <= {16'h5835,8'h22};
            8'd118: cfg_data <= {16'h5836,8'h22};
            8'd119: cfg_data <= {16'h5837,8'h26};
            8'd120: cfg_data <= {16'h5838,8'h44};
            8'd121: cfg_data <= {16'h5839,8'h24};
            8'd122: cfg_data <= {16'h583a,8'h26};
            8'd123: cfg_data <= {16'h583b,8'h28};
            8'd124: cfg_data <= {16'h583c,8'h42};
            8'd125: cfg_data <= {16'h583d,8'hce};
            //AWB(自动白平衡控制) 16'h5180~16'h519e
            8'd126: cfg_data <= {16'h5180,8'hff};
            8'd127: cfg_data <= {16'h5181,8'hf2};
            8'd128: cfg_data <= {16'h5182,8'h00};
            8'd129: cfg_data <= {16'h5183,8'h14};
            8'd130: cfg_data <= {16'h5184,8'h25};
            8'd131: cfg_data <= {16'h5185,8'h24};
            8'd132: cfg_data <= {16'h5186,8'h09};
            8'd133: cfg_data <= {16'h5187,8'h09};
            8'd134: cfg_data <= {16'h5188,8'h09};
            8'd135: cfg_data <= {16'h5189,8'h75};
            8'd136: cfg_data <= {16'h518a,8'h54};
            8'd137: cfg_data <= {16'h518b,8'he0};
            8'd138: cfg_data <= {16'h518c,8'hb2};
            8'd139: cfg_data <= {16'h518d,8'h42};
            8'd140: cfg_data <= {16'h518e,8'h3d};
            8'd141: cfg_data <= {16'h518f,8'h56};
            8'd142: cfg_data <= {16'h5190,8'h46};
            8'd143: cfg_data <= {16'h5191,8'hf8};
            8'd144: cfg_data <= {16'h5192,8'h04};
            8'd145: cfg_data <= {16'h5193,8'h70};
            8'd146: cfg_data <= {16'h5194,8'hf0};
            8'd147: cfg_data <= {16'h5195,8'hf0};
            8'd148: cfg_data <= {16'h5196,8'h03};
            8'd149: cfg_data <= {16'h5197,8'h01};
            8'd150: cfg_data <= {16'h5198,8'h04};
            8'd151: cfg_data <= {16'h5199,8'h12};
            8'd152: cfg_data <= {16'h519a,8'h04};
            8'd153: cfg_data <= {16'h519b,8'h00};
            8'd154: cfg_data <= {16'h519c,8'h06};
            8'd155: cfg_data <= {16'h519d,8'h82};
            8'd156: cfg_data <= {16'h519e,8'h38};
            //Gamma(伽马)控制 16'h5480~16'h5490
            8'd157: cfg_data <= {16'h5480,8'h01}; 
            8'd158: cfg_data <= {16'h5481,8'h08};
            8'd159: cfg_data <= {16'h5482,8'h14};
            8'd160: cfg_data <= {16'h5483,8'h28};
            8'd161: cfg_data <= {16'h5484,8'h51};
            8'd162: cfg_data <= {16'h5485,8'h65};
            8'd163: cfg_data <= {16'h5486,8'h71};
            8'd164: cfg_data <= {16'h5487,8'h7d};
            8'd165: cfg_data <= {16'h5488,8'h87};
            8'd166: cfg_data <= {16'h5489,8'h91};
            8'd167: cfg_data <= {16'h548a,8'h9a};
            8'd168: cfg_data <= {16'h548b,8'haa};
            8'd169: cfg_data <= {16'h548c,8'hb8};
            8'd170: cfg_data <= {16'h548d,8'hcd};
            8'd171: cfg_data <= {16'h548e,8'hdd};
            8'd172: cfg_data <= {16'h548f,8'hea};
            8'd173: cfg_data <= {16'h5490,8'h1d};
            //CMX(彩色矩阵控制) 16'h5381~16'h538b
            8'd174: cfg_data <= {16'h5381,8'h1e};
            8'd175: cfg_data <= {16'h5382,8'h5b};
            8'd176: cfg_data <= {16'h5383,8'h08};
            8'd177: cfg_data <= {16'h5384,8'h0a};
            8'd178: cfg_data <= {16'h5385,8'h7e};
            8'd179: cfg_data <= {16'h5386,8'h88};
            8'd180: cfg_data <= {16'h5387,8'h7c};
            8'd181: cfg_data <= {16'h5388,8'h6c};
            8'd182: cfg_data <= {16'h5389,8'h10};
            8'd183: cfg_data <= {16'h538a,8'h01};
            8'd184: cfg_data <= {16'h538b,8'h98};
            //SDE(特殊数码效果)控制 16'h5580~16'h558b
            8'd185: cfg_data <= {16'h5580,8'h06};
            8'd186: cfg_data <= {16'h5583,8'h40};
            8'd187: cfg_data <= {16'h5584,8'h10};
            8'd188: cfg_data <= {16'h5589,8'h10};
            8'd189: cfg_data <= {16'h558a,8'h00};
            8'd190: cfg_data <= {16'h558b,8'hf8};
            8'd191: cfg_data <= {16'h501d,8'h40}; //ISP MISC
            //CIP(颜色插值)控制 (16'h5300~16'h530c)
            8'd192: cfg_data <= {16'h5300,8'h08};
            8'd193: cfg_data <= {16'h5301,8'h30};
            8'd194: cfg_data <= {16'h5302,8'h10};
            8'd195: cfg_data <= {16'h5303,8'h00};
            8'd196: cfg_data <= {16'h5304,8'h08};
            8'd197: cfg_data <= {16'h5305,8'h30};
            8'd198: cfg_data <= {16'h5306,8'h08};
            8'd199: cfg_data <= {16'h5307,8'h16};
            8'd200: cfg_data <= {16'h5309,8'h08};
            8'd201: cfg_data <= {16'h530a,8'h30};
            8'd202: cfg_data <= {16'h530b,8'h04};
            8'd203: cfg_data <= {16'h530c,8'h06};
            8'd204: cfg_data <= {16'h5025,8'h00};
            //系统时钟分频 Bit[7:4]:系统时钟分频 input clock =24Mhz, PCLK = 48Mhz
            8'd205: cfg_data <= {16'h3035,8'h11}; 
            8'd206: cfg_data <= {16'h3036,8'h3c}; //PLL倍频
            8'd207: cfg_data <= {16'h3c07,8'h08};
            //时序控制 16'h3800~16'h3821
            8'd208: cfg_data <= {16'h3820,8'h46};
            8'd209: cfg_data <= {16'h3821,8'h01};
            8'd210: cfg_data <= {16'h3814,8'h31};
            8'd211: cfg_data <= {16'h3815,8'h31};
            8'd212: cfg_data <= {16'h3800,8'h00};
            8'd213: cfg_data <= {16'h3801,8'h00};
            8'd214: cfg_data <= {16'h3802,8'h00};
            8'd215: cfg_data <= {16'h3803,8'h04};
            8'd216: cfg_data <= {16'h3804,8'h0a};
            8'd217: cfg_data <= {16'h3805,8'h3f};
            8'd218: cfg_data <= {16'h3806,8'h07};
            8'd219: cfg_data <= {16'h3807,8'h9b};
            //设置输出像素个数
            //DVP 输出水平像素点数高4位
            8'd220: cfg_data <= {16'h3808,8'h04};
            //DVP 输出水平像素点数低8位
            8'd221: cfg_data <= {16'h3809,8'h00};
            //DVP 输出垂直像素点数高3位
            8'd222: cfg_data <= {16'h380a,8'h02};
            //DVP 输出垂直像素点数低8位
            8'd223: cfg_data <= {16'h380b,8'h58};
            //水平总像素大小高5位
            8'd224: cfg_data <= {16'h380c,8'h08};
            //水平总像素大小低8位 
            8'd225: cfg_data <= {16'h380d,8'h98};
            //垂直总像素大小高5位 
            8'd226: cfg_data <= {16'h380e,8'h03};
            //垂直总像素大小低8位     
            8'd227: cfg_data <= {16'h380f,8'hE8};
            8'd228: cfg_data <= {16'h3813,8'h06};
            8'd229: cfg_data <= {16'h3618,8'h00};
            8'd230: cfg_data <= {16'h3612,8'h29};
            8'd231: cfg_data <= {16'h3709,8'h52};
            8'd232: cfg_data <= {16'h370c,8'h03};
            8'd233: cfg_data <= {16'h3a02,8'h17}; //60Hz max exposure
            8'd234: cfg_data <= {16'h3a03,8'h10}; //60Hz max exposure
            8'd235: cfg_data <= {16'h3a14,8'h17}; //50Hz max exposure
            8'd236: cfg_data <= {16'h3a15,8'h10}; //50Hz max exposure
            8'd237: cfg_data <= {16'h4004,8'h02}; //BLC(背光) 2 lines
            8'd238: cfg_data <= {16'h4713,8'h03}; //JPEG mode 3
            8'd239: cfg_data <= {16'h4407,8'h04}; //量化标度
            8'd240: cfg_data <= {16'h460c,8'h22};     
            8'd241: cfg_data <= {16'h4837,8'h22}; //DVP CLK divider
            8'd242: cfg_data <= {16'h3824,8'h02}; //DVP CLK divider
            8'd243: cfg_data <= {16'h5001,8'ha3}; //ISP 控制
            8'd244: cfg_data <= {16'h3b07,8'h0a}; //帧曝光模式  
            //彩条测试使能 
            8'd245: cfg_data <= {16'h503d,8'h00}; //8'h00:正常模式 8'h80:彩条显示
            //测试闪光灯功能
            8'd246: cfg_data <= {16'h3016,8'h02};
            8'd247: cfg_data <= {16'h301c,8'h02};
            8'd248: cfg_data <= {16'h3019,8'h02}; //打开闪光灯
            8'd249: cfg_data <= {16'h3019,8'h00}; //关闭闪光灯
            //只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
            default : cfg_data <= {16'h300a,8'h00}; //器件ID高8位
        endcase
    end
end
endmodule
iic模块

sccb时序与iic时序几乎一模一样,因此直接使用iic模块来进行摄像头寄存器的配置

`timescale 1ns / 1ns

module  i2c_ctrl
#(
    parameter   DEVICE_ADDR     =   7'h3c     ,   //i2c设备地址
    parameter   SYS_CLK_FREQ    =   26'd50_000_000  ,   //输入系统时钟频率
    parameter   SCL_FREQ        =   18'd250_000         //i2c设备scl时钟频率
)
(
    input   wire            sys_clk     ,   //输入系统时钟,25MHz
    input   wire            sys_rst_n   ,   //输入复位信号,低电平有效
    input   wire            wr_en       ,   //输入写使能信号
    input   wire            rd_en       ,   //输入读使能信号
    input   wire            i2c_start   ,   //输入i2c触发信号
    input   wire            addr_num    ,   //输入i2c字节地址字节数
    input   wire    [15:0]  byte_addr   ,   //输入i2c字节地址
    input   wire    [7:0]   wr_data     ,   //输入i2c设备数据

    output  reg             i2c_clk     ,   //i2c驱动时钟
    output  reg             i2c_end     ,   //i2c一次读/写操作完成
    output  reg     [7:0]   rd_data     ,   //输出i2c设备读取数据
    output  reg             i2c_scl     ,   //输出至i2c设备的串行时钟信号scl
    inout   wire            i2c_sda         //输出至i2c设备的串行数据信号sda
);

//************************************************************************//
//******************** Parameter and Internal Signal *********************//
//************************************************************************//
// parameter define
parameter   CNT_CLK_MAX     =   (SYS_CLK_FREQ/SCL_FREQ) >> 2'd3   ;   //cnt_clk计数器计数最大值

parameter   CNT_START_MAX   =   8'd100; //cnt_start计数器计数最大值

parameter   IDLE            =   4'd00,  //初始状态
            START_1         =   4'd01,  //开始状态1
            SEND_D_ADDR     =   4'd02,  //设备地址写入状态 + 控制写
            ACK_1           =   4'd03,  //应答状态1
            SEND_B_ADDR_H   =   4'd04,  //字节地址高八位写入状态
            ACK_2           =   4'd05,  //应答状态2
            SEND_B_ADDR_L   =   4'd06,  //字节地址低八位写入状态
            ACK_3           =   4'd07,  //应答状态3
            WR_DATA         =   4'd08,  //写数据状态
            ACK_4           =   4'd09,  //应答状态4
            START_2         =   4'd10,  //开始状态2
            SEND_RD_ADDR    =   4'd11,  //设备地址写入状态 + 控制读
            ACK_5           =   4'd12,  //应答状态5
            RD_DATA         =   4'd13,  //读数据状态
            N_ACK           =   4'd14,  //非应答状态
            STOP            =   4'd15;  //结束状态

// wire  define
wire            sda_in          ;   //sda输入数据寄存
wire            sda_en          ;   //sda数据写入使能信号

// reg   define
reg     [7:0]   cnt_clk         ;   //系统时钟计数器,控制生成clk_i2c时钟信号
reg     [3:0]   state           ;   //状态机状态
reg             cnt_i2c_clk_en  ;   //cnt_i2c_clk计数器使能信号
reg     [1:0]   cnt_i2c_clk     ;   //clk_i2c时钟计数器,控制生成cnt_bit信号
reg     [2:0]   cnt_bit         ;   //sda比特计数器
reg             ack             ;   //应答信号
reg             i2c_sda_reg     ;   //sda数据缓存
reg     [7:0]   rd_data_reg     ;   //自i2c设备读出数据

//************************************************************************//
//******************************* Main Code ******************************//
//************************************************************************//
// cnt_clk:系统时钟计数器,控制生成clk_i2c时钟信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_clk <=  8'd0;
    else    if(cnt_clk == CNT_CLK_MAX - 1'b1)
        cnt_clk <=  8'd0;
    else
        cnt_clk <=  cnt_clk + 1'b1;

// i2c_clk:i2c驱动时钟
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        i2c_clk <=  1'b1;
    else    if(cnt_clk == CNT_CLK_MAX - 1'b1)
        i2c_clk <=  ~i2c_clk;

// cnt_i2c_clk_en:cnt_i2c_clk计数器使能信号
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_i2c_clk_en  <=  1'b0;
    else    if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
        cnt_i2c_clk_en  <=  1'b0;
    else    if(i2c_start == 1'b1)
        cnt_i2c_clk_en  <=  1'b1;

// cnt_i2c_clk:i2c_clk时钟计数器,控制生成cnt_bit信号
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_i2c_clk <=  2'd0;
    else    if(cnt_i2c_clk_en == 1'b1)
        cnt_i2c_clk <=  cnt_i2c_clk + 1'b1;

// cnt_bit:sda比特计数器
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_bit <=  3'd0;
    else    if((state == IDLE) || (state == START_1) || (state == START_2)
                || (state == ACK_1) || (state == ACK_2) || (state == ACK_3)
                || (state == ACK_4) || (state == ACK_5) || (state == N_ACK))
        cnt_bit <=  3'd0;
    else    if((cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
        cnt_bit <=  3'd0;
    else    if((cnt_i2c_clk == 2'd3) && (state != IDLE))
        cnt_bit <=  cnt_bit + 1'b1;

// state:状态机状态跳转
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        state   <=  IDLE;
    else    case(state)
        IDLE:
            if(i2c_start == 1'b1)
                state   <=  START_1;
            else
                state   <=  state;
        START_1:
            if(cnt_i2c_clk == 3)
                state   <=  SEND_D_ADDR;
            else
                state   <=  state;
        SEND_D_ADDR:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_1;
            else
                state   <=  state;
        ACK_1:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                begin
                    if(addr_num == 1'b1)
                        state   <=  SEND_B_ADDR_H;
                    else
                        state   <=  SEND_B_ADDR_L;
                end
             else
                state   <=  state;
        SEND_B_ADDR_H:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_2;
            else
                state   <=  state;
        ACK_2:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                state   <=  SEND_B_ADDR_L;
            else
                state   <=  state;
        SEND_B_ADDR_L:
            if((cnt_bit == 3'd7) && (cnt_i2c_clk == 3))
                state   <=  ACK_3;
            else
                state   <=  state;
        ACK_3:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                begin
                    if(wr_en == 1'b1)
                        state   <=  WR_DATA;
                    else    if(rd_en == 1'b1)
                        state   <=  START_2;
                    else
                        state   <=  state;
                end
             else
                state   <=  state;
        WR_DATA:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_4;
            else
                state   <=  state;
        ACK_4:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                state   <=  STOP;
            else
                state   <=  state;
        START_2:
            if(cnt_i2c_clk == 3)
                state   <=  SEND_RD_ADDR;
            else
                state   <=  state;
        SEND_RD_ADDR:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  ACK_5;
            else
                state   <=  state;
        ACK_5:
            if((cnt_i2c_clk == 3) && (ack == 1'b0))
                state   <=  RD_DATA;
            else
                state   <=  state;
        RD_DATA:
            if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
                state   <=  N_ACK;
            else
                state   <=  state;
        N_ACK:
            if(cnt_i2c_clk == 3)
                state   <=  STOP;
            else
                state   <=  state;
        STOP:
            if((cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
                state   <=  IDLE;
            else
                state   <=  state;
        default:    state   <=  IDLE;
    endcase

// ack:应答信号
always@(*)
    case    (state)
        IDLE,START_1,SEND_D_ADDR,SEND_B_ADDR_H,SEND_B_ADDR_L,
        WR_DATA,START_2,SEND_RD_ADDR,RD_DATA,N_ACK:
            ack <=  1'b1;
        ACK_1,ACK_2,ACK_3,ACK_4,ACK_5:
            if(cnt_i2c_clk == 2'd0)
                ack <=  sda_in ;
            else
                ack <=  ack;
        default:    ack <=  1'b1;
    endcase

// i2c_scl:输出至i2c设备的串行时钟信号scl
always@(*)
    case    (state)
        IDLE:
            i2c_scl <=  1'b1;
        START_1:
            if(cnt_i2c_clk == 2'd3)
                i2c_scl <=  1'b0;
            else
                i2c_scl <=  1'b1;
        SEND_D_ADDR,ACK_1,SEND_B_ADDR_H,ACK_2,SEND_B_ADDR_L,
        ACK_3,WR_DATA,ACK_4,START_2,SEND_RD_ADDR,ACK_5,RD_DATA,N_ACK:
            if((cnt_i2c_clk == 2'd1) || (cnt_i2c_clk == 2'd2))
                i2c_scl <=  1'b1;
            else
                i2c_scl <=  1'b0;
        STOP:
            if((cnt_bit == 3'd0) &&(cnt_i2c_clk == 2'd0))
                i2c_scl <=  1'b0;
            else
                i2c_scl <=  1'b1;
        default:    i2c_scl <=  1'b1;
    endcase

// i2c_sda_reg:sda数据缓存
always@(*)
    case    (state)
        IDLE:
            begin
                i2c_sda_reg <=  1'b1;
                rd_data_reg <=  8'd0;
            end
        START_1:
            if(cnt_i2c_clk <= 2'd0)
                i2c_sda_reg <=  1'b1;
            else
                i2c_sda_reg <=  1'b0;
        SEND_D_ADDR:
            if(cnt_bit <= 3'd6)
                i2c_sda_reg <=  DEVICE_ADDR[6 - cnt_bit];
            else
                i2c_sda_reg <=  1'b0;
        ACK_1:
            i2c_sda_reg <=  1'b1;
        SEND_B_ADDR_H:
            i2c_sda_reg <=  byte_addr[15 - cnt_bit];
        ACK_2:
            i2c_sda_reg <=  1'b1;
        SEND_B_ADDR_L:
            i2c_sda_reg <=  byte_addr[7 - cnt_bit];
        ACK_3:
            i2c_sda_reg <=  1'b1;
        WR_DATA:
            i2c_sda_reg <=  wr_data[7 - cnt_bit];
        ACK_4:
            i2c_sda_reg <=  1'b1;
        START_2:
            if(cnt_i2c_clk <= 2'd1)
                i2c_sda_reg <=  1'b1;
            else
                i2c_sda_reg <=  1'b0;
        SEND_RD_ADDR:
            if(cnt_bit <= 3'd6)
                i2c_sda_reg <=  DEVICE_ADDR[6 - cnt_bit];
            else
                i2c_sda_reg <=  1'b1;
        ACK_5:
            i2c_sda_reg <=  1'b1;
        RD_DATA:
            if(cnt_i2c_clk  == 2'd2)
                rd_data_reg[7 - cnt_bit]    <=  sda_in;
            else
                rd_data_reg <=  rd_data_reg;
        N_ACK:
            i2c_sda_reg <=  1'b1;
        STOP:
            if((cnt_bit == 3'd0) && (cnt_i2c_clk < 2'd3))
                i2c_sda_reg <=  1'b0;
            else
                i2c_sda_reg <=  1'b1;
        default:
            begin
                i2c_sda_reg <=  1'b1;
                rd_data_reg <=  rd_data_reg;
            end
    endcase

// rd_data:自i2c设备读出数据
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rd_data <=  8'd0;
    else    if((state == RD_DATA) && (cnt_bit == 3'd7) && (cnt_i2c_clk == 2'd3))
        rd_data <=  rd_data_reg;

// i2c_end:一次读/写结束信号
always@(posedge i2c_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        i2c_end <=  1'b0;
    else    if((state == STOP) && (cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
        i2c_end <=  1'b1;
    else
        i2c_end <=  1'b0;

// sda_in:sda输入数据寄存
assign  sda_in = i2c_sda;
// sda_en:sda数据写入使能信号
assign  sda_en = ((state == RD_DATA) || (state == ACK_1) || (state == ACK_2)
                    || (state == ACK_3) || (state == ACK_4) || (state == ACK_5))
                    ? 1'b0 : 1'b1;
// i2c_sda:输出至i2c设备的串行数据信号sda
assign  i2c_sda = (sda_en == 1'b1) ? i2c_sda_reg : 1'bz;

endmodule
OV5640 顶层模块
`timescale 1ns / 1ns
//
// Company: 
// Engineer: 
// 
// Create Date: 2024/03/21 17:51:47
// Design Name: 
// Module Name: ov5640_top
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module ov5640_top#(
    parameter PIC_CNT_MAX   = 4'd10         ,   //舍弃前10帧不稳定图像数据
    parameter SLAVE_ADDR    = 7'h3c         ,   // 器件地址(SLAVE_ADDR)
    parameter BIT_CTRL      = 1'b1          ,   // 字地址位控制参数(16b/8b)
    parameter CLK_FREQ      = 26'd25_000_000,   // i2c_dri 模块的驱动时钟频率(CLK_FREQ)
    parameter I2C_FREQ      = 18'd250_000   ,   // I2C 的 SCL 时钟频率
    parameter CFG_NUM       = 9'd256        ,   // 需配置的寄存器个数
    parameter CNT_WAIT_MAX  = 15'd20000         //配置寄存最大等待时间,超过跳过该寄存器配置
)
(
    input   wire            sys_clk         ,   //50Mhz
    input   wire            sys_rst_n       ,
    input   wire            ov5640_pclk     ,   //摄像头像素时钟
    input   wire            ov5640_vsync    ,   //摄像头行同步信号
    input   wire            ov5640_href     ,   //摄像头场同步信号
    input   wire            sys_init_done   ,   //系统初始化完成(DDR3 SDRAM + 摄像头)
    input   wire [ 7:0]     ov5640_data     ,   //摄像头图像数据

    output  wire             ov5640_wr_en    ,   //图像数据有效使能信号
    output  wire [15:0]      ov5640_data_out ,   //图像数据
    output  wire             cfg_done        ,
    output  wire             sccb_scl        ,
    output  wire             sccb_sda
    );

//wire define
    wire            cfg_one_end ;
    wire            cfg_start   ;
    wire    [23:0]  cfg_data    ;
    wire            cfg_clk ;
    i2c_ctrl 
#(
    .DEVICE_ADDR  (SLAVE_ADDR   ),   //i2c设备地址
    .SYS_CLK_FREQ (CLK_FREQ     ),   //输入系统时钟频率
    .SCL_FREQ     (I2C_FREQ     )    //i2c设备scl时钟频率
)
i2c_ctrl_inst
(
.sys_clk         (sys_clk       ),   //输入25MHz
.sys_rst_n       (sys_rst_n     ),   //输入复位信号,低电平有效
.wr_en           (1'b1          ),   //输入写使能信号
.rd_en           (1'b0          ),   //输入读使能信号
.i2c_start       (cfg_start     ),   //输入i2c触发信号
.addr_num        (BIT_CTRL      ),   //输入i2c字节地址字节数
.byte_addr       (cfg_data[23:8]),   //输入i2c字节地址
.wr_data         (cfg_data[7:0] ),   //输入i2c设备数据.
.i2c_clk         (cfg_clk       ),   //i2c驱动时钟
.i2c_end         (cfg_one_end   ),   //i2c一次读/写操作完成
.rd_data         (              ),   //输出i2c设备读取数据
.i2c_scl         (sccb_scl      ),   //输出至i2c设备的串行时钟信号scl
.i2c_sda         (sccb_sda      )    //输出至i2c设备的串行数据信号sda
);

OV5640_data
#(
    .PIC_CNT_MAX    (PIC_CNT_MAX)   //舍弃前10帧不稳定图像数据
)  OV5640_data_inst
(
.ov5640_pclk       (ov5640_pclk    ), //摄像头工作时钟 25MHz
.sys_rst_n         (sys_rst_n & sys_init_done), //复位信号
.ov5640_vsync      (ov5640_vsync   ), //场同步信号
.ov5640_href       (ov5640_href    ), //行同步信号
.ov5640_data       (ov5640_data    ), //图像信息
.ov5640_wr_en      (ov5640_wr_en   ), //像素数据写入DDR3使能
.ov5640_data_out   (ov5640_data_out)  //写入DDR3使能的像素数据
);


ov5640_cfg#(
.CFG_NUM         (CFG_NUM     ),            //需配置的寄存器个数
.CNT_WAIT_MAX    (CNT_WAIT_MAX)            //配置寄存最大等待时间,超过跳过该寄存器配置
)
ov5640_cfg_inst
(
.sys_clk        (cfg_clk    ), //时钟,IIC时钟
.sys_rst_n      (sys_rst_n  ), //复位
.cfg_one_end    (cfg_one_end), //一个寄存器配置完成,IIC传来

.cfg_start      (cfg_start  ), //单个寄存器配置信号
.cfg_data       (cfg_data   ), //寄存器地址+数据
.cfg_done       (cfg_done   )  //寄存器配置完成
    );
endmodule

摄像头各个模块例化
最后为了实现摄像头在LCD屏幕上的显示,又增加了DDR3模块与LCD显示模块。将摄像头数据缓存至ddr3之中,然后显示屏在读取。

显示顶层模块
`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2024/03/23 18:48:44
// Design Name: 
// Module Name: ov5640_ddr_lcd
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// ov5640摄像头采集数据,存储到DDR3中(带乒乓操作),然后显示到LCD上
// Dependencies: 
//                  2116815480@qq.com
//                  https://www.cnblogs.com/fangrunze
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module ov5640_ddr_lcd#(
    parameter SLAVE_ADDR    = 7'h3c         ,   // ov5640器件地址(SLAVE_ADDR)
    parameter BIT_CTRL      = 1'b1          ,   // 字地址位控制参数(16b/8b)
    parameter CLK_FREQ      = 26'd50_000_000,   // i2c_dri 模块的驱动时钟频率(CLK_FREQ)
    parameter I2C_FREQ      = 18'd250_000   ,   // I2C 的 SCL 时钟频率
    parameter CFG_NUM       = 9'd250        ,   // 需配置的寄存器个数
    parameter CNT_WAIT_MAX  = 15'd20000     ,   //配置ov5640寄存器等待时间
    parameter PIC_CNT_MAX   = 4'd10         ,   //舍弃前10帧不稳定图像数据


    parameter H_PIC         = 11'd1024      ,   //图片长度
    parameter W_PIC         = 10'd600      ,    //图片宽度
    parameter PIC_SIZE      = 20'd614400   ,    //图片像素个数
    parameter baby_blue     = 24'hADD8E6        //空余位置图片颜色  浅蓝色
)
(
    input   wire         sys_clk,
    input   wire         sys_rst_n,

//OV5640
    input   wire         ov5640_pclk  ,  //摄像头像素时钟
    input   wire         ov5640_vsync ,  //摄像头行同步信号
    input   wire         ov5640_href  ,  //摄像头场同步信号
    input   wire [ 7:0]  ov5640_data  ,  //摄像头图像数据
    output  wire         ov5640_rst_n ,  //摄像头复位
    output  wire         ov5640_pwdn  ,  //摄像头电源控制

    output  wire         sccb_scl     , //sccb_scl
    output  wire         sccb_sda     , //sccb_sda

//RGB-LCD 接口
    output  wire         lcd_de         , //LCD 数据使能信号
    output  wire         lcd_hs         , //LCD 行同步信号
    output  wire         lcd_vs         , //LCD 场同步信号
    output  wire         lcd_bl         , //LCD 背光控制信号
    output  wire         lcd_clk        , //LCD 像素时钟
    output  wire         lcd_rst        , //LCD 复位  
    output  wire [23:0]  lcd_rgb        , //LCD RGB数据
//DDR3接口
    inout   wire [15:0]  ddr3_dq        , //DDR3 数据
    inout   wire [1:0]   ddr3_dqs_n     , //DDR3 dqs负
    inout   wire [1:0]   ddr3_dqs_p     , //DDR3 dqs正
    output  wire [13:0]  ddr3_addr      , //DDR3 地址
    output  wire [2:0]   ddr3_ba        , //DDR3 banck 选择
    output  wire         ddr3_ras_n     , //DDR3 行选择
    output  wire         ddr3_cas_n     , //DDR3 列选择
    output  wire         ddr3_we_n      , //DDR3 读写选择
    output  wire         ddr3_reset_n   , //DDR3 复位
    output  wire [0:0]   ddr3_ck_p      , //DDR3 时钟正
    output  wire [0:0]   ddr3_ck_n      , //DDR3 时钟负
    output  wire [0:0]   ddr3_cke       , //DDR3 时钟使能
    output  wire [0:0]   ddr3_cs_n      , //DDR3 片选
    output  wire [1:0]   ddr3_dm        , //DDR3_dm
    output  wire [0:0]   ddr3_odt         //DDR3_odt
    );



//wire define==============================================================
    wire            locked          ;   //锁相环锁定
    wire            rst_n           ;   //系统复位
    wire            clk_50m         ;   //ov5640输入时钟 50Mhz
    wire            sys_init_done   ;   //系统初始化完成(DDR3 SDRAM + 摄像头)
    wire            ov5640_wr_en    ;   //图像数据有效使能信号
    wire [15:0]     ov5640_data_out ;   //ov5640输出的图像数据
    wire            cfg_done        ;   //ov5640寄存器配置完成
    wire            ddr3_clk        ;   //DDR3时钟     
    wire            data_req        ;   //ddr3读使能
    wire [15:0]     rd_data         ;   //FIFO读数据
    wire            calib_done      ;   //DDR3 控制器初始化完成标,高有效



    assign  sys_init_done = (calib_done)&(cfg_done);    //系统初始化完成(DDR3 SDRAM + 摄像头)
    assign  rst_n = sys_rst_n & calib_done & locked ;   //系统复位
   //ov5640_rst_n:摄像头复位,固定高电平
    assign  ov5640_rst_n = 1'b1;
    assign  ov5640_pwdn  = 1'b0;

//=========================================================================
//===时钟模块
//=========================================================================
clk_gen  clk_gen_inst
  (
    .clk_in1    (sys_clk    ) ,
    .reset      (~sys_rst_n ) , 

    .clk_out1   (clk_50m    ) , //50Mhz
    .clk_out2   (ddr3_clk   ) , //200Mhz
    .locked     (locked     )
 
  );

//=========================================================================
//===ov5640模块
//=========================================================================
ov5640_top#(
    .SLAVE_ADDR       (SLAVE_ADDR   ),  // 器件地址
    .BIT_CTRL         (BIT_CTRL     ),  // 字地址位控制参数
    .CLK_FREQ         (CLK_FREQ     ),  // i2c_dri 模块的驱动时钟频率
    .I2C_FREQ         (I2C_FREQ     ),  // I2C 的 SCL 时钟频率
    .CFG_NUM          (CFG_NUM      ),  // 需配置的寄存器个数
    .CNT_WAIT_MAX     (CNT_WAIT_MAX )   //配置ov5640寄存器等待时间
) 
ov5640_top_inst
(
    .sys_clk          (clk_50m    ) ,   //25Mhz
    .sys_rst_n        (rst_n          ) ,
    .ov5640_pclk      (ov5640_pclk    ) ,   //摄像头像素时钟
    .ov5640_vsync     (ov5640_vsync   ) ,   //摄像头行同步信号
    .ov5640_href      (ov5640_href    ) ,   //摄像头场同步信号
    .sys_init_done    (sys_init_done  ) ,   //系统初始化完成(DDR3 SDRAM + 摄像头)
    .ov5640_data      (ov5640_data    ) ,   //摄像头图像数据
    .ov5640_wr_en     (ov5640_wr_en   ) ,   //图像数据有效使能信号
    .ov5640_data_out  (ov5640_data_out) ,   //图像数据
    .cfg_done         (cfg_done       ) ,   //ov5640寄存器配置完成
    .sccb_scl         (sccb_scl       ) ,   //sccb_scl
    .sccb_sda         (sccb_sda       )     //sccb_sda
);
//=========================================================================
//==DDR读写控制部分
//=========================================================================
ddr3_ctrl_2port #(
    .FIFO_DW            (16             ),  //模块写 FIFO 的写数据和读 FIFO 的读数据位宽
    .PINGPANG           (1              ),  //PingPang 模式选择,0:PingPang 模式,1:PingPang 模式
    .WR_BYTE_ADDR_BEGIN (0              ),  //写数据存储空间的起始地址,
    .WR_BYTE_ADDR_END   ( PIC_SIZE*2    ),  //写数据存储空间的终止地址,一个地址对应一个字节
    .RD_BYTE_ADDR_BEGIN (0              ),  //取数据存储空间的起始地址,
    .RD_BYTE_ADDR_END   ( PIC_SIZE*2    )   //取数据存储空间的终止地址,
    )
inst_ddr3_ctrl_2port(
    .ddr3_clk200m       (ddr3_clk                   ),  //DDR3时钟
    .ddr3_rst_n         (sys_rst_n&locked           ),  //DDR3复位
    .ddr3_init_done     (calib_done                 ),  //DDR3 控制器初始化完成标,高有效
    .wrfifo_clr         ( ~(sys_rst_n&locked)       ),  //写 FIFO 清空控制信号,给高电平
    .wrfifo_clk         (ov5640_pclk                ),  //FIFO写时钟
    .wrfifo_wren        (ov5640_wr_en               ),  //fifo写使能
    .wrfifo_din         (ov5640_data_out            ),  //fifo写数据
    .wrfifo_full        (                           ),  //写 FIFO 的写满标识信号
    .wrfifo_wr_cnt      (                           ),  //写 FIFO 的写数据计数
    .rdfifo_clr         (  ~(sys_rst_n&locked)      ),  //读 FIFO 清空控制信号,给高电平
    .rdfifo_clk         (lcd_clk                    ),  //FIFO读时钟
    .rdfifo_rden        (data_req                   ),  //FIFO读使能
    .rdfifo_dout        (rd_data                    ),  //FIFO读数据
    .rdfifo_empty       (                           ),  //读FIFO空标志
    .rdfifo_rd_cnt      (                           ),  //读FIFO读数据计数
    //ddr3接口
    .ddr3_dq            (ddr3_dq                    ),  //DDR3 数据    
    .ddr3_dqs_n         (ddr3_dqs_n                 ),  //DDR3 dqs负
    .ddr3_dqs_p         (ddr3_dqs_p                 ),  //DDR3 dqs正  
    .ddr3_addr          (ddr3_addr                  ),  //DDR3 地址   
    .ddr3_ba            (ddr3_ba                    ),  //DDR3 banck 选择
    .ddr3_ras_n         (ddr3_ras_n                 ),  //DDR3 行选择
    .ddr3_cas_n         (ddr3_cas_n                 ),  //DDR3 列选择
    .ddr3_we_n          (ddr3_we_n                  ),  //DDR3 读写选择
    .ddr3_reset_n       (ddr3_reset_n               ),  //DDR3 复位
    .ddr3_ck_p          (ddr3_ck_p                  ),  //DDR3 时钟正
    .ddr3_ck_n          (ddr3_ck_n                  ),  //DDR3 时钟负
    .ddr3_cke           (ddr3_cke                   ),  //DDR3 时钟使能
    .ddr3_cs_n          (ddr3_cs_n                  ),  //DDR3 片选
    .ddr3_dm            (ddr3_dm                    ),  //DDR3_dm
    .ddr3_odt           (ddr3_odt                   )  //DDR3_odt
);  
//=========================================================================
//===LCD控制部分
//=========================================================================
top_rgb#(

   .H_PIC             (H_PIC    ),  //图片长度
   .W_PIC             (W_PIC    ),  //图片宽度
   .PIC_SIZE          (PIC_SIZE ),  //图片像素个数
   .baby_blue         (baby_blue)   //空余位置图片颜色  浅蓝色
) 
top_rgb_inst
(
   .sys_clk           (clk_50m   ), //系统时钟
   .sys_rst_n         (rst_n &sys_init_done      ), //系统复位
   .lcd_pclk          (clk_50m    ), //lcd 时钟
   .pi_data           (rd_data    ), //FIFO读数据
   .data_req          (data_req   ), //ddr3读使能
   .lcd_de            (lcd_de     ), //LCD 数据使能信号
   .lcd_hs            (lcd_hs     ), //LCD 行同步信号
   .lcd_vs            (lcd_vs     ), //LCD 场同步信号
   .lcd_bl            (lcd_bl     ), //LCD 背光控制信号
   .lcd_clk           (lcd_clk    ), //LCD 像素时钟
   .lcd_rst           (lcd_rst    ), //LCD 复位
   .lcd_rgb           (lcd_rgb    )  //LCD RGB数据
);
endmodule

顶层模块例化了3个小模块,其中时钟模块产生的50MHz时钟,用于系统以及摄像头使用,200MHz则提供给DDR3使用。
  注意:这里的ddr3使用的是小梅哥的程序,但是经过修改,支持乒乓操作,如果不使用乒乓操作,则会使得的显示的摄像头图像有明显的撕裂感!!!!!!!!!(具体修改见代码)
  其他模块就不再介绍,代码,下边上传的工程代码之中,会有详细的代码注释。

上机效果

使用屏幕:1024*600 屏幕
使用开发板:正点原子达芬奇FPGA开发板
image.png

源代码

链接: https://pan.baidu.com/s/1N1QqlSDH85mqPFGE5NXXPg?pwd=uwfd 提取码: uwfd 复制这段内容后打开百度网盘手机App,操作更方便哦

参考文献

[1] 正点原子. 达芬奇之FPGA开发指南
  [2] 野火. FPGA+Verilog开发实战指南——基于Xilinx+Spartan6
  [3] 小梅哥.小梅哥Xilinx ACX720 FPGA开发板自学教程V3.1.2

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是ov5640摄像头显示的简单例程: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/i2c-dev.h> #include <linux/types.h> #include <linux/spi/spidev.h> #define OV5640_ADDR 0x78 int fd_i2c; int ov5640_write_reg(unsigned short reg, unsigned char value) { unsigned char buf[2]; buf[0] = reg >> 8; buf[1] = reg & 0xff; if (write(fd_i2c, buf, 2) != 2) { return -1; } buf[0] = value; if (write(fd_i2c, buf, 1) != 1) { return -1; } return 0; } int ov5640_init() { ov5640_write_reg(0x3008, 0x80); usleep(10000); ov5640_write_reg(0x3103, 0x93); usleep(10000); ov5640_write_reg(0x3008, 0x00); usleep(10000); ov5640_write_reg(0x3008, 0x20); usleep(10000); ov5640_write_reg(0x3008, 0xa0); usleep(10000); ov5640_write_reg(0x3103, 0x03); usleep(10000); ov5640_write_reg(0x3008, 0x00); usleep(10000); ov5640_write_reg(0x3103, 0x93); usleep(10000); return 0; } int ov5640_start() { ov5640_write_reg(0x3103, 0x03); usleep(10000); ov5640_write_reg(0x3008, 0x02); usleep(10000); ov5640_write_reg(0x3008, 0xa2); usleep(10000); return 0; } int main() { fd_i2c = open("/dev/i2c-0", O_RDWR); if (fd_i2c < 0) { printf("Cannot open i2c device\n"); return -1; } if (ioctl(fd_i2c, I2C_SLAVE, OV5640_ADDR) < 0) { printf("Cannot set i2c slave address\n"); return -1; } if (ov5640_init() < 0) { printf("Cannot init ov5640 camera\n"); return -1; } if (ov5640_start() < 0) { printf("Cannot start ov5640 camera\n"); return -1; } printf("ov5640 camera started\n"); // Add your code to display the image captured by the ov5640 camera here return 0; } ``` 这个例程通过 i2c 总线与 ov5640 摄像头通信,初始化 ov5640 摄像头并启动它。在 ov5640 摄像头启动后,你需要自己编写代码来显示 ov5640 摄像头捕获的图像

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值