上电之后要做的是通过 SCCB 协议对摄像头的寄存器进行配置,在之前的博客中详细介绍过 SCCB 协议的基本原理以及和 IIC 协议的区别。经过半年后,在正点原子的 SCCB 配置寄存器的基础上,我进行了小小的改动,使得代码更加简洁易懂。
一、SCCB协议编写
SCCB协议的介绍见之前的博客《协议——SCCB与IIC的区别》,此处不再赘述。之前的 SCCB 协议需要在端口进行 8 位地址和16位地址的选择,这次我把选择开关删了,改为内部代码判断,端口更加简洁了。
1、端口例化
//SCCB寄存器配置 //--------------------------------------------------- sccb_ov5640_cfg u_sccb_ov5640_cfg ( .clk (sccb_dri_clk ), .rst_n (rst_n ), .sccb_vld (sccb_vld ), //SCCB配置有效信号 .sccb_done (sccb_done ), //SCCB配置一次结束 .sccb_en (sccb_en ), .sccb_data (sccb_data ), .sccb_cfg_done (sccb_cfg_done ) //SCCB配置全部结束 ); //SCCB时序驱动 //--------------------------------------------------- sccb #( .DEVICE_ID (8'h78 ), //器件ID .CLK (26'd24_000_000 ), //24Mhz .SCL (18'd250_000 ) //250Khz ) u_sccb ( .clk (cmos_xclk ), .rst_n (rst_n ), //SCCB input ------------------------------------ .sccb_en (sccb_en ), .sccb_addr (sccb_data[23:8] ), .sccb_data (sccb_data[7:0] ), //SCCB output ----------------------------------- .sccb_done (sccb_done ), .sccb_scl (cmos_scl ), .sccb_sda (cmos_sda ), //dri_clk --------------------------------------- .sccb_dri_clk (sccb_dri_clk ) //1Mhz );
OV7670 和 OV7725 的写 ID 都是 8'h42,而 OV5640 的写 ID 为 8'h78,这些都可以在各自的 datasheet 中查到,没什么说的。
SCCB协议的 SCL 要求不高于400Khz,我们设置为 250Khz,通过例化模块的参数传递可以直接更改这些信息,对于 sccb 代码内部不必再更改,方便移植。
sccb_start信号为开始配置标志,在上一讲博客中设计得到。sccb_en、sccb_data、sccb_done、sccb_dri_clk 都是和 sccb_cfg 模块进行交互的信号,其中 sccb_dri_clk 是 sccb_cfg 模块的时钟驱动信号。而 cmos_scl 和 cmos_sda 则是要输出到端口的信号。sccb_cfg_done 信号表示所有寄存器都配置完成,这个信号可以给之后的图像获取模块 cmos_capture 使用。
2、sccb 代码
//************************************************************************** // *** 名称 : sccb.v // *** 作者 : xianyu_FPGA // *** 博客 : https://www.cnblogs.com/xianyufpga/ // *** 日期 : 2019-08-10 // *** 描述 : SCCB控制器,只支持写 //************************************************************************** module sccb //========================< 参数 >========================================== #( parameter DEVICE_ID = 8'b01010000 , //器件ID parameter CLK = 26'd50_000_000 , //本模块的时钟频率 parameter SCL = 18'd250_000 //输出的SCL时钟频率 ) //========================< 端口 >========================================== ( input wire clk , //时钟 input wire rst_n , //复位,低电平有效 //SCCB input ---------------------------------------- input wire sccb_en , //SCCB触发信号 input wire [15:0] sccb_addr , //SCCB器件内地址 input wire [ 7:0] sccb_data , //SCCB要写的数据 //SCCB output --------------------------------------- output reg sccb_done , //SCCB一次操作完成 output reg sccb_scl , //SCCB的SCL时钟信号 inout wire sccb_sda , //SCCB的SDA数据信号 //dri_clk ------------------------------------------- output reg sccb_dri_clk //驱动SCCB操作的驱动时钟,1Mhz ); //========================< 状态机参数 >==================================== localparam IDLE = 6'b00_0001 ; //空闲状态 localparam DEVICE = 6'b00_0010 ; //写器件地址 localparam ADDR_16 = 6'b00_0100 ; //写字地址高8位 localparam ADDR_8 = 6'b00_1000 ; //写字地址低8位 localparam DATA = 6'b01_0000 ; //写数据 localparam STOP = 6'b10_0000 ; //结束 //========================< 信号 >========================================== reg sda_dir ; //SCCB数据(SDA)方向控制 reg sda_out ; //SDA输出信号 reg state_done ; //状态结束 reg [ 6:0] cnt ; //计数 reg [ 7:0] state_c ; //状态机当前状态 reg [ 7:0] state_n ; //状态机下一状态 reg [15:0] sccb_addr_t ; //地址寄存 reg [ 7:0] sccb_data_t ; //数据寄存 reg [ 9:0] clk_cnt ; //分频时钟计数 wire [ 8:0] clk_divide ; //模块驱动时钟的分频系数 //========================================================================== //== SDA数据输出或高阻 //========================================================================== assign sccb_sda = sda_dir ? sda_out : 1'bz; //========================================================================== //== 生成SCL的4倍时钟来驱动后面SCCB的操作,生成1Mhz的sccb_dri_clk //========================================================================== assign clk_divide = (CLK/SCL) >> 3; //>>3即除以8 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin sccb_dri_clk <= 1'b1; clk_cnt <= 10'd0; end else if(clk_cnt == clk_divide - 1'd1) begin clk_cnt <= 10'd0; sccb_dri_clk <= ~sccb_dri_clk; end else clk_cnt <= clk_cnt + 1'b1; end //========================================================================== //== 状态机 //========================================================================== always @(posedge sccb_dri_clk or negedge rst_n) begin if(!rst_n) state_c <= IDLE; else state_c <= state_n; end always @(*) begin case(state_c) IDLE: begin //空闲状态 if(sccb_en) state_n = DEVICE; else state_n = IDLE; end DEVICE: begin //写器件ID if(state_done) begin if(sccb_addr[15:8]!=0) state_n = ADDR_16; else if(sccb_addr[15:8]==0) state_n = ADDR_8 ; end else state_n = DEVICE; end ADDR_16: begin //写地址高8位 if(state_done) state_n = ADDR_8; else state_n = ADDR_16; end ADDR_8: begin //写地址低8位 if(state_done) state_n = DATA; else state_n = ADDR_8; end DATA: begin //写数据 if(state_done) state_n = STOP; else state_n = DATA; end STOP: begin //结束 if(state_done) state_n = IDLE; else state_n = STOP ; end default:state_n= IDLE; endcase end //========================================================================== //== 设计各路信号 //========================================================================== always @(posedge sccb_dri_clk or negedge rst_n) begin if(!rst_n) begin sccb_scl <= 1'b1; sda_out <= 1'b1; sda_dir <= 1'b1; sccb_done <= 1'b0; cnt <= 1'b0; state_done <= 1'b0; sccb_addr_t <= 1'b0; sccb_data_t <= 1'b0; end else begin state_done <= 1'b0; cnt <= cnt + 1'b1; case(state_c) //--------------------------------------------------- 空闲状态 IDLE: begin sccb_scl <= 1'b1; sda_out <= 1'b1; sda_dir <= 1'b1; sccb_done <= 1'b0; cnt <= 7'b0; if(sccb_en) begin sccb_addr_t <= sccb_addr; sccb_data_t <= sccb_data; end end //--------------------------------------------------- 写器件ID DEVICE: begin case(cnt) 7'd1 : sda_out <= 1'b0; 7'd3 : sccb_scl <= 1'b0; 7'd4 : sda_out <= DEVICE_ID[7]; 7'd5 : sccb_scl <= 1'b1; 7'd7 : sccb_scl <= 1'b0; 7'd8 : sda_out <= DEVICE_ID[6]; 7'd9 : sccb_scl <= 1'b1; 7'd11: sccb_scl <= 1'b0; 7'd12: sda_out <= DEVICE_ID[5]; 7'd13: sccb_scl <= 1'b1; 7'd15: sccb_scl <= 1'b0; 7'd16: sda_out <= DEVICE_ID[4]; 7'd17: sccb_scl <= 1'b1; 7'd19: sccb_scl <= 1'b0; 7'd20: sda_out <= DEVICE_ID[3]; 7'd21: sccb_scl <= 1'b1; 7'd23: sccb_scl <= 1'b0; 7'd24: sda_out <= DEVICE_ID[2]; 7'd25: sccb_scl <= 1'b1; 7'd27: sccb_scl <= 1'b0; 7'd28: sda_out <= DEVICE_ID[1]; 7'd29: sccb_scl <= 1'b1; 7'd31: sccb_scl <= 1'b0; 7'd32: sda_out <= DEVICE_ID[0]; 7'd33: sccb_scl <= 1'b1; 7'd35: sccb_scl <= 1'b0; 7'd36: begin sda_dir <= 1'b0; //从机应答 sda_out <= 1'b1; end 7'd37: sccb_scl <= 1'b1; 7'd38: state_done <= 1'b1; //状态结束 7'd39: begin sccb_scl <= 1'b0; cnt <= 1'b0; end default:; endcase end //--------------------------------------------------- 写字地址高8位 ADDR_16: begin case(cnt) 7'd0 : begin sda_dir <= 1'b1 ; sda_out <= sccb_addr_t[15]; end 7'd1 : sccb_scl <= 1'b1; 7'd3 : sccb_scl <= 1'b0; 7'd4 : sda_out <= sccb_addr_t[14]; 7'd5 : sccb_scl <= 1'b1; 7'd7 : sccb_scl <= 1'b0; 7'd8 : sda_out <= sccb_addr_t[13]; 7'd9 : sccb_scl <= 1'b1; 7'd11: sccb_scl <= 1'b0; 7'd12: sda_out <= sccb_addr_t[12]; 7'd13: sccb_scl <= 1'b1; 7'd15: sccb_scl <= 1'b0; 7'd16: sda_out <= sccb_addr_t[11]; 7'd17: sccb_scl <= 1'b1; 7'd19: sccb_scl <= 1'b0; 7'd20: sda_out <= sccb_addr_t[10]; 7'd21: sccb_scl <= 1'b1; 7'd23: sccb_scl <= 1'b0; 7'd24: sda_out <= sccb_addr_t[9]; 7'd25: sccb_scl <= 1'b1; 7'd27: sccb_scl <= 1'b0; 7'd28: sda_out <= sccb_addr_t[8]; 7'd29: sccb_scl <= 1'b1; 7'd31: sccb_scl <= 1'b0; 7'd32: begin sda_dir <= 1'b0; //从机应答 sda_out <= 1'b1; end 7'd33: sccb_scl <= 1'b1; 7'd34: state_done <= 1'b1; //状态结束 7'd35: begin sccb_scl <= 1'b0; cnt <= 1'b0; end default:; endcase end //--------------------------------------------------- 写字地址低8位 ADDR_8: begin case(cnt) 7'd0: begin sda_dir <= 1'b1 ; sda_out <= sccb_addr_t[7]; end 7'd1 : sccb_scl <= 1'b1; 7'd3 : sccb_scl <= 1'b0; 7'd4 : sda_out <= sccb_addr_t[6]; 7'd5 : sccb_scl <= 1'b1; 7'd7 : sccb_scl <= 1'b0; 7'd8 : sda_out <= sccb_addr_t[5]; 7'd9 : sccb_scl <= 1'b1; 7'd11: sccb_scl <= 1'b0; 7'd12: sda_out <= sccb_addr_t[4]; 7'd13: sccb_scl <= 1'b1; 7'd15: sccb_scl <= 1'b0; 7'd16: sda_out <= sccb_addr_t[3]; 7'd17: sccb_scl <= 1'b1; 7'd19: sccb_scl <= 1'b0; 7'd20: sda_out <= sccb_addr_t[2]; 7'd21: sccb_scl <= 1'b1; 7'd23: sccb_scl <= 1'b0; 7'd24: sda_out <= sccb_addr_t[1]; 7'd25: sccb_scl <= 1'b1; 7'd27: sccb_scl <= 1'b0; 7'd28: sda_out <= sccb_addr_t[0]; 7'd29: sccb_scl <= 1'b1; 7'd31: sccb_scl <= 1'b0; 7'd32: begin sda_dir <= 1'b0; //从机应答 sda_out <= 1'b1; end 7'd33: sccb_scl <= 1'b1; 7'd34: state_done <= 1'b1; //状态结束 7'd35: begin sccb_scl <= 1'b0; cnt <= 1'b0; end default:; endcase end //--------------------------------------------------- 写数据 DATA: begin case(cnt) 7'd0: begin sda_out <= sccb_data_t[7]; sda_dir <= 1'b1; end 7'd1 : sccb_scl <= 1'b1; 7'd3 : sccb_scl <= 1'b0; 7'd4 : sda_out <= sccb_data_t[6]; 7'd5 : sccb_scl <= 1'b1; 7'd7 : sccb_scl <= 1'b0; 7'd8 : sda_out <= sccb_data_t[5]; 7'd9 : sccb_scl <= 1'b1; 7'd11: sccb_scl <= 1'b0; 7'd12: sda_out <= sccb_data_t[4]; 7'd13: sccb_scl <= 1'b1; 7'd15: sccb_scl <= 1'b0; 7'd16: sda_out <= sccb_data_t[3]; 7'd17: sccb_scl <= 1'b1; 7'd19: sccb_scl <= 1'b0; 7'd20: sda_out <= sccb_data_t[2]; 7'd21: sccb_scl <= 1'b1; 7'd23: sccb_scl <= 1'b0; 7'd24: sda_out <= sccb_data_t[1]; 7'd25: sccb_scl <= 1'b1; 7'd27: sccb_scl <= 1'b0; 7'd28: sda_out <= sccb_data_t[0]; 7'd29: sccb_scl <= 1'b1; 7'd31: sccb_scl <= 1'b0; 7'd32: begin sda_dir <= 1'b0; //从机应答 sda_out <= 1'b1; end 7'd33: sccb_scl <= 1'b1; 7'd34: state_done <= 1'b1; //状态结束 7'd35: begin sccb_scl <= 1'b0; cnt <= 1'b0; end default:; endcase end //--------------------------------------------------- 结束 STOP: begin case(cnt) 7'd0: begin sda_dir <= 1'b1; sda_out <= 1'b0; end 7'd1 : sccb_scl <= 1'b1; 7'd3 : sda_out <= 1'b1; 7'd15: state_done <= 1'b1; //状态结束 7'd16: begin cnt <= 1'b0; sccb_done <= 1'b1; //sccb配置完成 end default:; endcase end endcase end end endmodule
3、SCCB协议配置寄存器代码
module sccb_ov5640_cfg //========================< 参数 >========================================== #( parameter REG_NUM = 240 , //寄存器个数 parameter CMOS_H_PIXEL = 24'd1024 , //CMOS水平方向像素个数 parameter CMOS_V_PIXEL = 24'd768 , //CMOS垂直方向像素个数 parameter TOTAL_H_PIXEL = CMOS_H_PIXEL+13'd1216 , //水平总像素大小 parameter TOTAL_V_PIXEL = CMOS_V_PIXEL+13'd504 //垂直总像素大小 ) //========================< 端口 >========================================== ( input wire clk , //时钟,1Mhz input wire rst_n , //复位,低电平有效 input wire sccb_vld , //SCCB配置有效信号 input wire sccb_done , //SCCB寄存器配置完成信号 output reg sccb_en , //SCCB触发执行信号 output reg [23:0] sccb_data , //SCCB要配置的地址与数据(高8位地址,低8位数据) output reg sccb_cfg_done //SCCB全部寄存器配置完成信号 ); //========================< 信号 >========================================== reg sccb_vld_r ; wire sccb_start ; reg [7:0] reg_cnt ; //寄存器配置个数计数器 //========================================================================== //== sccb_vld上升沿检测 //========================================================================== always @(posedge clk or negedge rst_n) begin if(!rst_n) sccb_vld_r <= 1'b0; else sccb_vld_r <= sccb_vld; end assign sccb_start = sccb_vld && ~sccb_vld_r; //========================================================================== //== sccb触发执行信号 //========================================================================== always @(posedge clk or negedge rst_n) begin if(!rst_n) sccb_en <= 1'b0; else if(sccb_start) //开始配置寄存器 sccb_en <= 1'b1; else if(sccb_done && reg_cnt < REG_NUM) //上一个配置完后立马配置下一个 sccb_en <= 1'b1; else sccb_en <= 1'b0; end //========================================================================== //== 寄存器配置个数计数 //========================================================================== always @(posedge clk or negedge rst_n) begin if(!rst_n) reg_cnt <= 8'd0; else if(sccb_en) reg_cnt <= reg_cnt + 8'b1; end //========================================================================== //== 所有寄存器全部配置完成信号 //========================================================================== always @(posedge clk or negedge rst_n) begin if(!rst_n) sccb_cfg_done <= 1'b0; else if((reg_cnt == REG_NUM) && sccb_done) sccb_cfg_done <= 1'b1; end //========================================================================== //== 配置寄存器地址与数据,Xclk=24Mhz //========================================================================== always @(posedge clk or negedge rst_n) begin if(!rst_n) sccb_data <= 24'b0; else begin case(reg_cnt) ...... default: endcase end end endmodule
上述代码是改自正点原子的FPGA教程,我将软件复位后的延时删了,因为在前面我们已经进行了硬件复位,所以完全没必要再进行寄存器软件复位,而且就算进行软件复位,不需要再延时也是OK的。
ov7725、ov7670和ov5640在这部分也是几乎一样的,只不过是寄存器和寄存器的个数那需要改一下,相关注意事项请看下面的详细介绍。
二、OV7670寄存器配置
1 //************************************************************************** 2 // *** 名称 : sccb_ov7670_cfg.v 3 // *** 作者 : xianyu_FPGA 4 // *** 博客 : https://www.cnblogs.com/xianyufpga/ 5 // *** 日期 : 2019-08-10 6 // *** 描述 : SCCB配置OV7670寄存器 7 //************************************************************************** 8 9 module sccb_ov7670_cfg 10 //========================< 参数 >========================================== 11 #( 12 parameter REG_NUM = 123 //寄存器个数 13 ) 14 //========================< 端口 >========================================== 15 ( 16 input wire clk , //时钟,1Mhz 17 input wire rst_n , //复位,低电平有效 18 input wire sccb_vld , //SCCB配置有效信号 19 input wire sccb_done , //SCCB寄存器配置完成信号 20 output reg sccb_en , //SCCB触发执行信号 21 output reg [15:0] sccb_data , //SCCB要配置的地址与数据(高8位地址,低8位数据) 22 output reg sccb_cfg_done //SCCB全部寄存器配置完成信号 23 ); 24 //========================< 信号 >========================================== 25 reg sccb_vld_r ; 26 wire sccb_start ; 27 reg [6:0] reg_cnt ; //寄存器配置个数计数器 28 //========================================================================== 29 //== sccb_vld上升沿检测 30 //========================================================================== 31 always @(posedge clk or negedge rst_n) begin 32 if(!rst_n) 33 sccb_vld_r <= 1'b0; 34 else 35 sccb_vld_r <= sccb_vld; 36 end 37 38 assign sccb_start = sccb_vld && ~sccb_vld_r; 39 //========================================================================== 40 //== sccb触发执行信号 41 //========================================================================== 42 always @(posedge clk or negedge rst_n) begin 43 if(!rst_n) 44 sccb_en <= 1'b0; 45 else if(sccb_start) //开始配置寄存器 46 sccb_en <= 1'b1; 47 else if(sccb_done && reg_cnt < REG_NUM) //上一个配置完后立马配置下一个 48 sccb_en <= 1'b1; 49 else 50 sccb_en <= 1'b0; 51 end 52 //========================================================================== 53 //== 寄存器配置个数计数 54 //========================================================================== 55 always @(posedge clk or negedge rst_n) begin 56 if(!rst_n) 57 reg_cnt <= 7'd0; 58 else if(sccb_en) 59 reg_cnt <= reg_cnt + 7'b1; 60 end 61 //========================================================================== 62 //== 所有寄存器全部配置完成信号 63 //========================================================================== 64 always @(posedge clk or negedge rst_n) begin 65 if(!rst_n) 66 sccb_cfg_done <= 1'b0; 67 else if((reg_cnt == REG_NUM) && sccb_done) 68 sccb_cfg_done <= 1'b1; 69 end 70 //========================================================================== 71 //== 配置寄存器地址与数据 72 //========================================================================== 73 always @(posedge clk or negedge rst_n) begin 74 if(!rst_n) 75 sccb_data <= 16'b0; 76 else begin 77 case(reg_cnt) //30fps,XCLK=24Mhz,PCLK=24Mhz,ID=0x42 78 //像素格式 ---------------------------------------------------------------------------------- 79 7'd0 : sccb_data <= 16'h12_04; //COM7 寄存器复位:否,图像选择:00_YUV,04_RGB 80 7'd1 : sccb_data <= 16'h40_d0; //COM15 RGB565,范围00-FF(YUV为80,,范围为01-FE) 81 // YUYV YVYU UYVY VYUY 82 7'd2 : sccb_data <= 16'h3a_04; //TSLB 与COM13配合,bit[3]:0 0 1 1 83 7'd3 : sccb_data <= 16'h3d_88; //COM13 与TSLB 配合,bit[3]: 0 1 0 1 84 //帧率控制(Xclk=24Mhz) ---------------------------------------------------------------------- 85 //fps 30 15 25 14.3 86 //pclk 24 12 24 12 87 7'd4 : sccb_data <= 16'h11_80; //CLKRC 80 00 80 00 88 7'd5 : sccb_data <= 16'h6b_0a; //DBLV 0a 0a 0a 0a 89 7'd6 : sccb_data <= 16'h2a_00; //EXHCH 00 00 00 00 90 7'd7 : sccb_data <= 16'h2b_00; //EXHCL 00 00 00 00 91 7'd8 : sccb_data <= 16'h92_00; //DM_LNL 00 00 66 1a 92 7'd9 : sccb_data <= 16'h93_00; //DM_LNH 00 00 00 00 93 7'd10 : sccb_data <= 16'h3b_0a; //COM11 0a 0a 0a 0a 94 //镜像控制 ---------------------------------------------------------------------------------- 95 7'd17 : sccb_data <= 16'h1e_01; //镜像翻转 正常模式01 垂直翻转11 96 // 水平翻转21 水平垂直翻转31 97 //测试图案 ---------------------------------------------------------------------------------- 98 // 正常 彩条 细纹 渐变彩条 99 7'd18 : sccb_data <= 16'h70_00; //测试图案 00 00 80 80 100 7'd19 : sccb_data <= 16'h71_01; //测试图案 01 81 01 81 101 //行场时序(默认值) -------------------------------------------------------------------------- 102 7'd20 : sccb_data <= 16'h17_11; //HSTART 行频开始高8位 103 7'd21 : sccb_data <= 16'h18_61; //HSTOP 行频结束高8位 104 7'd22 : sccb_data <= 16'h32_80; //HREF bit[5:3]行频开始低3位,bit[2:0]行频结束低3位 105 7'd23 : sccb_data <= 16'h19_03; //VSTART 场频开始高8位 106 7'd24 : sccb_data <= 16'h1a_7b; //VSTOP 场频结束高8位 107 7'd25 : sccb_data <= 16'h03_00; //VREF bit[3:2]场频开始低2位,bit[1:0]场频结束低2位 108 //其他 -------------------------------------------------------------------------------------- 109 7'd26 : sccb_data <= 16'h3e_00; //COM14 PCLK分频 110 7'd27 : sccb_data <= 16'h73_00; //SCALING DSP缩放时钟分频,与COM14需一致,选择不分频 111 7'd28 : sccb_data <= 16'h0c_00; 112 7'd29 : sccb_data <= 16'h7a_20; 113 7'd30 : sccb_data <= 16'h7b_1c; 114 7'd31 : sccb_data <= 16'h7c_28; 115 7'd32 : sccb_data <= 16'h7d_3c; 116 7'd33 : sccb_data <= 16'h7e_55; 117 7'd34 : sccb_data <= 16'h7f_68; 118 7'd35 : sccb_data <= 16'h80_76; 119 7'd36 : sccb_data <= 16'h81_80; 120 7'd37 : sccb_data <= 16'h82_88; 121 7'd38 : sccb_data <= 16'h83_8f; 122 7'd39 : sccb_data <= 16'h84_96; 123 7'd40 : sccb_data <= 16'h85_a3; 124 7'd41 : sccb_data <= 16'h86_af; 125 7'd42 : sccb_data <= 16'h87_c4; 126 7'd43 : sccb_data <= 16'h88_d7; 127 7'd44 : sccb_data <= 16'h89_e8; 128 7'd45 : sccb_data <= 16'h00_00; 129 7'd46 : sccb_data <= 16'h10_00; 130 7'd47 : sccb_data <= 16'h0d_00; 131 7'd48 : sccb_data <= 16'h14_28; 132 7'd49 : sccb_data <= 16'h24_75; 133 7'd50 : sccb_data <= 16'h25_63; 134 7'd51 : sccb_data <= 16'h26_A5; 135 7'd52 : sccb_data <= 16'h9f_78; 136 7'd53 : sccb_data <= 16'ha0_68; 137 7'd54 : sccb_data <= 16'ha1_03; 138 7'd55 : sccb_data <= 16'ha6_df; 139 7'd56 : sccb_data <= 16'ha7_df; 140 7'd57 : sccb_data <= 16'ha8_f0; 141 7'd58 : sccb_data <= 16'ha9_90; 142 7'd59 : sccb_data <= 16'haa_94; 143 7'd60 : sccb_data <= 16'h13_ef; 144 7'd61 : sccb_data <= 16'h0e_61; 145 7'd62 : sccb_data <= 16'h72_11; 146 7'd63 : sccb_data <= 16'h16_02; 147 7'd64 : sccb_data <= 16'h0f_4b; 148 7'd65 : sccb_data <= 16'h21_02; 149 7'd66 : sccb_data <= 16'h22_91; 150 7'd67 : sccb_data <= 16'h29_07; 151 7'd68 : sccb_data <= 16'h33_0b; 152 7'd69 : sccb_data <= 16'h35_0b; 153 7'd70 : sccb_data <= 16'h37_1d; 154 7'd71 : sccb_data <= 16'h38_71; 155 7'd72 : sccb_data <= 16'h39_2a; 156 7'd73 : sccb_data <= 16'h3c_78; 157 7'd74 : sccb_data <= 16'h4d_40; 158 7'd75 : sccb_data <= 16'h4e_20; 159 7'd76 : sccb_data <= 16'h69_00; 160 7'd77 : sccb_data <= 16'ha2_02; 161 7'd78 : sccb_data <= 16'h74_19; 162 7'd79 : sccb_data <= 16'h8d_4f; 163 7'd80 : sccb_data <= 16'h8e_00; 164 7'd81 : sccb_data <= 16'h8f_00; 165 7'd82 : sccb_data <= 16'h90_00; 166 7'd83 : sccb_data <= 16'h91_00; 167 7'd84 : sccb_data <= 16'h96_00; 168 7'd85 : sccb_data <= 16'h9a_80; 169 7'd86 : sccb_data <= 16'hb0_84; 170 7'd87 : sccb_data <= 16'hb1_0c; 171 7'd88 : sccb_data <= 16'hb2_0e; 172 7'd89 : sccb_data <= 16'hb3_82; 173 7'd90 : sccb_data <= 16'hb8_0a; 174 7'd91 : sccb_data <= 16'h43_14; 175 7'd92 : sccb_data <= 16'h44_f0; 176 7'd93 : sccb_data <= 16'h45_34; 177 7'd94 : sccb_data <= 16'h46_58; 178 7'd95 : sccb_data <= 16'h47_28; 179 7'd96 : sccb_data <= 16'h48_3a; 180 7'd97 : sccb_data <= 16'h59_88; 181 7'd98 : sccb_data <= 16'h5a_88; 182 7'd99 : sccb_data <= 16'h5b_44; 183 7'd100 : sccb_data <= 16'h5c_67; 184 7'd101 : sccb_data <= 16'h5d_49; 185 7'd102 : sccb_data <= 16'h5e_0e; 186 7'd103 : sccb_data <= 16'h64_04; 187 7'd104 : sccb_data <= 16'h65_20; 188 7'd105 : sccb_data <= 16'h66_05; 189 7'd106 : sccb_data <= 16'h94_04; 190 7'd107 : sccb_data <= 16'h95_08; 191 7'd108 : sccb_data <= 16'h6c_0a; 192 7'd109 : sccb_data <= 16'h6d_55; 193 7'd110 : sccb_data <= 16'h4f_80; 194 7'd111 : sccb_data <= 16'h50_80; 195 7'd112 : sccb_data <= 16'h51_00; 196 7'd113 : sccb_data <= 16'h52_22; 197 7'd114 : sccb_data <= 16'h53_5e; 198 7'd115 : sccb_data <= 16'h54_80; 199 7'd116 : sccb_data <= 16'h09_03; 200 7'd117 : sccb_data <= 16'h6e_11; 201 7'd118 : sccb_data <= 16'h6f_9f; 202 7'd119 : sccb_data <= 16'h55_00; 203 7'd120 : sccb_data <= 16'h56_40; 204 7'd121 : sccb_data <= 16'h57_80; 205 7'd122 : sccb_data <= 16'h15_00; 206 default: sccb_data <= 16'h1c_7f; //MIDH 制造商ID 高8位 207 endcase 208 end 209 end 210 211 endmodule
我只是把重要的寄存器提前了,可以通过更改 pll 寄存器而改变帧率,也可以通过窗口相关的寄存器改变输出的分辨率,但有些麻烦,我后面会介绍一种更好的办法来获取任意分辨率,以期适应我们的屏幕。
这里输出的分辨率为 640x480,Pclk 为 24Mhz,fps 是我通过测量得到的,那如何进行理论值计算呢?
datasheet 中有这样一张图,我们来算一下。一帧图像时间 = (2 x tPclk)x 510 x 784 = (2 x 510 x 784) / Pclk hz,一帧图像时间的倒数即为帧率 fps,因此帧率 fps = Pclk /(2 x 510 x 784),Pclk为24000000hz,因此计算得到 fps = 24000000 /(2 x 510 x 784)≈ 30.012。计算结果和实际测量值一致。
三、OV7725寄存器配置
1 //************************************************************************** 2 // *** 名称 : sccb_cfg.v 3 // *** 作者 : xianyu_FPGA 4 // *** 博客 : https://www.cnblogs.com/xianyufpga/ 5 // *** 日期 : 2019-08-10 6 // *** 描述 : SCCB配置ov7725寄存器 7 //************************************************************************** 8 9 module sccb_ov7725_cfg 10 //========================< 参数 >========================================== 11 #( 12 parameter REG_NUM = 70 //寄存器个数 13 ) 14 //========================< 端口 >========================================== 15 ( 16 input wire clk , //时钟,1Mhz 17 input wire rst_n , //复位,低电平有效 18 input wire sccb_vld , //SCCB配置有效信号 19 input wire sccb_done , //SCCB寄存器配置完成信号 20 output reg sccb_en , //SCCB触发执行信号 21 output reg [15:0] sccb_data , //SCCB要配置的地址与数据(高8位地址,低8位数据) 22 output reg sccb_cfg_done //SCCB全部寄存器配置完成信号 23 ); 24 //========================< 信号 >========================================== 25 reg sccb_vld_r ; 26 wire sccb_start ; 27 reg [6:0] reg_cnt ; //寄存器配置个数计数器 28 //========================================================================== 29 //== sccb_vld上升沿检测 30 //========================================================================== 31 always @(posedge clk or negedge rst_n) begin 32 if(!rst_n) 33 sccb_vld_r <= 1'b0; 34 else 35 sccb_vld_r <= sccb_vld; 36 end 37 38 assign sccb_start = sccb_vld && ~sccb_vld_r; 39 //========================================================================== 40 //== sccb触发执行信号 41 //========================================================================== 42 always @(posedge clk or negedge rst_n) begin 43 if(!rst_n) 44 sccb_en <= 1'b0; 45 else if(sccb_start) //开始配置寄存器 46 sccb_en <= 1'b1; 47 else if(sccb_done && reg_cnt < REG_NUM) //上一个配置完后立马配置下一个 48 sccb_en <= 1'b1; 49 else 50 sccb_en <= 1'b0; 51 end 52 //========================================================================== 53 //== 寄存器配置个数计数 54 //========================================================================== 55 always @(posedge clk or negedge rst_n) begin 56 if(!rst_n) 57 reg_cnt <= 7'd0; 58 else if(sccb_en) 59 reg_cnt <= reg_cnt + 7'b1; 60 end 61 //========================================================================== 62 //== 所有寄存器全部配置完成信号 63 //========================================================================== 64 always @(posedge clk or negedge rst_n) begin 65 if(!rst_n) 66 sccb_cfg_done <= 1'b0; 67 else if((reg_cnt == REG_NUM) && sccb_done) 68 sccb_cfg_done <= 1'b1; 69 end 70 //========================================================================== 71 //== 配置寄存器地址与数据 72 //========================================================================== 73 always @(posedge clk or negedge rst_n) begin 74 if(!rst_n) 75 sccb_data <= 16'b0; 76 else begin 77 case(reg_cnt) //30fps,XCLK=24Mhz,PCLK=24Mhz,ID=0x42 78 //基本设置 ---------------------------------------------------------------------------- 79 7'd0 : sccb_data <= {8'h12, 8'h06}; //COM7 复位选择:否;格式:VGA_RGB565 80 7'd1 : sccb_data <= {8'h0c, 8'h10}; //COM3 10正常模式; 90垂直翻转 81 // 50水平翻转; d0水平垂直翻转 82 //Bit[0] 0正常模式; 1彩条测试 83 //时序参数 ---------------------------------------------------------------------------- 84 7'd2 : sccb_data <= {8'h17, 8'h22}; //HSTART VGA:8'h22; QVGA:8'h3f 85 7'd3 : sccb_data <= {8'h18, 8'ha4}; //HSIZE VGA:8'ha4; QVGA:8'h50 86 7'd4 : sccb_data <= {8'h19, 8'h07}; //VSTART VGA:8'h07; QVGA:8'h03 87 7'd5 : sccb_data <= {8'h1a, 8'hf0}; //VSIZE VGA:8'hf0; QVGA:8'h78 88 7'd6 : sccb_data <= {8'h29, 8'ha0}; //HOutSize VGA:8'hA0; QVGA:8'hF0 89 7'd7 : sccb_data <= {8'h2c, 8'hf0}; //VOutSize VGA:8'hF0; QVGA:8'h78 90 //帧率PLL(Xclk=24Mhz) --------------------------------------------------------------- 91 //fps/pclk 30/24 15/12 25/24 14.3/12 92 7'd8 : sccb_data <= {8'h11, 8'h01}; // 01 03 01 03 93 7'd9 : sccb_data <= {8'h0d, 8'h41}; // 41 41 41 41 94 7'd10 : sccb_data <= {8'h2a, 8'h00}; // 00 00 00 00 95 7'd11 : sccb_data <= {8'h33, 8'h00}; // 00 00 66 1a 96 7'd12 : sccb_data <= {8'h34, 8'h00}; // 00 00 00 00 97 7'd13 : sccb_data <= {8'h2d, 8'h00}; // 00 00 00 00 98 7'd14 : sccb_data <= {8'h2e, 8'h00}; // 00 00 00 00 99 7'd15 : sccb_data <= {8'h0e, 8'h65}; // 65 65 65 65 100 7'd16 : sccb_data <= {8'h2b, 8'h00}; //60hz/50hz 默认00/60hz,有灯光干扰,fps如上 101 // 改为9e/50hz,无灯光干扰,fps会变 102 //DSP 控制 ---------------------------------------------------------------------------- 103 7'd17 : sccb_data <= {8'h42, 8'h7f}; //TGT_B 黑电平校准蓝色通道目标值 104 7'd18 : sccb_data <= {8'h4d, 8'h09}; //FixGain 模拟增益放大器 105 7'd19 : sccb_data <= {8'h63, 8'hf0}; //AWB_Ctrl0 自动白平衡控制字节0 106 7'd20 : sccb_data <= {8'h64, 8'hff}; //DSP_Ctrl1 DSP控制字节1 107 7'd21 : sccb_data <= {8'h65, 8'h00}; //DSP_Ctrl2 DSP控制字节2 108 7'd22 : sccb_data <= {8'h66, 8'h00}; //DSP_Ctrl3 DSP控制字节3 109 7'd23 : sccb_data <= {8'h67, 8'h00}; //DSP_Ctrl4 DSP控制字节4 110 //AGC AEC AWB ------------------------------------------------------------------------ 111 7'd24 : sccb_data <= {8'h13, 8'hff}; //COM8 自动增益/白平衡/曝光 112 7'd25 : sccb_data <= {8'h0f, 8'hc5}; //COM6 113 7'd26 : sccb_data <= {8'h14, 8'h11}; 114 7'd27 : sccb_data <= {8'h22, 8'h98}; 115 7'd28 : sccb_data <= {8'h23, 8'h03}; 116 7'd29 : sccb_data <= {8'h24, 8'h40}; 117 7'd30 : sccb_data <= {8'h25, 8'h30}; 118 7'd31 : sccb_data <= {8'h26, 8'ha1}; 119 7'd32 : sccb_data <= {8'h6b, 8'haa}; 120 7'd33 : sccb_data <= {8'h13, 8'hff}; 121 //参数调整 --------------------------------------------------------------------------- 122 7'd34 : sccb_data <= {8'h90, 8'h0a}; //EDGE1 边缘增强控制1 123 7'd35 : sccb_data <= {8'h91, 8'h01}; //DNSOff 阈值下限 124 7'd36 : sccb_data <= {8'h92, 8'h01}; //EDGE2 锐度(边缘增强)强度上限 125 7'd37 : sccb_data <= {8'h93, 8'h01}; //EDGE3 锐度(边缘增强)强度下限 126 7'd38 : sccb_data <= {8'h94, 8'h5f}; //MTX1 矩阵系数1 127 7'd39 : sccb_data <= {8'h95, 8'h53}; //MTX2 矩阵系数2 128 7'd40 : sccb_data <= {8'h96, 8'h11}; //MTX3 矩阵系数3 129 7'd41 : sccb_data <= {8'h97, 8'h1a}; //MTX4 矩阵系数4 130 7'd42 : sccb_data <= {8'h98, 8'h3d}; //MTX5 矩阵系数5 131 7'd43 : sccb_data <= {8'h99, 8'h5a}; //MTX6 矩阵系数6 132 7'd44 : sccb_data <= {8'h9a, 8'h1e}; //MTX_Ctrl 矩阵控制 133 7'd45 : sccb_data <= {8'h9b, 8'h3f}; //BRIGHT 亮度 134 7'd46 : sccb_data <= {8'h9c, 8'h25}; //CNST 对比度 135 7'd47 : sccb_data <= {8'h9e, 8'h81}; //UV/ADJ0 紫外线调控 136 7'd48 : sccb_data <= {8'ha6, 8'h06}; //SDE 特殊数字效果控制 137 7'd49 : sccb_data <= {8'ha7, 8'h65}; //USAT "U"饱和增益 138 7'd50 : sccb_data <= {8'ha8, 8'h65}; //VSAT "V"饱和增益 139 7'd51 : sccb_data <= {8'ha9, 8'h80}; //HUECOS cos值 140 7'd52 : sccb_data <= {8'haa, 8'h80}; //HUESIN sin值 141 //伽马控制 ------------------------------------------------------------------------- 142 7'd53 : sccb_data <= {8'h7e, 8'h0c}; 143 7'd54 : sccb_data <= {8'h7f, 8'h16}; 144 7'd55 : sccb_data <= {8'h80, 8'h2a}; 145 7'd56 : sccb_data <= {8'h81, 8'h4e}; 146 7'd57 : sccb_data <= {8'h82, 8'h61}; 147 7'd58 : sccb_data <= {8'h83, 8'h6f}; 148 7'd59 : sccb_data <= {8'h84, 8'h7b}; 149 7'd60 : sccb_data <= {8'h85, 8'h86}; 150 7'd61 : sccb_data <= {8'h86, 8'h8e}; 151 7'd62 : sccb_data <= {8'h87, 8'h97}; 152 7'd63 : sccb_data <= {8'h88, 8'ha4}; 153 7'd64 : sccb_data <= {8'h89, 8'haf}; 154 7'd65 : sccb_data <= {8'h8a, 8'hc5}; 155 7'd66 : sccb_data <= {8'h8b, 8'hd7}; 156 7'd67 : sccb_data <= {8'h8c, 8'he8}; 157 7'd68 : sccb_data <= {8'h8d, 8'h20}; 158 //others -------------------------------------------------------------------------- 159 7'd69 : sccb_data <= {8'h3d, 8'h03}; //模拟过程的直流偏移 160 endcase 161 end 162 end 163 164 endmodule
和 OV7670 差不多,注意一下第 13 个寄存器 8'h2b,根据韩彬的书《FPGA设计技巧与案例开发详解》介绍,假设我们原本寄存器配置的是 640x480@30fps,此时8'h2b的值为00,大陆的白炽灯频率为50hz,会导致图像效果出现条纹干扰。当把 8'h2b寄存器的值改为 9e 时,就可以避免该条纹干扰,但帧率却会由 30fps 变成 25fps。
四、OV5640寄存器配置
1 //************************************************************************** 2 // *** 名称 : sccb_ov5640_cfg.v 3 // *** 作者 : xianyu_FPGA 4 // *** 博客 : https://www.cnblogs.com/xianyufpga/ 5 // *** 日期 : 2019-08-10 6 // *** 描述 : SCCB配置ov5640寄存器 7 //************************************************************************** 8 9 module sccb_ov5640_cfg 10 //========================< 参数 >========================================== 11 #( 12 parameter REG_NUM = 240 , //寄存器个数 13 parameter CMOS_H_PIXEL = 24'd1024 , //CMOS水平方向像素个数 14 parameter CMOS_V_PIXEL = 24'd768 , //CMOS垂直方向像素个数 15 parameter TOTAL_H_PIXEL = CMOS_H_PIXEL+13'd1216 , //水平总像素大小 16 parameter TOTAL_V_PIXEL = CMOS_V_PIXEL+13'd504 //垂直总像素大小 17 ) 18 //========================< 端口 >========================================== 19 ( 20 input wire clk , //时钟,1Mhz 21 input wire rst_n , //复位,低电平有效 22 input wire sccb_vld , //SCCB配置有效信号 23 input wire sccb_done , //SCCB寄存器配置完成信号 24 output reg sccb_en , //SCCB触发执行信号 25 output reg [23:0] sccb_data , //SCCB要配置的地址与数据(高8位地址,低8位数据) 26 output reg sccb_cfg_done //SCCB全部寄存器配置完成信号 27 ); 28 //========================< 信号 >========================================== 29 reg sccb_vld_r ; 30 wire sccb_start ; 31 reg [7:0] reg_cnt ; //寄存器配置个数计数器 32 //========================================================================== 33 //== sccb_vld上升沿检测 34 //========================================================================== 35 always @(posedge clk or negedge rst_n) begin 36 if(!rst_n) 37 sccb_vld_r <= 1'b0; 38 else 39 sccb_vld_r <= sccb_vld; 40 end 41 42 assign sccb_start = sccb_vld && ~sccb_vld_r; 43 //========================================================================== 44 //== sccb触发执行信号 45 //========================================================================== 46 always @(posedge clk or negedge rst_n) begin 47 if(!rst_n) 48 sccb_en <= 1'b0; 49 else if(sccb_start) //开始配置寄存器 50 sccb_en <= 1'b1; 51 else if(sccb_done && reg_cnt < REG_NUM) //上一个配置完后立马配置下一个 52 sccb_en <= 1'b1; 53 else 54 sccb_en <= 1'b0; 55 end 56 //========================================================================== 57 //== 寄存器配置个数计数 58 //========================================================================== 59 always @(posedge clk or negedge rst_n) begin 60 if(!rst_n) 61 reg_cnt <= 8'd0; 62 else if(sccb_en) 63 reg_cnt <= reg_cnt + 8'b1; 64 end 65 //========================================================================== 66 //== 所有寄存器全部配置完成信号 67 //========================================================================== 68 always @(posedge clk or negedge rst_n) begin 69 if(!rst_n) 70 sccb_cfg_done <= 1'b0; 71 else if((reg_cnt == REG_NUM) && sccb_done) 72 sccb_cfg_done <= 1'b1; 73 end 74 //========================================================================== 75 //== 配置寄存器地址与数据,Xclk=24Mhz 76 //========================================================================== 77 always @(posedge clk or negedge rst_n) begin 78 if(!rst_n) 79 sccb_data <= 24'b0; 80 else begin 81 case(reg_cnt) //Xclk=24Mhz FPS=30fps Pclk=24Mhz(不解) 82 //基本配置 ------------------------------------------------------------------------------------ 83 8'd0 : sccb_data <= {16'h3008,8'h02}; //复位休眠 Bit[7]:复位 Bit[6]:休眠 84 8'd1 : sccb_data <= {16'h300e,8'h58}; //DVP 使能 DVP enable 85 8'd2 : sccb_data <= {16'h4300,8'h61}; //格式控制 RGB565 86 8'd3 : sccb_data <= {16'h503d,8'h00}; //测试图案 00正常 80彩条 81混乱 82棋盘 87 //PLL(11_23为20fps) --------------------------------------------------------------------------- 88 8'd4 : sccb_data <= {16'h3035,8'h21}; //PLL分频 11/1x 21/2x 32/3x 89 8'd5 : sccb_data <= {16'h3036,8'h69}; //PLL倍频 23/1÷ 46/2÷ 69/3÷ 90 8'd6 : sccb_data <= {16'h3037,8'h03}; //PLL分频 bit[4]:0/1 bypass/÷2 91 //ISP(VGA模式) -------------------------------------------------------------------------------- 92 8'd7 : sccb_data <= {16'h3800,8'h00}; 93 8'd8 : sccb_data <= {16'h3801,8'h00}; 94 8'd9 : sccb_data <= {16'h3802,8'h00}; 95 8'd10 : sccb_data <= {16'h3803,8'h04}; 96 8'd11 : sccb_data <= {16'h3804,8'h0a}; 97 8'd12 : sccb_data <= {16'h3805,8'h3f}; 98 8'd13 : sccb_data <= {16'h3806,8'h07}; 99 8'd14 : sccb_data <= {16'h3807,8'h9b}; 100 //输出窗口设置 -------------------------------------------------------------------------------- 101 8'd15 : sccb_data <= {16'h3808,{4'd0,CMOS_H_PIXEL[11:8] }}; //水平像素点数高4位 102 8'd16 : sccb_data <= {16'h3809, CMOS_H_PIXEL[ 7:0] }; //水平像素点数低8位 103 8'd17 : sccb_data <= {16'h380a,{5'd0,CMOS_V_PIXEL[10:8] }}; //垂直像素点数高3位 104 8'd18 : sccb_data <= {16'h380b, CMOS_V_PIXEL[ 7:0] }; //垂直像素点数低8位 105 8'd19 : sccb_data <= {16'h380c,{3'd0,TOTAL_H_PIXEL[12:8] }}; //水平总像素大小高5位 106 8'd20 : sccb_data <= {16'h380d, TOTAL_H_PIXEL[ 7:0] }; //水平总像素大小低8位 107 8'd21 : sccb_data <= {16'h380e,{3'd0,TOTAL_V_PIXEL[12:8] }}; //垂直总像素大小高5位 108 8'd22 : sccb_data <= {16'h380f, TOTAL_V_PIXEL[ 7:0] }; //垂直总像素大小低8位 109 //预缩放 -------------------------------------------------------------------------------------- 110 8'd23 : sccb_data <= {16'h3810,8'h00}; //Timing Hoffset[11:8] 111 8'd24 : sccb_data <= {16'h3811,8'h10}; //Timing Hoffset[7:0] 112 8'd25 : sccb_data <= {16'h3812,8'h00}; //Timing Voffset[10:8] 113 8'd26 : sccb_data <= {16'h3813,8'h06}; //Timing Voffset[7:0] 114 8'd27 : sccb_data <= {16'h3814,8'h31}; //Timing X INC 115 8'd28 : sccb_data <= {16'h3815,8'h31}; //Timing Y INC 116 8'd29 : sccb_data <= {16'h3820,8'h40}; //上下翻转:40/46 117 8'd30 : sccb_data <= {16'h3821,8'h07}; //左右翻转:01/07 118 //SCCB ---------------------------------------------------------------------------------------- 119 8'd31 : sccb_data <= {16'h3103,8'h02}; //Bit[1]:1 PLL Clock 120 8'd32 : sccb_data <= {16'h3108,8'h01}; //系统分频 121 //VCM ----------------------------------------------------------------------------------------- 122 8'd33 : sccb_data <= {16'h3600,8'h08}; //VCM控制,用于自动聚焦 123 8'd34 : sccb_data <= {16'h3601,8'h33}; //VCM控制,用于自动聚焦 124 //AEC/AGC ------------------------------------------------------------------------------------- 125 8'd35 : sccb_data <= {16'h3a02,8'h17}; //60Hz max exposure 126 8'd36 : sccb_data <= {16'h3a03,8'h10}; //60Hz max exposure 127 8'd37 : sccb_data <= {16'h3a0f,8'h30}; //AEC控制;stable range in high 128 8'd38 : sccb_data <= {16'h3a10,8'h28}; //AEC控制;stable range in low 129 8'd39 : sccb_data <= {16'h3a11,8'h60}; //AEC控制; fast zone high 130 8'd40 : sccb_data <= {16'h3a13,8'h43}; //AEC(自动曝光控制) 131 8'd41 : sccb_data <= {16'h3a14,8'h17}; //50Hz max exposure 132 8'd42 : sccb_data <= {16'h3a15,8'h10}; //50Hz max exposure 133 8'd43 : sccb_data <= {16'h3a18,8'h00}; //AEC 增益上限 134 8'd44 : sccb_data <= {16'h3a19,8'hf8}; //AEC 增益上限 135 8'd45 : sccb_data <= {16'h3a1b,8'h30}; //AEC控制;stable range out high 136 8'd46 : sccb_data <= {16'h3a1e,8'h26}; //AEC控制;stable range out low 137 8'd47 : sccb_data <= {16'h3a1f,8'h14}; //AEC控制; fast zone low 138 //5060Hz -------------------------------------------------------------------------------------- 139 8'd48 : sccb_data <= {16'h3c01,8'h34}; 140 8'd49 : sccb_data <= {16'h3c04,8'h28}; 141 8'd50 : sccb_data <= {16'h3c05,8'h98}; 142 8'd51 : sccb_data <= {16'h3c06,8'h00}; //light meter 1 阈值[15:8] 143 8'd52 : sccb_data <= {16'h3c07,8'h08}; //light meter 1 阈值[7:0] 144 8'd53 : sccb_data <= {16'h3c08,8'h00}; //light meter 2 阈值[15:8] 145 8'd54 : sccb_data <= {16'h3c09,8'h1c}; //light meter 2 阈值[7:0] 146 8'd55 : sccb_data <= {16'h3c0a,8'h9c}; //sample number[15:8] 147 8'd56 : sccb_data <= {16'h3c0b,8'h40}; //sample number[7:0] 148 //BLC ----------------------------------------------------------------------------------------- 149 8'd57 : sccb_data <= {16'h4001,8'h02}; //BLC(黑电平校准)补偿起始行号 150 8'd58 : sccb_data <= {16'h4004,8'h02}; //BLC(背光) 2 lines 151 8'd59 : sccb_data <= {16'h4005,8'h1a}; //BLC(黑电平校准)补偿始终更新 152 //ISP ----------------------------------------------------------------------------------------- 153 8'd60 : sccb_data <= {16'h5000,8'ha7}; //ISP 控制 154 8'd61 : sccb_data <= {16'h5001,8'ha3}; //ISP 控制 155 8'd62 : sccb_data <= {16'h501d,8'h40}; //ISP 控制 156 8'd63 : sccb_data <= {16'h501f,8'h01}; //ISP RGB 157 //LENC(镜头校正)控制 16'h5800~16'h583d -------------------------------------------------------- 158 8'd64 : sccb_data <= {16'h5800,8'h23}; 159 8'd65 : sccb_data <= {16'h5801,8'h14}; 160 8'd66 : sccb_data <= {16'h5802,8'h0f}; 161 8'd67 : sccb_data <= {16'h5803,8'h0f}; 162 8'd68 : sccb_data <= {16'h5804,8'h12}; 163 8'd69 : sccb_data <= {16'h5805,8'h26}; 164 8'd70 : sccb_data <= {16'h5806,8'h0c}; 165 8'd71 : sccb_data <= {16'h5807,8'h08}; 166 8'd72 : sccb_data <= {16'h5808,8'h05}; 167 8'd73 : sccb_data <= {16'h5809,8'h05}; 168 8'd74 : sccb_data <= {16'h580a,8'h08}; 169 8'd75 : sccb_data <= {16'h580b,8'h0d}; 170 8'd76 : sccb_data <= {16'h580c,8'h08}; 171 8'd77 : sccb_data <= {16'h580d,8'h03}; 172 8'd78 : sccb_data <= {16'h580e,8'h00}; 173 8'd79 : sccb_data <= {16'h580f,8'h00}; 174 8'd80 : sccb_data <= {16'h5810,8'h03}; 175 8'd81 : sccb_data <= {16'h5811,8'h09}; 176 8'd82 : sccb_data <= {16'h5812,8'h07}; 177 8'd83 : sccb_data <= {16'h5813,8'h03}; 178 8'd84 : sccb_data <= {16'h5814,8'h00}; 179 8'd85 : sccb_data <= {16'h5815,8'h01}; 180 8'd86 : sccb_data <= {16'h5816,8'h03}; 181 8'd87 : sccb_data <= {16'h5817,8'h08}; 182 8'd88 : sccb_data <= {16'h5818,8'h0d}; 183 8'd89 : sccb_data <= {16'h5819,8'h08}; 184 8'd90 : sccb_data <= {16'h581a,8'h05}; 185 8'd91 : sccb_data <= {16'h581b,8'h06}; 186 8'd92 : sccb_data <= {16'h581c,8'h08}; 187 8'd93 : sccb_data <= {16'h581d,8'h0e}; 188 8'd94 : sccb_data <= {16'h581e,8'h29}; 189 8'd95 : sccb_data <= {16'h581f,8'h17}; 190 8'd96 : sccb_data <= {16'h5820,8'h11}; 191 8'd97 : sccb_data <= {16'h5821,8'h11}; 192 8'd98 : sccb_data <= {16'h5822,8'h15}; 193 8'd99 : sccb_data <= {16'h5823,8'h28}; 194 8'd100: sccb_data <= {16'h5824,8'h46}; 195 8'd101: sccb_data <= {16'h5825,8'h26}; 196 8'd102: sccb_data <= {16'h5826,8'h08}; 197 8'd103: sccb_data <= {16'h5827,8'h26}; 198 8'd104: sccb_data <= {16'h5828,8'h64}; 199 8'd105: sccb_data <= {16'h5829,8'h26}; 200 8'd106: sccb_data <= {16'h582a,8'h24}; 201 8'd107: sccb_data <= {16'h582b,8'h22}; 202 8'd108: sccb_data <= {16'h582c,8'h24}; 203 8'd109: sccb_data <= {16'h582d,8'h24}; 204 8'd110: sccb_data <= {16'h582e,8'h06}; 205 8'd111: sccb_data <= {16'h582f,8'h22}; 206 8'd112: sccb_data <= {16'h5830,8'h40}; 207 8'd113: sccb_data <= {16'h5831,8'h42}; 208 8'd114: sccb_data <= {16'h5832,8'h24}; 209 8'd115: sccb_data <= {16'h5833,8'h26}; 210 8'd116: sccb_data <= {16'h5834,8'h24}; 211 8'd117: sccb_data <= {16'h5835,8'h22}; 212 8'd118: sccb_data <= {16'h5836,8'h22}; 213 8'd119: sccb_data <= {16'h5837,8'h26}; 214 8'd120: sccb_data <= {16'h5838,8'h44}; 215 8'd121: sccb_data <= {16'h5839,8'h24}; 216 8'd122: sccb_data <= {16'h583a,8'h26}; 217 8'd123: sccb_data <= {16'h583b,8'h28}; 218 8'd124: sccb_data <= {16'h583c,8'h42}; 219 8'd125: sccb_data <= {16'h583d,8'hce}; 220 //AWB(自动白平衡控制) 16'h5180~16'h519e ------------------------------------------------------- 221 8'd126: sccb_data <= {16'h5180,8'hff}; 222 8'd127: sccb_data <= {16'h5181,8'hf2}; 223 8'd128: sccb_data <= {16'h5182,8'h00}; 224 8'd129: sccb_data <= {16'h5183,8'h14}; 225 8'd130: sccb_data <= {16'h5184,8'h25}; 226 8'd131: sccb_data <= {16'h5185,8'h24}; 227 8'd132: sccb_data <= {16'h5186,8'h09}; 228 8'd133: sccb_data <= {16'h5187,8'h09}; 229 8'd134: sccb_data <= {16'h5188,8'h09}; 230 8'd135: sccb_data <= {16'h5189,8'h75}; 231 8'd136: sccb_data <= {16'h518a,8'h54}; 232 8'd137: sccb_data <= {16'h518b,8'he0}; 233 8'd138: sccb_data <= {16'h518c,8'hb2}; 234 8'd139: sccb_data <= {16'h518d,8'h42}; 235 8'd140: sccb_data <= {16'h518e,8'h3d}; 236 8'd141: sccb_data <= {16'h518f,8'h56}; 237 8'd142: sccb_data <= {16'h5190,8'h46}; 238 8'd143: sccb_data <= {16'h5191,8'hf8}; 239 8'd144: sccb_data <= {16'h5192,8'h04}; 240 8'd145: sccb_data <= {16'h5193,8'h70}; 241 8'd146: sccb_data <= {16'h5194,8'hf0}; 242 8'd147: sccb_data <= {16'h5195,8'hf0}; 243 8'd148: sccb_data <= {16'h5196,8'h03}; 244 8'd149: sccb_data <= {16'h5197,8'h01}; 245 8'd150: sccb_data <= {16'h5198,8'h04}; 246 8'd151: sccb_data <= {16'h5199,8'h12}; 247 8'd152: sccb_data <= {16'h519a,8'h04}; 248 8'd153: sccb_data <= {16'h519b,8'h00}; 249 8'd154: sccb_data <= {16'h519c,8'h06}; 250 8'd155: sccb_data <= {16'h519d,8'h82}; 251 8'd156: sccb_data <= {16'h519e,8'h38}; 252 //Gamma(伽马)控制 16'h5480~16'h5490 ----------------------------------------------------------- 253 8'd157: sccb_data <= {16'h5480,8'h01}; 254 8'd158: sccb_data <= {16'h5481,8'h08}; 255 8'd159: sccb_data <= {16'h5482,8'h14}; 256 8'd160: sccb_data <= {16'h5483,8'h28}; 257 8'd161: sccb_data <= {16'h5484,8'h51}; 258 8'd162: sccb_data <= {16'h5485,8'h65}; 259 8'd163: sccb_data <= {16'h5486,8'h71}; 260 8'd164: sccb_data <= {16'h5487,8'h7d}; 261 8'd165: sccb_data <= {16'h5488,8'h87}; 262 8'd166: sccb_data <= {16'h5489,8'h91}; 263 8'd167: sccb_data <= {16'h548a,8'h9a}; 264 8'd168: sccb_data <= {16'h548b,8'haa}; 265 8'd169: sccb_data <= {16'h548c,8'hb8}; 266 8'd170: sccb_data <= {16'h548d,8'hcd}; 267 8'd171: sccb_data <= {16'h548e,8'hdd}; 268 8'd172: sccb_data <= {16'h548f,8'hea}; 269 8'd173: sccb_data <= {16'h5490,8'h1d}; 270 //CMX(彩色矩阵控制) 16'h5381~16'h538b --------------------------------------------------------- 271 8'd174: sccb_data <= {16'h5381,8'h1e}; 272 8'd175: sccb_data <= {16'h5382,8'h5b}; 273 8'd176: sccb_data <= {16'h5383,8'h08}; 274 8'd177: sccb_data <= {16'h5384,8'h0a}; 275 8'd178: sccb_data <= {16'h5385,8'h7e}; 276 8'd179: sccb_data <= {16'h5386,8'h88}; 277 8'd180: sccb_data <= {16'h5387,8'h7c}; 278 8'd181: sccb_data <= {16'h5388,8'h6c}; 279 8'd182: sccb_data <= {16'h5389,8'h10}; 280 8'd183: sccb_data <= {16'h538a,8'h01}; 281 8'd184: sccb_data <= {16'h538b,8'h98}; 282 //SDE(特殊数码效果)控制 16'h5580~16'h558b ----------------------------------------------------- 283 8'd185: sccb_data <= {16'h5580,8'h06}; 284 8'd186: sccb_data <= {16'h5583,8'h40}; 285 8'd187: sccb_data <= {16'h5584,8'h10}; 286 8'd188: sccb_data <= {16'h5589,8'h10}; 287 8'd189: sccb_data <= {16'h558a,8'h00}; 288 8'd190: sccb_data <= {16'h558b,8'hf8}; 289 //CIP(颜色插值)控制 (16'h5300~16'h530c) ------------------------------------------------------- 290 8'd191: sccb_data <= {16'h5300,8'h08}; 291 8'd192: sccb_data <= {16'h5301,8'h30}; 292 8'd193: sccb_data <= {16'h5302,8'h10}; 293 8'd194: sccb_data <= {16'h5303,8'h00}; 294 8'd195: sccb_data <= {16'h5304,8'h08}; 295 8'd196: sccb_data <= {16'h5305,8'h30}; 296 8'd197: sccb_data <= {16'h5306,8'h08}; 297 8'd198: sccb_data <= {16'h5307,8'h16}; 298 8'd199: sccb_data <= {16'h5309,8'h08}; 299 8'd200: sccb_data <= {16'h530a,8'h30}; 300 8'd201: sccb_data <= {16'h530b,8'h04}; 301 8'd202: sccb_data <= {16'h530c,8'h06}; 302 //测试闪光灯功能 ------------------------------------------------------------------------------ 303 8'd203: sccb_data <= {16'h3000,8'h00}; //系统块复位控制 304 8'd204: sccb_data <= {16'h3004,8'hff}; //时钟使能控制 305 8'd205: sccb_data <= {16'h3017,8'hff}; //I/O控制[3:0] 00:input ff:output 306 8'd206: sccb_data <= {16'h3018,8'hff}; //I/O控制[7:2] 00:input ff:output 307 8'd207: sccb_data <= {16'h3016,8'h02}; 308 8'd208: sccb_data <= {16'h301c,8'h02}; 309 8'd209: sccb_data <= {16'h3019,8'h02}; //打开闪光灯 310 8'd210: sccb_data <= {16'h3019,8'h00}; //关闭闪光灯 311 //others -------------------------------------------------------------------------------------- 312 8'd211: sccb_data <= {16'h3612,8'h29}; 313 8'd212: sccb_data <= {16'h3618,8'h00}; 314 8'd213: sccb_data <= {16'h3620,8'h52}; 315 8'd214: sccb_data <= {16'h3621,8'he0}; 316 8'd215: sccb_data <= {16'h3622,8'h01}; 317 8'd216: sccb_data <= {16'h302d,8'h60}; //系统控制 318 8'd217: sccb_data <= {16'h3630,8'h36}; 319 8'd218: sccb_data <= {16'h3631,8'h0e}; 320 8'd219: sccb_data <= {16'h3632,8'he2}; 321 8'd220: sccb_data <= {16'h3633,8'h12}; 322 8'd221: sccb_data <= {16'h3634,8'h40}; 323 8'd222: sccb_data <= {16'h3635,8'h13}; 324 8'd223: sccb_data <= {16'h3636,8'h03}; 325 8'd224: sccb_data <= {16'h3703,8'h5a}; 326 8'd225: sccb_data <= {16'h3704,8'ha0}; 327 8'd226: sccb_data <= {16'h3705,8'h1a}; 328 8'd227: sccb_data <= {16'h3708,8'h64}; 329 8'd228: sccb_data <= {16'h3709,8'h52}; 330 8'd229: sccb_data <= {16'h370b,8'h60}; 331 8'd230: sccb_data <= {16'h370c,8'h03}; 332 8'd231: sccb_data <= {16'h3715,8'h78}; 333 8'd232: sccb_data <= {16'h3717,8'h01}; 334 8'd233: sccb_data <= {16'h371b,8'h20}; 335 8'd234: sccb_data <= {16'h3731,8'h12}; 336 8'd235: sccb_data <= {16'h3901,8'h0a}; 337 8'd236: sccb_data <= {16'h3905,8'h02}; 338 8'd237: sccb_data <= {16'h3906,8'h10}; 339 8'd238: sccb_data <= {16'h3b07,8'h0a}; //帧曝光模式 340 8'd239: sccb_data <= {16'h4407,8'h04}; //量化标度 341 default:sccb_data <= {16'h300a,8'h00}; //器件ID高8位 342 endcase 343 end 344 end 345 346 347 348 endmodule
ov5640的寄存器配置如上所示,可以配出 1024x768@30fps 的图像,该寄存器配置表同样来自正点原子的改编。注意一下,OV5640的寄存器地址是 16 位的,加上数据,sccb_data 的值为 24 位,这点和OV7670、OV7725不一样。
1、窗口输出的疑惑
其窗口输出分三个部分:
(1)ISP 输入窗口设置( ISP input size):0x3800~0x3807
(2)预缩放窗口设置( pre-scaling size) :0X3810~0X3821
(3)输出窗口设置( data output size) :0X3808~0X380B
按照很多开发板提供的寄存器参考表确实能得到图像,但是很奇怪。首先 ISP 部分的寄存器。尽管输出分辨率很大(例如1024x768),但是很多人的ISP寄存器部分都是参照的 VGA(640x480)模式下的值,最后竟然也能输出。其次是预缩放窗口的寄存器,这个没有深究,仅仅试了下上下翻转和左右翻转。最后是输出窗口的寄存器,正点原子的还加上了0x380C~0x380F,而且和0x3808~0x380B是配合的。如果不改0x380C~0x380F,那还没什么问题,当我试着更改这些值时,有时会不出图像,有时图像会缩小或放大。
2、Pclk的疑惑
OV7670 和 OV7725 的 Pclk 很容易通过 PLL 的调配得到,但是 OV5640 却不一样。首先,OV5640 的 PLL 有三个,如下所示:
8'd4 : sccb_data <= {16'h3035,8'h21}; //PLL分频 11/1÷ 21/2÷ 32/3÷ 8'd5 : sccb_data <= {16'h3036,8'h69}; //PLL倍频 23/1x 46/2x 69/3x 8'd6 : sccb_data <= {16'h3037,8'h03}; //PLL分频 bit[4]:0/1 bypass/÷2
先说寄存器 16'h3037寄存器,该寄存器默认值是03(也有很多人设为13),bit[4]的意思是旁路 PLL(即不管)或 2 分频,而 bit[3:0] 在 datasheet 中也有分频的意义,可却没有说明怎么回事。我这里设置 3037=03,即旁路pll,以下的说明都以这个为前提。
然后是16'h3035寄存器,该pll寄存器比较简单,其[7:4]的值代表着分频系数。
最后是寄存器16'h3036,这个寄存器的值就奇怪了,很多人写的是69,我研究发现 69 应该是 x3 的意思,相应的 23 是 x1,46 是 x2,当 3035 为 11 而 3036 为 23 时,fps为20,以此为基准就可以对这两个寄存器配置不同的值达到想要的帧率了,如下是我实验测得的不同配置下的 fps 值。(如何测量 fps 值在下一讲会整理)
虽然 3036 寄存器和 3037 寄存器没有完全搞懂,但是用最傻瓜的办法还是得到了一些规律,至少可以得到一些想要的 fps 的值了。但是 Pclk 为多少呢?完全没办法知道!首先是这些测量结果和《ov5640寄存器参考列表》等PDF的值有出入,可能是设置窗口的数据和那些手册不完全一致。其次按道理说,fps 增大那么 Pclk 也会增大,但是我测量 Pclk 的结果却很诡异,根本不是那么回事。关于 Pclk 的相关资料很少,我实在搞不懂,如果有人知道,烦请指教一二。
多说一句,OV7725 和 OV5640 的成像效果都很好,而 OV7670 的效果则非常差,我试了很多人的配置表,都是一样的效果,非常差劲。而且很奇怪的是,OV7670 的图像效果是旋转了 90 度的,非常别扭,我查看寄存器列表也没有发现改变旋转的寄存器配置。所以说,如果嫌 OV5640 贵,那可以用 OV7725,千万别为了便宜去买 OV7670,这个摄像头就是个垃圾,谁用谁恶心!
至此,我们完成了 SCCB 的配置,下一讲的内容是 cmos_capture_rgb565,即 DVP 窗口输出的设置。
参考资料:
[1]正点原子FPGA教程
[2]小梅哥《OV5640图像采集从原理到应用》
[3]开源骚客《SDRAM那些事儿》
[4]韩彬, 于潇宇, 张雷鸣. FPGA设计技巧与案例开发详解[M]. 电子工业出版社, 2014.