1、实验目的
使用逻辑派Z1开发板(基于紫光同创 compa 系列 PGC4KD-6ILPG144 芯片)编写HC_SR04超声波测距模块驱动代码,最终使用开发板按键触发超声波测距模块的测距操作。并将测距得到的距离使用串口发送到上位机。
2、实验原理
2.1、超声波测距原理
超声波测距是一种利用超声波在空气中的传播时间来测量距离的方法。只要知道超声波的传播速度以及发射超声波与接收到反射声波的时间就能计算出物体的距离。其具体操作过程如下(不考虑温度、压强对声波传输的影响):1.发射超声波 超声波测距模块首先通过一个超声波换能器(通常是压电陶瓷或压电薄膜)发射出一束超声波脉冲。这些脉冲在空气中以一定的速度传播。2.接收反射波 当超声波脉冲被目标物体反射后,返回的反射波被同一个或另一个超声波换能器接收。测距模块记录从发射到接收反射波的时间。3.计算距离 根据超声波的传播时间和传播速度,只需要将(传播时间×声速)/2就可以得到目标物体与测距模块之间的距离。这里的传播时间是从发射到接收到反射波的总时间,除以2是因为超声波往返的总时间包括去程和返程。 超声波测距模块图如下:
2.2HC_SR04超声波测距模块工作原理
HC-SR04超声波测距模块是一种经济实用的非接触式距离测量设备,广泛用于各种需要精确测距的应用,如机器人避障、汽车倒车雷达等。该模块包含一个超声波发射器和接收器,通过四个引脚(VCC、Trig、Echo、GND)与微控制器连接。工作时,需要微控制器向Trig引脚发送一个10微秒的高电平脉冲,触发模块发射超声波脉冲。模块接收到反射波后,Echo引脚输出一个与超声波往返时间成正比的高电平脉冲,微控制器需要通过测量Echo引脚高电平脉冲的持续时间,利用距离=(传播时间×声速)/2计算出目标距离。
其具体控制时序如下:
我们使用逻辑派Z1开发板作为微控制器,首先我们向HC-SR04模块的trig引脚发送一个持续10us的触发信号,HC-SR04收到我们给出的触发信号之后,会使用超声波探头发射8个40khz的脉冲。当测距结束后,HC-SR04会通过echo引脚发送一个高电平信号,高电平持续时间就是声波往返物体经历的时间。我们利用这段时间就可以计算出物体到超声波模块的距离。
3、代码设计
超声波驱动模块端口如下:
超声波驱动代码如下:
`timescale 1ns / 1ps
module HC_SR04_drive(
input clk ,
input rst_n ,
input cap_flag /* synthesis PAP_MARK_DEBUG="true" */,
output reg [ 7: 0] distance /* synthesis PAP_MARK_DEBUG="true" */,
output reg data_vld /* synthesis PAP_MARK_DEBUG="true" */,
output reg data_vld_d ,
output reg [ 3: 0] s_g ,//个位
output reg [ 3: 0] s_s ,//十位
output reg [ 3: 0] s_b ,//百位
output reg [ 3: 0] s_q ,//千位
input echo /* synthesis PAP_MARK_DEBUG="true" */,
output trig /* synthesis PAP_MARK_DEBUG="true" */
);
parameter MAX_CNT = 50 ; // 计数到100时产生1μs脉冲
reg [ 9: 0] cnt_trig ;//12us计数器
reg cnt_en ;
reg echo_syn1 ;
reg echo_syn2 ;
reg echo_syn3 ;
reg [ 6: 0] cnt_1us ;
reg [ 12: 0] cnt_us_num /* synthesis PAP_MARK_DEBUG="true" */;
wire echo_fall ;
assign echo_fall = ~echo_syn2 & echo_syn3;//下降沿
assign trig = cnt_en;
//时钟同步以及打拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
echo_syn1 <= 0;
echo_syn2 <= 0;
end
else begin
echo_syn1 <= echo;
echo_syn2 <= echo_syn1;
echo_syn3 <= echo_syn2;
end
end
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_en <= 1'b0;
else if(cap_flag)
cnt_en <= 1'b1;
else if(cnt_trig == 10'd500 -1)
cnt_en <= 1'b0;
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_trig <= 10'b0;
else if(cnt_trig == 10'd500 -1)
cnt_trig <= 10'b0;
else if(cnt_en)
cnt_trig <= cnt_trig + 1'b1;
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_1us <= 7'b0;
else if((cnt_1us==MAX_CNT-1)||(cap_flag))
cnt_1us <= 7'b0;
else if(echo_syn3)
cnt_1us <= cnt_1us + 1'b1;
always @(posedge clk or negedge rst_n)
if(!rst_n)
cnt_us_num <= 13'b0;
else if((cap_flag))
cnt_us_num <= 13'b0;
else if((echo_syn3)&&(cnt_1us==MAX_CNT-1))
cnt_us_num <= cnt_us_num + 1'b1;
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
distance<= 8'b0;
data_vld<= 1'b0;
end
else if(echo_fall)begin
distance <= (cnt_us_num * 17)/1000;
data_vld <= 1'b1;
end
else
data_vld<= 1'b0;
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
s_g <= 0;
s_s <= 0;
s_b <= 0;
s_q <= 0;
data_vld_d<= 1'b0;
end else begin
s_g <= distance % 10;
s_s <= (distance / 10) % 10;
s_b <= (distance / 100) % 10;
s_q <= (distance / 1000) % 10;
data_vld_d<= data_vld;
end
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
这个模块的功能是通过控制HC-SR04超声波测距模块的触发信号和接收反射信号,测量目标物体的距离并将结果输出。模块根据触发标志信号cap_flag启动测距过程。在代码62行,当cap_flag有效时,将cnt_en拉高,开始10us的触发计数器cnt_trig的计数当计数到499时(模块时钟是50Mhz,一个时钟周期是20ns,10us的时间就是500个时钟周期,由于计数器是从0开始计数,所以计数最大值是499),清零并拉低cnt_en。这个cnt_en信号持续了10us,将其作为超声波测距模块的触发信号输出。
由于超声波模块输出的信号时钟与我们开发板的时钟不同步,我们声明echo_syn1、echo_syn2、echo_syn3使用多级寄存器打拍的方式对echo信号进行时钟同步。当echo_syn3有效时,启动微秒计数器cnt_1us对echo高电平持续时间进行计数。同时使用cnt_us_num对高电平持续时间有几微秒进行记录。当捕获到echo的下降沿之后,结合cnt_us_num的值对距离进行计算。通过以下这行可以实现: distance <= (cnt_us_num * 17)/1000 其实是distance <= (cnt_us_num * 340)/2/1000;模块中对其进行了适当的简化,除以1000是将单位转换为厘米。最终,模块将距离值分解为个位、十位、百位和千位,通过输出寄存器输出。
模块顶层代码端口如下:
模块顶层代码如下:
`timescale 1ns / 1ps
module HC_SR04_top(
input clk ,
input rst_n ,
input key_in ,
input echo ,
output trig ,
output uart_txd
);
parameter CNT_MAX = 20'd999_999; //消抖计数器
parameter STR = 192'hb3_ac_c9_f9_b2_a8_c4_a3_bf_e9_b2_e2_be_e0_d6_b5_ce_aa_a3_ba_c0_e5_c3_d7;//"超声波模块测距值为:厘米"的GB2312编码
//信号定义
wire data_vld_d ;
wire [ 31: 0] temperature_data ;
wire uart_tx_busy ;
wire uart_tx_done /*synthesisPAP_MARK_DEBUG="true"*/;
reg [ 7: 0] uart_tx_data ;
reg uart_tx_req /*synthesisPAP_MARK_DEBUG="true"*/;
reg work_en /*synthesisPAP_MARK_DEBUG="true"*/;
reg [ 6: 0] tx_byte_cnt /*synthesisPAP_MARK_DEBUG="true"*/;
reg [ 19: 0] cnt_20ms ;//消抖计数器
reg key_flag ;
reg [ 7: 0] ascii_table [0:9] ;//储存0~9的ASCII值
wire [ 3: 0] s_g ;//个位
wire [ 3: 0] s_s ;//十位
wire [ 3: 0] s_b ;//百位
wire [ 3: 0] s_q ;//千位;
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
cnt_20ms <= 20'b0;
else if(key_in == 1'b1)
cnt_20ms <= 20'b0;
else if(cnt_20ms == CNT_MAX && key_in == 1'b0)
cnt_20ms <= cnt_20ms;
else
cnt_20ms <= cnt_20ms + 1'b1;
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX - 1'b1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
work_en <= 1'b0;
else if(data_vld_d == 1'b1)
work_en <= 1'b1;
else if(tx_byte_cnt== 7'd28&&uart_tx_done)
work_en <= 1'b0;
else
work_en <= work_en;
//串口发送逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx_byte_cnt <= 7'b0;
end else if (work_en) begin
if (tx_byte_cnt == 7'd28&&uart_tx_done) // 总共发送 29 字节
tx_byte_cnt <= 7'b0;
else if(uart_tx_done)
tx_byte_cnt <= tx_byte_cnt + 1'b1;
end
end
// 串口发送数据控制
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
uart_tx_req <= 1'b0;
uart_tx_data <= 8'b0;
ascii_table[0] = 8'd48; //'0'
ascii_table[1] = 8'd49; //'1'
ascii_table[2] = 8'd50; //'2'
ascii_table[3] = 8'd51; //'3'
ascii_table[4] = 8'd52; //'4'
ascii_table[5] = 8'd53; //'5'
ascii_table[6] = 8'd54; //'6'
ascii_table[7] = 8'd55; //'7'
ascii_table[8] = 8'd56; //'8'
ascii_table[9] = 8'd57; //'9'
end else if (work_en && !uart_tx_busy) begin
case (tx_byte_cnt) // 根据当前字节计数器发送数据
7'd0: uart_tx_data <= STR[191:184]; // "超"
7'd1: uart_tx_data <= STR[183:176]; // "超"
7'd2: uart_tx_data <= STR[175:168]; // "声"
7'd3: uart_tx_data <= STR[167:160]; // "声"
7'd4: uart_tx_data <= STR[159:152]; // "波"
7'd5: uart_tx_data <= STR[151:144]; // "波"
7'd6: uart_tx_data <= STR[143:136]; // "模"
7'd7: uart_tx_data <= STR[135:128]; // "模"
7'd8: uart_tx_data <= STR[127:120]; // "块"
7'd9: uart_tx_data <= STR[119:112]; // "块"
7'd10: uart_tx_data <= STR[111:104]; // "测"
7'd11: uart_tx_data <= STR[103:96]; // "测"
7'd12: uart_tx_data <= STR[95:88]; // "距"
7'd13: uart_tx_data <= STR[87:80]; // "距"
7'd14: uart_tx_data <= STR[79:72]; // "值"
7'd15: uart_tx_data <= STR[71:64]; // "值"
7'd16: uart_tx_data <= STR[63:56]; // "为"
7'd17: uart_tx_data <= STR[55:48]; // "为"
7'd18: uart_tx_data <= STR[47:40]; // ":"
7'd19: uart_tx_data <= STR[39:32]; // ":"
7'd20: uart_tx_data <= ascii_table[s_q]; // 测量距离的个位
7'd21: uart_tx_data <= ascii_table[s_b]; // 测量距离的十位
7'd22: uart_tx_data <= ascii_table[s_s]; // 测量距离的百位
7'd23: uart_tx_data <= ascii_table[s_g]; // 测量距离的千位
7'd24: uart_tx_data <= STR[31:24]; // "厘"
7'd25: uart_tx_data <= STR[23:16]; // "厘"
7'd26: uart_tx_data <= STR[15:8]; // "米"
7'd27: uart_tx_data <= STR[7:0]; // "米"
7'd28: uart_tx_data <= 8'h0A; // 换行符
default: uart_tx_data <= 8'b0;
endcase
uart_tx_req <= 1'b1; // 拉高请求信号
end else begin
uart_tx_req <= 1'b0; // 请求信号拉高仅一个时钟周期
end
end
HC_SR04_drive u_HC_SR04_drive(
.clk (clk ),
.rst_n (rst_n ),
.cap_flag (key_flag ),
.distance ( ),
.data_vld ( ),
.data_vld_d (data_vld_d ),
.s_g (s_g ),// 个位
.s_s (s_s ),// 十位
.s_b (s_b ),// 百位
.s_q (s_q ),// 千位
.echo (echo ),
.trig (trig )
);
uart_tx u_uart_tx(
.clk (clk ),
.rst_n (rst_n ),
.uart_tx_req (uart_tx_req&work_en ),// 发送使能请求
.uart_tx_data (uart_tx_data ),// UART要发送的数据
.uart_txd (uart_txd ),// UART发送端口
.uart_tx_done (uart_tx_done ),//8bit数据发送完成
.uart_tx_busy (uart_tx_busy ) // 发送忙
);
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
我们在顶层模块将超声波测距得到的数据通过串口发送到上位机进行查看,汉字使用GB2312编码,模块中初始化了一个ascii_table用来储存阿拉伯数字0~9的ASCII值,将超声波测距得到的数据分为个位、十位、百位、千位在ascii_table索引到对应的ASCII值进行发送。其中串口发送部分的知识在之前的章节已有介绍,读者若对这部分不熟悉,可到串口回环章节进行查看。
约束代码如下:
define_attribute {p:trig} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:trig} {PAP_IO_LOC} {92}
define_attribute {p:trig} {PAP_IO_VCCIO} {1.2}
define_attribute {p:trig} {PAP_IO_STANDARD} {LVCMOS12}
define_attribute {p:trig} {PAP_IO_DRIVE} {2}
define_attribute {p:trig} {PAP_IO_NONE} {TRUE}
define_attribute {p:trig} {PAP_IO_SLEW} {SLOW}
define_attribute {p:uart_txd} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:uart_txd} {PAP_IO_LOC} {33}
define_attribute {p:uart_txd} {PAP_IO_VCCIO} {1.2}
define_attribute {p:uart_txd} {PAP_IO_STANDARD} {LVCMOS12}
define_attribute {p:uart_txd} {PAP_IO_DRIVE} {2}
define_attribute {p:uart_txd} {PAP_IO_NONE} {TRUE}
define_attribute {p:uart_txd} {PAP_IO_SLEW} {SLOW}
define_attribute {p:clk} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:clk} {PAP_IO_LOC} {5}
define_attribute {p:clk} {PAP_IO_VCCIO} {1.2}
define_attribute {p:clk} {PAP_IO_STANDARD} {LVCMOS12}
define_attribute {p:clk} {PAP_IO_PULLUP} {TRUE}
define_attribute {p:echo} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:echo} {PAP_IO_LOC} {91}
define_attribute {p:echo} {PAP_IO_VCCIO} {1.2}
define_attribute {p:echo} {PAP_IO_STANDARD} {LVCMOS12}
define_attribute {p:echo} {PAP_IO_PULLUP} {TRUE}
define_attribute {p:key_in} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:key_in} {PAP_IO_LOC} {20}
define_attribute {p:key_in} {PAP_IO_VCCIO} {1.2}
define_attribute {p:key_in} {PAP_IO_STANDARD} {LVCMOS12}
define_attribute {p:key_in} {PAP_IO_PULLUP} {TRUE}
define_attribute {p:rst_n} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:rst_n} {PAP_IO_LOC} {19}
define_attribute {p:rst_n} {PAP_IO_VCCIO} {1.2}
define_attribute {p:rst_n} {PAP_IO_STANDARD} {LVCMOS12}
define_attribute {p:rst_n} {PAP_IO_PULLUP} {TRUE}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
4、代码仿真
由于本次实验涉及到与HC_SR04超声波测距模块的交互,编写测试代码模拟HC_SR04超声波测距模块的答复比较麻烦,而且不能贴近真实的模块真实的测距情景。所以我们可以使用PDS在线debug的功能对HC_SR04驱动模块进行验证。
我们在HCSR04驱动模块代码中对关键信号打上标记(/ synthesis PAP*MARK_DEBUG="true" */),使用在线debug工具对模块进行分析。 我们将采样触发信号cap_flag、计算得到的距离distance、输出的数据有效信号data_vld、超声波测距模块输入的回响信号echo、输入给超声波测距模块的触发信号trig、回响信号的微秒计数器cnt_us_num打上标记,进行分析。
我们将触发模式设置为cap_flag信号高电平触发模式,按下按键,cap_flag被拉高(下图中cap_flag的值为1,只是被标志线遮挡住了),同时触发信号trig正确的拉高了。说明已经向超声波测距模块发送了测距触发信号。
接下来在超声波测距模块30cm处放置一物体(需要大一些),将触发模式设置为data_vld信号高电平触发,按下开发板按键k0,发现计算出的距离值为30,微秒计数器的值为1769,符合我们的预期。如下图所示:
5、实验现象
将板卡与超声波模块根据约束文件正确连接后,将程序下载进入板卡,按下板卡按键k0,将会触发一次测距,并将得到的结果通过串口打印到上位机现象如下:
在30cm处放置一纸箱,按下板卡key0之后上位机接收如下:
超声波测距实验串口打印结果如下图所示: