锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

基于FPGA的OV5640摄像头驱动

时间:2024-01-04 13:07:01 h60固态继电器

基于FPGA的OV5640摄像头驱动

  • 一、OV5640的相关介绍
    • (1)野火的OV5640引脚图
    • (2)引脚介绍
    • (3)功能框图
  • 二、SCCB时序介绍------与IIC基本相似
    • (1)上电时序-程序主要根据官方文件的时序图编写
      • 代码(严格按照时序图完成)
    • (2)读
      • 时序分析
      • 程序
    • (3) 写
      • SSCB写寄存器部分
      • SSCB寄存器地址和数据源(应用野火代码)
    • (4)顶层测试
      • 顶层代码
      • 测试结果
    • (5) 使用inout的注意事项
  • 三、摄像头数据读取
  • 由于个人SDRAM设计原因,后续补充


一、OV5640的相关介绍

(1)野火的OV5640引脚图

(2)引脚介绍

(3)功能框图


<1>OV5640 根据这些寄存器配置的参数运行的控制寄存器,这些参数由外部控制器通过 SIO_C 和 SIO_D 引脚写入, SIO_C与 SIO_D 跟踪使用的通信协议 I2C十分类似。

<2>OV5640通信、控制信号和外部时钟,包括 PCLK、 HREF 及VSYNC它们是像素同步时钟、行同步信号和帧同步信号,与液晶屏控制中的信号非常相似。 RESETB引脚为低电平时复位整个传感器芯片,PWDN 控制芯片进入低功耗模式。注意最后一个 XCLK引脚,它跟 PCLK
完全不同, XCLK 时钟信号用于驱动整个传感器芯片,外部输入OV5640的信号;而 PCLK是 OV5640输出数据时的同步信号是由 OV5640 输出信号。 XCLK外部控制器可提供外部晶振或外部控制器。

<3>感光矩阵,光信号在这里转换成电信号,经过各种处理,这些信号存储成由像素点表示的数字图像。

<4>包含了 DSP 处理单元将根据控制寄存器的配置进行一些基本的图像处理操作。这部分还包括图像格式转换单元和压缩单元,最终通过Y0-Y一般来说,我们使用9引脚输出 8根据数据线传输,此时仅使用 Y2-Y9 引脚。


<5>VCM 在处理单元中,他将通过图像分析实现图像的自动对焦功能。为了实现自动对焦,还需要将自动对焦固件下载到模块中,然后在相机实验中详细介绍此功能。

二、SCCB时序介绍------与IIC基本相似

注:当主机写入从机指令或数据时,串行数据线SDA串行时钟上的数据SCL
通常为高电写入从机设备,每次只写一个数据;串行数据线SDA串行时钟中的数据
SCL为了保证低电平时的数据更新SCL平时采集高电SDA数据的稳定性。

(1)上电时序-程序主要根据官方文件的时序图编写

代码(严格按照时序图完成)

module Power_ctrl #(     parameter    CNT_5MS         =       20'd250_000,     parameter    CNT_1MS         =       20'd50_000,     parameter    CNT_20MS        =       20'd1000_000 ) (     input           wire                    sys_clk,     input           wire                    rst,          output          wire                    pwdn,     output          wire                    resetb,     output          wire                    sccb_en,     output          wire                    clk_en );  reg [20:0] cnt;  assign pwdn = (cnt >= CNT_5MS - 1'd1)?1'd0:1'd1; assign resetb = (cnt >= CNT_5MS   CNT_1MS - 1'd1)?1'd1:1'd0; assign sccb_en = (cnt >= (CNT_5MS   CN_1MS + CNT_20MS - 1'd1))?1'd1:1'd0;
assign clk_en = (cnt >= CNT_5MS - 1'd1)?1'd1:1'd0;

always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        cnt <= 1'd0;
    else if(cnt == (CNT_5MS + CNT_1MS + CNT_20MS - 1'd1))
        cnt <= cnt;
    else
        cnt <= cnt + 1'd1;

endmodule

(2)读

IIC:

SSCB:

读程序验证以读ID来实现:

时序图:

时序分析

Start:是用来启动读的标志位,为1时有效
statue:一共13个状态
cnt:这是一个用来统计scl周期个数的变量
cnt_stop_flag:在STOP状态下,需要先用到2个周期来完成STOP的操作,这时cnt_stop_flag为0,当完成后cnt_stop_flag置1
cnt_stop:用来对STOP状态下的计时
I2C_CLK_x:这是一个用来仿真验证的波形,它与scl相差135°的相位,在它的下降沿sda发生改变
cnt_clkx:统计I2C_CLK_x一个周期所需的计数值
cnt_clkx_flag:在cnt_clkx下降沿的前一个周期置一,下降沿处置零
I2C_SDA:sda信号线
Data:{ID,ADD},24位
I2C_CLK:就是SCL
data_flag:DATA状态下,在数据稳定的地方置1一个周期,用来判断什么时候读数据
data:读出的数据
cnt_clk:统计I2C_CLK一个周期所需的计数值
cnt_clkx_flag:在I2C_CLK下降沿的前一个周期置一,下降沿处置零
read_en:结束一次读操作的标志

由于在SSCB时序开始时是sda先产生下降沿,且sda的数据需在scl高电平时保持稳定,所以采用135°相位差的方法来完成,135不是固定的,可以进行一定的修改,sda数据在每个I2C_CLK_x下降沿处改变,在数据发送和接收状态时,每次发8个数据,然后就就进入下一状态,在这里有个问题就是ID号的第八位是省去的,实际的ID就是七位,因为之前没注意,导致第一次仿真无误后上板时出错,查阅资料发现IIC的应答阶段与SSCB不太一样,其实也差不多,就是SSCB的应答处理你可以不用判断是否应答成功,因为SSCB不一定为返回给你正确的应答值,本程序的x表示为应答状态,即不关心状态,在STOP状态时,先利用好两个周期的时间来完成STOP的操作,然后又是和开头的操作一样,重新开始,然后发读的ID,本程序中,sda在所有的x状态和DATA状态都为1’dz,接着开始读数据即可,然后进行NO ACK操作,sda主动置为高电平,完成后就再进行一次STOP操作,结束整个读操作。(注意:SSCB没有IIC的连续读和连续写)

开始位:

停止位:

上面两图第一个信号是scl,第二个是sda

程序

module SSCB_ID
(
    input       wire                sys_clk,
    input       wire                rst,
    input       wire                read_en,
    input       wire    [15:0]      addr,
    input       wire    [7:0]       ID,
    input       wire                sda_rd,
    
    output      reg             sda,
    output      reg             scl,
    output      reg             read_end,
	output	    reg     [7:0]   Data_reg,
    output      reg     [12:0]  statue,
    output      wire    [23:0]  Cmd
);

localparam
    IDLE            =           13'b0_0000_0000_0001,
    START_ID1       =           13'b0_0000_0000_0010,
    x_ack1          =           13'b0_0000_0000_0100,
    ADD_H           =           13'b0_0000_0000_1000,
    x_ack2          =           13'b0_0000_0001_0000,
    ADD_L           =           13'b0_0000_0010_0000,
    x_ack3          =           13'b0_0000_0100_0000,
    STOP            =           13'b0_0000_1000_0000,
    START_ID2       =           13'b0_0001_0000_0000,
    x_ack4          =           13'b0_0010_0000_0000,
    DATA            =           13'b0_0100_0000_0000,
    NO              =           13'b0_1000_0000_0000,
    END             =           13'b1_0000_0000_0000;
    
localparam
    CLOCK       =       30'd50_000_000,
    I2C_CLK     =       30'd400_000,
    START_MAX   =       (CLOCK/I2C_CLK)*3/4 - 1'd1,
    MAX         =       CLOCK/I2C_CLK - 1'd1,
    MAX_2       =       (CLOCK/I2C_CLK)/2 - 1'd1;
    
//wire [23:0] Cmd;

//reg sda;

assign Cmd = { 
        ID,addr};

//reg sda;

reg [20:0]  cnt;
reg [20:0]  cnt_stop;
reg         cnt_stop_flag;

reg         I2C_CLK_x;
reg [20:0]  cnt_CLKx;
reg         cnt_clkx_flag;

reg         data_flag;

reg [20:0]  cnt_clk;
reg         cnt_clk_flag;

//结束标志
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        read_end <= 1'd0;
    else if(statue == END && cnt_clk == MAX - 1'd1)
        read_end <= 1'd1;
    else
        read_end <= 1'd0;

//相移135°的I2C信号计数
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        cnt_CLKx <= 1'd0;
    else if(cnt_CLKx == MAX)
        cnt_CLKx <= 1'd0;
    else if(read_en == 1'd1 && (statue == STOP) && cnt_stop_flag == 1'd1)
        cnt_CLKx <= cnt_CLKx + 1'd1;
    else if(read_en == 1'd1 
            && (statue !=STOP) 
            && statue != NO && statue != END)
        cnt_CLKx <= cnt_CLKx + 1'd1;
    else
        cnt_CLKx <= 1'd0;

//相移135°的I2C信号周期更替标志 
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        cnt_clkx_flag <= 1'd0;
    else if(cnt_CLKx == MAX - 1'd1)
        cnt_clkx_flag <= 1'd1;
    else
        cnt_clkx_flag <= 1'd0;
        
//相移135°的I2C信号产生 
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        I2C_CLK_x <= 1'd1;
    else if((statue == STOP && cnt_stop_flag != 1'd1) 
            || (statue == x_ack3 && cnt_clkx_flag == 1'd1)
            || (statue == DATA && cnt == 6'd8 && cnt_clkx_flag == 1'd1)
            || (statue == NO && statue == END))
        I2C_CLK_x <= 1'd1;
    else if(cnt_CLKx <= MAX_2 -1'd1 && read_en == 1'd1)
        I2C_CLK_x <= 1'd0;
    else if(cnt_CLKx <= MAX - 1'd1 && read_en == 1'd1)
        I2C_CLK_x <= 1'd1;
    else if(read_en != 1'd1)
        I2C_CLK_x <= 1'd1;
        
//STOP状态时的计数停
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        cnt_stop_flag <= 1'd0;
    else if(statue == STOP && cnt == 2'd2 && cnt_stop == MAX)
        cnt_stop_flag <= 1'd1;
    else if(statue == START_ID2)
        cnt_stop_flag <= 1'd0;
        
//STOP状态下让I2C总线停止两个周期的计
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        cnt_stop <= 1'd0;
    else if(statue == STOP && cnt_stop == MAX)
        cnt_stop <= 1'd0;
    else if(statue == STOP && cnt_stop_flag != 1'd1)
        cnt_stop <= cnt_stop + 1'd1;
    else
        cnt_stop <= 1'd0;
        
//SCL时钟生成
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        cnt_clk <= 1'd0;
     else if(statue == STOP || statue == IDLE)
        cnt_clk <= 1'd0;
     else if(cnt_clk == MAX)
        cnt_clk <= 1'd0;
     else
        cnt_clk <= cnt_clk + 1'd1;
        
//SCL时钟生成
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        scl <= 1'd1;
    else if(statue == END && cnt_clk_flag == 1'd1)
        scl <= 1'd1;
    else if(statue == STOP || (statue == x_ack3 && cnt_clkx_flag == 1'd1))
        scl <= 1'd1;
    else if(statue == STOP && cnt_stop_flag == 1'd1 && cnt_clkx_flag == 1'd1)
        scl <= 1'd0;
    else if(statue == IDLE && cnt_CLKx == START_MAX)
        scl <= 1'd0;
    else if(cnt_clk <= MAX_2 && read_en == 1'd1 && statue != IDLE)
        scl <= 1'd0;
    else if(cnt_clk < MAX && read_en == 1'd1 && statue != IDLE)
        scl <= 1'd1;
    else if(cnt_clk == MAX && read_en == 1'd1 && statue != IDLE)
        scl <= 1'd0;
    else if(read_en != 1'd1)
        scl <= 1'd1;

//全局计数
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        cnt <= 1'd0; 
    else if(statue == STOP)
        begin
            if(cnt_stop == MAX && cnt == 2'd2)
                cnt <= 1'd0;
             else if(cnt_stop == MAX)
                cnt <= cnt + 1'd1;
             else
                cnt <= cnt;
        end
     else if(statue == NO && statue == END)
        cnt <= 1'd0;
     else if(cnt_clkx_flag == 1'd1 && cnt == 6'd8)
        cnt <= 1'd0;
     else if(cnt_clkx_flag == 1'd1)
        cnt <= cnt + 1'd1;
     else
        cnt <= cnt;
        
//I2C信号周期更替标志 
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        cnt_clk_flag <= 1'd0;
    else if(cnt_clk == MAX - 1'd1)
        cnt_clk_flag <= 1'd1;
    else
        cnt_clk_flag <= 1'd0;
        
//data数据记录标志
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        data_flag <= 1'd0;
    else if(statue == DATA && cnt_clk == START_MAX)
        data_flag <= 1'd1;
    else
        data_flag <= 1'd0;

//数据存储
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
         Data_reg <= 1'd0;
    else if(statue == DATA && data_flag == 1'd1)
        Data_reg <= { 
        Data_reg[6:0],sda_rd};
    else
        Data_reg <= Data_reg;       
        
//状态机跳转 
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        statue <= IDLE;
    else case(statue)
        IDLE:
            if(read_en == 1'd1 && cnt_CLKx == START_MAX)
                statue <= START_ID1;
                
        START_ID1:
            if(cnt == 8'd8 && cnt_clkx_flag == 1'd1)
                statue <= x_ack1;
                
        x_ack1:
            if(cnt_clkx_flag == 1'd1)
                statue <= ADD_H;
                
        ADD_H:
            if(cnt == 8'd8 && cnt_clkx_flag == 1'd1)
                statue <= x_ack2;
                
        x_ack2:
            if(cnt_clkx_flag == 1'd1)
                statue <= ADD_L;
                
        ADD_L:
            if(cnt == 8'd8 && cnt_clkx_flag == 1'd1)
                statue <= x_ack3;
                
        x_ack3:
            if(cnt_clkx_flag == 1'd1)
                statue <= STOP;
                
        STOP:
            if(cnt_stop_flag == 1'd1 && cnt_CLKx == START_MAX)
                statue <= START_ID2;
                
        START_ID2:
            if(cnt == 8'd8 && cnt_clkx_flag == 1'd1)
                statue <= x_ack4;
                
        x_ack4:
            if(cnt_clkx_flag == 1'd1)
                statue <= DATA;
                
        DATA:
            if(cnt == 8'd8 && cnt_clkx_flag == 1'd1)
                statue <= NO;
                
        NO:
            if(cnt_clk_flag == 1'd1)
                statue <= END;
                
        END:     
            if(cnt_clk_flag == 1'd1)
                statue <= IDLE;
                
        default:statue <= IDLE;
    endcase 
    
always@(posedge sys_clk or negedge rst)
    if(rst == 1'd0)
        sda <= 1'd1;
    else case(statue)
        IDLE:
            if(read_en == 1'd1)
                sda <= 1'd0;
            else
                sda <= 1'd1;
                
        START_ID1:
            if(cnt_clkx_flag == 1'd1 && cnt < 6'd7)
                sda <= Cmd[22 - cnt];
            else if(cnt_clkx_flag == 1'd1 && cnt == 6'd7)
                sda <= 1'd0;
            else if(cnt_clkx_flag == 1'd1 && cnt == 6'd8)
                sda <= 1'd0;
                
        x_ack1:
            if(cnt_clkx_flag == 1'd1)
                sda <= Cmd[15 - cnt];
                
        ADD_H:
            if(cnt_clkx_flag == 1'd1 && cnt <= 6'd7)
                sda <= Cmd[15 - cnt];
            else if(cnt_clkx_flag == 1'd1 && cnt == 6'd8)
                sda <= 1'd0;
                
        x_ack2:
            if(cnt_clkx_flag == 1'd1)
                sda <= Cmd[7 - cnt];
                
        ADD_L:
            if(cnt_clkx_flag == 1'd1 && cnt <= 6'd7)
                sda <= Cmd[7 - cnt];
            else if(cnt_clkx_flag == 1'd1 && cnt == 6'd8)
                sda <= 1'd0;
                
        x_ack3:
            if(cnt_clkx_flag == 1'd1)
                sda <= 1'd0;
                
        STOP:
            if(cnt_stop_flag != 1'd1 && cnt == 1'd0)
                sda <= 1'd0;
            else if(cnt_stop_flag == 1'd1)
                sda <= 1'd0;
            else
                sda <= 1'd1;
                
        START_ID2:
            if(cnt_clkx_flag == 1'd1 && cnt < 6'd7)
                sda <= Cmd[22 - cnt];
            else if(cnt_clkx_flag == 1'd1 && cnt == 6'd7)
                sda <= 1'd1;
            else if(cnt_clkx_flag == 1'd1 && cnt == 6'd8)
                sda <= 1'd0;
                
        x_ack4:
            sda <= 1'd0;     
                
        DATA:
            if(cnt_clkx_flag == 1'd1 && cnt == 6'd8)
                sda <= 1'd1;
            else
                sda <= 1'd0;
                
        NO:     
            if(cnt_clk_flag == 1'd1)
                sda <= 1'd0;
            else
                sda <= 1'd1;
                
        END:
            if(cnt_clk_flag == 1'd1)
                sda <= 1'd1;
                
        default:sda <= 1'd1;
    endcase

/* assign sda_x = (statue == x_ack1 || statue == x_ack2 || statue == x_ack3 || statue == x_ack4 || (statue == DATA))?1'dz:sda; */

endmodule

结果演示:

(3) 写

IIC与SSCB通用
设置寄存器时会用到

SSCB写寄存器部分

module SSCB_WR
(
    input       wire                sys_clk,
    input       wire                rst,
    input       wire                WR_en,
    input       wire    [15:0]      addr,
    input       wire    [7:0]       ID,
    input       wire    [7:0]       data,
    
    output      reg             sda,
    output      reg             scl,
    output      reg             WR_end,
    output      reg     [9:0]   statue
);

localparam
    IDLE            =           10'b0000000001,
    START_ID1       =           10'b0000000010,
    x_ack1          =           10'b0000000100,
    ADD_H           =           10'b0000001000,
    x_ack2          =           10'b0000010000,
    ADD_L           =           10'b0000100000,
    x_ack3          =           10'b0001000000,
    DATA            =           10'b0010000000,
    x_ack4          =           10'b0100000000,
    END             =           10'b1000000000;
    
localparam
    CLOCK       =       30'd50_000_000,
    I2C_CLK     =       30'd400_000,
    START_MAX   <

相关文章