Verilog 超声波测距:从时序控制到距离计算的模块化设计

张开发
2026/4/15 18:54:24 15 分钟阅读

分享文章

Verilog 超声波测距:从时序控制到距离计算的模块化设计
1. 超声波测距原理与Verilog实现思路超声波测距听起来很高科技其实原理特别简单。想象一下你在山谷里大喊一声然后听回声——超声波测距就是这个原理的电子版。模块发射超声波遇到障碍物反射回来我们只要计算声波往返时间就能算出距离。我在做智能小车项目时这个功能帮了大忙。具体到Verilog实现核心就四步触发发射、捕获回波、计算时间、换算距离。这里有个坑要注意声速会受温度影响但一般室内应用可以忽略。我实测过在20℃环境下声速约343m/s换算成us级时间就是每毫米往返需要5.8us。不过实际代码里我们用0.173这个魔术数字这是把声速和单位换算都考虑进去的简化公式。2. 模块化设计架构2.1 时钟分频模块先说说vlg_en这个模块。FPGA的时钟动不动就50MHz但超声波测距需要的是us级精度。我常用的是把50MHz20ns周期分频成1MHz1us周期。代码里这个参数P_CLK_PERIORD就是输入时钟周期单位是ns。这里有个小技巧用parameter定义常量后期修改特别方便。module vlg_en #( parameter P_CLK_PERIORD 20 //50MHz时钟 )( input clk, input rst_n, output reg clk_en ); //分频计数器最大值计算 localparam P_DIVCLK_MAX 1000/P_CLK_PERIORD - 1; reg [7:0] r_divcnt; always (posedge clk or negedge rst_n) begin if(!rst_n) r_divcnt 0; else if(r_divcnt P_DIVCLK_MAX) r_divcnt r_divcnt 1; else r_divcnt 0; end always (posedge clk) begin clk_en (r_divcnt P_DIVCLK_MAX); end endmodule2.2 触发信号生成模块vlg_tirg模块负责产生10us的TRIG脉冲这个脉冲就像扣动扳机让超声波模块开始工作。我建议把周期设为100ms这样测距频率就是10Hz既不会太频繁也不会太慢。调试时发现个有趣现象如果TRIG脉冲太短比如小于10us模块可能不响应太长又会降低刷新率。module vlg_tirg ( input clk, input rst_n, input clk_en, //1MHz时钟使能 output reg trig ); //100ms100_000us localparam P_TRIG_PERIORD_MAX 100_000 - 1; localparam P_TRIG_HIGH_MAX 10; //10us高电平 reg [16:0] tricnt; always (posedge clk or negedge rst_n) begin if(!rst_n) tricnt 0; else if(clk_en) begin tricnt (tricnt P_TRIG_PERIORD_MAX) ? tricnt 1 : 0; end end always (posedge clk) begin trig (tricnt 0) (tricnt P_TRIG_HIGH_MAX); end endmodule3. 回波时间捕获技术3.1 边沿检测技巧vlg_echo模块最核心的技术就是边沿检测。我一般用两级寄存器做同步既防亚稳态又能准确捕捉跳变。这里有个细节echo信号来自外部模块一定要先同步到FPGA时钟域曾经有个项目因为没做同步测距结果时不时抽风。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 ~r_echo[1] r_echo[0]; //上升沿 wire neg_echo r_echo[1] ~r_echo[0]; //下降沿 reg cnt_en; reg [15:0] echo_cnt; always (posedge clk or negedge rst_n) begin if(!rst_n) r_echo 0; else r_echo {r_echo[0], echo}; end3.2 高电平计时实现计时逻辑要注意三点1)只在echo高电平时计数 2)用clk_en控制计数精度 3)下降沿时锁存计数值。实测发现用1MHz时钟时最大测距约5.6米65535us对小车够用了。如果要测更远可以把计数器改成32位。always (posedge clk or negedge rst_n) begin if(!rst_n) cnt_en 0; else if(pos_echo) cnt_en 1; else if(neg_echo) cnt_en 0; end always (posedge clk or negedge rst_n) begin if(!rst_n) echo_cnt 0; else if(!cnt_en) echo_cnt 0; else if(clk_en) echo_cnt echo_cnt 1; end always (posedge clk or negedge rst_n) begin if(!rst_n) t_us 0; else if(neg_echo) t_us echo_cnt; end endmodule4. 距离计算优化方案4.1 定点数运算技巧cal模块的s0.173*t涉及浮点运算但在FPGA里浮点计算太耗资源。我的解决方案是定点数运算把0.173放大4096倍得到709最后结果右移12位。这样既保证精度又节省资源。调试时发现用移位相加代替乘法器可以进一步节省LUT。module cal ( input clk, input rst_n, input [15:0] t_us, output [14:0] s_mm ); //7095121286441 wire [25:0] sum1 t_us 9; //512*t wire [25:0] sum2 t_us 7; //128*t wire [25:0] sum3 t_us 6; //64*t wire [25:0] sum4 t_us 2; //4*t wire [25:0] sum5 t_us; //1*t wire [25:0] sum_total sum1 sum2 sum3 sum4 sum5; assign s_mm sum_total[25:12]; //右移12位 endmodule4.2 乘法器IP核使用当需要更高性能时可以用Xilinx的乘法器IP核。在Vivado里创建IP时选择Multiplier类型配置为16bit x 10bit无符号乘法。记得把Pipeline Stages设为2这样时序更稳定。我在Artix-7上实测用IP核比纯LUT实现节省30%资源。5. 系统集成与调试5.1 顶层模块设计vlg_top就像乐高底座把所有模块插在一起。接口设计有个原则控制信号用寄存器输出状态信号用wire输入。我习惯把参数集中定义在顶层这样修改起来一目了然。特别注意跨时钟域信号要加同步器特别是echo信号。module vlg_top( input clk, input rst_n, input echo, output trig, output [14:0] distance_mm ); parameter P_CLK_PERIORD 20; //50MHz wire clk_en; wire [15:0] t_us; vlg_en #(.P_CLK_PERIORD(P_CLK_PERIORD)) u_en( .clk(clk), .rst_n(rst_n), .clk_en(clk_en)); vlg_tirg u_trig( .clk(clk), .rst_n(rst_n), .clk_en(clk_en), .trig(trig)); vlg_echo u_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(distance_mm)); endmodule5.2 测试平台搭建好的TB文件能事半功倍。我用$random生成随机延时模拟不同距离。注意给echo信号加500ns延迟模拟硬件响应时间。波形查看重点trig脉冲宽度、echo响应延迟、计数器清零时机。module tb_top(); reg clk; reg rst_n; reg echo; wire trig; wire [14:0] s_mm; vlg_top uut(.*); initial begin clk 1; forever #10 clk ~clk; end initial begin rst_n 0; echo 0; #200 rst_n 1; repeat(5) begin (posedge trig); #5000 echo 1; #($urandom_range(26000, 11)*1000); echo 0; end $finish; end endmodule6. 常见问题与解决方案6.1 信号抖动处理实际项目中echo信号常有毛刺。我的应对方案1)硬件上加RC滤波 2)Verilog里用计数器去抖。比如连续3个周期高电平才认为有效。在低速应用如10Hz刷新时这个方案特别有效。6.2 温度补偿方案虽然我们忽略了温度影响但对精度要求高的场合可以加DS18B20温度传感器。实测温度每升高1℃声速增加0.6m/s。可以在cal模块里动态调整乘法系数公式变为s (331.4 0.6*T)*t/2其中T是摄氏温度。6.3 多模块协同工作当系统中有多个超声波模块时要分时复用触发信号。我的方案是用状态机轮询每个模块间隔100ms触发。关键是要确保前一个模块的echo信号结束再触发下一个否则会互相干扰。

更多文章