作者:李西锐 校对:陆辉
大侠好,欢迎来到FPGA技术江湖。本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。
系统性的掌握技术开发以及相关要求,对个人就业以及职业发展都有着潜在的帮助,希望对大家有所帮助。本次带来Vivado系列,超声波驱动设计。话不多说,上货。
一、简介
声音是我们日常生活中不可缺少的一种信号,在传递信息的同时,也在生活中的各个领域有较多的应用。根据声音的频率,我们将声音大致划分为三个阶段,人耳的听力范围,一般在20Hz~20000Hz之间。低于这个范围,我们称之为次声波;高于这个范围,称之为超声波。超声波的应用比较广泛,比如:超声波检查、超声波碎石、超声波清洗、超声波测速、超声波测距等等。此次我们就来研究一下它的其中一项应用:超声波测距。
我们用到的试验模块为HC-SR04超声波模块,它的测量距离在2cm~400cm之间。测量精度在3mm左右。模块包含了超声波的发射器、接收器和控制电路。超声波发射器在启动后会发出固定频率的方波,用声波去测量距离,不需要我们接触被测物体,在空间上使得我们测距变得方便很多。
二、工作原理
1、采用IO口TRIG触发测距,给至少10us的高电平信号,测量周期建议在60ms以上,以防止发射的信号对回响信号造成影响。
2、模块会自动发送8个40Khz的方波信号,接收器自动检测是否有回响信号返回。
3、有信号返回时,通过IO口ECHO输出一个高电平信号,高电平持续的时间就是方波从发射到返回的时间。测量距离=(高电平时间*声速(340m/s))/2;
在此需要我们注意的事,发射器是自动发送方波信号的,而且会自动检测是否有信号返回,这让我们省去了一大部分工作,使得测量变得简单。其次,在计算测量距离时,我们要将计算出来的结果除以2,因为我们测得的时间是往返的时间,也就是双倍的路程。
三、实物图
四、电气参数
五、超声波时序图
在时序图中,我们可以看出,我们需要生成一个周期至少为60ms,且高电平维持时间至少为10us的一个触发信号。
六、实验要求
此次设计,要求能够正常驱动模块,计算出的距离,计算其平均值以保证准确性。数码管上显示出距离,单位为m,精确到mm。并且,蜂鸣器能够根据距离响出不同频率的报警声音,距离越近,响声频率越频繁。
七、设计框架
八、设计实现
在计算回响信号的时间时,我们可以检测回响信号的上升沿和下降沿来作为计时器的开始和结束。在我们计算出距离之后,可以每三个数据计算一次平均值。然后将数据输出给其他模块。
首先,我们新建工程。
选择新建文件,然后先新建顶层文件
按照我们所画框架,写入顶层端口。
重复上述新建文件的过程,新建ultrasonic_driver文件,代码如下:
1 module ultrasonic_driver(
2
3 input wire clk,
4 input wire rst_n,
5 input wire echo,
6 output reg trig,
7 output reg [11:0] distance,
8 output reg data_valid
9 );
10
11 parameter t = 3_000_000;
12
13 reg [21:0] cnt;
14 reg state;
15 reg echo_r, echo_rr;
16 reg [20:0] echo_cnt;
17 reg [20:0] cnt_temp;
18 wire [11:0] d_r;
19 reg [35:0] temp;
20 reg data_valid_r;
21
22 always @ (posedge clk) echo_r <= echo;
23 always @ (posedge clk) echo_rr <= echo_r;
24
25 always @ (posedge clk, negedge rst_n)
26 begin
27 if(rst_n == 1'b0)
28 cnt <= 22'd0;
29 else if(cnt == t - 1)
30 cnt <= 22'd0;
31 else
32 cnt <= cnt + 1'b1;
33 end
34
35 always @ (posedge clk, negedge rst_n)
36 begin
37 if(rst_n == 1'b0)
38 trig <= 1'b0;
39 else if(cnt < 1000)
40 trig <= 1'b1;
41 else
42 trig <= 1'b0;
43 end
44
45 always @ (posedge clk, negedge rst_n)
46 begin
47 if(rst_n == 1'b0)
48 begin
49 echo_cnt <= 21'd0;
50 state <= 1'd0;
51 data_valid_r <= 1'b0;
52 echo_cnt <= 21'd0;
53 end
54 else
55 case(state)
56 1'd0 : begin
57 if(echo_r & (~echo_rr))
58 state <= 1'd1;
59 else
60 begin
61 state <= 1'd0;
62 data_valid_r <= 1'b0;
63 end
64 end
65 1'd1 : begin
66 if((~echo_r) & echo_rr)
67 begin
68 state <= 1'd0;
69 echo_cnt <= 21'd0;
70 cnt_temp <= echo_cnt;
71 data_valid_r <= 1'b1;
72 end
73 else
74 begin
75 state <= 1'd1;
76 echo_cnt <= echo_cnt + 1'b1;
77 cnt_temp <= cnt_temp;
78 end
79 end
80 endcase
81 end
82
83 assign d_r = cnt_temp * 34 / 10_000;
84
85 always @ (posedge clk, negedge rst_n)
86 begin
87 if(rst_n == 1'b0)
88 temp <= 36'd0;
89 else if(data_valid_r)
90 temp <= {temp[23:0],d_r};
91 else
92 temp <= temp;
93 end
94
95 always @ (posedge clk) data_valid <= data_valid_r;
96
97 always @ (posedge clk, negedge rst_n)
98 begin
99 if(rst_n == 1'b0)
100 distance <= 12'd0;
101 else if(data_valid)
102 distance <= (temp[35:24] + temp[23:12] + temp[11:0]) / 3;
103 else
104 distance <= distance;
105 end
106
107 endmodule
在完成测距时,输出一个valid信号,这个信号要作为后续我们保存数据以及计算平均值的标志信号。
数码管代码如下:
1 module seven_tube_driver(
2
3 input wire clk,
4 input wire rst_n,
5 input wire [11:0] data,
6
7 output reg [5:0] sel,
8 output wire [7:0] seg
9 );
10
11 parameter t = 50000;
12
13 reg [15:0] cnt;
14 reg [3:0] show_data;
15 reg [7:0] seg_r;
16
17 always @ (posedge clk, negedge rst_n)
18 begin
19 if(rst_n == 1'b0)
20 cnt <= 16'd0;
21 else if(cnt == t - 1)
22 cnt <= 16'd0;
23 else
24 cnt <= cnt + 1'b1;
25 end
26
27 always @ (posedge clk, negedge rst_n)
28 begin
29 if(rst_n == 1'b0)
30 sel <= 6'b111_110;
31 else if(cnt == t - 1)
32 sel <= {sel[4:0],sel[5]};
33 else
34 sel <= sel;
35 end
36
37 always @ (*)
38 begin
39 case(sel)
40 6'b111_110 : show_data = 4'hf;
41 6'b111_101 : show_data = 4'hf;
42 6'b111_011 : show_data = data/1000;
43 6'b110_111 : show_data = data/100%10;
44 6'b101_111 : show_data = data/10%10;
45 6'b011_111 : show_data = data%10;
46 default : show_data = 4'd0;
47 endcase
48 end
49
50 always @ (*)
51 begin
52 case(show_data)
53 4'd0 : seg_r = 8'b1100_0000;
54 4'd1 : seg_r = 8'b1111_1001;
55 4'd2 : seg_r = 8'b1010_0100;
56 4'd3 : seg_r = 8'b1011_0000;
57 4'd4 : seg_r = 8'b1001_1001;
58 4'd5 : seg_r = 8'b1001_0010;
59 4'd6 : seg_r = 8'b1000_0010;
60 4'd7 : seg_r = 8'b1111_1000;
61 4'd8 : seg_r = 8'b1000_0000;
62 4'd9 : seg_r = 8'b1001_0000;
63 default: seg_r = 8'd0;
64 endcase
65 end
66
67 assign seg = (sel == 6'b111_011) ? (seg_r & 8'b0111_1111) : seg_r;
68
69 endmodule
第67行的作用是为了在显示时,显示一个小数点,这样数码管显示的数值单位就为米,精确度为毫米。
在蜂鸣器模块中,蜂鸣器的响声为“嘀嘀”的响声,我们可以根据距离的大小,让蜂鸣器响声的快慢作出改变。
我们根据距离改变计数器的最大计数次数,以达到两次“嘀嘀”响声的时间间隔发生变化。
写好代码之后,我们做一下仿真,代码如下:
1 `timescale 1ns / 1ps
2
3 module ultrasonic_tb;
4
5 reg clk;
6 reg rst_n;
7 reg echo;
8 wire trig;
9 wire [5:0] sel;
10 wire [7:0] seg;
11 wire beep;
12
13 defparam ultrasonic_inst.ultrasonic_driver_inst.t = 3000;
14
15 initial begin
16 clk = 1'b0;
17 rst_n = 1'b0;
18 echo = 1'b0;
19 #105;
20 rst_n = 1'b1;
21 #1000;
22
23 repeat(10) begin
24 @ (negedge trig);
25 #1002;
26 echo = 1'b1;
27 #20000;
28 echo = 1'b0;
29 end
30 #10000;
31 $stop;
32 end
33
34 always #10 clk = ~clk;
35
36 ultrasonic ultrasonic_inst(
37 .clk (clk ),
38 .rst_n (rst_n ),
39 .echo (echo ),
40 .trig (trig ),
41 .sel (sel ),
42 .seg (seg ),
43 .beep (beep )
44 );
45
46 endmodule
仿真图如下:
从图中我们可以看出,距离在多次采样之后达到了稳定值。由于我们仿真时间给的较短,所以距离的数值不大,但是已经足够看出结果。