赞
踩
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
:输出像素大小。
上图表示了OV5640在DVP模式下的数据输出时序,同时列出了以VGA(640×480)分辨率为例的不同区间的时间。
tp
:在RGB565格式下,一个
t
p
tp
tp等于两倍的
t
P
C
L
K
t_{PCLK}
tPCLK,因为一个像素的有效数据需要两个时钟周期传输。
OV5640拥有三种曝光模式,在模式0下,帧曝光控制引脚FREQ
作输入,曝光脉冲请求通过FPGA发出;在模式1下,曝光请求通过I2C(SCCB)总线发出,帧曝光控制引脚FREQ
作输出,通知外部主机(FPGA)将要开始曝光,LED功能只有在模式0和模式1下才会工作;滚动曝光模式下曝光控制功能失效。
在了解闪光灯信号strobe
信号之前,需要注意,闪光灯的所有工作模式只在帧曝光模式0和模式1下有效。滚动帧曝光下闪光灯不工作。
"Xenon"是指氙灯(xenon flash)模式。氙灯是一种基于氙气放电的强光源,当电流通过充满氙气的灯管时,氙气被激发产生亮白色的光。在摄影和摄像中,氙灯闪光灯被广泛用于提供短时间内的高强度照明,以便在拍摄照片或视频时,尤其是在低光环境下,获得更好的曝光效果。在脉冲请求strobe request
信号到来后,经历三帧时间,strobe pulse
将输出一个极短的脉冲。该功能需要通过寄存器配置。上电后默认工作在该模式下。
LED1和LED2模式均为LED闪烁模式,在LED曝光期间的帧数据会跳过输出。该功能需要寄存器配置。
在LED3模式下,LED灯会保持常亮。
与LED工作模式无关,我们可以通过SCCB总线发送指令修改寄存器的值,手动开启或关闭闪光灯。通过依次配置寄存器0x3016
、0x301C
、0x3019
的Bit[1]
即可打开或关闭闪光灯。
可以从原理图中看到,该模块可以输出一个24MHz的时钟给外部。
SCCB(Serial Camera Control Bus,串行摄像头控制总线)是由OV(OMNIVISION)公司定义和发展的两线/三线式串行总线。该总线控制OV系列摄像头大部分的功能,包括图像数据格式、分辨篇以及图像处理参数等。两线SCCB只能实现“一主一从”的控制,而三线结构可以实现对多个从机进行控制。OV公司为了减少传感器引脚的封装,现在SCCB大多采用两线式接口总线。
SIO_C/SCL
:只能由主机(FPGA)配置;SIO_D/SDA
:上面有一个三态门,可以实现双向数据传输,可以由主机控制,也可以由从机控制SCCB协议与I2C协议十分相似,甚至在有些驱动历程中直接将I2C的驱动程序拿来使用。了解I2C协议之前有必要熟悉I2C协议的相关使用,在这里不作过多赘述。
SCCB 总线跟 I2C 十分类似,起始信号、停止信号与 I2C 一样,SCCB 定义数据传输的基本单元为相(phase),每个相传输一个字节数据。SCCB 只包含三种传输周期:三相写周期、两相写周期和两相读周期。
三相写周期:依次为(开始信号)设备地址(写命令)、寄存器地址、数据(结束信号)。
两相写周期:依次为(开始信号)设备地址(写命令)、寄存器地址(结束信号)。
两相读周期:依次为(开始信号)设备地址(读命令)、数据(结束信号)。
在写入数据时,只执行三相写周期;在读出数据时,先执行两相写周期(虚写),再执行两相读周期。在SCCB协议中,每一相后有一个X
信号,表示不需要关心这一位信号,与I2C协议不同,SCCB协议不用等待从器件响应(拉低)。两相读周期最后的NA
信号表示不响应,即主机不会拉低信号线给从机发送响应。
OV5640通过SCCB总线写入对应位置寄存器设置输出像素大小。实际上I2C的驱动程序完全可以兼容SCCB通信,所以重点在于OV5640初始化工作中对寄存器值的操作,在正点原子的历程中要配置250个寄存器,还是相当繁琐的。其中首先要注意的就是输出像素大小的定义。
/* 部分设置代码…… */ // 设置感光区域的“开窗大小”,开窗区域不是最终的显示区域 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
/* 部分设置代码…… */ // 设定画幅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]
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]};
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。
网上的例程多数使用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方面的学习笔记。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。