基于FPGA的超声波测距

一 实验原理

HC-SR04 超声波测距模块可提供 2cm-400cm 的非接触式距离感测功能,测距精度可达高到的非接触式距离感测功能,测距精度可达高到 3mm ;模块包括超声波发射器、接收器与控制电路。

HC-SR04超声波测距模块共有四个引脚(见下图):
HC-SR04_front
HC-SR04_back

  • Vcc:电源供电
  • Trig:输入触发信号(可以触发测距)
  • Echo:传出信号回响(可以传回时间差)
  • GND:接地

工作流程大概就是:
(1) IO 口TRIG 触发测距,给最少 10us 的高电平;
(2)模块自动发送 8 个 40khz 的方波,自动检测是否有信号返回;
(3)有信号返回,通过 IO 口 ECHO 输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。测试距离=(高电平时间*声速(340M/S))/2;
在这里插入图片描述

二 设计思路

2.1 思路

首先FPGA需要给超声波提供一个10us以上的触发信号(需要实现的),告诉超声波开始测距,超声波通过trigger口接收到信号后,就自动循环发出8个40KHz(200us)的周期电平(超声波模块实现的,不需要verilog实现)然后检测回波,如果超声波模块检测到了回波,就会通过echo输出口,输出一个回响信号,该回响会持续信号发射到接收时间长度的高电平。所以需要一个计时器来记录这段高电平持续(FPGA需要实现的,接收到echo上升沿后就开启计时,下降沿就停止)的时间长短。得到这个时间后就可以通过计算公式计算出大致的距离。

  • 10us以上的计数器发送高电平
  • echo计数器计时上升沿(触发)到下降沿的时间
  • echo计数器的最大值是多少(可以根据超声波测试的距离设定,由上可知,超声波最远4m,计算可得持续时间最大值应该是8/340*10^3 = 24ms左右,可设定为30ms)
  • 时间基数都是以us为单位,fpga的基频是50MHz(20ns),分频到1MHz(1us)。

三 hc_sr代码实现

3.1 时钟分频

module clk_div(
    input       wire        clk,
    input       wire        rst_n,
    output      reg         clk_us
);
    parameter   CNT_MAX = 5'd25; //1us有50个时钟周期,一半一半

    reg     [4:0]       cnt;
    wire                add_cnt;
    wire                end_cnt;

    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt <= 0;
            clk_us <= 1'b0;
        end 
        else if(add_cnt)begin 
            if(end_cnt)begin
                clk_us <= ~clk_us;//取反
                cnt <= 0;
            end
            else begin 
                cnt <= cnt + 1'b1;
            end
        end 
        else begin 
            cnt <= cnt;
        end 
    end

    assign add_cnt = 1'b1;
    assign end_cnt = add_cnt && cnt == CNT_MAX - 1;
    
endmodule

3.2 hc_sr发送trig

//发送15us的触发信号给hc_sr
module hc_sr_trig (
    input       wire        clk_us,
    input       wire        rst_n,
    output      wire        trig_out
);

//接收一次信号最长间隔时间是:
//发送8个40KHz是200us。
//最长距离是400cm,来回时长为24ms左右
//设定间隔为30ms发送一次trig
//则需要时钟1MHz个数30_000

parameter       MAX_CNT = 15'd30_000;

//定义一个计数器
reg         [14:0]          cnt;
wire                        add_cnt;
wire                        end_cnt;

always @(posedge clk_us or negedge rst_n)begin 
    if(!rst_n)begin
         cnt <= 0;
    end 
    else if(add_cnt)begin 
        if(end_cnt)begin
            cnt <= 0;
        end
        else begin
            cnt <= cnt + 1'b1;
        end
    end 
    else begin 
        cnt <= cnt;
    end 
end

assign add_cnt = 1'b1;
assign end_cnt = add_cnt && cnt == MAX_CNT - 1;

assign trig_out = cnt <15 ? 1'b1 : 1'b0;//发送15us的高电平

endmodule

3.3 hc_sr接收echo,输出距离

//接收hc_sr的echo信号,并解析数据
module hc_sr_echo (
    input       wire        clk     ,
    input       wire        clk_us  ,
    input       wire        rst_n   ,
    input       wire        echo    ,
    output      reg [24:0]  data_out  //距离单位为cm,乘以1000的值
);
    //echo最多测量400cm
    parameter       MAX_ECHO = 15'd25_000;

    //检测下降沿
    reg         r1_echo, r2_echo;
    wire        echo_pos, echo_neg;

    reg     [14:0]      cnt    ;
    wire                add_cnt;
    wire                end_cnt;

    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
             r1_echo <= 0;
             r2_echo <= 0;
        end 
        //打拍,延时一个时钟周期
        else begin 
            r1_echo <= echo;
            r2_echo <= r1_echo;
        end
    end

    //如果r2_echo为1,并且r1_echo为0,则此时为下降沿
    assign echo_neg = r2_echo & ~r1_echo;
    //如果r1_echo为1,并且r2_echo为0,则此时为上升沿
    assign echo_pos = ~r2_echo & r1_echo;

    //当echo为1时开始计数
    always @(posedge clk_us or negedge rst_n)begin 
        if(!rst_n)begin
            cnt <= 0;
        end 
        else if(add_cnt)begin 
            if(end_cnt)begin
                cnt <= cnt;//如果没有检测到障碍物,cnt就保持
            end
            else begin
                cnt <= cnt + 1'b1;
            end
        end 
        else begin 
            cnt <= 0;
        end 
    end

    assign add_cnt = echo;
    assign end_cnt = add_cnt && cnt == MAX_ECHO - 1;

    //当检测到下降沿的时候,就开始计算距离
    //cnt最高计数为25_000us,换算成距离,t=cnt * 10^(-3)*34/2(cm)
    //最大数值为t = cnt*17/1000
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            data_out <= 0;
        end 
        else if(echo_neg)begin 
            data_out <= (cnt << 4) + cnt;//cnt*17
        end 
        else begin 
            data_out <= data_out;
        end
    end
endmodule

3.4 hc_sr的顶层模块

module hc_sr_driver (
    input       wire        clk,
    input       wire        rst_n,
    input       wire        echo,

    output      wire        trig_out,
    output      wire [24:0]  data_out//实际距离还需要除以1000
);

    wire                clk_us;

    clk_div clk_div_inst (
        .clk        (clk)       ,
        .rst_n      (rst_n)     ,
        .clk_us     (clk_us)
    );

    hc_sr_trig hc_sr_trig_inst (
        .clk_us     (clk_us)    ,
        .rst_n      (rst_n)     ,
        .trig_out   (trig_out)
    );

    hc_sr_echo hc_sr_echo_inst (
        .clk        (clk)       ,
        .clk_us     (clk_us)    ,
        .rst_n      (rst_n)     ,
        .echo       (echo)      ,
        .data_out   (data_out)//距离单位cm乘以1000的值
    );

endmodule

四 数码管显示代码实现

DE115的数码管和之前使用的板子不同,数码管不再是片选和段选选中,DE115上的是分开的,所以需要考虑8个单独的数码管。代码如下:

module sel_driver(
    input   wire        Clk     ,
    input   wire        Rst_n   ,
    input   wire [24:0] data_o  ,

    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    ,
    output  wire [6:0]  hex7    ,
    output  wire [6:0]  hex8     
);

parameter   NOTION  = 4'd10,
            FUSHU   = 4'd11;
parameter   MAX20us = 10'd1000;
reg [9:0]   cnt_20us;
reg [7:0]   sel_r;
reg [3:0]   number;
reg [6:0]   seg_r;
reg [6:0]   hex1_r;
reg [6:0]   hex2_r;
reg [6:0]   hex3_r;
reg [6:0]   hex4_r;
reg [6:0]   hex5_r;
reg [6:0]   hex6_r;
reg [6:0]   hex7_r;
reg [6:0]   hex8_r;



//20微妙计数器
always @(posedge Clk or negedge Rst_n) begin
    if (!Rst_n) begin
        cnt_20us <= 10'd0;
    end
    else if (cnt_20us == MAX20us - 1'd1) begin
        cnt_20us <= 10'd0;
    end
    else begin
        cnt_20us <= cnt_20us + 1'd1;
    end
end



//单个信号sel_r位拼接约束
always @(posedge Clk or negedge Rst_n) begin
    if (!Rst_n) begin
        sel_r <= 8'b11_11_11_10;
    end
    else if (cnt_20us == MAX20us - 1'd1) begin
        sel_r <= {sel_r[6:0],sel_r[7]};
    end
    else begin
        sel_r <= sel_r;
    end
end

/*拿到数字*/
always @(*) begin
    case (sel_r)
        8'b11_11_11_10:     number  = data_o/100_000                                ;//百位
        8'b11_11_11_01:     number  = (data_o%100_000)/10_000                       ;//十位
        8'b11_11_10_11:     number  = (data_o%10_000)/1_000                         ;//个位cm
        8'b11_11_01_11:     number  = FUSHU                                         ;//表示小数点
        8'b11_10_11_11:     number  = (data_o%1_000)/1_00                           ;
        8'b11_01_11_11:     number  = (data_o%1_00)/1_0                             ;
        8'b10_11_11_11:     number  = data_o%10                                     ;
        default:            number  = 4'd0                                          ;
    endcase
end

/*通过数字解析出seg值*/
always @(*) begin
    case (number)
        4'd0    :       seg_r   =  7'b100_0000;
        4'd1    :       seg_r   =  7'b111_1001;
        4'd2    :       seg_r   =  7'b010_0100;
        4'd3    :       seg_r   =  7'b011_0000;
        4'd4    :       seg_r   =  7'b001_1001;
        4'd5    :       seg_r   =  7'b001_0010;
        4'd6    :       seg_r   =  7'b000_0010;
        4'd7    :       seg_r   =  7'b111_1000;
        4'd8    :       seg_r   =  7'b000_0000;
        4'd9    :       seg_r   =  7'b001_0000;
        NOTION  :       seg_r   =  7'b111_1111;
        FUSHU   :       seg_r   =  7'b011_1111;
        default :       seg_r   =  7'b111_1111;
    endcase
end

always @(*) begin
    case (sel_r)
		8'b11_11_11_10:     hex1_r = seg_r;
		8'b11_11_11_01:     hex2_r = seg_r;
		8'b11_11_10_11:     hex3_r = seg_r;
		8'b11_11_01_11:     hex4_r = seg_r;
		8'b11_10_11_11:     hex5_r = seg_r;
		8'b11_01_11_11:     hex6_r = seg_r;
		8'b10_11_11_11:     hex7_r = seg_r;
		8'b01_11_11_11:     hex8_r = seg_r;
		default:            seg_r  = seg_r;
	endcase
end

assign  hex1 = hex1_r;
assign  hex2 = hex2_r;
assign  hex3 = hex3_r;
assign  hex4 = hex4_r;
assign  hex5 = hex5_r;
assign  hex6 = hex6_r;
assign  hex7 = hex7_r;
assign  hex8 = hex8_r;

endmodule

五 顶层

module top (
    input   wire        clk     ,
    input   wire        rst_n   ,
    input   wire        echo    ,

    output  wire        trig_out,
    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    ,
    output  wire [6:0]  hex7    ,
    output  wire [6:0]  hex8    ,
    output  wire        beep    ,
    output  wire [3:0]  led 
);

wire    [24:0]  data_out;

hc_sr_driver hc_sr_driver_inst(
    .clk            (clk),
    .rst_n          (rst_n),
    .echo           (echo),

    .trig_out       (trig_out),
    .data_out       (data_out)
);

sel_driver sel_driver_inst(
    .Clk        (clk        ),
    .Rst_n      (rst_n      ),
    .data_o     (data_out   ),

    .hex1       (hex1),
    .hex2       (hex2),
    .hex3       (hex3),
    .hex4       (hex4),
    .hex5       (hex5),
    .hex6       (hex6),
    .hex7       (hex7),
    .hex8       (hex8) 
);
endmodule

六 tcl文件

package require ::quartus::project

set_location_assignment PIN_Y2 -to clk
set_location_assignment PIN_M23 -to rst_n
set_location_assignment PIN_AC15 -to echo
set_location_assignment PIN_AB22 -to trig_out
set_location_assignment PIN_AA14 -to hex1[6]
set_location_assignment PIN_AG18 -to hex1[5]
set_location_assignment PIN_AF17 -to hex1[4]
set_location_assignment PIN_AH17 -to hex1[3]
set_location_assignment PIN_AG17 -to hex1[2]
set_location_assignment PIN_AE17 -to hex1[1]
set_location_assignment PIN_AD17 -to hex1[0]
set_location_assignment PIN_AC17 -to hex2[6]
set_location_assignment PIN_AA15 -to hex2[5]
set_location_assignment PIN_AB15 -to hex2[4]
set_location_assignment PIN_AB17 -to hex2[3]
set_location_assignment PIN_AA16 -to hex2[2]
set_location_assignment PIN_AB16 -to hex2[1]
set_location_assignment PIN_AA17 -to hex2[0]
set_location_assignment PIN_AH18 -to hex3[6]
set_location_assignment PIN_AF18 -to hex3[5]
set_location_assignment PIN_AG19 -to hex3[4]
set_location_assignment PIN_AH19 -to hex3[3]
set_location_assignment PIN_AB18 -to hex3[2]
set_location_assignment PIN_AC18 -to hex3[1]
set_location_assignment PIN_AD18 -to hex3[0]
set_location_assignment PIN_AE18 -to hex4[6]
set_location_assignment PIN_AF19 -to hex4[5]
set_location_assignment PIN_AE19 -to hex4[4]
set_location_assignment PIN_AH21 -to hex4[3]
set_location_assignment PIN_AG21 -to hex4[2]
set_location_assignment PIN_AA19 -to hex4[1]
set_location_assignment PIN_AB19 -to hex4[0]
set_location_assignment PIN_Y19 -to hex5[6]
set_location_assignment PIN_AF23 -to hex5[5]
set_location_assignment PIN_AD24 -to hex5[4]
set_location_assignment PIN_AA21 -to hex5[3]
set_location_assignment PIN_AB20 -to hex5[2]
set_location_assignment PIN_U21 -to hex5[1]
set_location_assignment PIN_V21 -to hex5[0]
set_location_assignment PIN_W28 -to hex6[6]
set_location_assignment PIN_W27 -to hex6[5]
set_location_assignment PIN_Y26 -to hex6[4]
set_location_assignment PIN_W26 -to hex6[3]
set_location_assignment PIN_Y25 -to hex6[2]
set_location_assignment PIN_AA26 -to hex6[1]
set_location_assignment PIN_AA25 -to hex6[0]
set_location_assignment PIN_U24 -to hex7[6]
set_location_assignment PIN_U23 -to hex7[5]
set_location_assignment PIN_W25 -to hex7[4]
set_location_assignment PIN_W22 -to hex7[3]
set_location_assignment PIN_W21 -to hex7[2]
set_location_assignment PIN_Y22 -to hex7[1]
set_location_assignment PIN_M24 -to hex7[0]
set_location_assignment PIN_H22 -to hex8[6]
set_location_assignment PIN_J22 -to hex8[5]
set_location_assignment PIN_L25 -to hex8[4]
set_location_assignment PIN_L26 -to hex8[3]
set_location_assignment PIN_E17 -to hex8[2]
set_location_assignment PIN_F22 -to hex8[1]
set_location_assignment PIN_G18 -to hex8[0]

七 实现效果

在这里插入图片描述

总结

从实验结果观察发现测量距离同实际还是具有一定的误差,可能是由传感器本身的噪声、回波的多路径传播、环境的干扰等原因造成。

参考资料

HC-SR04超声波测距模块的原理
基于DE2 115开发板驱动HC_SR04超声波测距模块【附源码】
【嵌入式系统应用开发】FPGA——基于HC-SR04超声波测距

  • 25
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值