基于FPGA的超声波测距

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的分频,还有测量周期等,这些都是根据模块的说明书来确定的。总的来说,本次的代码并不是特别难,只需要根据看懂说明书,将里面的功能都实现就可以了。

参考

参考文章——1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值