此部分可参考IIC系列文章:
(1)I2C 接口控制器理论讲解
(2)I2C接口控制设计与实现
(3)I2C连续读写实现
本节将在实验“基于 DDR3 的串口传图帧缓存系统设计”实现一个图像采集显示应用系统,来设计 OV2640 应用系统中所需的基本应用逻辑电路。
提示:以下是本篇文章正文内容,下面案例可供参考
所谓的 OV2640 摄像头初始化模块,其工作就是完成对 OV2640 中众多模式设置寄存器的写入操作。本质上就是使用 I2C 控制器将一些预先定义好的数据值写入到 OV2640 对应的寄存器中。所以我们完全可以使用之前设计好的 I2C 控制器加上一个存储好所有 OV2640 所需设置的寄存器参数的查找表,配合一定的控制逻辑实现,该设计实际比较简单。下图为我们设计的一个比较通用的 OV2640 初始化逻辑电路。
图 中 例 化 了 设 计 好 的 通 用 I2C 控 制 器 ( i2c_control )【参考文章最上方的i2c_control 文章】 和 一 个 ROM 查 找 表( camera_init_table ), 在 camera_init 顶 层 逻 辑 中 , 通 过 简 单 的 状 态 机 循 环 读 取camera_init_table 中的数据值并通过 i2c_control 即可写入到 OV2640 的各个寄存器中。
使用时,仅需在整个系统顶层例化的时候加入如下代码即可
camera_init
#(
.IMAGE_TYPE ( 0 ),// 0: RGB; 1: JPEG
.IMAGE_WIDTH ( IMAGE_WIDTH ),// 图片宽度
.IMAGE_HEIGHT( IMAGE_HEIGHT ),// 图片高度
.IMAGE_FLIP ( 0 ),// 0: 不翻转,1: 上下翻转
.IMAGE_MIRROR( 0 ) // 0: 不镜像,1: 左右镜像
)camera_init
(
.Clk (loc_clk50m ),
.Rst_p (g_rst_p ),
.Init_Done (camera_init_done ),
.camera_rst_n(camera_rst_n ),
.camera_pwdn ( ),
.i2c_sclk (camera_sclk ),
.i2c_sdat (camera_sdat )
);
在代码中,加入了几个参数定义,每个参数对应了 OV5640 使用时的一个常用模式。现就这几个模式参数分别介绍如下。
使用时,关于控制图像的类型、分辨率、翻转、镜像,可以通过修改#后面括号里面的对应内容。根据这些设置,camera_init.v 中通过条件编译的方式选择性的编译代码,从而达到通过参数设置不同工种模式的目的。整个条件编译主要分为两个分支,一个是图像类型为 RGB 类型的分支,另外一个是 JPEG 类型的分支。每个分支里面又根据不同的参数设置来控制图片的翻转和镜像,所以有四种组合,为了匹配四种模式,这里使用了 generate-if语句来实现。以下是部分代码:
generate
ov5640///
if(CAMERA_TYPE == "ov2640")
begin
assign device_id = 8'h60;
assign addr_mode = 1'b0;
assign addr = {
8'd0, lut[15:8]};
assign wrdata = lut[7:0];
if(IMAGE_TYPE == RGB) //------RGB------
begin
assign lut_size = 185;
ov2640_init_table_rgb #(
.IMAGE_WIDTH (IMAGE_WIDTH ),
.IMAGE_HEIGHT (IMAGE_HEIGHT ),
.IMAGE_FLIP_EN (IMAGE_FLIP_EN ),
.IMAGE_MIRROR_EN (IMAGE_MIRROR_EN )
)ov2640_init_table_rgb_inst
(
.addr (cnt ),
.clk (Clk ),
.q (lut )
);
end
else //------JPEG------
begin
assign lut_size = 250;
ov5640_init_table_jpeg #(
.IMAGE_WIDTH (IMAGE_WIDTH ),
.IMAGE_HEIGHT (IMAGE_HEIGHT ),
.IMAGE_FLIP_EN (IMAGE_FLIP_EN ),
.IMAGE_MIRROR_EN (IMAGE_MIRROR_EN )
)ov5640_init_table_jpeg_inst
(
.addr (cnt ),
.clk (Clk ),
.q (lut )
);
end
end
ov7725
else if(CAMERA_TYPE == "ov7725")
begin
assign device_id = 8'h42;
assign addr_mode = 1'b0;
assign addr = lut[15:8];
assign wrdata = lut[7:0];
if(IMAGE_TYPE == RGB) //------RGB------
begin
assign lut_size = 68;
ov7725_init_table_rgb #(
.IMAGE_WIDTH (IMAGE_WIDTH ),
.IMAGE_HEIGHT (IMAGE_HEIGHT ),
.IMAGE_FLIP_EN (IMAGE_FLIP_EN ),
.IMAGE_MIRROR_EN (IMAGE_MIRROR_EN )
)ov7725_init_table_rgb_inst
(
.addr (cnt ),
.clk (Clk ),
.q (lut )
);
end
end
endgenerate
/
设计合理的复位时序,有助于正确的让 OV2640 进入正常工作状态。复位时序在大家设计时经常不被关注,但是却经常直接导致了设计的不成功,所以这里介绍下 OV2640 的复位时序设计。
一般的器件都会在其 datasheet 中提供其上电和复位时序要求,OV2640 也不例外,下图为 OV2640 器件手册中提供的 OV5640 的上电复位时序图。
根据手册描述:
PWDN 信号为掉电控制信号,当该引脚为高电平时整个图像传感器的模拟和数字部分都处于掉电不工作状态,为低电平时整个器件才可以开始工作;
如果不希望控制 PWDN 信号,则该信号可以直接接到低电平;
如果需要控制 PWDN 信号,则需要在上电至少 5ms 之后再拉低 PWDN。
RESET 信号为复位信号,低电平复位。
RESET 引脚必须在上电稳定后保持 t3(1ms)时间后才能变为高电平让整个系统开始工作。
SCCB 接口工作需要在复位信号拉高 t4(20ms)时间之后才能开始工作
XCLK 信号必须在 SCCB 接口正常工作之前 1ms 就就绪,换句话说,如果 XCLK 没有正常提供,OV5640 的 SCCB 接口也将无法正常的读写寄存器。
据此可以总结出对于 OV5640 的从上电到开始读写寄存器的一个基本的控制顺序:
1、 PWDN 和 RESET 都为低电平。
2、 等待 PWDN
3、 等待至少 1ms 的时间,此期间 RESET 一直为低电平,然后拉高 RESET 信号
4、 RESET 拉高之后,再等待至少 20ms 的时间
5、 开始通过 SCCB 读写 OV5640 的寄存器
6、 XVCLK 提前操作 SCCB 的 1ms 时间一般都能满足,所以一般情况下可以不用关心。
在实际设计时,只需要使用一个计数器即可实现。以下为 camera_init 代码中设计的复位时序的相关代码。
//-------------------------------------------------------------------------------------------------------------------
//上电并复位完成 20ms 后再配置摄像头,这里为了优化逻辑,直接使延迟比较值为 21'h1007ff,大约21.012460ms
//上电到摄像头复位时间为1ms ,这里为简便设置为20'h00C400,为1.003520ms
wire Go; //initial enable
reg [20:0] delay_cnt;
always @ (posedge Clk or negedge Rst_n)
if (!Rst_n)
delay_cnt <= 20'd0;
else if (delay_cnt == 21'h100800)
delay_cnt <= delay_cnt;
else
delay_cnt <= delay_cnt + 1'd1;
assign Go = (delay_cnt == 21'h1007ff) ? 1'b1 : 1'b0;
assign camera_rst_n = (delay_cnt > 21'h00C400);
//-------------------------------------------------------------------------------------------------------------------
/-------------------------------------------------------------------------------------------------------------------
//状态机产生wrreg_req、与延时i2c_dly_cnt_max信号
reg [1:0]state;
always@(posedge Clk or negedge Rst_n)
if(~Rst_n)begin
state <= 0;
wrreg_req <= 1'b0;
i2c_dly_cnt_max <= 32'd0;
end
else if(cnt < lut_size)begin
case(state)
0:
if(Go)
state <= 1;
else
state <= 0;
1:
begin
wrreg_req <= 1'b