前言
VGA显示模式
:分辨率为640*480,刷新速率为60hz
ov7725输出格式
:RGB565
ov7725摄像头分辨率
:640×480
ov7725输出时序
:VGA时序
需求
:处理ov7725传输到FPGA的图像,经过图像处理后,再将图像使用VGA传输到显示屏
正文
原理图上摄像头和FPGA的引脚相连的地方,下图位为野火开发板原理图:
一、摄像头配置模块
在使用ov7725之前,需要对ov7725里面的寄存器进行配置,这里使用I2C对其内部寄存器进行配置
1.1 寄存器配置模块
这个模块用来提供寄存器的地址和数据
如对所有寄存器进行复位配置:7'd0 : i2c_data <= {8'h12, 8'h80};
在ov7725中,高8位是地址,低8位是数据
通过这些寄存器的配置,可以控制摄像头输出图像的分辨率大小、图像格式、图像处理及图像方向
等
// 功能:把数据传给i2c驱动,进而实现对摄像头内部70个寄存器进行配置
// 传地址和数据给驱动
// 注意信号间的依赖关系
module i2c_ov7725_rgb565_cfg(
input clk,//由i2c驱动模块输出的时钟
input i2c_done,// i2c完成一次传输,从 start——> stop 完成一个寄存器的配置,一共要配置70个
input rst_n,
output reg init_done,//初始化完成信号:70个寄存器都配置完成,并且最后一个寄存器的i2c_done也传回来了,此时即可拉高该信号
output reg [15:0] i2c_data,//高8位地址、低8位数据
output reg i2c_exec //拉高时, i2c 开始工作(start信号)
);
parameter REG_70 = 70; //需要配置的寄存器总数
// 延时计数器
// 延时2次:
// 1、复位释放后延时
// 2、第一个寄存器配置完成后,延时
reg [10:0] cnt_delay_2;//实现延迟2次的计数器,计到1023清零
reg [6:0] cnt_reg_70;
// =============================================================================
// 延时的目的:等待ov7725稳定下来,ov7725数据手册规定,复位后需要延时
// 复位:1、硬件复位:上电复位、复位按键复位
// 2、软件复位:配置的第一个寄存器,复位所有寄存器,即对ov7725进行软件复位
//
// 第一个输出信号:i2c_exec
// =============================================================================
always@(posedge clk or negedge rst_n)begin
if(!rst_n )begin
cnt_delay_2 <= 10'd0;
end
else if(cnt_delay_2 < 10'd1023)begin
cnt_delay_2 <= cnt_delay_2 + 10'd1;
end
else if((cnt_reg_70 == 7'd1) && (i2c_done))begin// 完成第一个寄存器的配置
cnt_delay_2 <= 10'd0;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n )begin
cnt_reg_70 <= 7'd0;
end
else if(i2c_exec)begin
cnt_reg_70 <= cnt_reg_70 + 7'd1;// i2c开始信号到来,就代表开始配置寄存器
end
else
cnt_reg_70 <= cnt_reg_70;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n )begin
i2c_exec <= 1'd0;
end
else if(cnt_delay_2 == 10'd1022)begin //上电复位后已经延时1ms,才能去配置第一个寄存器;然后配置完第一个复位寄存器后,再延时1ms
i2c_exec <= 1'd1;
end
else if(init_done && (cnt_reg_70 != 7'd1) && (cnt_reg_70 < REG_70)) // 上一个配置完成+除去第一个复位寄存器+还没配置完70个
i2c_exec <= 1'd1;
else
i2c_exec <= 1'd0;
end
// =============================================================================
// 第二个输出信号:init_done
// =============================================================================
always@(posedge clk or negedge rst_n)begin
if(!rst_n )begin
init_done <= 1'd0;
end
else if((cnt_reg_70 == REG_70) && (i2c_done))begin //配置70个寄存器且最后一个配置完成信号传回来了
init_done <= 1'd1;
end
else
init_done <= 1'd0;
end
// =============================================================================
// 第三个输出信号:[15:0]i2c_data
// =============================================================================
always@(posedge clk or negedge rst_n)begin
if(!rst_n )begin
i2c_data <= 16'd0;
end
else begin
case(cnt_reg_70)
//先对寄存器进行软件复位,使寄存器恢复初始值
//寄存器软件复位后,需要延时1ms才能配置其它寄存器
7'd0 : i2c_data <= {8'h12, 8'h80}; //COM7 BIT[7]:复位所有的寄存器
7'd1 : i2c_data <= {8'h3d, 8'h03}; //COM12 模拟过程直流补偿
7'd2 : i2c_data <= {8'h15, 8'h00}; //COM10 href/vsync/pclk/data信号控制
7'd3 : i2c_data <= {8'h17, 8'h26}; //HSTART 水平起始位置
7'd4 : i2c_data <= {8'h18, 8'ha0}; //HSIZE 水平尺寸
7'd5 : i2c_data <= {8'h19, 8'h07}; //VSTRT 垂直起始位置
7'd6 : i2c_data <= {8'h1a, 8'hf0}; //VSIZE 垂直尺寸
7'd7 : i2c_data <= {8'h32, 8'h00}; //HREF 图像开始和尺寸控制,控制低位
7'd8 : i2c_data <= {8'h29, 8'ha0}; //HOutSize 水平输出尺寸
7'd9 : i2c_data <= {8'h2a, 8'h00}; //EXHCH 虚拟像素MSB
7'd10 : i2c_data <= {8'h2b, 8'h00}; //EXHCL 虚拟像素LSB
7'd11 : i2c_data <= {8'h2c, 8'hf0}; //VOutSize 垂直输出尺寸
7'd12 : i2c_data <= {8'h0d, 8'h41}; //COM4 PLL倍频设置(multiplier) 设置4倍频
//Bit[7:6]: 0:1x 1:4x 2:6x 3:8x
7'd13 : i2c_data <= {8'h11, 8'h00}; //CLKRC 内部时钟配置
//Freq=multiplier/[(CLKRC[5:0]+1)*2]
7'd14 : i2c_data <= {8'h12, 8'h06}; //COM7 输出VGA RGB565格式
7'd15 : i2c_data <= {8'h0c, 8'h10}; //COM3 Bit[0]: 0:图像数据 1:彩条测试
//DSP 控制
7'd16 : i2c_data <= {8'h42, 8'h7f}; //TGT_B 黑电平校准蓝色通道目标值
7'd17 : i2c_data <= {8'h4d, 8'h09}; //FixGain 模拟增益放大器
7'd18 : i2c_data <= {8'h63, 8'hf0}; //AWB_Ctrl0 自动白平衡控制字节0
7'd19 : i2c_data <= {8'h64, 8'hff}; //DSP_Ctrl1 DSP控制字节1
7'd20 : i2c_data <= {8'h65, 8'h00}; //DSP_Ctrl2 DSP控制字节2
7'd21 : i2c_data <= {8'h66, 8'h00}; //DSP_Ctrl3 DSP控制字节3
7'd22 : i2c_data <= {8'h67, 8'h00}; //DSP_Ctrl4 DSP控制字节4
//AGC AEC AWB
//COM8 Bit[2]:自动增益使能 Bit[1]:自动白平衡使能 Bit[0]:自动曝光功能
7'd23 : i2c_data <= {8'h13, 8'hff}; //COM8
7'd24 : i2c_data <= {8'h0f, 8'hc5}; //COM6
7'd25 : i2c_data <= {8'h14, 8'h11};
7'd26 : i2c_data <= {8'h22, 8'h98};
7'd27 : i2c_data <= {8'h23, 8'h03};
7'd28 : i2c_data <= {8'h24, 8'h40};
7'd29 : i2c_data <= {8'h25, 8'h30};
7'd30: i2c_data <= {8'h26, 8'ha1};
7'd31: i2c_data <= {8'h6b, 8'haa};
7'd32: i2c_data <= {8'h13, 8'hff};
//matrix sharpness brightness contrast UV
7'd33 : i2c_data <= {8'h90, 8'h0a}; //EDGE1 边缘增强控制1
//DNSOff 降噪阈值下限,仅在自动模式下有效
7'd34 : i2c_data <= {8'h91, 8'h01}; //DNSOff
7'd35 : i2c_data <= {8'h92, 8'h01}; //EDGE2 锐度(边缘增强)强度上限
7'd36 : i2c_data <= {8'h93, 8'h01}; //EDGE3 锐度(边缘增强)强度下限
7'd37 : i2c_data <= {8'h94, 8'h5f}; //MTX1 矩阵系数1
7'd38 : i2c_data <= {8'h95, 8'h53}; //MTX1 矩阵系数2
7'd39 : i2c_data <= {8'h96, 8'h11}; //MTX1 矩阵系数3
7'd40 : i2c_data <= {8'h97, 8'h1a}; //MTX1 矩阵系数4
7'd41 : i2c_data <= {8'h98, 8'h3d}; //MTX1 矩阵系数5
7'd42 : i2c_data <= {8'h99, 8'h5a}; //MTX1 矩阵系数6
7'd43 : i2c_data <= {8'h9a, 8'h1e}; //MTX_Ctrl 矩阵控制
7'd44 : i2c_data <= {8'h9b, 8'h3f}; //BRIGHT 亮度
7'd45 : i2c_data <= {8'h9c, 8'h25}; //CNST 对比度
7'd46 : i2c_data <= {8'h9e, 8'h81};
7'd47 : i2c_data <= {8'ha6, 8'h06}; //SDE 特殊数字效果控制
7'd48 : i2c_data <= {8'ha7, 8'h65}; //USAT "U"饱和增益
7'd49 : i2c_data <= {8'ha8, 8'h65}; //VSAT "V"饱和增益
7'd50 : i2c_data <= {8'ha9, 8'h80}; //VSAT "V"饱和增益
7'd51 : i2c_data <= {8'haa, 8'h80}; //VSAT "V"饱和增益
//伽马控制 :伽马校准和文档上的一样
7'd52 : i2c_data <= {8'h7e, 8'h0c};
7'd53 : i2c_data <= {8'h7f, 8'h16};
7'd54 : i2c_data <= {8'h80, 8'h2a};
7'd55 : i2c_data <= {8'h81, 8'h4e};
7'd56 : i2c_data <= {8'h82, 8'h61};
7'd57 : i2c_data <= {8'h83, 8'h6f};
7'd58 : i2c_data <= {8'h84, 8'h7b};
7'd59 : i2c_data <= {8'h85, 8'h86};
7'd60 : i2c_data <= {8'h86, 8'h8e};
7'd61 : i2c_data <= {8'h87, 8'h97};
7'd62 : i2c_data <= {8'h88, 8'ha4};
7'd63 : i2c_data <= {8'h89, 8'haf};
7'd64 : i2c_data <= {8'h8a, 8'hc5};
7'd65 : i2c_data <= {8'h8b, 8'hd7};
7'd66 : i2c_data <= {8'h8c, 8'he8};
7'd67 : i2c_data <= {8'h8d, 8'h20};
7'd68 : i2c_data <= {8'h0e, 8'h65}; //COM5
7'd69 : i2c_data <= {8'h09, 8'h00}; //COM2 Bit[1:0] 输出电流驱动能力
//只读存储器,防止在case中没有列举的情况,之前的寄存器被重复改写
default:i2c_data <= {8'h1C, 8'h7F}; //MIDH 制造商ID 高8位
endcase
end
else
end
endmodule
1.2 i2c驱动
这个模块用来传输需要配置的寄存器的值
//
module i2c_dri(
input clk , //250K
input rst_n ,
//i2c 接口
input i2c_exec , //I2C触发执行信号
input bit_ctrl , //字地址位控制(16b/8b)
input i2c_rh_wl , //I2C读写控制信号
input [15:0] i2c_addr , //I2C器件内地址
input [ 7:0] i2c_data_w , //I2C要写的数据
output reg [ 7:0] i2c_data_r , //I2C读出的数据
output reg i2c_done , //I2C一次操作完成
output reg i2c_ack , //I2C应答标志 0:应答 1:未应答
output reg scl , //I2C的SCL时钟信号
inout sda , //I2C的SDA信号
//用户接口
output reg dri_clk //驱动scl、sda这2根信号线,从clk 4倍频得到25K*4=1M
);
parameter SLAVE_ADDR = 7'b1010000;// 从机地址
parameter CLK_FREQ = 26'd50_000_000;
parameter I2C_FREQ = 18'd250_000;
parameter DIV_CLK_MAX = (clk_cnt_24 >> 1'd1)-1'd1;//分频计数器计满值
// 定义状态量
localparam IDLE = 8'b0000_0001;
localparam SLAVE_ADDR = 8'b0000_0010;
localparam ADDR_H = 8'b0000_0100;
localparam ADDR_L = 8'b0000_1000;
localparam W_DATA = 8'b0001_0000;
localparam R_ADDR = 8'b0010_0000;
localparam R_DATA = 8'b0100_0000;
localparam STOP = 8'b1000_0000;
reg state;
reg next_state;
assign sda = sda_en ? sda_out : 1'bz;
assign sda_in = sda;
// ============================================================
// 求分频系数
// 分频系数越小,频率越大
wire [5:0] div_50; //分频系数
assign div_50 = (CLK_FREQ/I2C_FREQ) >> 2'd2;
// 通过求得的分频系数,作为分频数
reg [4:0] clk_cnt_24;
always@(posedge clk or negedge rst_n)begin
if(!rst_n )begin
clk_cnt_24 <= 5'd0;
end
else if(clk_cnt_24 == DIV_CLK_MAX)begin
clk_cnt_24 <= 5'd0;
end
else
clk_cnt_24 <= clk_cnt_24 + 5'd1;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n )begin
dri_clk <= 1'd0;
end
else if(clk_cnt_24 == DIV_CLK_MAX)begin
dri_clk <= ~dri_clk;
end
else
dri_clk <= dri_clk;
end
// ============================================================
// ----------------------------------------------------------------------
// 状态机第一段
always@(posedge clk or negedge rst_n)begin
if(!rst_n )begin
state <= IDLE;
end
else begin
state <= next_state;
end
end
// 状态机第二段
// 状态之间的转换看跳转图!!很清晰
reg now_state_done;
reg bit_ctrl;
always@(*)begin
next_state <= IDLE;
case(state)
IDLE:
if(i2c_exec)
next_state = SLAVE_ADDR;
else
next_state = next_state;
SLAVE_ADDR:
if(now_state_done)begin
if(bit_ctrl)// bit_ctrl=1表示传16bit地址
next_state = ADDR_H;//高8位
else
next_state = ADDR_L;
else
next_state = SLAVE_ADDR;
end
ADDR_H:
if(now_state_done)
next_state = ADDR_L;
else
next_state = ADDR_H;
// 读数据
ADDR_L:
if(now_state_done)begin
if(wr_flage)//读
next_state = R_ADDR;
else
next_state = W_DATA;
end
else
next_state = ADDR_L;
R_ADDR:
if(now_state_done)
next_state = R_DATA;
else
next_state = R_ADDR;
R_DATA:
if(now_state_done)
next_state = STOP;
else
next_state = R_DATA;
//写数据
W_DATA:
if(now_state_done)
next_state = STOP;
else
next_state = W_DATA;
STOP:
if(now_state_done)
next_state = IDLE;
else
next_state = STOP;
default:
next_state = IDLE;
endcase
end
// 状态机第三段
//i2c的2个接口在空闲状态是高电平
reg now_state_done;
reg [6:0] cnt_always_add;
reg i2c_rh_wl_reg;
reg [7:0] i2c_data_w_reg;
reg [7:0] i2c_data_r_reg;
reg [15:0] i2c_addr_reg;
always@(posedge clk or negedge rst_n)begin
if(!rst_n )begin
scl <= 1'd1;
sda_out <= 1'd1;
sda_en <= 1'd1;
// 一直在的信号
now_state_done <= 1'd0;
cnt_always_add <= 7'd0;
i2c_rh_wl_reg <= 1'd0;
i2c_data_w_reg <= 8'd0;
i2c_data_r_reg <= 8'd0;
i2c_addr_reg <= 16'd0;
i2c_ack <= 1'd0;
end
// 在外部让now_state_done=0,只有在内部每个状态完成后,才将其拉高
now_state_done <= 1'd0;
// 在外部一直+1,只有在内部结束才清零
cnt_always_add <= cnt_always_add + 7'd1;
case(state)
IDLE:
if(i2c_exec)begin//驱动IDLE的都是i2c_exec信号,将输入信号寄存到内部寄存器
i2c_rh_wl_reg <= i2c_rh_wl;//其他模块传来的读写控制信号
i2c_data_w_reg <= i2c_data_w;
i2c_addr_reg <= i2c_addr;
end
SLAVE_ADDR:begin // sda_out在scl低电平中间更新
case(cnt_always_ad)
7'd1: scl <= 1'd1;
7'd3: scl <= 1'd0;
7'd4: sda_out <= SLAVE_ADDR[6];
7'd5: scl <= 1'd1;
7'd7: scl <= 1'd0;
7'd8: sda_out <= SLAVE_ADDR[5];
7'd9: scl <= 1'd1;
7'd11: scl <= 1'd0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'd1;
7'd15: scl <= 1'd0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'd1;
7'd19: scl <= 1'd0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'd1;
7'd23: scl <= 1'd0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'd1;
7'd27: scl <= 1'd0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'd1;
7'd31: scl <= 1'd0;//=======================================================以上相同
7'd32: sda_out <= 1'd0;//最低位=0:写, 到此传完8bit数据
7'd23: scl <= 1'd1;
7'd35: scl <= 1'd0;
7'd36: sda_en <= 1'd0;// sda为输入端口,输入从机给的应答信号
7'd37: scl <= 1'd1;
7'd38: begin
st_done <= 1'b1;
if(sda_in)//从机给的无应答信号
i2c_ack <= 1'd1;//拉高,无应答
end
//虽然在38时st_done已经拉高,此时状态已经跳转,但是跳转的是next_state,而此处case里面是state,state比next_state慢一拍,因此还是要执行39
7'd39: begin
cnt_always_add <= 6'd0;
scl <= 1'd0;//?????
end
default: ;
endcase
end
ADDR_H:begin
case(cnt_always_ad)
7'd0: begin
sda_en <= 1'd1;//让sda位输出端口
sda_out <= i2c_addr_reg[15];//将寄存的地址输出
end
7'd1: scl <= 1'd1;
7'd3: scl <= 1'd0;
7'd4: sda_out <= i2c_addr_reg[14];
7'd5: scl <= 1'd1;
7'd7: scl <= 1'd0;
7'd8: sda_out <= i2c_addr_reg[13];
7'd9: scl <= 1'd1;
7'd11: scl <= 1'd0;
7'd12: sda_out <= i2c_addr_reg[12];
7'd13: scl <= 1'd1;
7'd15: scl <= 1'd0;
7'd16: sda_out <= i2c_addr_reg[11];
7'd17: scl <= 1'd1;
7'd19: scl <= 1'd0;
7'd20: sda_out <= i2c_addr_reg[10];
7'd21: scl <= 1'd1;
7'd23: scl <= 1'd0;
7'd24: sda_out <= i2c_addr_reg[9];
7'd25: scl <= 1'd1;
7'd27: scl <= 1'd0;
7'd28: sda_out <= i2c_addr_reg[8];
7'd29: scl <= 1'd1;
7'd31: scl <= 1'd0;
7'd32: sda_en <= 1'd1;
7'd33: scl <= 1'd1;
7'd34: begin
st_done <= 1'b1;
if(sda_in)//从机给的无应答信号
i2c_ack <= 1'd1;//拉高,无应答
end
7'd35: begin
cnt_always_add <= 6'd0;
scl <= 1'd0;//?????
end
default:;
endcase
end
ADDR_L:begin
case(cnt_always_add)
7'd0: begin
sda_en <= 1'd1;//让sda位输出端口
sda_out <= i2c_addr_reg[7];//将寄存的地址输出
end
7'd1: scl <= 1'd1;
7'd3: scl <= 1'd0;
7'd4: sda_out <= i2c_addr_reg[6];
7'd5: scl <= 1'd1;
7'd7: scl <= 1'd0;
7'd8: sda_out <= i2c_addr_reg[5];
7'd9: scl <= 1'd1;
7'd11: scl <= 1'd0;
7'd12: sda_out <= i2c_addr_reg[4];
7'd13: scl <= 1'd1;
7'd15: scl <= 1'd0;
7'd16: sda_out <= i2c_addr_reg[3];
7'd17: scl <= 1'd1;
7'd19: scl <= 1'd0;
7'd20: sda_out <= i2c_addr_reg[2];
7'd21: scl <= 1'd1;
7'd23: scl <= 1'd0;
7'd24: sda_out <= i2c_addr_reg[1];
7'd25: scl <= 1'd1;
7'd27: scl <= 1'd0;
7'd28: sda_out <= i2c_addr_reg[0];
7'd29: scl <= 1'd1;
7'd31: scl <= 1'd0;
7'd32: sda_en <= 1'd1;
7'd33: scl <= 1'd1;
7'd34: begin
st_done <= 1'b1;
if(sda_in)//从机给的无应答信号
i2c_ack <= 1'd1;//拉高,无应答
end
7'd35: begin
cnt_always_add <= 6'd0;
scl <= 1'd0;//?????
end
default:;
endcase
end
W_DATA:begin
case(cnt_always_add)
7'd0: begin
sda_en <= 1'd1;
sda_out <= i2c_data_w_reg[7];
end
7'd1: scl <= 1'd1;
7'd3: scl <= 1'd0;
7'd4: sda_out <= i2c_data_w_reg[6];
7'd5: scl <= 1'd1;
7'd7: scl <= 1'd0;
7'd8: sda_out <= i2c_data_w_reg[5];
7'd9: scl <= 1'd1;
7'd11: scl <= 1'd0;
7'd12: sda_out <= i2c_data_w_reg[4];
7'd13: scl <= 1'd1;
7'd15: scl <= 1'd0;
7'd16: sda_out <= i2c_data_w_reg[3];
7'd17: scl <= 1'd1;
7'd19: scl <= 1'd0;
7'd20: sda_out <= i2c_data_w_reg[2];
7'd21: scl <= 1'd1;
7'd23: scl <= 1'd0;
7'd24: sda_out <= i2c_data_w_reg[1];
7'd25: scl <= 1'd1;
7'd27: scl <= 1'd0;
7'd28: sda_out <= i2c_data_w_reg[0];
7'd29: scl <= 1'd1;
7'd31: scl <= 1'd0;
7'd32: sda_en <= 1'd1;
7'd33: scl <= 1'd1;
7'd34: begin
st_done <= 1'b1;
if(sda_in)//从机给的无应答信号
i2c_ack <= 1'd1;//拉高,无应答
end
7'd35: begin
cnt_always_add <= 6'd0;
scl <= 1'd0;//?????
end
default:;
endcase
end
R_ADDR:begin // 从哪个从机读
case(cnt_always_ad)
7'd0: sda_en <= 1'd1;
7'd1: scl <= 1'd1;
7'd3: scl <= 1'd0;
7'd4: sda_out <= SLAVE_ADDR[6];
7'd5: scl <= 1'd1;
7'd7: scl <= 1'd0;
7'd8: sda_out <= SLAVE_ADDR[5];
7'd9: scl <= 1'd1;
7'd11: scl <= 1'd0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'd1;
7'd15: scl <= 1'd0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'd1;
7'd19: scl <= 1'd0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'd1;
7'd23: scl <= 1'd0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'd1;
7'd27: scl <= 1'd0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'd1;
7'd31: scl <= 1'd0;//=======================================================以上相同
7'd32: sda_out <= 1'd1;//最低位=1:读, 到此传完8bit数据
7'd33: scl <= 1'd1;
7'd35: scl <= 1'd0;
7'd36: sda_en <= 1'd0;// sda为输入端口,输入从机给的应答信号
7'd37: scl <= 1'd1;
7'd38: begin
st_done <= 1'b1;
if(sda_in)//从机给的无应答信号
i2c_ack <= 1'd1;//拉高,无应答
end
//虽然在38时st_done已经拉高,此时状态已经跳转,但是跳转的是next_state,而此处case里面是state,state比next_state慢一拍,因此还是要执行39
7'd39: begin
cnt_always_add <= 6'd0;
scl <= 1'd0;//?????
end
default: ;
endcase
end
R_DATA:begin
case(cnt_always_ad)
7'd0: sda_en <= 1'd0;
7'd1: i2c_data_r_reg[7] <= sda_in;
7'd3: scl <= 1'd0;
7'd5: begin
scl <= 1'd1;
i2c_data_r_reg[6] <= sda_in;
end
7'd7: scl <= 1'd0;
7'd9: begin
scl <= 1'd1;
i2c_data_r_reg[5] <= sda_in;
end
7'd11: scl <= 1'd0;
7'd13: begin
scl <= 1'd1;
i2c_data_r_reg[4] <= sda_in;
end
7'd15: scl <= 1'd0;
7'd17: begin
scl <= 1'd1;
i2c_data_r_reg[3] <= sda_in;
end
7'd19: scl <= 1'd0;
7'd21: begin
scl <= 1'd1;
i2c_data_r_reg[2] <= sda_in;
end
7'd23: scl <= 1'd0;
7'd25: begin
scl <= 1'd1;
i2c_data_r_reg[1] <= sda_in;
end
7'd27: scl <= 1'd0;
7'd29: begin
scl <= 1'd1;
i2c_data_r_reg[0] <= sda_in;
end
7'd31: scl <= 1'd0;//=======================================================以上相同
7'd32: sda_en <= 1'd1;//不需要从机应答
7'd33: scl <= 1'd1;
7'd34: now_state_done <= 1'd1;
7'd35: begin
scl <= 1'd0;
cnt <= 1'd0;
i2c_data_r <= i2c_data_r_reg;
end
default: ;
endcase
end
STOP: begin
case(cnt)
7'd0: begin
sda_en <= 1'b1; //结束I2C
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1;
7'd15: now_state_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
i2c_done <= 1'b1; //向上层模块传递I2C结束信号
end
default : ;
endcase
end
default: ;
endcase
end
endmodule
二、图像采集模块
对摄像头的内存器配置完成后,摄像头就可以开始工作啦
2.1 ov7725输出的时序图
要看懂ov7725的VGA输出时序,才能在正确的地方采集到摄像头传输过来的数据
上面的信号是摄像头输出的,其中href和hsync用同一个引脚输出的,为了方便,使用href作为行同步信号
通过观察上图可知:
- 场同步信号有效部分传输480行
- 行同步信号有效部分传输640个像素点
- 只有当
行同步信号=1 && 场同步信号=0
时,此时数据线上的数据才有效
行同步信号有效:传一行数据
将上面图中其中一段有效部分放大得到OV7725输出RGB565格式
的时序图:
由于ov7725在传输RGB图像时,只用到[9:2]这8根数据引脚
,因此要传输GRB565的16位数据,需要分成2次传输
2.2 采集摄像头输出的图像
从上面可知,摄像头有8根数据线用来传输图像数据,但是要表示一个像素的完整信息,需要用16位数据,因此,需要将摄像头传输来的数据拼接成16bit
从摄像头采集数据,这个数据将存在SDRAM中
// 采集摄像头输出的图像数据
// RGB565格式的图像,一个像素点要用5+6+5=16bit 数据来表示
// 而ov7725的数据线只有8位,因此需要用2个时钟传输16bit数据
module cmos_capture_data(
input rst_n,
// 从摄像头传入的信号
input cam_pclk,// 像素时钟周期
input cam_vsync,// 场同步
input cam_href, // 行同步
input [7:0] cam_data,
// 用户接口
output reg coms_frame_vsync,// 帧有效
output reg coms_frame_href, // 行有效
output reg coms_frame_valid,// 该信号拉高,表示此时数据有效
output reg [15:0] coms_frame_data // 采集到的有效数据
);
// 70个寄存器配置完成后,不采集前10帧图像
//ov7725数据手册:Table5中的ts
parameter WAIT_10_frame = 4'd10;
// 采集 场同步信号 的上升沿
reg cam_vsync_reg1;
reg cam_vsync_reg2;
reg cam_href_reg1;
reg cam_href_reg2;
wire pose_vsync;
always@(posedge cam_pclk or negedge rst_n)begin
if(!rst_n )begin
cam_vsync_reg1 <= 1'd0;
cam_vsync_reg2 <= 1'd0;
end
else begin
cam_vsync_reg1 <= cam_vsync;
cam_vsync_reg2 <= cam_vsync_reg1;
end
end
always@(posedge cam_pclk or negedge rst_n)begin
if(!rst_n )begin
cam_href_reg1 <= 1'd0;
cam_href_reg2 <= 1'd0;
end
else begin
cam_href_reg1 <= cam_href;
cam_href_reg2 <= cam_href_reg1;
end
end
assign pose_vsync = (~cam_vsync_reg2) && cam_vsync_reg1;
// 帧有效、行有效打2拍的原因:采集高8位用了1 clk,采集低8位用了1 clk ,拼接用了 1clk
// 因此数据满了2 clk,于是要将帧有效、行有效打2拍来和拼接后的数据同步
assign coms_frame_vsync = frame_flage ? cam_vsync_reg2 : 1'd0;
assign coms_frame_href = frame_flage ? cam_href_reg2 : 1'd0;
// 这里对数据有效信号打1拍也是为了和拼接后的数据同步
assign coms_frame_valid = frame_flage ? byte_flage_reg1 : 1'd0;
assign coms_frame_data = frame_flage ? data_contact_16 : 16'd0;
// 计数器:计数丢弃的10帧
reg [3:0] wait_10_frame;
always@(posedge cam_pclk or negedge rst_n)begin
if(!rst_n )begin
wait_10_frame <= 4'd0;
end
else if((pose_vsync) && (wait_10_frame < WAIT_10_frame))begin
wait_10_frame <= wait_10_frame + 4'd1;
end
else
wait_10_frame <= wait_10_frame;
end
// 等10帧后,有个信号告诉其他信号,数据已经可以用了
reg frame_flage;
always@(posedge cam_pclk or negedge rst_n)begin
if(!rst_n )begin
frame_flage <= 1'd0;
end
else if((pose_vsync) && (wait_10_frame == WAIT_10_frame))begin
frame_flage <= 1'd1;// 摄像头数据有效
end
else
frame_flage <= frame_flage;
end
// 8bit拼接为16bit
reg [7:0] data_reg1;
reg [15:0] data_contact_16;
reg byte_flage;
always@(posedge cam_pclk or negedge rst_n)begin
if(!rst_n )begin
data_reg1 <= 8'd0;
data_contact_16 <= 16'd0;
byte_flage <= 1'd0;
end
else if(cam_href)begin //行有效
data_reg1 <= cam_data;// 先把高8位寄存起来
byte_flage <= ~ byte_flage;// 表示已经传完8位,byte_flage=0,传输高8位;byte_flage=1,传输低8位
if(byte_flage)begin
data_contact_16 <= {data_reg1,cam_data};
end
end
else begin
data_reg1 <= 8'd0;
//data_contact_16 <= 16'd0;
byte_flage <= 1'd0;
end
end
// 将byte_flage打一拍
// 目的:使得数据有效信号和拼接后的数据同步
reg byte_flage_reg1;
always@(posedge cam_pclk or negedge rst_n)begin
if(!rst_n )begin
byte_flage_reg1 <= 1'd0;
end
else begin
byte_flage_reg1 <= byte_flage;
end
end
endmodule
三、图像存储模块
3.1 SDRAM
这部分先放一放
四、图像显示模块
4.1 VGA显示
该模块功能
:
1.给显示器输出行同步、场同步、图像数据
2.给SDRAM输入x、y坐标后,接收SDRAM传来的改 坐标下的图像数据
需要按照下面的时序,输出行同步、场同步,以及输出有效数据
module vga_div(
input vga_clk,//25M
input rst_n,//vga_clk稳定后,释放复位
//给显示器
output reg vga_hs,
output reg vga_vs,
output reg [15:0] vga_rgb,//三原色
output data_req,//向SADRAM请求输入像素点数据
output [10:0] pixel_x,
output [10:0] pixel_y,
input [15:0] pixel_data//根据输入到SDRAM中的x、y坐标,得到从SFRAM输出的该店坐标的像素点数据
);
//640*480 60FPS_25MHz
parameter H_SYNC = 10'd96; //行同步
parameter H_BACK = 10'd48; //行显示后沿
parameter H_DISP = 10'd640; //行有效数据
parameter H_FRONT = 10'd16; //行显示前沿
parameter H_TOTAL = 10'd800; //行扫描周期
parameter V_SYNC = 10'd2; //场同步
parameter V_BACK = 10'd33; //场显示后沿
parameter V_DISP = 10'd480; //场有效数据
parameter V_FRONT = 10'd10; //场显示前沿
parameter V_TOTAL = 10'd525; //场扫描周期
// 声明2个计数器,分别用于行计数和场计数
reg [10:0] cnt_h;
reg [10:0] cnt_v;
wire vga_en;
wire data_req;
always@(posedge clk or negedge rst_n)begin
if(!rst_n )begin
cnt_h <= 11'd0;
end
else if(cnt_h < H_TOTAL-1'd1)begin//一行计到799
cnt_h <= cnt_h + 11'd1;
end
else
cnt_h <= 11'd0;
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n )begin
cnt_v <= 11'd0;
end
else if(cnt_v < V_TOTAL-1'd1)begin//一行计到799
cnt_v <= cnt_v + 11'd1;
end
else
cnt_v <= 11'd0;
end
//根据VGA行同步时序、场同步时序得到行同步信号、场同步信号
always@(*)begin
if((cnt_h > V_SYNC + H_BACK) && (cnt_h < H_TOTAL - H_FRONT))begin//有效数据部分
vga_hs <= 1'd1;
end
else
vga_hs <= 1'd0;
end
always@(*)begin
if((cnt_v > H_SYNC + V_BACK) && (cnt_v < V_TOTAL - V_FRONT))begin//有效数据部分
vga_vs <= 1'd1;
end
else
vga_vs <= 1'd0;
end
assign vga_en = ((cnt_h > V_SYNC + H_BACK) && (cnt_h < H_TOTAL - H_FRONT))
&&((cnt_v > H_SYNC + V_BACK) && (cnt_v < V_TOTAL - V_FRONT))
? 1'd1:1'd0;//标志出有效数据部分
assign vga_rgb = vga_en ? pixel_data : 16'd0;
//请求像素点颜色数据输入
assign data_req = (((cnt_h >= H_SYNC+H_BACK-1'b1) && (cnt_h < H_SYNC+H_BACK+H_DISP-1'b1))
&& ((cnt_v >= V_SYNC+V_BACK) && (cnt_v < V_SYNC+V_BACK+V_DISP)))
? 1'b1 : 1'b0;//需要从SDRAM得到传回数据,因此需要提前一个时钟周期
//像素点坐标
assign pixel_x = data_req ? (cnt_h - (H_SYNC + H_BACK - 1'b1)) : 10'd0;
assign pixel_y = data_req ? (cnt_v - (V_SYNC + V_BACK - 1'b1)) : 10'd0;
endmodule
VGA接口的缺点:
1.传输的信号是模拟信号,信号容易受干扰
2.体积大
4.2 VGA转HDMI
HDMI接口的有点:
1.抗干扰能力强
2.体积小
3.兼容性好,可以同时传输数字信号和音频
4.2.1 编码
8bit
并行数据——10bit
并行数据
直流均衡:0=1个数
module encode
(
input wire sys_clk , //时钟信号
input wire sys_rst_n , //复位信号,低有效
input wire [7:0] data_in , //输入8bit待编码数据
input wire c0 , //控制信号c0
input wire c1 , //控制信号c1
input wire de , //使能信号
output reg [9:0] data_out //输出编码后的10bit数据
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter DATA_OUT0 = 10'b1101010100,
DATA_OUT1 = 10'b0010101011,
DATA_OUT2 = 10'b0101010100,
DATA_OUT3 = 10'b1010101011;
//wire define
wire condition_1 ; //条件1
wire condition_2 ; //条件2
wire condition_3 ; //条件3
wire [8:0] q_m ; //第一阶段转换后的9bit数据
//reg define
reg [3:0] data_in_n1 ; //待编码数据中1的个数
reg [7:0] data_in_reg ; //待编码数据打一拍
reg [3:0] q_m_n1 ; //转换后9bit数据中1的个数
reg [3:0] q_m_n0 ; //转换后9bit数据中0的个数
reg [4:0] cnt ; //视差计数器,0-1个数差别,最高位为符号位
reg de_reg1 ; //使能信号打一拍
reg de_reg2 ; //使能信号打两拍
reg c0_reg1 ; //控制信号c0打一拍
reg c0_reg2 ; //控制信号c0打两拍
reg c1_reg1 ; //控制信号c1打一拍
reg c1_reg2 ; //控制信号c1打两拍
reg [8:0] q_m_reg ; //q_m信号打一拍
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//data_in_n1:待编码数据中1的个数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_in_n1 <= 4'd0;
else
data_in_n1 <= data_in[0] + data_in[1] + data_in[2]
+ data_in[3] + data_in[4] + data_in[5]
+ data_in[6] + data_in[7];
//data_in_reg:待编码数据打一拍
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_in_reg <= 8'b0;
else
data_in_reg <= data_in;
//condition_1:条件1
assign condition_1 = ((data_in_n1 > 4'd4) || ((data_in_n1 == 4'd4)//??直接写>=4不行吗
&& (data_in_reg[0] == 1'b0)));
//q_m:第一阶段转换后的9bit数据
assign q_m[0] = data_in_reg[0];
assign q_m[1] = (condition_1) ? (q_m[0] ^~ data_in_reg[1]) : (q_m[0] ^ data_in_reg[1]);
assign q_m[2] = (condition_1) ? (q_m[1] ^~ data_in_reg[2]) : (q_m[1] ^ data_in_reg[2]);
assign q_m[3] = (condition_1) ? (q_m[2] ^~ data_in_reg[3]) : (q_m[2] ^ data_in_reg[3]);
assign q_m[4] = (condition_1) ? (q_m[3] ^~ data_in_reg[4]) : (q_m[3] ^ data_in_reg[4]);
assign q_m[5] = (condition_1) ? (q_m[4] ^~ data_in_reg[5]) : (q_m[4] ^ data_in_reg[5]);
assign q_m[6] = (condition_1) ? (q_m[5] ^~ data_in_reg[6]) : (q_m[5] ^ data_in_reg[6]);
assign q_m[7] = (condition_1) ? (q_m[6] ^~ data_in_reg[7]) : (q_m[6] ^ data_in_reg[7]);
assign q_m[8] = (condition_1) ? 1'b0 : 1'b1;
//q_m_n1:转换后9bit数据中1的个数
//q_m_n0:转换后9bit数据中0的个数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
q_m_n1 <= 4'd0;
q_m_n0 <= 4'd0;
end
else
begin
q_m_n1 <= q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7];
q_m_n0 <= 4'd8 - (q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7]);
end
//condition_2:条件2
assign condition_2 = ((cnt == 5'd0) || (q_m_n1 == q_m_n0));
//condition_3:条件3
assign condition_3 = (((~cnt[4] == 1'b1) && (q_m_n1 > q_m_n0))
|| ((cnt[4] == 1'b1) && (q_m_n0 > q_m_n1)));
//数据打拍,为了各数据同步
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
de_reg1 <= 1'b0;
de_reg2 <= 1'b0;
c0_reg1 <= 1'b0;
c0_reg2 <= 1'b0;
c1_reg1 <= 1'b0;
c1_reg2 <= 1'b0;
q_m_reg <= 9'b0;
end
else
begin
de_reg1 <= de;
de_reg2 <= de_reg1;
c0_reg1 <= c0;
c0_reg2 <= c0_reg1;
c1_reg1 <= c1;
c1_reg2 <= c1_reg1;
q_m_reg <= q_m;
end
//data_out:输出编码后的10bit数据
//cnt:视差计数器,0-1个数差别,最高位为符号位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
data_out <= 10'b0;
cnt <= 5'b0;
end
else
begin
if(de_reg2 == 1'b1)
begin
if(condition_2 == 1'b1)
begin
data_out[9] <= ~q_m_reg[8];
data_out[8] <= q_m_reg[8];
data_out[7:0] <= (q_m_reg[8]) ? q_m_reg[7:0] : ~q_m_reg[7:0];
cnt <= (~q_m_reg[8]) ? (cnt + q_m_n0 - q_m_n1) : (cnt + q_m_n1 - q_m_n0);
end
else
begin
if(condition_3 == 1'b1)
begin
data_out[9] <= 1'b1;
data_out[8] <= q_m_reg[8];
data_out[7:0] <= ~q_m_reg[7:0];
cnt <= cnt + {q_m_reg[8], 1'b0} + (q_m_n0 - q_m_n1);
end
else
begin
data_out[9] <= 1'b0;
data_out[8] <= q_m_reg[8];
data_out[7:0] <= q_m_reg[7:0];
cnt <= cnt - {~q_m_reg[8], 1'b0} + (q_m_n1 - q_m_n0);
end
end
end
else
begin
case ({c1_reg2, c0_reg2})
2'b00: data_out <= DATA_OUT0;
2'b01: data_out <= DATA_OUT1;
2'b10: data_out <= DATA_OUT2;
default:data_out <= DATA_OUT3;
endcase
cnt <= 5'b0;
end
end
endmodule
4.2.2 并串转换
10bit并行
数据——10bit串行
数据
单端信号——差分信号
单沿采样——双沿采样
module par_to_ser
(
input wire clk_5x , //输入系统时钟
input wire [9:0] par_data , //输入并行数据
output wire ser_data_p , //输出串行差分数据
output wire ser_data_n //输出串行差分数据
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//wire define
wire [4:0] data_rise = {par_data[8],par_data[6],
par_data[4],par_data[2],par_data[0]};
wire [4:0] data_fall = {par_data[9],par_data[7],
par_data[5],par_data[3],par_data[1]};
//reg define
reg [4:0] data_rise_s = 0;
reg [4:0] data_fall_s = 0;
reg [2:0] cnt = 0;
always @ (posedge clk_5x)
begin
cnt <= (cnt[2]) ? 3'd0 : cnt + 3'd1;
data_rise_s <= cnt[2] ? data_rise : data_rise_s[4:1];//计满就将5位上升沿数据补上,没计满就移位
data_fall_s <= cnt[2] ? data_fall : data_fall_s[4:1];
end
//********************************************************************//
//**************************** Instantiate ***************************//
//********************************************************************//
//------------- ddio_out_inst0 -------------
ddio_out ddio_out_inst0
(
.datain_h (data_rise_s[0] ),
.datain_l (data_fall_s[0] ),
.outclock (~clk_5x ),
.dataout (ser_data_p )
);
//------------- ddio_out_inst1 -------------
ddio_out ddio_out_inst1
(
.datain_h (~data_rise_s[0]),
.datain_l (~data_fall_s[0]),
.outclock (~clk_5x ),
.dataout (ser_data_n )
);
endmodule
五、附录
5.1 摄像头
本篇使用的摄像头引脚含义:
5.2 开发板
使用了下面几款开发板,其中列出摄像头模块的原理图