【FPGA】摄像头模块OV5640



一、OV5640简介

1.1 基本概述

  OV5640是OV(OMNIVISION)公司设计的一款CMOS图像传感器,最高输出500万像素的图像,最高分辨篇格式为QSXVGA(2592×1944),数据接口采用DVP,控制接口为SCCB。可以输出RGB565/RGB555/RGB444、YUV(422/420)、YCbCr422和JPEG格式,可以对图像进行白平衡、饱和度、色度、锐度、Gamma曲线等调节。图像分辨率、帧率可调。
  OV5640支持LED补光、MIPI(移动产业处理器接口,常用于手机摄像头)输出接口和 DVP(数字视频并行,本次实验使用这个接口) 输出接口选择、ISP(图像信号处理)以及AFC(自动聚焦控制)等功能。
在这里插入图片描述
  控制OV5640的核心在于了解它的时序电路和数据输出之间的关系,在FPGA端需要注意以下标黄的端口,它们都是OV5640的控制输入引脚。下面对时序生成和系统逻辑控制电路的引脚作一些说明:

  • PWDN休眠控制
  • RESETB复位信号
  • FREX帧曝光控制
  • GPIO[3:0]:和自动聚焦和防震动相关配置有关(本次实验没有用到)
  • PCLK:像素同步时钟(由模块板载晶振提供)
  • HREF:行同步信号,可以理解为像素数据有效信号
  • VSYNC:场同步信号
  • STROBE:闪光灯控制(连接到模块板载LED)

  OV5640支持2592×1944(QSXVGA)及以下任意分辨率图像的输出。它是通过缩放和修改相关像素定位寄存器实现的。相关配置寄存器的配置地址及意义由下图给出。
在这里插入图片描述

  • X/Y_ADDR_ST/END:输出像素的开始位置和结束位置。
  • X/Y_OFFSET:缩放前输出场的偏移。
  • X/Y_OUTPUT_SIZE:输出像素大小。

1.2 工作时序

1.2.1 DVP Timing(数据传输时序)

在这里插入图片描述
在这里插入图片描述
  上图表示了OV5640在DVP模式下的数据输出时序,同时列出了以VGA(640×480)分辨率为例的不同区间的时间。

  • tp:在RGB565格式下,一个 t p tp tp等于两倍的 t P C L K t_{PCLK} tPCLK,因为一个像素的有效数据需要两个时钟周期传输。

1.2.2 帧曝光工作模式

在这里插入图片描述
  OV5640拥有三种曝光模式,在模式0下,帧曝光控制引脚FREQ作输入,曝光脉冲请求通过FPGA发出;在模式1下,曝光请求通过I2C(SCCB)总线发出,帧曝光控制引脚FREQ作输出,通知外部主机(FPGA)将要开始曝光,LED功能只有在模式0和模式1下才会工作;滚动曝光模式下曝光控制功能失效。
在这里插入图片描述

1.3 OV5640 闪光灯工作模式

在这里插入图片描述

  在了解闪光灯信号strobe信号之前,需要注意,闪光灯的所有工作模式只在帧曝光模式0和模式1下有效。滚动帧曝光下闪光灯不工作。

1.3.1 Xenon Flash(氙灯闪烁)模式

  "Xenon"是指氙灯(xenon flash)模式。氙灯是一种基于氙气放电的强光源,当电流通过充满氙气的灯管时,氙气被激发产生亮白色的光。在摄影和摄像中,氙灯闪光灯被广泛用于提供短时间内的高强度照明,以便在拍摄照片或视频时,尤其是在低光环境下,获得更好的曝光效果。在脉冲请求strobe request信号到来后,经历三帧时间,strobe pulse将输出一个极短的脉冲。该功能需要通过寄存器配置。上电后默认工作在该模式下。
在这里插入图片描述

1.3.2 LED 1&2 模式

  LED1和LED2模式均为LED闪烁模式,在LED曝光期间的帧数据会跳过输出。该功能需要寄存器配置。
在这里插入图片描述
在这里插入图片描述

1.3.3 LED 3模式

  在LED3模式下,LED灯会保持常亮。
在这里插入图片描述

1.3.4 手动开启闪光灯

  与LED工作模式无关,我们可以通过SCCB总线发送指令修改寄存器的值,手动开启或关闭闪光灯。通过依次配置寄存器0x30160x301C0x3019Bit[1]即可打开或关闭闪光灯。

在这里插入图片描述
在这里插入图片描述
asd

1.4 模块硬件设计

  可以从原理图中看到,该模块可以输出一个24MHz的时钟给外部。
在这里插入图片描述

二、SCCB通信协议

  SCCB(Serial Camera Control Bus,串行摄像头控制总线)是由OV(OMNIVISION)公司定义和发展的两线/三线式串行总线。该总线控制OV系列摄像头大部分的功能,包括图像数据格式、分辨篇以及图像处理参数等。两线SCCB只能实现“一主一从”的控制,而三线结构可以实现对多个从机进行控制。OV公司为了减少传感器引脚的封装,现在SCCB大多采用两线式接口总线。

  • SIO_C/SCL:只能由主机(FPGA)配置;
  • SIO_D/SDA:上面有一个三态门,可以实现双向数据传输,可以由主机控制,也可以由从机控制

  SCCB协议与I2C协议十分相似,甚至在有些驱动历程中直接将I2C的驱动程序拿来使用。了解I2C协议之前有必要熟悉I2C协议的相关使用,在这里不作过多赘述。

2.1 SCCB读写时序

  SCCB 总线跟 I2C 十分类似,起始信号、停止信号与 I2C 一样,SCCB 定义数据传输的基本单元为相(phase),每个相传输一个字节数据。SCCB 只包含三种传输周期:三相写周期、两相写周期和两相读周期。

  1. 三相写周期:依次为(开始信号)设备地址(写命令)、寄存器地址、数据(结束信号)。
    在这里插入图片描述

  2. 两相写周期:依次为(开始信号)设备地址(写命令)、寄存器地址(结束信号)。
    在这里插入图片描述

  3. 两相读周期:依次为(开始信号)设备地址(读命令)、数据(结束信号)。
    在这里插入图片描述

  在写入数据时,只执行三相写周期;在读出数据时,先执行两相写周期(虚写),再执行两相读周期。在SCCB协议中,每一相后有一个X信号,表示不需要关心这一位信号,与I2C协议不同,SCCB协议不用等待从器件响应(拉低)。两相读周期最后的NA信号表示不响应,即主机不会拉低信号线给从机发送响应。

2.2 SCCB与I2C的区别

  1. SCCB第九位为不关心位,而I2C传输协议中为应答位。
  2. SCCB每次传输不超过三相(Phase),即不能连续读写;I2C可以连续读写。
  3. SCCB读传输协议中没有重复开始的操作,在虚写完成之后,必须先发出一次停止信号,再重新发出开始信号;I2C协议中不需要在虚写之后添加停止,只需要再发送一次开始信号并执行后续操作即可。

三、代码设计

3.1 设置输出像素大小

在这里插入图片描述
  OV5640通过SCCB总线写入对应位置寄存器设置输出像素大小。实际上I2C的驱动程序完全可以兼容SCCB通信,所以重点在于OV5640初始化工作中对寄存器值的操作,在正点原子的历程中要配置250个寄存器,还是相当繁琐的。其中首先要注意的就是输出像素大小的定义。

3.1.1 设置 ISP input size(X/Y_ADDR)

	/* 部分设置代码…… */

	// 设置感光区域的“开窗大小”,开窗区域不是最终的显示区域
	8'd212:
        i2c_data <= {16'h3800,8'h00};
    8'd213:
        i2c_data <= {16'h3801,8'h00};	// 起始点x坐标 16'h0000
    8'd214:
        i2c_data <= {16'h3802,8'h00};
    8'd215:
        i2c_data <= {16'h3803,8'h04};	// 起始点x坐标 16'h0004
    8'd216:
        i2c_data <= {16'h3804,8'h0a};
    8'd217:
        i2c_data <= {16'h3805,8'h3f};	// 终止点x坐标 16'h0a3f = 16'd2623
    8'd218:
        i2c_data <= {16'h3806,8'h07};
    8'd219:
        i2c_data <= {16'h3807,8'h9b};	// 终止点y坐标 16'h079b = 16'd1947
	

3.1.2 设置 pre-scaling size (OFFSET)

	/* 部分设置代码…… */

	// 设定画幅OFFSET,在这里X_OFFSET = 0x0010 (16)
	8'd46 :
        i2c_data <= {16'h3810,8'h00}; 	// Timing Hoffset[11:8]
    8'd47 :
        i2c_data <= {16'h3811,8'h10}; 	// Timing Hoffset[7:0]
    8'd48 :
        i2c_data <= {16'h3812,8'h00}; 	// Timing Voffset[10:8]

	/* 部分设置代码…… */

	// OFFSET设置,Y_OFFSET = 0x0006(6)
	8'd228:
        i2c_data <= {16'h3813,8'h06};	 // Timing Voffset[7:0]

3.1.3 设置 data output size(OUTPUT)

  OV5640内部的ISP算法可以直接将压缩前获得的图像通过算法压缩成定义的像素大小,只需要在输入中给出像素大小即可。

	/* 部分设置代码…… */

	//设置输出像素个数(ISP压缩到800*480)
    //DVP 输出水平像素点数高4位
    8'd220:
        i2c_data <= {16'h3808,{4'd0,cmos_h_pixel[11:8]}};
    //DVP 输出水平像素点数低8位
    8'd221:
        i2c_data <= {16'h3809,cmos_h_pixel[7:0]};
    //DVP 输出垂直像素点数高3位
    8'd222:
        i2c_data <= {16'h380a,{5'd0,cmos_v_pixel[10:8]}};
    //DVP 输出垂直像素点数低8位
    8'd223:
        i2c_data <= {16'h380b,cmos_v_pixel[7:0]};


    //水平总像素大小高5位
    8'd224:
        i2c_data <= {16'h380c,{3'd0,total_h_pixel[12:8]}};
    //水平总像素大小低8位
    8'd225:
        i2c_data <= {16'h380d,total_h_pixel[7:0]};
    //垂直总像素大小高5位
    8'd226:
        i2c_data <= {16'h380e,{3'd0,total_v_pixel[12:8]}};
    //垂直总像素大小低8位
    8'd227:
        i2c_data <= {16'h380f,total_v_pixel[7:0]};
         

3.2 读取摄像头输出像素数据

3.2.1 获取像素数据及其仿真

  • cmos_capture_data.v
module cmos_capture_data (
	input i_rst_n,
	input i_cam_pclk,

	input i_cam_vsync,
	input i_cam_href,
	input [7:0] i_cam_data,

	output o_cmos_frame_vsync,
	output o_cmos_frame_href,
	output o_cmos_frame_data_valid,
	output [15:0] o_cmos_frame_data
);

	/* -------------------parameter define---------------------- */
	localparam WAIT_FRAME = 4'd10;

	/* -------------------reg define---------------------------- */
	reg r_cam_vsync_d0;				// beat for synchronization
	reg r_cam_vsync_d1;
	reg r_cam_href_d0;				// beat for synchronization
	reg r_cam_href_d1;

	reg [3:0] r_cmos_ps_cnt;		// wait for frame count to stabilized
	reg [7:0] r_cam_data_d0;
	reg [15:0] r_cam_data_temp;		// temp register for 8-bit data transfer to 16-bit data

	reg r_byte_upper8_flag;			// 16-bit RGB data transfer done flag
	reg r_byte_upper8_flag_d0;

	reg r_frame_valid;				// valid when frame is stable

	/* -------------------wire define--------------------------- */
	wire w_pos_vsync;

	/* -------------------combinational logic------------------- */
	assign w_pos_vsync = (~r_cam_vsync_d1) & r_cam_vsync_d0;
	assign o_cmos_frame_vsync = r_frame_valid ? r_cam_vsync_d1 : 1'b0;
	assign o_cmos_frame_href = r_frame_valid ? r_cam_href_d1 : 1'b0;

	assign o_cmos_frame_data_valid = r_frame_valid ? r_byte_upper8_flag_d0 : 1'b0;	// beat for keep o_cmos_frame_data_valid and o_cmos_frame_data synchronize
	assign o_cmos_frame_data = r_frame_valid ? r_cam_data_temp : 16'd0;

	/* -------------------sequencial logic---------------------- */
	// beat twice for detecting posedge of vsync and href
	always @(posedge i_cam_pclk or negedge i_rst_n) begin
		if (!i_rst_n) begin
			r_cam_vsync_d0 <= 'd0;
			r_cam_vsync_d1 <= 'd0;
	        r_cam_href_d0  <= 'd0;
			r_cam_href_d1  <= 'd0;
		end
		else begin
			r_cam_vsync_d0 <= i_cam_vsync;
			r_cam_vsync_d1 <= r_cam_vsync_d0;
	        r_cam_href_d0  <= i_cam_href;
			r_cam_href_d1  <= r_cam_href_d0;
		end
	end

	// count for frame count to stabilized
	always @(posedge i_cam_pclk or negedge i_rst_n) begin
		if (!i_rst_n) begin
			r_cmos_ps_cnt <= 4'd0;
		end
		else if (w_pos_vsync && (r_cmos_ps_cnt < WAIT_FRAME)) begin
			r_cmos_ps_cnt <= r_cmos_ps_cnt + 4'd1;
		end
	end

	// frame data valid
	always @(posedge i_cam_pclk or negedge i_rst_n) begin
		if (!i_rst_n) begin
			r_frame_valid <= 'd0;
		end
		else if (w_pos_vsync && (r_cmos_ps_cnt == WAIT_FRAME)) begin
			r_frame_valid <= 'd1;
		end
	end

	// 8-bit RGB data trans to 16-bit data
	always @(posedge i_cam_pclk or negedge i_rst_n) begin
		if (!i_rst_n) begin
			r_cam_data_temp <= 16'd0;
			r_cam_data_d0 <= 8'd0;
			r_byte_upper8_flag <= 1'b0;
		end
		else if (i_cam_href) begin
			r_byte_upper8_flag <= ~r_byte_upper8_flag;
			r_cam_data_d0 <= i_cam_data;
			if (r_byte_upper8_flag)
				r_cam_data_temp <= {r_cam_data_d0, i_cam_data};
		end
		else begin
			r_byte_upper8_flag <= 1'b0;
			r_cam_data_d0 <= 8'd0;
		end
	end

	// beat for keep o_cmos_frame_data_valid and o_cmos_frame_data synchronize
	always @(posedge i_cam_pclk or negedge i_rst_n) begin
		if (!i_rst_n) begin
			r_byte_upper8_flag_d0 <= 1'b0;
		end
		else begin
			r_byte_upper8_flag_d0 <= r_byte_upper8_flag;
		end
	end

endmodule

  • tb_cmos_capture_data.v
`timescale 1ns/1ns
module tb_cmos_capture_data ();

    reg i_rst_n;
    reg i_cam_pclk;
    reg i_cam_vsync;
    reg i_cam_href;
    reg [7:0] i_cam_data;

    wire o_cmos_frame_vsync;
    wire o_cmos_frame_href;
    wire o_cmos_frame_data_valid;
    wire [15:0] o_cmos_frame_data;

    always #5 i_cam_pclk = ~i_cam_pclk;

    initial begin
        i_rst_n = 0;
        i_cam_pclk = 0;
        i_cam_vsync = 0;
        i_cam_href = 0;
        i_cam_data = {$random} % 256;
        #20
        i_rst_n = 1;
        repeat (10) begin
            #500 i_cam_vsync = 1;
            #200 i_cam_vsync = 0;
            repeat (10) begin
                #1000 i_cam_href = 1;
                #200 i_cam_href = 0;
            end
        end

        repeat (10) begin
            #500 i_cam_vsync = 1;
            #200 i_cam_vsync = 0;
            repeat (10) begin
                #1000 i_cam_href = 1;
                repeat (50) @(posedge i_cam_pclk) i_cam_data = {$random} % 256;
                i_cam_href = 0;
            end
        end

    end

    cmos_capture_data u_cmos_capture_data (
        .i_rst_n(i_rst_n),
        .i_cam_pclk(i_cam_pclk),
        .i_cam_vsync(i_cam_vsync),
        .i_cam_href(i_cam_href),
        .i_cam_data(i_cam_data),
        .o_cmos_frame_vsync(o_cmos_frame_vsync),
        .o_cmos_frame_href(o_cmos_frame_href),
        .o_cmos_frame_data_valid(o_cmos_frame_data_valid),
        .o_cmos_frame_data(o_cmos_frame_data)
    );

endmodule

仿真波形如下所示:
在这里插入图片描述

在这里插入图片描述
可以通过仿真波形分析出接收数据端的编程思路:o_cmos_frame_href的上升沿代表一帧的开始,o_cmos_frame_href代表一行数据的开始,数据接收端只需要在always语句块中通过判断o_cmos_frame_data_valid为1时将RGB565数据读入即可。如果驱动获取数据模块的时钟为50MHz,数据接收端的数据速率即为25MHz。

3.2.2 SCCB驱动程序及其仿真

网上的例程多数使用I2C的协议进行魔改,但是SCCB协议比I2C驱动简单很多,且配置OV5640的工作模式一般不需要读取OV5640寄存器数据。专门写一版SCCB驱动可以节省逻辑资源。

  • sccb_div.v
/*
 * File Created: Monday, 22nd April 2024 17:23:25
 * 		 Author: Include everything
 *
 * Last Modified: Tuesday, 30th April 2024 22:23:13
 *
 * Function: SCCB driver for OV5640.
 *           Write data to config registers of OV5640.
 */

module sccb_div (
    input i_clk,                        // 33.333MHz
    input i_rst_n,

    input [15:0] i_reg_addr,            // OV5640 register address
    input [ 7:0] i_reg_data,            // OV5640 register data

    input i_sccb_exec,                  // SCCB timing exec signal
    // input i_sccb_wr_rd_ctrl,            // SCCB write and read control, "0" for write, "1" for read

    output o_sccb_done,
    output o_sccb_sioc,                 // SCCB serial output clock
    inout io_sccb_siod                  // SCCB serial output data

    // output [7:0] o_sccb_rdata           // read data from OV5640 (non-useful for this project)
);
    /* ---------------param define--------------------- */
    parameter SCCB_ID = 8'h78;

    // state machine
    parameter ST_IDLE               = 7'b0000001;
    parameter ST_SEND_SALVE_ADDR    = 7'b0000010;
    parameter ST_SEND_REG_ADDR_U8   = 7'b0000100;
    parameter ST_SEND_REG_ADDR_L8   = 7'b0001000;
    parameter ST_SEND_DATA          = 7'b0010000;
    parameter ST_ACK                = 7'b0100000;
    parameter ST_STOP               = 7'b1000000;

    /* ---------------reg define----------------------- */
    reg [6:0] r_current_state;
    reg [6:0] r_next_state;

    reg r_state_done;

    reg r_siod_data_temp;               // siod bus output data temp
    reg r_sioc_temp;
    reg r_sccb_done;

    reg [6:0] r_timing_count;

    reg [4:0] r_data_index;

    reg r_st_slave_addr_done;
    reg r_st_reg_addr_upper8_done;
    reg r_st_reg_addr_lower8_done;
    reg r_st_send_data_done;

    reg [7:0] r_reg_addr_u8;
    reg [7:0] r_reg_addr_l8;
    reg [7:0] r_reg_data;

    /* ---------------wire define---------------------- */
    wire w_siod_dir_ctrl;                // inout port direction control, "0" for output, "1" for input

    /* ---------------combinational logic-------------- */
    assign o_sccb_sioc = r_sioc_temp;
    assign io_sccb_siod = w_siod_dir_ctrl ? 1'bz : r_siod_data_temp;
    assign o_sccb_done = r_sccb_done;

    assign w_siod_dir_ctrl = 1'b0;      // siod default is output mode

    always @(*) begin
        r_next_state = ST_IDLE;

        case (r_current_state)
            ST_IDLE: begin
                if (i_sccb_exec)
                    r_next_state = ST_SEND_SALVE_ADDR;
                else
                    r_next_state = ST_IDLE;
            end
            ST_ACK : begin
                if (r_state_done) begin
                    if (r_st_slave_addr_done) begin
                        r_next_state = ST_SEND_REG_ADDR_U8;
                    end
                    else if (r_st_reg_addr_upper8_done) begin
                        r_next_state = ST_SEND_REG_ADDR_L8;
                    end
                    else if (r_st_reg_addr_lower8_done) begin
                        r_next_state = ST_SEND_DATA;
                    end
                    else if (r_st_send_data_done) begin
                        r_next_state = ST_STOP;
                    end
                end
                else
                    r_next_state = ST_ACK;
            end
            ST_SEND_SALVE_ADDR: begin
                if (r_state_done)
                    r_next_state = ST_ACK;
                else
                    r_next_state = ST_SEND_SALVE_ADDR;
            end
            ST_SEND_REG_ADDR_U8: begin
                if (r_state_done)
                    r_next_state = ST_ACK;
                else
                    r_next_state = ST_SEND_REG_ADDR_U8;
            end
            ST_SEND_REG_ADDR_L8: begin
                if (r_state_done)
                    r_next_state = ST_ACK;
                else
                    r_next_state = ST_SEND_REG_ADDR_L8;
            end
            ST_SEND_DATA: begin
                if (r_state_done)
                    r_next_state = ST_ACK;
                else
                    r_next_state = ST_SEND_DATA;
            end
            ST_STOP: begin
                if (r_state_done)
                    r_next_state = ST_IDLE;
                else
                    r_next_state = ST_STOP;
            end
            default: ;
        endcase

    end

    /* ---------------sequential logic------------------ */
    // sioc generator based on i_clk == 33.333MHz
    // if r_timing_count == 21, sioc is high; if r_timing_count == 63, sioc is low
    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            r_timing_count <= 7'd0;
            r_sioc_temp <= 1'b0;
        end
        else if (r_timing_count == 7'd42) begin
            r_sioc_temp <= 1'b0;
            r_timing_count <= r_timing_count + 1;
        end
        else if (r_timing_count == 7'd84) begin
            r_timing_count <= 7'd0;
            r_sioc_temp <= 1'b1;
        end
        else
            r_timing_count <= r_timing_count + 1;
    end

    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n)
            r_current_state <= ST_IDLE;
        else
            r_current_state <= r_next_state;
    end

    always @(posedge i_clk or negedge i_rst_n) begin
        if (!i_rst_n) begin
            r_data_index <= 5'd8;
            r_siod_data_temp <= 1'b1;
            r_sioc_temp <= 1'b1;
            r_st_slave_addr_done <= 1'b0;
            r_st_reg_addr_upper8_done <= 1'b0;
            r_st_reg_addr_lower8_done <= 1'b0;
            r_st_send_data_done <= 1'b0;
            r_reg_addr_u8 <= 8'd0;
            r_reg_addr_l8 <= 8'd0;
            r_reg_data <= 8'd0;
            r_timing_count <= 8'd0;
        end
        else begin
            r_state_done <= 1'b0;
            r_sccb_done <= 1'b0;
            case (r_current_state)
                ST_IDLE: begin
                    r_data_index <= 5'd8;
                    r_siod_data_temp <= 1'b1;
                    r_sioc_temp <= 1'b1;
                    r_st_slave_addr_done <= 1'b0;
                    r_st_reg_addr_upper8_done <= 1'b0;
                    r_st_reg_addr_lower8_done <= 1'b0;
                    r_st_send_data_done <= 1'b0;
                    r_reg_addr_u8 <= 8'd0;
                    r_reg_addr_l8 <= 8'd0;
                    r_reg_data <= 8'd0;
                    r_timing_count <= 8'd0;
                end
                ST_ACK: begin
                    if (r_timing_count == 7'd21) begin
                        r_state_done <= 1'b1;
                    end
                    if (r_timing_count == 7'd22) begin
                        r_st_slave_addr_done = 0;
                        r_st_reg_addr_upper8_done = 0;
                        r_st_reg_addr_lower8_done = 0;
                        r_st_send_data_done = 0;
                    end
                end
                ST_SEND_SALVE_ADDR: begin
                    if (r_timing_count == 7'd21) begin          // sioc is high
                        if (r_data_index == 5'd8 && r_sioc_temp == 1'b1)
                            r_siod_data_temp <= 1'b0;           // start signal
                        else if (r_data_index == 5'd0) begin
                            r_state_done <= 1'b1;
                            r_st_slave_addr_done <= 1'b1;
                            r_data_index <= 5'd8;
                        end
                    end
                    else if (r_timing_count == 6'd63) begin     // sioc is low
                        r_siod_data_temp <= SCCB_ID[r_data_index - 1];
                        if (r_data_index != 5'd0) begin
                            r_data_index <= r_data_index - 1;
                        end
                    end
                end
                ST_SEND_REG_ADDR_U8: begin
                    if (r_timing_count == 7'd21) begin          // sioc is high
                        if (r_data_index == 5'd0) begin
                            r_state_done <= 1'b1;
                            r_st_reg_addr_upper8_done <= 1'b1;
                            r_data_index <= 5'd8;
                        end
                    end
                    else if (r_timing_count == 6'd63) begin     // sioc is low
                        r_siod_data_temp <= i_reg_addr[8 + r_data_index - 1];
                        if (r_data_index != 5'd0) begin
                            r_data_index <= r_data_index - 1;
                        end
                    end
                end
                ST_SEND_REG_ADDR_L8: begin
                    if (r_timing_count == 7'd21) begin          // sioc is high
                        if (r_data_index == 5'd0) begin
                            r_state_done <= 1'b1;
                            r_st_reg_addr_lower8_done <= 1'b1;
                            r_data_index <= 5'd8;
                        end
                    end
                    else if (r_timing_count == 6'd63) begin     // sioc is low
                        r_siod_data_temp <= i_reg_addr[r_data_index - 1];
                        if (r_data_index != 5'd0) begin
                            r_data_index <= r_data_index - 1;
                        end
                    end
                end
                ST_SEND_DATA: begin
                    if (r_timing_count == 7'd21) begin          // sioc is high
                        if (r_data_index == 5'd0) begin
                            r_state_done <= 1'b1;
                            r_st_send_data_done <= 1'b1;
                            r_data_index <= 5'd8;
                        end
                    end
                    else if (r_timing_count == 6'd63) begin     // sioc is low
                        r_siod_data_temp <= i_reg_data[r_data_index - 1];
                        if (r_data_index != 5'd0) begin
                            r_data_index <= r_data_index - 1;
                        end
                    end
                end
                ST_STOP: begin
                    if (r_timing_count == 7'd21) begin
                        r_siod_data_temp <= 1'b1;
                        r_sccb_done <= 1'b1;
                        r_state_done <= 1'b1;
                    end
                    if (r_timing_count == 7'd63) begin
                        r_siod_data_temp <= 1'b0;
                    end
                end
                default: ;
            endcase
        end
    end

endmodule

  • sccb_div_tb.v
/*
 * File Created: Tuesday, 30th April 2024 17:16:39
 *  	 Author: Include everything
 *
 * Last Modified: Tuesday, 30th April 2024 22:32:48
 *
 * Function: Testbench for sccb_div module
 */

`timescale 1ns/1ns
module sccb_div_tb();

    // Parameters

    //Ports
    reg  i_clk;
    reg  i_rst_n;
    reg [15:0] i_reg_addr;
    reg [ 7:0] i_reg_data;
    reg  i_sccb_exec;
    wire  o_sccb_done;
    wire  o_sccb_sioc;
    wire  io_sccb_siod;
    wire [7:0] o_sccb_rdata;

    sccb_div  sccb_div_inst (
                  .i_clk(i_clk),
                  .i_rst_n(i_rst_n),
                  .i_reg_addr(i_reg_addr),
                  .i_reg_data(i_reg_data),
                  .i_sccb_exec(i_sccb_exec),
                  .o_sccb_done(o_sccb_done),
                  .o_sccb_sioc(o_sccb_sioc),
                  .io_sccb_siod(io_sccb_siod)
                  // .o_sccb_rdata(o_sccb_rdata)
              );

    always #5 i_clk = !i_clk ;

    initial begin
        i_clk = 0;
        i_rst_n = 0;
        i_sccb_exec = 0;
        i_reg_addr = 16'h1234;
        i_reg_data = 8'h5A;

        #10 i_rst_n = 1;

        #100 i_sccb_exec = 1;
        #10 i_sccb_exec = 0;

        i_reg_addr = 16'h3012;
        i_reg_data = 8'h88;

        @(posedge o_sccb_done) #200 i_sccb_exec = 1;
        #10 i_sccb_exec = 0;

    end

endmodule

仿真波形如下:
在这里插入图片描述


  持续不定期更新完善中……


  原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、FPGA方面的学习笔记。


  • 16
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
FPGA设计实现OV5640 摄像头采集数据VGA显示输出Verilog设计逻辑Quartus工程源码文件,FPGA型号Cyclone4E系列中的EP4CE10F17C8,Quartus版本18.0。 module ov5640_rgb565_1024x768_vga( input sys_clk , //系统时钟 input sys_rst_n , //系统复位,低电平有效 //摄像头接口 input cam_pclk , //cmos 数据像素时钟 input cam_vsync , //cmos 场同步信号 input cam_href , //cmos 行同步信号 input [7:0] cam_data , //cmos 数据 output cam_rst_n , //cmos 复位信号,低电平有效 output cam_pwdn , //cmos 电源休眠模式选择信号 output cam_scl , //cmos SCCB_SCL线 inout cam_sda , //cmos SCCB_SDA线 //SDRAM接口 output sdram_clk , //SDRAM 时钟 output sdram_cke , //SDRAM 时钟有效 output sdram_cs_n , //SDRAM 片选 output sdram_ras_n , //SDRAM 行有效 output sdram_cas_n , //SDRAM 列有效 output sdram_we_n , //SDRAM 写有效 output [1:0] sdram_ba , //SDRAM Bank地址 output [1:0] sdram_dqm , //SDRAM 数据掩码 output [12:0] sdram_addr , //SDRAM 地址 inout [15:0] sdram_data , //SDRAM 数据 //VGA接口 output vga_hs , //行同步信号 output vga_vs , //场同步信号 output [15:0] vga_rgb //红绿蓝三原色输出 ); //parameter define parameter SLAVE_ADDR = 7'h3c ; //OV5640的器件地址7'h3c parameter BIT_CTRL = 1'b1 ; //OV5640的字节地址为16位 0:8位 1:16位 parameter CLK_FREQ = 26'd65_000_000; //i2c_dri模块的驱动时钟频率 65MHz parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率,不超过400KHz parameter CMOS_H_PIXEL = 24'd1024 ; //CMOS水平方向像素个数,用于设置SDRAM缓存大小 parameter CMOS_V_PIXEL = 24'd768 ; //CMOS垂直方向像素个数,用于设置SDRAM缓存大小 //wire define wire clk_100m ; //100mhz时钟,SDRAM操作时钟 wire clk_100m_shift ; //100mhz时
FPGA是可编程逻辑器件,而ov5640是一种可用于图像传感器的摄像头模块,以太网是一种广泛应用于计算机网络中的通信协议。提到"FPGA ov5640 以太网",我认为可能是指在FPGA中使用ov5640摄像头模块,并通过以太网进行数据传输的应用场景。 在这种应用中,FPGA可以被用来控制和处理ov5640传感器收集到的图像数据。FPGA通常具有高度可编程性和并行处理能力,可以实现对图像数据的实时处理、压缩、滤波等功能。同时,FPGA还可以提供接口和协议转换功能,将ov5640传感器采集到的数据转换为以太网协议的数据。通过以太网,这些数据可以被传输到其他设备或计算机进行进一步的处理或存储。 为了实现"FPGA ov5640 以太网"的应用,以下是一些可能的方法和步骤: 1. 首先,需要将ov5640摄像头模块连接到FPGA的输入端口。这可能涉及到电路连接和信号适配的设计。 2. 接下来,需要在FPGA中编写代码,配置和控制ov5640摄像头模块。这包括设置摄像头的分辨率、帧率、曝光时间等参数。可以使用现有的IP核或自定义的逻辑来实现这些功能。 3. 在FPGA中,可以使用适当的图像处理算法对摄像头采集到的图像数据进行处理。这可能包括图像滤波、边缘检测、目标识别等功能。 4. 同时,FPGA还需要实现以太网协议栈,将图像数据转换为以太网数据包,并通过以太网接口发送出去。这可能需要使用现有的以太网IP核或自行开发。 通过这些步骤,FPGA可以实现ov5640摄像头模块的控制和数据传输功能,并将图像数据通过以太网发送到其他设备。这样,用户可以实现对ov5640摄像头的远程数据访问和处理。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Include everything

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值