基于FPGA的OV5640摄像头驱动
时间:2024-01-04 13:07:01
基于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 <