Verilog 实现超声波测距
 教学视频: https://www.bilibili.com/video/BV1Ve411x75W?p=33&spm_id_from=pageDriver&vd_source=19ae31dff4056e52d2729a4ca212602b
超声波测距原理
参考资料:STM32的超声波测距程序_超声波测距stm32程序_VaderZhang的博客-CSDN博客
推荐一波自己的文章:STM32蓝牙控制循迹避障小车源代码——3.舵机、超声波测距模块_stem32超声波舵机代码_灵风_Brend的博客-CSDN博客
- 超声波模块工作原理:
 输出TRIG触发测距,需要给最少10us的高电平信呈;
 模块自动发送8个40KHZ的方波,自动检测是否有信号返回;
 有信号返回,通过IO口ECHO输出高电平,高电平持续时间就是超声波从发射到返回的时间;
 测试距离=高电平持续时间*声速/2
需求分析与功能定义:
- 每隔100ms时间,定时产生10us时间的TRIG高脉冲给到超声波测距模块,用于触发超声波测距模块工作
- 采集回响信号ECHO的高脉冲保持时间
- 将ECHO高脉冲保持时间换算成距离信息:s = 0.173*t
- 人机交互
代码思路:(详细教学可以看最上面的链接)
代码组成:

vlg_en :输出clk_en信号,对输入时钟clk做分频计数,产生1us的时钟使能信号(计数单位为us)
module vlg_en #(
	parameter P_CLK_PERIORD = 20	//clk的时钟周期为20ms
)
( 
	input 		clk,
	input 		rst_n,
	
	output reg 	clk_en
);
localparam P_DIVCLK_MAX = 1000/P_CLK_PERIORD - 1;	//分频计数器的最大值
reg [7:0] r_divcnt;
///
//对输入时钟clk做分频计数,产生1us的时钟使能信号
always @(posedge clk or negedge rst_n) begin
	if(!rst_n)
		r_divcnt <= 8'b0;
	else if(r_divcnt < P_DIVCLK_MAX)
		r_divcnt <= r_divcnt + 1'b1;
	else
		r_divcnt <= 8'b0;
end
///
//产生时钟使能信号
always @(posedge clk) begin
	if(r_divcnt == P_DIVCLK_MAX)
		clk_en <= 1'b1;
	else
		clk_en <= 1'b0;
end
endmodule
vlg_tirg :每隔100ms时间,定时产生10us时间的TRIG高脉冲给到超声波测距模块,用于触发超声波测距模块工作
module vlg_tirg
( 
	input 		clk,
	input 		rst_n,
	input 		clk_en,
	
	output reg 	trig
);
localparam P_TRIG_PERIORD_MAX = 100_000 - 1;	//100ms计数最大值
localparam P_TRIG_HIGH_MAX = 10;			//10us高脉冲保持时间
reg [16:0] tricnt;
///
//100ms周期计数
always @(posedge clk or negedge rst_n) begin
	if(!rst_n)
		tricnt <= 'b0;
	else if(clk_en)begin
		if(tricnt < P_TRIG_PERIORD_MAX)
			tricnt <= tricnt + 1'b1;
		else
			tricnt <= 'b0;
	end		
end
///
//产生保持10us的trig信号
always @(posedge clk) begin
	if((tricnt > 'b0)&&(tricnt <= P_TRIG_HIGH_MAX))
		trig <= 1'b1;
	else
		trig <= 1'b0;
end
endmodule
上面两个信号的波形展示:

vlg_echo : 采集回响信号ECHO的高脉冲保持时间。(echo信号的高电平保持时间即为超声波往返的时间)
module vlg_echo
( 
	input 		clk,
	input 		rst_n,
	input 		clk_en,
	input 		echo,
	output reg [15:0] t_us
);
reg [1:0] r_echo;
wire pos_echo,neg_echo;
reg cnt_en;
reg [15:0] echo_cnt;
///
//对echo信号锁存两拍,获取边沿检测信号,产生计数使能信号cnt_en
always @(posedge clk or negedge rst_n) begin
	if(!rst_n)
		r_echo <= 'b0;
	else
		r_echo <= {r_echo[0],echo};	//高位锁存,低位移位	
end
assign pos_echo = ~r_echo[1] & r_echo[0];
assign neg_echo = r_echo[1] & ~r_echo[0];
always @(posedge clk or negedge rst_n) begin
	if(!rst_n)
		cnt_en <= 'b0;
	else if(pos_echo)
		cnt_en <= 1'b1;
	else if(neg_echo)
		cnt_en <= 1'b0;
	else ;
end
///
//对echo信号的高电平保持时间进行1us为单位的计数
always @(posedge clk or negedge rst_n) begin
	if(!rst_n)
		echo_cnt <= 'b0;
	else if(!cnt_en)
		echo_cnt <= 'b0;
	else if(clk_en)
		echo_cnt <= echo_cnt + 1'b1;
	else ;
end	
///
//对echo_cnt计数最大值做锁存
always @(posedge clk or negedge rst_n) begin
	if(!rst_n)
		t_us <= 'b0;
	else if(neg_echo)
		t_us <= echo_cnt;
end
endmodule
cal :将时间计算为距离。测试距离=高电平持续时间 * 声速/2。 (s = 0.173*t)
module cal
( 
	input 		clk,
	input 		rst_n,
	input [15:0] t_us,
	output [14:0] s_mm
);
/*	s=0.173*t
	s*4096=0.173*t*4096=709*t	 避免小数部分
	s=709*t/4096=709*t>>12
	
	709实现方法:  1)乘法器
				2)709=512+128+64+4+1
	本代码使用乘法器来实现。直接调用乘法器IP核
*/
wire [25:0] mult_result;
mult_gen_0 u_mult_gen_0 (
  .CLK(clk),  // input wire CLK
  .A(10'd709),      // input wire [9 : 0] A
  .B(t_us),      // input wire [15 : 0] B
  .P(mult_result)      // output wire [25 : 0] P
);
assign s_mm = mult_result[25:12];
endmodule
顶层文件 vlg_top
module vlg_top(
	input 	clk,
	input 	rst_n,
	
	output 	trig
);
localparam P_CLK_PERIORD = 20;
//接口声明
reg clk;
reg rst_n;
reg echo;
wire clk_en;
wire trig;
wire [15:0] t_us;
wire [14:0] s_mm;
//使能时钟产生模块
vlg_en #(
	.P_CLK_PERIORD (P_CLK_PERIORD)	//clk的时钟周期为20ns
)
u_vlg_en( 
	.clk 		(clk),
	.rst_n 		(rst_n),
	
	.clk_en 	(clk_en)
);
//产生触发信号trig
vlg_tirg u_vlg_tirg(
	.clk	(clk),
    .rst_n	(rst_n),
    .clk_en	(clk_en),
    .trig   (trig)
);
//测距模块的回响信号echo的高电平采集时间
vlg_echo u_vlg_echo(
	.clk	(clk),
	.rst_n	(rst_n),
	.clk_en	(clk_en),
	.echo	(echo),
	.t_us   (t_us)
);
//乘法器,计算距离
cal u_cal(
	.clk	(clk),
	.rst_n	(rst_n),
	.t_us	(t_us),
	.s_mm   (s_mm)
);
endmodule
调用乘法器IP核
点击IP核,输入MUL,进行下面的操作:


TB文件
`timescale 1ns/1ps
module tb_top();
reg clk;
reg rst_n;
reg echo;
wire [14:0] s_mm;
vlg_top u_vlg_top(
	.clk	(clk),
	.rst_n	(rst_n),
	.trig   (trig)
);
//产生时钟
initial clk = 1;
always #10 clk = ~clk;
//测试激励产生
initial begin
	rst_n = 0;
	echo = 0;
	#200;
	rst_n = 1;
	
end
//函数实现 s=0.173*t
function real function_t2s;
	input real t;
	begin
		function_t2s = 0.173*t;
	end
endfunction
integer tricnt = 0;
integer dly_time;
always @(posedge trig)begin
	tricnt = tricnt + 1;
	#5000;
	echo = 1;
	dly_time = 11+{$random}%26011;		//11<t<26011
	#500;
	$display("test %0d:\n dly_time=%0d us\n s=%0d mm\n",tricnt,dly_time,s_mm,function_t2s(dly_time));
	#(dly_time*1000);
	echo = 0;
end
endmodule
仿真结果
- 仿真波形

- 结果



















