FPGA超声波测距
FPGA超声波测距前言一、超声波测距的原理二、程序设计思路三、好了,不多BB,我们上代码顶层模块测量模块显示模块总结前言开发板选用的是黑金AX301,超声波测距模块为HS-04。之前努力写了一大堆前言,将原理的,然后打算等代码写完一起发,结果找不见了,那重写一次吧,这次写的简单一点哈~~一、超声波测距的原理(1)采用 IO 触发测距,给至少 10us 的高电平信号;(2)模块自动发送 8 个 40
前言
开发板选用的是黑金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哈
记得一键三连哦~~~
有些地方水平有限(懒得改了),还请大家批评指正!!!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)