手把手教你写Ov7725摄像头数据采集模块(带Verilog代码)
上一节咱们学习了OV7725的VGA传输协议,对于数据传输的特点有了初步了解,这篇博客主要目的在于使用Verilog实现一个OV7725摄像头的数据采集模块,与咱们这个模块对接的是后一级的SDRAM存储器,其将作为数据的缓存仓库,以便后续对图像进行处理。
学习目标
- 搭建一个提取摄像头数据的模块与SDRAM对接
- 学习分析和设计数字模块的方法
信息汇总
- 前10帧图像不稳定,需要丢弃。
- Ov7725输出管脚有四个:
其中PCLK的时钟频率为24MHz。
- 帧有效 VSYNC为低电平0,行有效 HREF为高电平1,PCLK上升沿数据有效。
- 一个PCLK上升沿传递一个字节,一个像素点数据需要两个PCLK周期。
问题分析
首先我们要确定模块的接口数目,除了上面提到的四个输入管脚,我们需要复位信号,以及输出一个16bit的数据,并且由于我们的后级连接的是SDRAM模块,因此我们还需要一个写信号标志位,高电平有效,并且与输出的数据同步,占时两个PCLK周期。
module ov_data(
input wire Pclk, //像素时钟信号
input wire sys_reset_n, //复位信号
input wire Href, // 行有效信号
input wire Vsync, // 场同步信号
input wire [7:0] ov_data_in, // 输入信号8bit
output wire data_wr_en, // 输出写有效信号
output wire [15:0] ov_data_out // 输出数据16bit
);
endmodule
下一步自然而然我们就要分析如何采集输入数据信号了。我们很容易想到,数据有效的时候 Vsync 为低电平0,Href为高电平1,然后使用一个Pclk敏感的always内嵌一个if判断即可取到有效数据。
这就是我们的最初的骨架,但是还有很多不足之处,首先就是前10帧的无效数据该怎么进行滤除?
思路其实很明确,我们需要一个计数器,每一帧信号计数器加一,从0到9,计数10帧,并且我们还需要一个指示位,来指示帧是否有效。好了,知道了大体的电路组成,咱们就要面对任何计数器都必须要解决的困难:如何确定计数条件?
这就需要我们分析信号的特点了!
从上图可以看出,两个高电平之间的低电平是数据,我们可以把VSYNC信号分割开来可以发现,每个周期包含了一个高电平和一个低电平。
有些同学可能此时会产生一个大胆的想法:就是使用VSYNC作为触发器的触发信号,诶,这样每个上升沿计数一次不就可以计数帧数了吗?其实这种想法特别好,但是呢会有些问题,主要的原因就是VSYNC有可能不稳定。大家注意到没有,一般always的时序电路的触发电平往往是时钟信号,主要原因就是时钟信号比较稳定,振荡较少或者没有,这样触发器往往会比较稳定,不会乱跳。
因此我们首选的触发信号还是PCLK信号,这里博主提供一种思路,就是每次PCLK上升沿来临时存储当前的VSYNC,当前一次的值为0,并且后一次的值为1时,则判定为一帧。
代码补充如下:
module ov_data(
input wire Pclk, //像素时钟信号
input wire sys_reset_n, //复位信号
input wire Href, // 行有效信号
input wire Vsync, // 场同步信号
input wire [7:0] ov_data_in, // 输入信号8bit
output wire data_wr_en, // 输出写有效信号
output wire [15:0] ov_data_out // 输出数据16bit
);
reg [3:0] v_cnt; //帧计数
reg v_temp; //帧存储
reg v_valid; //帧有效指示位
always@(posedge Pclk or negedge sys_reset_n)begin
if(sys_reset_n == 1'b0)begin
v_temp <= 1'b0;
v_cnt <= 4'd0;
v_valid <= 1'b0;
end else if(((v_temp == 1'b0)&&(Vsync == 1'b1))(v_cnt == 4'd10))begin// 计数器计数完前10个周期,正准备计数第11个周期时
v_cnt <= 4'd0;
v_valid <= 1'b1;
end
else if((v_temp == 1'b0)&&(Vsync == 1'b1))begin//如果前一个周期为0,后一个周期为1
v_cnt <= v_cnt+4'd1; //帧计数加1
v_temp <= Vsync;
v_valid <= v_valid;
end else begin //否则保持不变
v_cnt <= v_cnt;
v_temp <= Vsync;
v_valid <= v_valid;
end
end
endmodule
总结一下:
- 计数器的要素:1、计数条件 2、计满条件 3、时钟选择
好了,一顿操作下,我们解决了前10帧丢弃的问题,现在摆在我们面前的另一个问题是如何把两个八位的数据拼接成16位的数据,并且输出。
我们细细琢磨一下:我们肯定要有一个拼接的操作,而且,这个拼接的操作是要两个数据拼接一次,所以,拼接操作的时钟就得是2个PCLK,并且后一个PCLK要执行将前一个PCLK的数据与当前PCLK的数据拼接的任务。因此我们明确,一定会有一个数据暂存寄存器,暂存前一个PCLK的数据,并且我们要对PCLK进行二分频。
这时不少同学肯定会想,诶,二分频,就用计数器做,然后if判断是第一个PCLK还是第二个PCLK,分别执行不同的操作就可以啦!
思路完全正确,但是二分频可以更加简单,如果我们采用计数的方法,那么这个值肯定是从0到1,这样计数其实就相当于进行一次翻转操作,这样完全可以简化计数器带来的用if语句判断计数是否溢出的操作。因此我们的思路就形成了:PCLK的上升沿,将一个计数器的值翻转,再判断如果是0则存储当前数据,并且最终输出的数据保持不变,如果是1,则进行拼接输出操作。
补充完整的代码如下:
/*
By WWD,2022/9/4
转载请注明出处
*/
module ov_data(
input wire Pclk, //像素时钟信号
input wire sys_reset_n, //复位信号
input wire Href, // 行有效信号
input wire Vsync, // 场同步信号
input wire [7:0] ov_data_in, // 输入信号8bit
output wire data_wr_en, // 输出写有效信号
output wire [15:0] ov_data_out // 输出数据16bit
);
reg [3:0] v_cnt; //帧计数
reg v_temp; //帧存储
reg v_valid; //帧有效指示位
reg [7:0] ov_data_reg;//数据暂存寄存器
reg data_cnt;//数据输出计数--2分频
reg wr_en_reg;//读写标志寄存位
reg [15:0] ov_data_out_reg;
always@(posedge Pclk or negedge sys_reset_n)begin
if(sys_reset_n == 1'b0)begin
v_temp <= 1'b0;
v_cnt <= 4'd0;
v_valid <= 1'b0;
end else if((v_temp == 1'b0)&&(Vsync == 1'b1)&&(v_cnt == 4'd10))begin// 计数器计数完前10个周期,正准备计数第11个周期时
v_cnt <= 4'd0;
v_valid <= 1'b1;
end
else if((v_temp == 1'b0)&&(Vsync == 1'b1))begin//如果前一个周期为0,后一个周期为1
v_cnt <= v_cnt+4'd1; //帧计数加1
v_temp <= Vsync;
v_valid <= v_valid;
end else begin //否则保持不变
v_cnt <= v_cnt;
v_temp <= Vsync;
v_valid <= v_valid;
end
end
always@(posedge Pclk or negedge sys_reset_n)begin
if(sys_reset_n == 1'b0)begin
ov_data_reg <= 8'b0;
data_cnt <= 1'b0;
ov_data_out_reg <= 16'b0;
wr_en_reg <= 1'b0;
end else if((Href == 1'b0)&&(v_valid == 1'b1))begin//Href有效时
ov_data_reg <= ov_data_in;
data_cnt <= ~data_cnt;
if(data_cnt == 1'b0)begin
ov_data_out_reg <= ov_data_out_reg; //还未到时候,保持住数据
wr_en_reg <= 1'b0; //读写标志保持0
end else begin
ov_data_out_reg <= {ov_data_reg,ov_data_in}; //到时候了,开始拼接!
wr_en_reg <= 1'b1; //读写标志置1
end
end else begin //Href无效时
ov_data_reg <= 8'b0;
data_cnt <= 1'b0;
wr_en_reg <= 1'b0;
ov_data_out_reg <= ov_data_out_reg;
end
end
assign data_wr_en = wr_en_reg;
assign ov_data_out = ov_data_out_reg;
endmodule
仿真测试
TB文件:
我们使用野火电子的TB文件进行测试(模块接口兼容)
`timescale 1ns/1ns
// Author : EmbedFire
// Create Date : 2019/09/25
// Module Name : tb_ov7725_data
// Project Name : ov7725_vga_640x480
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : OV7725摄像头图像数据采集模块仿真文件
//
// Revision : V1.0
// Additional Comments:
//
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司 : http://www.embedfire.com
// 论坛 : http://www.firebbs.cn
// 淘宝 : https://fire-stm32.taobao.com
module tb_ov_data();
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter H_VALID = 10'd640 , //行有效数据
H_TOTAL = 10'd784 ; //行扫描周期
parameter V_SYNC = 10'd4 , //场同步
V_BACK = 10'd18 , //场时序后沿
V_VALID = 10'd480 , //场有效数据
V_FRONT = 10'd8 , //场时序前沿
V_TOTAL = 10'd510 ; //场扫描周期
//wire define
wire ov7725_wr_en ; //有效图像使能信号
wire [15:0] ov7725_data_out ; //有效图像数据
wire ov7725_href ; //行同步信号
wire ov7725_vsync ; //场同步信号
//reg define
reg sys_clk ; //模拟时钟信号
reg sys_rst_n ; //模拟复位信号
reg [7:0] ov7725_data ; //模拟摄像头采集图像数据
reg [11:0] cnt_h ; //行同步计数器
reg [9:0] cnt_v ; //场同步计数器
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//时钟、复位信号
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
#200
sys_rst_n <= 1'b1 ;
end
always #20 sys_clk = ~sys_clk;
//cnt_h:行同步信号计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_h <= 12'd0 ;
else if(cnt_h == ((H_TOTAL * 2) - 1'b1))
cnt_h <= 12'd0 ;
else
cnt_h <= cnt_h + 1'd1 ;
//ov7725_href:行同步信号
assign ov7725_href = (((cnt_h >= 0)
&& (cnt_h <= ((H_VALID * 2) - 1'b1)))
&& ((cnt_v >= (V_SYNC + V_BACK))
&& (cnt_v <= (V_SYNC + V_BACK + V_VALID - 1'b1))))
? 1'b1 : 1'b0 ;
//cnt_v:场同步信号计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_v <= 10'd0 ;
else if((cnt_v == (V_TOTAL - 1'b1))
&& (cnt_h == ((H_TOTAL * 2) - 1'b1)))
cnt_v <= 10'd0 ;
else if(cnt_h == ((H_TOTAL * 2) - 1'b1))
cnt_v <= cnt_v + 1'd1 ;
else
cnt_v <= cnt_v ;
//vsync:场同步信号
assign ov7725_vsync = (cnt_v <= (V_SYNC - 1'b1)) ? 1'b1 : 1'b0 ;
//ov7725_data:模拟摄像头采集图像数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
ov7725_data <= 8'd0;
else if(ov7725_href == 1'b1)
ov7725_data <= ov7725_data + 1'b1;
else
ov7725_data <= 8'd0;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------- ov7725_data_inst -------------
ov_data ov7725_data_inst
(
.sys_reset_n (sys_rst_n ), //复位信号
.Pclk (sys_clk ), //摄像头像素时钟
.Href (ov7725_href ), //摄像头行同步信号
.Vsync (ov7725_vsync ), //摄像头场同步信号
.ov_data_in (ov7725_data ), //摄像头图像数据
.data_wr_en (ov7725_wr_en ), //图像数据有效使能信号
.ov_data_out (ov7725_data_out) //图像数据
);
endmodule
波形验证
我们首先可以看到,Valid信号省略了前10帧信号,完全正确。
起始段的拼接与写使能信号也完全正确。
结束段也符合要求。
至此,本模块仿真完美实现!
总结
本节文章带领大家体验了一把如何进行数字模块设计的过程,其实无外乎就是不断地分析,不断地将大的模块分解为小的模块的过程,作为RTL级的设计,无外乎围绕的就是 触发器,选择器,计数器,等等子模块,体现在Verilog上就是 always块,if语句,赋值语句等等,来实现最终的功能。
我们要培养的就是分析问题的能力,要有敏锐的判断力,去判断子模块的选择,并且大脑中要有电路,要会“自我仿真”,“自我纠错”。
下一节,我们将介绍如何使用SDRAM。
相关文章
- 留言模块,MySQL查询数据问题
- python 内建模块_simulink常用模块
- 深度阐述Nodejs模块机制
- 7.Nginx实践之使用Lua-nginx模块脚本连接Redis数据库读取静态资源并隐式展现
- 基于STM32的RC522模块读写数据块以及电子钱包充值扣款系统的设计
- 5种前端代码共享方案:npm包、git submodules、脚手架模板、复制、UMD或模块联邦
- 计算机无法与振弦采集模块通讯
- numpy的random模块详细解析详解大数据
- python中telnetlib模块的使用详解大数据
- 模块XLslib模块在Linux上的应用(xlsliblinux)
- 运维学python之爬虫基础篇(三)urllib模块高级用法
- Puppet模块(一):NTP模块及Cron资源
- 深入理解Linux模块的工作原理及应用方法(linux模块)
- 数据同步Oracle EBS模块之间的数据同步(oracleebs模块)
- 轻松安装mssql模块,让您更轻松获取所需数据(安装mssql模块)
- Oracle仓库模块实现数据集中管理(oracle仓库模块应用)
- jQuery数据缓存模块进化史详细介绍
- Python实现根据指定端口探测服务器/模块部署的方法