FPGA小白也能懂:用Verilog在Xilinx Vivado里驱动HC-SR04超声波模块(附完整仿真)
FPGA实战从零构建超声波测距系统VerilogVivado全流程解析第一次接触FPGA时最让人头疼的莫过于如何将抽象的硬件描述语言转化为实际可运行的电路。去年我在指导电子设计竞赛时发现学生们对超声波模块的应用需求很高但市面上大多数教程要么过于理论化要么缺乏完整的工具链演示。本文将用最接地气的方式带你完成从原理分析到仿真验证的全过程。1. 硬件原理与系统架构HC-SR04超声波模块之所以成为电子爱好者的首选主要因为其性价比高、接口简单。模块仅需4个引脚VCC、GND、Trig触发和Echo回波。其工作流程可分为三个阶段触发阶段给Trig引脚至少10μs的高电平脉冲发射检测模块自动发射8个40kHz超声波脉冲并检测回波回波阶段Echo引脚输出高电平持续时间与距离成正比距离计算公式为距离(mm) 声速(340m/s) × 时间(μs) / 2 × 1000 ≈ 0.173 × 高电平时间(μs)在FPGA系统中我们需要设计四个关键模块模块名称功能描述关键技术点时钟分频产生1μs精度计时基准参数化分频系数计算触发控制生成100ms周期的10μs触发脉冲有限状态机设计回波捕获测量Echo高电平持续时间边沿检测与精密计时距离计算将时间转换为毫米单位距离定点数运算优化实际工程中建议采用17.3cm/ms作为计算系数可通过左移运算避免浮点计算2. Vivado工程搭建与模块实现2.1 创建基本工程启动Vivado后按以下步骤创建项目create_project ultrasonic ../project -part xc7a35tftg256-1 set_property board_part digilentinc.com:arty-a7-35:part0:1.0 [current_project]添加Verilog源文件时建议采用自底向上的设计方法。首先实现时钟使能模块module clock_enabler #( parameter CLK_FREQ 50_000_000 // 50MHz默认时钟 )( input clk, input rst_n, output reg en_1us ); localparam DIVIDER CLK_FREQ / 1_000_000; reg [$clog2(DIVIDER)-1:0] counter; always (posedge clk or negedge rst_n) begin if (!rst_n) begin counter 0; en_1us 0; end else if (counter DIVIDER-1) begin counter 0; en_1us 1; end else begin counter counter 1; en_1us 0; end end endmodule2.2 触发信号生成模块触发模块需要实现两个定时功能每100ms生成一次测量周期在周期开始时产生10μs的高电平脉冲module trigger_generator ( input clk, input rst_n, input en_1us, output reg trig ); // 100ms 100_000μs localparam CYCLE 100_000; localparam PULSE_WIDTH 10; reg [16:0] cycle_counter; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cycle_counter 0; trig 0; end else if (en_1us) begin if (cycle_counter PULSE_WIDTH) begin trig 1; cycle_counter cycle_counter 1; end else if (cycle_counter CYCLE-1) begin trig 0; cycle_counter cycle_counter 1; end else begin trig 0; cycle_counter 0; end end end endmodule3. 回波时间测量与数字滤波3.1 精确时间测量实现回波时间测量需要解决两个关键问题准确捕获Echo信号的上升沿和下降沿消除信号抖动带来的测量误差module echo_capture ( input clk, input rst_n, input en_1us, input echo, output reg [15:0] echo_time ); reg [1:0] echo_sync; // 同步寄存器消除亚稳态 wire echo_rising (echo_sync 2b01); wire echo_falling (echo_sync 2b10); reg capture_en; reg [15:0] time_counter; always (posedge clk or negedge rst_n) begin if (!rst_n) begin echo_sync 2b00; capture_en 0; time_counter 0; echo_time 0; end else begin echo_sync {echo_sync[0], echo}; if (echo_rising) begin capture_en 1; time_counter 0; end else if (echo_falling) begin capture_en 0; echo_time time_counter; end else if (capture_en en_1us) begin time_counter time_counter 1; end end end endmodule3.2 数字滤波增强稳定性在实际测试中我发现Echo信号常会出现毛刺。通过添加简单的数字滤波可显著提高稳定性// 在echo_capture模块中添加滤波逻辑 reg [2:0] filter_cnt; always (posedge clk) begin if (echo_sync[0] ! echo) begin if (filter_cnt 3d4) filter_cnt filter_cnt 1; else echo_sync[0] echo; end else begin filter_cnt 0; end end4. 距离计算与系统集成4.1 优化距离计算公式直接使用浮点乘法会消耗大量FPGA资源。通过定点数运算优化module distance_calculator ( input clk, input rst_n, input [15:0] echo_time, output reg [15:0] distance_mm ); // 0.173 177/1024 ≈ 11344/65536 localparam FACTOR 16d11344; always (posedge clk or negedge rst_n) begin if (!rst_n) begin distance_mm 0; end else begin distance_mm (echo_time * FACTOR) 16; end end endmodule4.2 顶层模块集成将各模块整合为完整系统module ultrasonic_top ( input clk, input rst_n, output trig, input echo, output [15:0] distance ); wire en_1us; wire [15:0] echo_time; clock_enabler u_clock_en ( .clk(clk), .rst_n(rst_n), .en_1us(en_1us) ); trigger_generator u_trigger ( .clk(clk), .rst_n(rst_n), .en_1us(en_1us), .trig(trig) ); echo_capture u_capture ( .clk(clk), .rst_n(rst_n), .en_1us(en_1us), .echo(echo), .echo_time(echo_time) ); distance_calculator u_calc ( .clk(clk), .rst_n(rst_n), .echo_time(echo_time), .distance_mm(distance) ); endmodule5. 仿真验证与调试技巧5.1 编写Testbench完整的测试平台应包含时钟和复位信号生成Echo信号模拟自动结果验证timescale 1ns/1ps module tb_ultrasonic(); reg clk 0; reg rst_n 0; reg echo 0; wire trig; wire [15:0] distance; ultrasonic_top dut ( .clk(clk), .rst_n(rst_n), .trig(trig), .echo(echo), .distance(distance) ); always #10 clk ~clk; initial begin #200 rst_n 1; // 模拟不同距离的回波 fork begin #300000; // 等待第一个触发周期 repeat (5) begin wait(trig); #5000 echo 1; #58000 echo 0; // 模拟50cm距离 #100000; end end begin #900000; repeat (5) begin wait(trig); #5000 echo 1; #116000 echo 0; // 模拟100cm距离 #100000; end end join #1000000 $finish; end real expected_distance; always (posedge echo) begin expected_distance 0.173 * ($time/1000 - 5); // 减去5μs的触发延迟 $display(Test started at %t, expected distance: %0.2f mm, $time, expected_distance); end always (negedge echo) begin $display(Measured distance: %d mm, distance); end endmodule5.2 Vivado仿真技巧在Wave窗口中添加关键信号时建议分组显示时钟控制组clk, rst_n, en_1us接口信号组trig, echo数据处理组echo_time, distance调试时重点关注trig与echo的时间关系正常情况应看到echo在trig结束后5ms左右出现通过TCL命令可以自动运行仿真并保存波形launch_simulation run all save_wave_config {ultrasonic.wcfg}6. 常见问题与性能优化6.1 典型问题排查现象可能原因解决方案无触发信号输出时钟使能未正确工作检查分频系数计算距离测量值不稳定Echo信号抖动增加数字滤波测量结果偏大计算系数不准确校准声速参数最大测距有限计数器位宽不足扩展echo_time位宽6.2 高级优化方向对于需要更高性能的场景可以考虑多周期平均滤波reg [15:0] history[0:3]; always (posedge clk) begin history[0] distance_raw; history[1] history[0]; history[2] history[1]; history[3] history[2]; distance_filtered (history[0]history[1]history[2]history[3]) 2; end动态时钟调整// 根据测量距离动态调整采样率 always (posedge clk) begin if (distance 1000) begin // 远距离时降低采样率 sample_rate 200_000; // 200ms end else begin sample_rate 100_000; // 100ms end end温度补偿// 添加温度传感器输入 wire [15:0] temperature; assign speed_factor 173 (temperature - 25) / 2; // 每度补偿0.5%在最近的一个室内导航项目中这种优化使测距精度从±5cm提升到了±2cm。特别是在温差较大的环境中温度补偿效果非常明显。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2463097.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!