系列篇章目录
FPGA一键测距仪之数码管篇
FPGA一键测距仪之[按键+控制+蜂鸣器]篇
FPGA一键测距仪之终篇
第一个FPGA小项目:基于BASYS3的超声波一键测距仪
前言
本篇会对超声波测距模块进行详细的讲解,包括测距原理、各模块的时序图构思以及代码实现。
所用到的软件工具:
- Vivado 2019.1
- Modelsim SE-64 10.4
- MATLAB R2018a
- Excel
一、超声波测距原理
拿特权老师的图来解释一下。如下图所示,超声波测距传感器通过两个TX、RX收发头输出、返回超声波来测距,测距传感器和FPGA之间通过TRIG和ECHO引脚交互波形数据,使得FPGA可以获取传感器测得的距离。
如上图所示,要想让传感器TX头发送超声波,我们需要在TRIG引脚上输入高电平持续时间大于10us的脉冲信号,TX头就会发送8个的40khz脉冲,并且在第8个脉冲下降沿的位置传感器主动拉高ECHO电平。当40khz脉冲碰到障碍物然后返回,被RX头采集到后,同样在第8个脉冲下降沿的位置拉低ECHO电平,这样一来我们让FPGA采集计数ECHO引脚的高电平时间t。再根据这个t来算出距离s,在室温25摄氏度下音速v为346m/s,高电平时间t代表着声波从TX头到障碍物再到RX头所花费的时间,由初中物理知识可知:2 * s = v * t,代入音速反解s,得到s(单位:米) = 173 * t(单位:秒),可对于FPGA来说处理时间的单位为us更为合适,所以将单位换算一下就可以得到s(单位:毫米) = 0.173 * t(单位:微秒),既然测出了时间,那么距离也就知道了。
二、测距模块框图的构思
下图是我构思的框图,暂时忽略vlg_key(按键)和vlg_ctrl(控制)模块,可以看到构想的超声波测距模块内部的组成,包括vlg_en(分频使能时钟模块)、vlg_trig(超声波触发模块)、vlg_echo(回响接收模块)、vlg_filt(均值滤波模块)、vlg_review(稳定检测模块)和vlg_cal(数学运算模块)这6份子模块文件。
工作流程大致如下:分频使能时钟模块将100Mhz系统时钟分频为周期1us的使能时钟,使能时钟为超声波触发模块(vlg_trig)和回响信号模块(vlg_echo)提供1us的计数单位;超声波触发模块负责产生触发脉冲驱动硬件传感器工作,回响接收模块处理硬件传感器传回的回响信号,对回响脉冲高电平时间计数;再通过均值滤波模块把采集到的n个回响高电平时间进行滤波,然后分别输出给数学运算模块(vlg_cal)和稳定检测模块(vlg_review),数学运算模块负责将时间转换成距离,稳定检测模块负责检测m个滤波后的回响脉冲高电平时间是否趋于一致,一致即判定数据已稳定,并会输出一个高脉冲。
下图为最后编译出来的原理图文件,符合构思框图,可以实现超声波的测距功能。测距模块外部留有输出给后级模块的引脚(如o_check_posedge、o_s_mm)、方便前级控制模块接入的信号引脚(如i_en)、还有接到硬件超声波传感器的Trig和Echo引脚。
三、分频使能时钟模块(vlg_en.v)的设计
1. 时序图的构思
时序图如下所示,因为最后输出的时间是以us为单位的,所以我们要让该模块在外部使能信号i_en下产生1us为周期的超声波使能时钟,为内部的vlg_trig和vlg_echo模块提供工作时钟。当i_en信号拉高时寄存器r_divcnt从0开始计数,当i_en拉低后r_divcnt计数值清零不会计数。在i_en为高电平的前提下,由100M/1M = 100可知分频计数范围是0~99,最大值计到99后归0继续计数;在r_divcnt=99后一个时钟周期下将o_clk_en拉高,此时o_clk_en就是我们要的后级的使能时钟。
2.代码实现
/*
模块功能:产生周期1us的使能脉冲
*/
`timescale 1ns/1ps
module vlg_en #(
parameter SYS_CLK_STEP = 10 //系统时钟100MHz,一周期10ns
)
(
input i_clk,
input i_rst,
input i_en,
output reg o_clk_en
);
/*****************1us计数器****************/
localparam DIV_CNT_MAX = 1000/SYS_CLK_STEP;
reg [7:0] r_divcnt;
always @(posedge i_clk)
if(i_rst) r_divcnt <= 'b0;
else if(i_en)
begin
if(r_divcnt < (DIV_CNT_MAX-1)) r_divcnt <= r_divcnt + 1'b1;
else r_divcnt <= 'b0;
end
else r_divcnt <= 'b0;
/*****************************************/
/**************产生使能脉冲**************/
always @(posedge i_clk)
if(i_rst) o_clk_en <= 1'b0;
else if(r_divcnt == (DIV_CNT_MAX-1)) o_clk_en <= 1'b1;
else o_clk_en <= 1'b0;
/**************************************/
endmodule
四、超声波触发模块(vlg_trig.v)的设计
1.时序图的构思
如下图所示,寄存器r_trig_cnt在1us使能时钟i_clk_en下进行计数。为了让o_trig信号产生周期100ms、高电平为10us的脉冲,需要计算一下r_trig_cnt的计数最大值,由100ms/1us = 100,000可得,计数最大值为99,999,即r_trig_cnt计数到99,999后清零再次计数,在计数值为1~11时将o_trig拉高,即可产生我们要求的触发脉冲o_trig。
2.代码实现
/*
模块功能:产生100ms为周期、高电平持续10us的脉冲
*/
`timescale 1ns/1ps
module vlg_trig (
input i_clk,
input i_rst,
input i_clk_en,
output reg o_trig
);
localparam TRIG_CNT_MAX = 100_000;
localparam TRIG_CNT_10US = 10;
reg [31:0] r_trig_cnt;
/*******************100ms计数******************/
always @(posedge i_clk)
if(i_rst) r_trig_cnt <= 'b0;
else if(i_clk_en)
begin
if(r_trig_cnt < (TRIG_CNT_MAX-1)) r_trig_cnt <= r_trig_cnt + 1'b1;
else r_trig_cnt <= 'b0;
end
else ;
/*********************************************/
/****************产生10us的高脉冲***************/
always @(posedge i_clk)
if(i_rst) o_trig <= 1'b0;
else if((r_trig_cnt > 0)&&(r_trig_cnt <= TRIG_CNT_10US)) o_trig <= 1'b1;
else o_trig <= 1'b0;
/*********************************************/
endmodule
五、回响接收模块(vlg_echo.v)的设计
1.时序图的构思
如下图所示,在vld_echo模块的设计中,i_echo接收传感器返回的回响脉冲,由于原始的回响脉冲有可能异步于时钟,所以先对回响脉冲进行上升沿/下降沿的边沿检测,利用边沿检测后的r_pos_echo和r_neg_echo来让回响脉冲和时钟边沿同步,同步成r_cnt_en再对它进行高电平时间计数,即在r_cnt_en为1的前提下根据使能时钟1us的周期进行计数,当r_cnt_en = 0后清零停止计数。在r_neg_echo产生高脉冲后(即表示i_echo回响信号产生下降沿跳变),我们让计数寄存器r_echo_cnt计到的数n锁存到o_t_us中,并输出到后级模块;在锁存的同时,还会产生一个o_echo_en脉冲信号,用作给后级提供回响脉冲个数的信号。
2.代码实现
/*
模块功能:测量echo引脚返回的高脉冲时间
*/
`timescale 1ns/1ps
module vlg_echo (
input i_clk,
input i_rst,
input i_echo,
input i_clk_en,
output reg [15:0] o_t_us,
output reg o_echo_en
);
reg [1:0] r_echo;
wire w_pos_echo,w_neg_echo;
reg r_cnt_en;
reg [15:0] r_cnt_echo;
/******************获取边沿检测信号*****************/
always @(posedge i_clk)
if(i_rst) r_echo <= 'b0;
else r_echo <= {r_echo[0],i_echo};
assign w_pos_echo = r_echo[0] & ~r_echo[1];
assign w_neg_echo = ~r_echo[0] & r_echo[1];
/*************************************************/
/*****************产生计数使能信号*****************/
always @(posedge i_clk)
if(i_rst) r_cnt_en <= 1'b0;
else if(w_pos_echo) r_cnt_en <= 1'b1;
else if(w_neg_echo) r_cnt_en <= 1'b0;
else;
/************************************************/
/***************对使能信号高脉冲进行计数**************/
always @(posedge i_clk)
if(i_rst) r_cnt_echo <= 'b0;
else if(r_cnt_en)
begin
if(i_clk_en) r_cnt_echo <= r_cnt_echo + 1'b1;
else;
end
else r_cnt_echo <= 'b0;
/**************************************************/
/*****************对计数值锁存输出****************/
always @(posedge i_clk)
if(i_rst) o_t_us <= 'b0;
else if(w_neg_echo) o_t_us <= r_cnt_echo;
/***********************************************/
/****************使能信号有效输出*****************/
always @(posedge i_clk)
if(i_rst) o_echo_en <= 1'b0;
else o_echo_en <= w_neg_echo;
/***********************************************/
endmodule
六、数学运算模块(vlg_cal.v)的设计
1.代码实现
vlg_cal模块用于解决s = 0.173 * t的转换问题,首先得清楚FPGA是不能做浮点运算的,我们就需要通过手段把浮点运算变成定点数据的运算。在这里我们先将s = 0.173 * t左右两边同时乘上4096,得到4096 *s= 709 * t,再除4096,这里很有讲究,因为4096 = 2^12,所以x除以4096就相当于x在二进制下右移12位,即此时s = (709 * t) >> 12,当然这里右移的12位也可以改成其他的位数,对应着不同的算术精度,这里就不再讨论了。
接着我们需要处理709 * t的问题,可以直接在时钟周期下直接相乘,也可以使用乘法器IP核,这里面有不同方案的对比取舍,具体可以看特权同学的视频:不同Multiplier设计的性能比对,从8:34开始
因为我的Modelsim仿真工具版本较低,导致配置乘法器IP核时出现意想不到的报错,所以我选择了直接相乘的方案,这也是合理的,同样能在FPGA内部被综合成乘法器。
/*
模块功能:乘法器
*/
`timescale 1ns/1ps
module vlg_cal (
input i_clk,
input i_rst,
input [15:0] i_t_us,
output [15:0] o_s_mm
);
/*************s=0.173*t变成整数乘法的过程***********/
// s*4096 = 0.173*4096*t
// => s*4096 = 709*t
// => s = 709*t / 4096
// => s = 709*t >> 12
/************************************************/
localparam MUL_N = 10'd709;
reg [25:0] w_mult_result;
always @(posedge i_clk)
if(i_rst) w_mult_result <= 'b0;
else w_mult_result <= MUL_N * i_t_us;
/***********乘法器IP例化,实现709*t的功能***********/
/*
mult_gen_0 your_instance_name (
.CLK(i_clk), // input wire CLK
.A(MUL_N), // input wire [9 : 0] A
.B(i_t_us), // input wire [15 : 0] B
.P(w_mult_result) // output wire [25 : 0] P
);
/*************************************************/
assign o_s_mm = {2'b00,w_mult_result[25:12]};
endmodule
七、不加滤波模块的仿真
1.超声波模块例化程序
`timescale 1ns/1ps
module vlg_ultrawave(
input i_clk,
input i_rst,
input i_en,
input i_echo,
output o_trig,
output [15:0] o_s_mm
);
localparam SYS_CLK_STEP = 10;
wire w_clk_en;
wire [15:0] w_t_us;
wire [15:0] w_t_filt_us;
wire w_echo_en;
/**********使能时钟产生模块**********/
vlg_en #(
.SYS_CLK_STEP(SYS_CLK_STEP)
)
uut_vlg_en(
.i_clk(i_clk),
.i_rst(i_rst),
.i_en(i_en),
.o_clk_en(w_clk_en)
);
/***********************************/
/***********产生触发信号o_trig驱动超声波模块工作*********/
vlg_trig uut_vlg_trig(
.i_clk(i_clk),
.i_rst(i_rst),
.i_clk_en(w_clk_en),
.o_trig(o_trig)
);
/****************************************************/
/**********超声波模块回响信号i_echo引脚高电平时间测量**********/
vlg_echo uut_vlg_echo(
.i_clk(i_clk),
.i_rst(i_rst),
.i_echo(i_echo),
.i_clk_en(w_clk_en),
.o_t_us(w_t_us)
//.o_echo_en(w_echo_en)
);
/**********************************************************/
/**********将回响信号高电平时间(us)通过公式转换成距离(mm)**********/
vlg_cal uut_vlg_cal(
.i_clk(i_clk),
.i_rst(i_rst),
.i_t_us(w_t_us),
.o_s_mm(o_s_mm)
);
/*************************************************************/
endmodule
2.仿真代码
在这份仿真文件中,我们例化了vlg_ultrawave模块,在i_en拉高的时候随机产生不同高电平时长的i_echo信号做测试,i_en拉高的4.8秒内可以产生48个随机的距离数据。
`timescale 1ns/1ps
module testbench_top();
//参数定义
`define CLK_PERIORD 10 //时钟周期设置为10ns(100MHz)
//接口申明
reg clk;
reg rst;
reg i_en;
reg i_echo;
wire o_trig;
wire [15:0] o_s_mm;
//对被测试的设计进行例化
vlg_ultrawave uut_vlg_ultrawave(
.i_clk(clk),
.i_rst(rst),
.i_en(i_en),
.i_echo(i_echo),
.o_trig(o_trig),
.o_s_mm(o_s_mm)
);
//复位和时钟产生
//时钟和复位初始化、复位产生
initial begin
clk <= 0;
rst <= 1;
#1000;
rst <= 0;
end
//时钟产生
always #(`CLK_PERIORD/2) clk = ~clk;
//测试激励产生
initial begin
i_echo = 0;
@(negedge rst); //等待复位完成
@(posedge clk);
repeat(100_000) begin
@(posedge clk);
i_en = 0;
end
i_en = 1;
#4_000_000_000;
i_en = 1;
#800_000_000;
i_en = 0;
#300_000_000;
$stop;
end
integer tricnt = 0;
integer dly_time;
integer target_distance;
always @(posedge o_trig)
begin
tricnt = tricnt + 1;
#5000;
i_echo = 1;
//dly_time = dly_time + 58;
dly_time = 11+{$random}%26000; //11<t<26011
target_distance = function_t2s(dly_time);
$display("Test %0d:\tdistance of testbench = %0dmm",tricnt,target_distance);
#(dly_time*1000);
i_echo = 0;
end
initial begin
#1;
$monitor("o_s_mm = %0dmm",o_s_mm);
end
//函数实现运算s=t*0.173
function real function_t2s;
input real t;
begin
function_t2s = 0.173*t;
end
endfunction
endmodule
3.波形分析
由下图我们看到,每当i_echo脉冲高电平结束的下降沿到来后输出的距离数据得到一次更新,target_distance为测试程序设定的距离值,o_s_mm是我们通过捕获i_echo计数、转换后的距离值,可见下图中这两个值基本符合+/-3mm的误差范围。
八、均值滤波模块(vlg_filt.v)的设计
1.代码实现
在vlg_filt模块的设计中,我们要实现最近测量的16个回响脉冲高电平时间取均值滤波,也就是对16个数据取平均值。先根据vlg_echo模块输出的i_echo_en脉冲来判断是否有数据,有数据的情况下移位锁存16个回响脉冲高电平时间,然后对16个数据求和取均值,取均值时将数据之和右移4位,表示除以16,最终输出这个o_t_filt_us信号给后级模块。
/*
模块功能:对最近测量的16个回响脉冲高电平时间求均值滤波
*/
`timescale 1ns/1ps
module vlg_filt (
input i_clk,
input i_rst,
input i_echo_en,
input [15:0] i_t_us,
output [15:0] o_t_filt_us
);
/*****************锁存16个回响脉冲高电平时间数据****************/
reg [15:0] r_record_us[15:0];
integer i;
always @(posedge i_clk)
if(i_rst)
begin
for(i=0;i<16;i=i+1)
begin
r_record_us[i] <= 'b0;
end
end
else if(i_echo_en)
begin
r_record_us[0] <= i_t_us;
for(i=0;i<15;i=i+1)
begin
r_record_us[i+1] <= r_record_us[i];
end
end
/************************************************************/
/*********************16个数据求和取均值*********************/
reg [19:0] r_record_sum_us;
always @(posedge i_clk)
if(i_rst) r_record_sum_us <= 'b0;
else r_record_sum_us <= r_record_us[0] + r_record_us[1] + r_record_us[2] + r_record_us[3] + r_record_us[4] + r_record_us[5] + r_record_us[6] + r_record_us[7] + r_record_us[8] + r_record_us[9] + r_record_us[10] + r_record_us[11] + r_record_us[12] + r_record_us[13] + r_record_us[14] + r_record_us[15];
assign o_t_filt_us = r_record_sum_us[19:4]; //右移4位,表示除以16
/**********************************************************/
endmodule
2.仿真分析
带有均值滤波功能的模块例化程序如下所示。
`timescale 1ns/1ps
module vlg_ultrawave(
input i_clk,
input i_rst,
input i_en,
input i_echo,
output o_trig,
output [15:0] o_s_mm
//output o_check_posedge
);
localparam SYS_CLK_STEP = 10;
wire w_clk_en;
wire [15:0] w_t_us;
wire [15:0] w_t_filt_us;
wire w_echo_en;
/**********使能时钟产生模块**********/
vlg_en #(
.SYS_CLK_STEP(SYS_CLK_STEP)
)
uut_vlg_en(
.i_clk(i_clk),
.i_rst(i_rst),
.i_en(i_en),
.o_clk_en(w_clk_en)
);
/***********************************/
/***********产生触发信号o_trig驱动超声波模块工作*********/
vlg_trig uut_vlg_trig(
.i_clk(i_clk),
.i_rst(i_rst),
.i_clk_en(w_clk_en),
.o_trig(o_trig)
);
/****************************************************/
/**********超声波模块回响信号i_echo引脚高电平时间测量**********/
vlg_echo uut_vlg_echo(
.i_clk(i_clk),
.i_rst(i_rst),
.i_echo(i_echo),
.i_clk_en(w_clk_en),
.o_t_us(w_t_us),
.o_echo_en(w_echo_en)
);
/**********************************************************/
/****************均值滤波后的回响信号高电平时间****************/
vlg_filt uut_vlg_filt(
.i_clk(i_clk),
.i_rst(i_rst),
.i_echo_en(w_echo_en),
.i_t_us(w_t_us),
.o_t_filt_us(w_t_filt_us)
);
/**********************************************************/
/**********将回响信号高电平时间(us)通过公式转换成距离(mm)**********/
vlg_cal uut_vlg_cal(
.i_clk(i_clk),
.i_rst(i_rst),
.i_t_us(w_t_filt_us),
.o_s_mm(o_s_mm)
);
/*************************************************************/
endmodule
仿真测试程序任和不带有滤波模块的相同,会随机产生48个echo回响脉冲来让超声波测距模块采集时间测量距离。
下面我从48个距离数据中去除了前15个,从第16个到第48个的距离数据(以毫米为单位,滤波前和滤波后都有)被记录在Excel表格中,如下图所示。
下面写一段Matlab代码来把这些数据画成一张曲线图,代码如下所示:
test_array = xlsread('D:\test.xls','sheet1');
x = test_array(1,:);
y1 = test_array(2,:);
y2 = test_array(3,:);
plot(x,y1,'g',x,y2,'b','LineWidth',2);
xlabel("数据个数/个");
ylabel("测量距离/mm");
这样有下图,图中绿色线条为未滤波的测量数据,蓝色为滤波后的数据。
由上图可见,观察这33个因测量随机产生的距离而得到的滤波以及未滤波的数据,我们可以发现:未滤波的数据表现的和随机数据一样不稳定,滤波后的数据整体变化幅度明显减弱,相对未滤波的更加平滑;数值总体上处于未滤波数据的中间位置。我们还可以发现,蓝色曲线的变化趋势类似于绿色曲线,并且蓝色曲线有点滞后于绿色曲线,以上这些对比可以体会到均值滤波的效果。
九、稳定检测模块(vlg_review.v)的设计
1.代码实现
在vlg_review模块中,我们设计的功能是检测滤波后的时间数据是否稳定,这里锁存4个时间数据,同样是根据vlg_echo模块输出的i_echo_en脉冲来判断是否有数据,有数据就锁存,然后判断4个数据是否非0且相等,是的话拉高r_checkstable,否则保持r_checkstable低电平。然后再对r_checkstable进行上升沿检测,产生上升沿脉冲信号o_check_posedge,此信号输出到外部,反馈给vlg_ctrl模块从而达到关闭超声波测距模块的作用。
/*
模块功能:检测滤波后的时间数据是否稳定
*/
`timescale 1ns/1ps
module vlg_review (
input i_clk,
input i_rst,
input i_echo_en,
input [15:0] i_t_filt_us,
output o_check_posedge
);
/***************锁存4个滤波后的时间数据****************/
reg [15:0] r_record_filt_us[3:0];
integer i;
always @(posedge i_clk)
if(i_rst)
begin
for(i=0;i<4;i=i+1)
begin
r_record_filt_us[i] <= 'b0;
end
end
else if(i_echo_en)
begin
r_record_filt_us[0] <= i_t_filt_us;
for(i=0;i<3;i=i+1)
begin
r_record_filt_us[i+1] <= r_record_filt_us[i];
end
end
else;
/**************************************************/
/**************判断4个数据是否非0且相等**************/
reg r_checkstable;
reg [1:0] r_check_pulse;
always @(posedge i_clk)
if(i_rst) r_checkstable <= 1'b0;
else if((r_record_filt_us[0]!='b0)&&(r_record_filt_us[1]!='b0)&&(r_record_filt_us[2]!='b0)&&(r_record_filt_us[3]!='b0))
begin
if((r_record_filt_us[0]==r_record_filt_us[1])&&(r_record_filt_us[1]==r_record_filt_us[2])&&(r_record_filt_us[2]==r_record_filt_us[3])) r_checkstable <= 1'b1; //4个数据相等则将r_checkstable拉高
else r_checkstable <= 1'b0; //4个数据不相等则将r_checkstable拉低
end
else r_checkstable <= 1'b0;
/**************************************************/
/****************边沿检测,取r_checkstable的上升沿跳变信号做输出**************/
always @(posedge i_clk)
if(i_rst) r_check_pulse <= 2'b00;
else r_check_pulse <= {r_check_pulse[0],r_checkstable};
assign o_check_posedge = r_check_pulse[0] & ~r_check_pulse[1];
/************************************************************************/
endmodule
十、超声波模块(vlg_ultrawave.v)的测试
1.模块例化代码
`timescale 1ns/1ps
module vlg_ultrawave(
input i_clk,
input i_rst,
input i_en,
input i_echo,
output o_trig,
output [15:0] o_s_mm,
output o_check_posedge
);
localparam SYS_CLK_STEP = 10;
wire w_clk_en;
wire [15:0] w_t_us;
wire [15:0] w_t_filt_us;
wire w_echo_en;
/**********使能时钟产生模块**********/
vlg_en #(
.SYS_CLK_STEP(SYS_CLK_STEP)
)
uut_vlg_en(
.i_clk(i_clk),
.i_rst(i_rst),
.i_en(i_en),
.o_clk_en(w_clk_en)
);
/***********************************/
/***********产生触发信号o_trig驱动超声波模块工作*********/
vlg_trig uut_vlg_trig(
.i_clk(i_clk),
.i_rst(i_rst),
.i_clk_en(w_clk_en),
.o_trig(o_trig)
);
/****************************************************/
/**********超声波模块回响信号i_echo引脚高电平时间测量**********/
vlg_echo uut_vlg_echo(
.i_clk(i_clk),
.i_rst(i_rst),
.i_echo(i_echo),
.i_clk_en(w_clk_en),
.o_t_us(w_t_us),
.o_echo_en(w_echo_en)
);
/**********************************************************/
/****************均值滤波后的回响信号高电平时间****************/
vlg_filt uut_vlg_filt(
.i_clk(i_clk),
.i_rst(i_rst),
.i_echo_en(w_echo_en),
.i_t_us(w_t_us),
.o_t_filt_us(w_t_filt_us)
);
/**********************************************************/
/**********将回响信号高电平时间(us)通过公式转换成距离(mm)**********/
vlg_cal uut_vlg_cal(
.i_clk(i_clk),
.i_rst(i_rst),
.i_t_us(w_t_filt_us),
.o_s_mm(o_s_mm)
);
/*************************************************************/
/**************检测滤波后的高电平时间数据是否稳定****************/
vlg_review uut_vlg_review(
.i_clk(i_clk),
.i_rst(i_rst),
.i_echo_en(w_echo_en),
.i_t_filt_us(w_t_filt_us),
.o_check_posedge(o_check_posedge)
);
/*************************************************************/
endmodule
2.仿真程序
在仿真程序中,我们产生一个高电平时间线性增大的i_echo回响信号,当dly_time增大到348us时,也就是理想测得的距离为348*0.173 = 60mm后我们让i_echo高电平时间不变,来观察接入超声波测距模块后各个输出的情况。
`timescale 1ns/1ps
module testbench_top();
//参数定义
`define CLK_PERIORD 10 //时钟周期设置为10ns(100MHz)
//接口申明
reg clk;
reg rst;
reg i_en;
reg i_echo;
wire o_trig;
wire [15:0] o_s_mm;
wire o_check;
//对被测试的设计进行例化
vlg_ultrawave uut_vlg_ultrawave(
.i_clk(clk),
.i_rst(rst),
.i_en(i_en),
.i_echo(i_echo),
.o_trig(o_trig),
.o_s_mm(o_s_mm),
.o_check_posedge(o_check)
);
//复位和时钟产生
//时钟和复位初始化、复位产生
initial begin
clk <= 0;
rst <= 1;
#1000;
rst <= 0;
end
//时钟产生
always #(`CLK_PERIORD/2) clk = ~clk;
//测试激励产生
initial begin
i_echo = 0;
@(negedge rst); //等待复位完成
@(posedge clk);
repeat(100_000) begin
@(posedge clk);
i_en = 0;
end
i_en = 1;
#4_000_000_000;
i_en = 0;
#300_000_000;
$stop;
end
integer tricnt = 0;
integer dly_time = 0;
integer target_distance;
always @(posedge o_trig)
begin
tricnt = tricnt + 1;
#5000;
i_echo = 1;
if(dly_time==348) dly_time = 348;
else
begin
dly_time = dly_time + 58;
end
//dly_time = 11+{$random}%26000; //11<t<26011
target_distance = function_t2s(dly_time);
$display("Test %0d:\tdistance of testbench = %0dmm",tricnt,target_distance);
#(dly_time*1000);
i_echo = 0;
end
initial begin
#1;
$monitor("o_s_mm = %0dmm",o_s_mm);
end
//函数实现运算s=t*0.173
function real function_t2s;
input real t;
begin
function_t2s = 0.173*t;
end
endfunction
endmodule
3.波形分析
由下图可见,当target_distance增大到60后不变,因为经过滤波器,所以输出信号o_s_mm会比target_distance慢16拍才会到达60,到了60后由于被检测模块检测到4拍的数据相同,所以判断4拍后数据稳定,o_check随即产生一个高脉冲信号输出,见下图黄圈所示,可见之前的vlg_review模块设计符合要求。其余的信号如o_trig也是输出正常的,由此可知我们的vlg_ultrawave模块设计成功!
文章来源:https://www.toymoban.com/news/detail-787810.html
总结
本篇详细讲解了FPGA一键测距仪小项目中超声波测距模块的具体实现,从测距原理、各模块的时序图构思以及代码实现都记录了一遍,实现的仿真效果也都和预期差不多,总的来说还是学到了不少关于FPGA自顶向下模块化的设计的方法。
下一篇:FPGA一键测距仪之数码管篇文章来源地址https://www.toymoban.com/news/detail-787810.html
到了这里,关于FPGA一键测距仪之超声波模块篇的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!