基于FPGA的HC_SR04超声波测距
文章目录
1. 实验目的
学习并掌握HC_SR04模块的使用
2. 实验要求
使用DE2开发板驱动HC_SR04模块,并将所测得数据显示到开发板上的数码管,同时通过UART向上位机传输数据。
3. 实验原理
超声波原理:
HC-SR04超声波测距模块可提供 2cm-400cm的非接触式距离感测功能,测距精度可达高到 3mm;模块包括超声波发射器、接收器与控制电路。图1为HC-SR04外观,其基本工作原理为给予此超声波测距模块一触发信号后模块发射超声波,当超声波投射到物体而反射回来时,模块输出一回响信号,以触发信号和回响信号间的时间差,来判定物体的距离。图2为模块时序图。
以上时序图表明只需要提供一个10uS 以上脉冲触发信号,该模块内部将发出8 个40kHz 周期电平并检测回波。一旦检测到有回波信号则输出回响信号。回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。
公式:uS/58=厘米或者uS/148=英寸;或是:距离=高电平时间*声速(340M/S)/2;建议测量周期为60ms 以上,以防止发射信号对回响信号的影响。
4. 基于FPGA的各模块实现
4.1 hc_sr_trig
module hc_sr_trig(
input wire clk ,
input wire rst_n ,
output wire trig //触发测距信号
);
//trig:超声波触发信号,高电平至少为10us,同时考虑到信号不要重叠,所以当距离为5000mm时的来回时间做为超声波触发信号的周期T
//T=(5/340) * 1000ms = 14.7ms,来回就是29.4ms,所以就让触发脉冲的周期至少为30ms,高电平时间为10us
//为了防止发射信号对回响信号产生影响,这里直接设定为100ms的周期,高电平为10us
parameter CNT_10US_MAX = 9'd500;
parameter CNT_100MS_MAX = 23'd5000_000;
reg [22:0] cnt_100ms ;
wire cnt_100ms_flag ;
reg trig_r;
//cnt_100ms and cnt_100ms_flag
assign cnt_100ms_flag = cnt_100ms==CNT_100MS_MAX - 1'b1;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_100ms<=23'd0;
end
else if(cnt_100ms_flag) begin
cnt_100ms<=23'd0;
end
else begin
cnt_100ms<=cnt_100ms+1'b1;
end
end
//trig
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
trig_r<=1'b0;
end
else if(cnt_100ms<CNT_10US_MAX) begin
trig_r<=1'b1;
end
else begin
trig_r<=1'b0;
end
end
assign trig = trig_r;
endmodule
4.2 hc_sr_echo
module hc_sr_echo(
input wire clk ,
input wire rst_n ,
input wire echo ,
output reg fall_flag_r1, //下降沿标志的打拍信号
output wire [12:0] data_o //检测距离,保留3位小数,*1000实现
);
/*计算:s=340*t/2 m = 340_000 mm * t /2
记脉冲个数:N
s=N*20*340_000/1000_000_000/2 mm = N*0.0034 mm = N*34/10000 ;
5000/0.0034=1470588 21bit ; 34 6bit
*/
reg echo_r1 ;
reg echo_r2 ;
wire raise_flag ;//上升沿标志信号
wire fall_flag ;//下降沿标志信号
reg [20:0] cnt_pulse ;//考虑的范围25~4000mm
reg [20:0] temp_pulse ;
reg [12:0] data_bin ;
//raise_flag
assign raise_flag = ((~echo_r2) && (echo_r1)) ? 1'b1 : 1'b0 ;
assign fall_flag = ((echo_r2) && (~echo_r1)) ? 1'b1 : 1'b0 ;
//beat time
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
echo_r1<=1'b0;
echo_r2<=1'b0;
end
else begin
echo_r1<=echo;
echo_r2<=echo_r1;
end
end
//cnt_pulse
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_pulse<=21'd0;
end
else if(raise_flag) begin
cnt_pulse<=21'd1;//直接从1开始计数,计算的时候就不需要再进行加1操作了
end
else if(echo_r2) begin
cnt_pulse<=cnt_pulse+1'b1;
end
else begin
cnt_pulse<=cnt_pulse;
end
end
//temp_pulse
always @ (posedge clk or negedge rst_n ) begin
if(!rst_n) begin
temp_pulse<=21'd0;
end
else if(fall_flag) begin
temp_pulse<=cnt_pulse;
end
else begin
temp_pulse<=temp_pulse;
end
end
//计算距离,以mm为单位,s=N*34/10000
always @ (posedge clk or negedge rst_n ) begin
if(!rst_n) begin
fall_flag_r1<=1'b0;
end
else begin
fall_flag_r1<=fall_flag;
end
end
//data_bin
always @ (posedge clk or negedge rst_n ) begin
if(!rst_n) begin
data_bin<=1'b0;
end
else if(fall_flag_r1) begin
data_bin<=temp_pulse*34/10000;
end
else begin
data_bin<=data_bin;
end
end
assign data_o = data_bin;
endmodule
4.3 均值滤波
//均值滤波算法
module filter
(
input wire clk ,
input wire rst_n ,
input wire [12:0] data_bin ,
input wire fall_flag_r1 ,
output wire [12:0] data_ave
);
reg [12:0] data_temp [7:0];
reg [15:0] data_sum ;
//data_temp:移位暂存8次测距的数值
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
data_temp[0]<=13'd0;
data_temp[1]<=13'd0;
data_temp[2]<=13'd0;
data_temp[3]<=13'd0;
data_temp[4]<=13'd0;
data_temp[5]<=13'd0;
data_temp[6]<=13'd0;
data_temp[7]<=13'd0;
end
else if(fall_flag_r1) begin
data_temp[0]<=data_bin ;
data_temp[1]<=data_temp[0] ;
data_temp[2]<=data_temp[1] ;
data_temp[3]<=data_temp[2] ;
data_temp[4]<=data_temp[3] ;
data_temp[5]<=data_temp[4] ;
data_temp[6]<=data_temp[5] ;
data_temp[7]<=data_temp[6] ;
end
end
//data_sum:暂存的8个数据之和
always @ (posedge clk or negedge rst_n ) begin
if(!rst_n) begin
data_sum<=1'd0;
end
else begin
data_sum<=data_temp[0]+data_temp[1]+data_temp[2]+data_temp[3]+data_temp[4]+data_temp[5]+data_temp[6]+data_temp[7];
end
end
//data_ave
assign data_ave = data_sum[15:3] ;//截取高13位,相当于右移3位,即除以8的效果。
endmodule
4.4 二进制码转化为8421BCD码
//二进制码转化为8421BCD码
module bin_to_bcd
(
input wire clk ,//The onboard clock frequency is 50MHz.
input wire rst_n ,
input wire [12:0] data_ave ,
output reg [15:0] data_bcd
);
parameter SHIFT_CNT_MAX = 5'd14 ;//输入的二进制数有多少位就进行多少次比较移位操作
reg shift_flag ;
reg [4:0] shift_cnt ;
reg [28:0] data_shift ;
wire complete_flag ;
wire shift_cnt_flag ;
//shift_flag
always @ (posedge clk or negedge rst_n ) begin
if(!rst_n) begin
shift_flag <= 1'b0;
end
else begin
shift_flag <= ~shift_flag;
end
end
//shift_cnt
assign shift_cnt_flag = shift_cnt==SHIFT_CNT_MAX ;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
shift_cnt <= 5'd0;
end
else if(shift_flag) begin
if(shift_cnt_flag) begin
shift_cnt <= 5'd0;
end
else begin
shift_cnt <= shift_cnt+1'b1;
end
end
else begin
shift_cnt <= shift_cnt;
end
end
//data_shift
always @ (posedge clk or negedge rst_n ) begin
if(!rst_n) begin
data_shift <= 29'd0;
end
else if((!shift_cnt) && (shift_flag)) begin
data_shift <= {16'b0,data_ave};
end
else if((shift_cnt>=1'b1) && (shift_cnt <= 5'd13)) begin
if(!shift_flag) begin //shift_flag==0,比较
data_shift[16:13] <= (data_shift[16:13] > 4'd4) ? (data_shift[16:13]+4'd3) : data_shift[16:13];
data_shift[20:17] <= (data_shift[20:17] > 4'd4) ? (data_shift[20:17]+4'd3) : data_shift[20:17];
data_shift[24:21] <= (data_shift[24:21] > 4'd4) ? (data_shift[24:21]+4'd3) : data_shift[24:21];
data_shift[28:25] <= (data_shift[28:25] > 4'd4) ? (data_shift[28:25]+4'd3) : data_shift[28:25];
end
else begin//shift_flag==1,移位
data_shift <= data_shift<<1'b1;
end
end
else begin
data_shift <= data_shift;
end
end
//complete_flag
assign complete_flag = (shift_cnt_flag && shift_flag) ? 1'b1: 1'b0 ;
//data_bcd
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
data_bcd <= 16'd0;
end
else if(complete_flag) begin
data_bcd <= data_shift[28:13];
end
else begin
data_bcd <= data_bcd;
end
end
endmodule
4.5 UART
使用了RS232 UART IP核,具体可以参考Quartus RS232 UART IP核 使用 Verilog_quartus 串口ip核
module FPGA_UART (
input wire clk ,
input wire rst_n ,
input wire uart_rx ,
input wire [16:00] data_o ,
output wire uart_tx
);
parameter state_wait = 4'd0;
parameter state_send = 4'd1;
reg [7:0] uart_send_data = 8'd0;
reg uart_send_valid;
wire uart_send_ready;
reg [31:0] counter = 32'd0;
reg [3:0] state = state_wait;
reg send_flag = 0; // 这个标志位的作用应该是表示移出数据标志位,这里懒得改了
reg end_flag = 0;
wire [15:00] data_buf; // 测试数据
assign data_buf = data_o[15:0];
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
uart_send_valid <= 0;
counter <= 32'd0;
uart_send_data <= 8'd0;
state <= state_wait;
send_flag = 0;
end
else begin
case (state)
state_wait: begin
counter <= counter + 32'd1;
if(counter >= 32'd24999999) begin
counter <= 32'd0;
state <= state_send;
uart_send_data <= data_buf[15:8]; // 提前移出第一个字节
end
end
state_send: begin
if(end_flag) begin
end_flag <= 0;
uart_send_valid <= 0;
send_flag <= 0;
state <= state_wait;
end
else begin
uart_send_valid <= 1;
send_flag <= 1;
if(send_flag) begin // 这里是为了让uart_send_valid维持一个周期后等第一个字节发送完成后移出后面的字节
uart_send_data <= data_buf[7:0]; // 模拟字节移出
end_flag = 1; // 强行退出发送状态,实际运用中会判断数据长度
end
end
end
endcase
end
end
uart u0 (
.clk_clk (clk), // clk.clk
.reset_reset_n (rst_n), // reset.reset_n
// .rs232_0_from_uart_ready (<connected-to-rs232_0_from_uart_ready>), // rs232_0_avalon_data_receive_source.ready
// .rs232_0_from_uart_data (<connected-to-rs232_0_from_uart_data>), // .data
// .rs232_0_from_uart_error (<connected-to-rs232_0_from_uart_error>), // .error
// .rs232_0_from_uart_valid (<connected-to-rs232_0_from_uart_valid>), // .valid
.rs232_0_to_uart_data (uart_send_data), // rs232_0_avalon_data_transmit_sink.data
// .rs232_0_to_uart_error (<connected-to-rs232_0_to_uart_error>), // .error
.rs232_0_to_uart_valid (uart_send_valid), // .valid
.rs232_0_to_uart_ready (uart_send_ready), // .ready
.rs232_0_UART_RXD (uart_rx), // rs232_0_external_interface.RXD
.rs232_0_UART_TXD (uart_tx) // .TXD
);
endmodule
4.6 顶层文件
module hc_sr_top (
input wire clk ,
input wire rst_n ,
input wire echo ,
input wire uart_rx ,
output wire trig ,
output wire uart_tx ,
output wire [6:0] hex0 ,
output wire [6:0] hex1 ,
output wire [6:0] hex2 ,
output wire [6:0] hex3 ,
output wire [6:0] hex4 ,
output wire [6:0] hex5 ,
output wire [6:0] hex6
);
wire fall_flag_r1 ;
wire [12:0] data_o ;//得到的超声波测得的距离值,以mm为单位
wire [12:0] data_ave ;//均值滤波后的数据
wire [15:0] data_bcd ;//显示的四位BCD码数据
hc_sr_trig u_hc_sr_trig(
.clk (clk ),
.rst_n (rst_n ),
.trig (trig )
);
hc_sr_echo u_hc_sr_echo(
.clk (clk ),
.rst_n (rst_n ),
.echo (echo ),
.fall_flag_r1 (fall_flag_r1 ),
.data_o (data_o )
);
filter u_filter
(
.clk (clk ),
.rst_n (rst_n ),
.data_bin (data_o ),
.fall_flag_r1 (fall_flag_r1 ),
.data_ave (data_ave )
);
//二进制码转化为8421BCD码
bin_to_bcd u_bin_to_bcd
(
.clk (clk ),//The onboard clock frequency is 50MHz.
.rst_n (rst_n ),
.data_ave (data_ave ),
.data_bcd (data_bcd )
);
FPGA_UART u_FPGA_UART(
.clk (clk ),
.rst_n (rst_n ),
.uart_rx (uart_rx ),
.data_o (data_o ),
.uart_tx (uart_tx )
);
endmodule
5. 编译烧录
数码管显示:
串口显示:
6. 总结
经测,所测距离与实际距离基本一致,模块功能基本实现。
在测试中间如果将测距模块从一个姿态转换到另一个姿态,则会出现1-2s的错误数据,待稳定后恢复正常,且如果距离过远则数据不准确。
参考文章
https://blog.csdn.net/qq_43546203/article/details/125281386
https://blog.csdn.net/qq_43546203/article/details/125282050?spm=1001.2014.3001.5502
https://blog.csdn.net/qq_39641836/article/details/124306045
https://www.bilibili.com/video/BV1Le4y187d6/?spm_id_from=333.999.0.0&vd_source=9893d5a008b45dacd32a48a05a4a5790