前言

开发板选用的是黑金AX301,超声波测距模块为HS-04。之前努力写了一大堆前言,将原理的,然后打算等代码写完一起发,结果找不见了,那重写一次吧,这次写的简单一点哈~~


一、超声波测距的原理

(1)采用 IO 触发测距,给至少 10us 的高电平信号;
(2)模块自动发送 8 个 40khz 的方波,自动检测是否有信号返回;(这个我们在代码中就不用去管啦)
(3)有信号返回,通过 IO 输出一高电平,高电平持续的时间就是超声波从发射到返回的时间
(4)测试距离=(高电平时间*声速(340M/S))/2; 。

在这里插入图片描述

二、程序设计思路

1.首先呢我们拿到元器件,看到一共有四个引脚,分别是VCC,GND,Trig(发送端)和Echo(接收端)
然后我们在代码中主要写Trig和Echo两个引脚就行啦

2.让我先看一眼之前写的代码

3.首先,我们FPGA自带的系统时钟是50M的,也就是每0.02us可以计数一次。我们先通过对Trig计数,我这里是将cnt_trig在计数小于500(即10us)的时候置为高电平,符合时序图触发信号的条件,然后我们周期取大一点,避免反射的那部分超声波对结果进行干扰,于是直接取了cnt_trig到1_000_000,即20ms的周期。

4.写完了Trig,我们再来看Echo

5.Echo主要是接受反射回来的超声波信号,那么在代码里就是检测Echo收到信号的上升沿,和下降沿,做好标记,利用它们俩间隔的计数值的大小,乘上时钟的频率,就能套公式算出距离了~

6.提一下优化的思路:如果只利用50M系统时钟去计算距离的话,首先就是耗资源,因为会涉及到乘除法,这会大大占用我们有限的逻辑资源嗷。所以我们可以采用17khz的时钟去计时,下面的代码里我们会写到。还有我们存储的时候,是四位数组存一个数字(因为要表示0-9必须用到4位的二进制),然后显示,如果我们在前面算距离的时候不去做特殊处理,只是简单的cnt+1的话,,用a[3:0]<=b[3:0],会不准确。(ps可以用除法,但是前面说了耗内存)

7.举个栗子在这里插入图片描述比如我们得到的距离是123,因为我们传到数码管是一位一位拎出来显示的,也就是1位十进制对应4位二进制。123对应的二进制是111_1011,如果我们用 num[15:12] <= data [15:12]; num[11:8] <= data[11:8] ; num[7:4] <= data[7:4] ; num[3:0] <= data [3:0];这样的语句不用到除法,省逻辑。但是会得到70的结果(低位是11超过了9归类到defaul显示0)。
所以我们在前面计算的时候,直接用BCD码就好了,大于9的直接进位就完事了。
当然你也可以用除法算喽。我记得好像也会有跟上面类似的错误,而且占资源,所以我们不推荐除法一位位去取~

三、好了,不多BB,我们上代码

代码如下:

顶层模块

module top_Ranging(
input	CLK_50M,
input RST,

//超声波测距用的端口
input wire Echo,
output Trig,

//数码管显示
output [6:0] seg_duan,
output [2:0] seg_sel

);

wire  [15:0] data;

measurement U1(
		.CLK_50M(CLK_50M),
		.RST (RST),
		.Echo (Echo),
		.Trig (Trig) ,
		.data(data)
);

display U2(
		.CLK_50M	(CLK_50M),
		.RST 		(RST),
		.data(data),
		.seg_duan (seg_duan),
		.seg_sel (seg_sel)

);

endmodule

测量模块

module measurement(
input CLK_50M,
input RST,
input Echo,
output reg Trig,
output  [15:0] data

);

//Trig 
reg [23:0] cnt_trig;
always @ (posedge CLK_50M or negedge RST)
begin
	if(!RST)
		cnt_trig<=1'b0;
	else
		if(cnt_trig =='d500) begin	//高电平时间		
			Trig<=0;
			cnt_trig<=cnt_trig+1'b1;
			end
		else 
			begin
				if(cnt_trig=='d1_000_000)//低电平时间,两者加起来就是周期
					begin
						Trig<=1;
						cnt_trig<=0;
					end				
				else
					cnt_trig<=cnt_trig+1'b1;
			end
end


//检测上升沿下降沿

reg Echo_2,Echo_1,cnt_en,flag;
assign pose_Echo =(~Echo_2)&&Echo_1;//这里是表示上升下降沿的方法,可以细细品味一下,以后直接用就完事了
assign nege_Echo = Echo_2&&(~Echo_1);
parameter S0 = 2'b00, S1 = 2'b01, S2 = 2'b10; 
reg[1:0] curr_state;
reg [15:0] cnt;
reg [15:0] dis_reg;
reg [15:0] cnt_17k;
always @ (posedge CLK_50M or negedge RST)
begin
	if(!RST)
		begin
			Echo_1 <= 1'b0;
			Echo_2 <= 1'b0;
			cnt_17k <=1'b0;
			dis_reg <=1'b0;
			curr_state <= S0;
		end
	else	
		begin
			Echo_1<=Echo;
			Echo_2<=Echo_1;
			case(curr_state)
			S0:begin
					if (pose_Echo)
						curr_state <= S1;
					else
						begin
							cnt <= 1'b0;
						end
				end
			S1:begin
					if(nege_Echo)
						curr_state <= S2;
					else
						begin
							if(cnt_17k <16'd2940)//这个是因为有小数,大致的17khz计数,不是最准确的,但是也很准了
							begin
								cnt_17k <= cnt_17k + 1'b1;
							end
							else
							begin//下面大家可以看到,判断语句》=’d10咱们就进位,就不会出现例子的错误了,如果不在乎资源
								//那么你简单的cnt++也可以试一试,只是后面要用到除法取位,后面数码管那边赋值不能跟我的方法一样了
								cnt_17k <= 1'b0;
								cnt[3:0] <= cnt[3:0] +1'b1;
							end
								if(cnt[3:0] >= 'd10)
								begin
									cnt [3:0] <=1'b0;
									cnt [7:4]<=cnt[7:4]+1'b1;
								end
								if (cnt[7:4] >= 'd10)
								begin
									cnt[7:4]<=1'b0;
									cnt[11:8]<=cnt[11:8]+1'b1;
								end
								if (cnt[11:8]>='d10)
								begin
									cnt[11:8]<=1'b0;
									cnt[15:12]<=cnt[15:12]+1'b1;
								end
								
								if(cnt[15:12]>='d10)
								begin
									cnt[15:12]<=1'b0;
								end					
						end				
				end
			S2:begin
				dis_reg<=cnt;
				cnt <=1'b0;
				curr_state <= S0;
				end
			endcase	
		end
end

assign data = dis_reg ; //对输出的data赋值,因为取的时钟频率是17Khz,340*T/2=170*(1/17000)*100=T单位是cm
endmodule

显示模块

module display(
		input  CLK_50M,
		input  RST,
		input  [15:0]data,
		output reg [6:0]seg_duan,//显示器段选
		output reg [2:0]seg_sel//显示器位选
    );
reg [3:0]  clk_cnt;        // 时钟分频计数器
reg dri_clk;        // 数码管的驱动时钟,5MHz
reg[3:0] scan_sel;     //Scan select counter
reg[3:0] seg_data;
reg [15:0] num;
reg flag ;        // 标志信号(标志着cnt0计数达1ms)
reg [2:0] cnt_sel;        // 数码管位选计数器
reg [12:0]  cnt0;        // 数码管驱动时钟计数器
always @ (posedge CLK_50M or negedge RST) 
begin
 if(!RST) begin
       clk_cnt <= 4'd0;
       dri_clk <= 1'b1;
   end
   else if(clk_cnt ==4) begin
       clk_cnt <= 4'd0;
       dri_clk <= ~dri_clk;
   end
   else begin
       clk_cnt <= clk_cnt + 1'b1;
       dri_clk <= dri_clk;
   end
end

always @ (  posedge dri_clk or negedge RST) 
begin
    if (!RST)
        num<= 16'd0;
    else 
	 begin
			 num[15:12] <= data [15:12];
          num[11:8]  <=	data[11:8] ;
			 num[7:4]   <=	data[7:4] ;
          num[3:0]   <= data [3:0];
	end
end

always @ (posedge dri_clk or negedge RST) begin
    if (RST == 1'b0) begin
        cnt0 <= 13'b0;
        flag <= 1'b0;
     end
    else if (cnt0 < 'd4999) begin
        cnt0 <= cnt0 + 1'b1;
        flag <= 1'b0;
     end
    else begin
        cnt0 <= 13'b0;
        flag <= 1'b1;
     end
end

always @ (posedge dri_clk or negedge RST) begin
    if (RST == 1'b0)
        cnt_sel <= 3'b0;
    else if(flag) begin
        if(cnt_sel < 3'd3) //六位数码管轮流显示
            cnt_sel <= cnt_sel + 1'b1;
        else
            cnt_sel <= 3'b0;
    end
    else
        cnt_sel <= cnt_sel;
end




always@(posedge dri_clk or negedge RST)
begin
	if(!RST)
	begin
		seg_sel <= 3'b111;
		seg_data <= 4'd0;
	end
	else
	begin
		case(cnt_sel)
			//first digital led
			4'd0:
			begin
				seg_sel <= 3'b110;
				seg_data <= num[3:0];
			end
			//second digital led
			4'd1:
			begin
				seg_sel <= 3'b101;
				seg_data <= num[7:4];
			end
			//...
			4'd2:
			begin
				seg_sel <= 3'b011;
				seg_data <= num[11:8];
			end

			default:
			begin
				seg_sel <= 3'b111;
				seg_data <= 4'b0;
			end
		endcase
	end
end

	 
	 
	 
//七段译码器(显示器)
always@(*)
begin
	case(seg_data)
		//*共阳
		'd0:seg_duan<=7'b1000000;
		'd1:seg_duan<=7'b1111001;
		'd2:seg_duan<=7'b0100100;
		'd3:seg_duan<=7'b0110000;
		'd4:seg_duan<=7'b0011001;
		'd5:seg_duan<=7'b0010010;
		'd6:seg_duan<=7'b0000010;
		'd7:seg_duan<=7'b1111000;
		'd8:seg_duan<=7'b0000000;
		'd9:seg_duan<=7'b0010000;
		default seg_duan<=7'b1000000;
	endcase	
end
endmodule



总结

在这里插入图片描述
主要是说明能用,大家可以优化一下准应该是还蛮准的,单位是cm哈
记得一键三连哦~~~
有些地方水平有限(懒得改了),还请大家批评指正!!!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐