开发板选用的是黑金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的直接进位就完事了。
当然你也可以用除法算喽。我记得好像也会有跟上面类似的错误,而且占资源,所以我们不推荐除法一位位去取~
代码如下:
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哈
记得一键三连哦~~~
有些地方水平有限(懒得改了),还请大家批评指正!!!