1、产品特点:
HC-SR04超声波测距模块可提供2cm-400cm 的非接触式距离感测功能,测距精度可达高到3mm;模块包括超声波发射器、接收器与控制电路。
基本工作原理:
(1)采用 IO口TRIG 触发测距,给最少10μs 的高电平信呈。
(2)模块自动发送8个 40khz 的方波,自动检测是否有信号返回;
(3)有信号返回,通过 IO 口 ECHO 输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。测试距离=(高电平时间*声速(340M/S))/2;
2、时序图:
时序图说明&思路:
以上时序图表明你只需要提供一个 10uS 以上脉冲触发信号,该模块内部将发出8个 40kHz 周期电平并检测回波。一旦检测到有回波信号则输出回响信号。回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。公式:uS/58=厘米或者 uS/148=英寸;或是:距离=高电平时间*声速(340M/S)/2;
建议测量周期为 60ms 以上,以防止发射信号对回响信号的影响。
3、系统架构设计:
4、代码编写:
将50MHz分频为1MHz
module ns2us (
input wire clk,
input wire rst_n,
output wire clk_us
);
/*
FPGA开发板的频率为50MHz(周期为20ns),将其分频为1MHz(周期为1μs=1000ns)
*/
parameter nsclk_2_usclk = 6'd49; // 50(0~49) * 20ns = 1000ns = 1us
reg [5:0] cnt_clks;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 0)
begin
cnt_clks <= 6'd0;
end
else if (cnt_clks == nsclk_2_usclk)
begin
cnt_clks <= 6'd0;
end
else
begin
cnt_clks <= cnt_clks + 1'd1;
end
end
assign clk_us = (cnt_clks == nsclk_2_usclk);
endmodule
测量周期为200ms,计时200ms
module start_measure (
input wire clk_us,
input wire rst_n,
output wire trig
);
// 前10us为高电平,建议测量周期大于60ms,这里就改为200ms
parameter MS_200 = 18'd199_999; // 200_000 - 1
reg [17:0] cnt_us;
always @(posedge clk_us or negedge rst_n)
begin
if (rst_n == 0)
begin
cnt_us <= 18'd0;
end
else if (cnt_us == MS_200)
begin
cnt_us <= 18'd0;
end
else
begin
cnt_us <= cnt_us + 18'd1;
end
end
assign trig = cnt_us < 18'd10 ? 1'd1 : 1'd0; // 前10个周期为高电平
endmodule
计算距离
module get_distance (
input wire clk,
input wire clk_us,
input wire rst_n,
input wire echo,
output wire [8:0] distance
);
parameter MAX_DISTANCE = 400; // 最大测量距离:400cm
parameter DISTANCE_2_CLK = 15'd23_500 - 15'd1; // 将400cm从发射到返回要多少个μs时钟(大约是23500个)
// echo是测量距离的重要变量,其高电平的保持时间代表超声波从发射到返回的时间间隔
reg echo_r0;
reg echo_r1;
wire echo_pose; // echo的上升沿
wire echo_nege; // echo的下降沿
reg [14:0] cnt_us; // 计数高电平持续了多少个us时钟周期
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
echo_r0 <= 1'b0;
echo_r1 <= 1'b0;
end
else
begin
echo_r0 <= echo;
echo_r1 <= echo_r0;
end
end
assign echo_pose = echo_r0 & ~echo_r1;
assign echo_nege = ~echo_r0 & echo_r1;
// 根据flag来判断高低电平
reg flag;
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0) begin flag <= 1'b0; end
else if (echo_pose) begin flag <= 1'b1; end
else if (echo_nege) begin flag <= 1'b0; end
else begin flag <= flag; end
end
// 高电平持续了多少个时钟周期
always @(posedge clk_us or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
cnt_us <= 15'd0;
end
else if (flag)
begin
if (cnt_us == DISTANCE_2_CLK)
begin
cnt_us <= cnt_us;
end
else
begin
cnt_us <= cnt_us + 15'd1;
end
if (cnt_us >= DISTANCE_2_CLK)
begin
cnt_us <= DISTANCE_2_CLK;
end
end
else
begin
cnt_us <= 15'd0;
end
end
// 根据高电平持续时间来计算距离
// 距离 = 0.034 cm/μs * 时间(μs) / 2
// 34 * 时间 / 2 / 1000
reg [18:0] temp_distance;
always @(posedge clk_us or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
temp_distance <= 19'd0;
end
else if (echo_nege) // 当下降沿的时候,也就是从高电平结束了,计算距离
begin
temp_distance <= cnt_us * 34;
end
else
begin
temp_distance <= 19'd0;
end
end
assign distance = temp_distance / 1000;
endmodule
数码管模块
module digit_tube (
input wire clk ,
input wire rst_n ,
input wire [3:0] hundred_place , // 中间的是相当于是数字信息,
input wire [3:0] ten_place ,
input wire [3:0] one_place ,
output wire [5:0] seg_sel ,
output wire [7:0] seg_ment
);
parameter MS_1 = 49_999; // 1ms有50_000个周期
parameter ZERO = 8'b1100_0000,
ONE = 8'b1111_1001,
TWO = 8'b1010_0100,
THREE = 8'b1011_0000,
FOUR = 8'b1001_1001,
FIVE = 8'b1001_0010,
SIX = 8'b1000_0010,
SEVEN = 8'b1111_1000,
EIGHT = 8'b1000_0000,
NINE = 8'b1001_0000,
DEFAULT = 8'b1111_1110;
reg [15:0] cnt_1ms; // 计数10ms,相当于是刷新帧率了
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
cnt_1ms <= 16'd0;
end
else if (cnt_1ms == MS_1)
begin
cnt_1ms <= 16'd0;
end
else
begin
cnt_1ms <= cnt_1ms + 1'd1;
end
end
/*
数码管控制
*/
// 控制哪块数码管亮
reg [5:0] seg_sel_temp; // 位选信号
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0)
begin
seg_sel_temp <= 6'b111_110;
end
else if (cnt_1ms == MS_1)
begin
seg_sel_temp <= {seg_sel_temp[4:0], seg_sel_temp[5]};
end
else
begin
seg_sel_temp <= seg_sel_temp;
end
end
assign seg_sel = seg_sel_temp;
// 控制数码管的显示内容
reg [7:0] seg_ment_temp; // 段选信号
reg [4:0] number; // 显示哪个数字
always @(posedge clk or negedge rst_n)
begin
if (rst_n == 1'd0)
begin
seg_ment_temp <= 8'b1111_1110;
end
else
begin
case (number)
5'd0: seg_ment_temp <= ZERO ;
5'd1: seg_ment_temp <= ONE ;
5'd2: seg_ment_temp <= TWO ;
5'd3: seg_ment_temp <= THREE ;
5'd4: seg_ment_temp <= FOUR ;
5'd5: seg_ment_temp <= FIVE ;
5'd6: seg_ment_temp <= SIX ;
5'd7: seg_ment_temp <= SEVEN ;
5'd8: seg_ment_temp <= EIGHT ;
5'd9: seg_ment_temp <= NINE ;
default: seg_ment_temp <= DEFAULT ;
endcase
end
end
assign seg_ment = seg_ment_temp;
// 控制要显示什么内容
always @(*)
begin
if (seg_sel_temp == 6'b011_111) // 第一个数码管亮
begin
number = ZERO;
end
else if (seg_sel_temp == 6'b101_111)
begin
number = ZERO;
end
else if (seg_sel_temp == 6'b110_111)
begin
number = ZERO;
end
else if (seg_sel_temp == 6'b111_011)
begin
number = hundred_place;
end
else if (seg_sel_temp == 6'b111_101)
begin
number = ten_place;
end
else if (seg_sel_temp == 6'b111_110)
begin
number = one_place;
end
else
begin
number = number;
end
end
endmodule
顶层模块编写
module top (
input wire clk,
input wire rst_n,
input wire echo
// output wire [5:0] seg_sel,
// output wire [8:0] seg_ment
);
wire clk_us_temp;
ns2us inst_ns2us (
.clk (clk),
.rst_n (rst_n),
.clk_us(clk_us_temp)
);
wire trig_temp;
start_measure inst_start_measure(
.clk_us (clk_us_temp),
.rst_n (rst_n),
.trig (trig_temp)
);
wire [8:0] distance_temp;
get_distance get_distance_inst(
.clk (clk),
.clk_us (clk_us_temp),
.rst_n (rst_n),
.echo (echo),
.distance (distance_temp)
);
endmodule
last、总结:
本次的代码有些地方需要根据超声波模块的说明书来编写,比如说50MHz的分频,还有测量周期等,这些都是根据模块的说明书来确定的。总的来说,本次的代码并不是特别难,只需要根据看懂说明书,将里面的功能都实现就可以了。