1、模块来源
2、参数
工作电压:3.3 - 5.5V
工作电流:15 mA
感应角度:小于15度
探测距离:2 - 400CM/1 - 13
探测精度:0.3CM
触发输入脉冲宽度: 10us TTL脉冲
管脚数量:4 Pin
3、时序说明
HC-SR04超声波测距模块是一种经济实用的非接触式距离测量设备,广泛用于各种需要精确测距的应用,如机器人避障、汽车倒车雷达等。该模块包含一个超声波发射器和接收器,通过四个引脚(VCC、Trig、Echo、GND)与微控制器连接。工作时,需要微控制器向Trig引脚发送一个10微秒的高电平脉冲,触发模块发射超声波脉冲。模块接收到反射波后,Echo引脚输出一个与超声波往返时间成正比的高电平脉冲,微控制器需要通过测量Echo引脚高电平脉冲的持续时间,物体的距离=((空气中的声速)*时间)/2 计算出目标距离。
其具体控制时序如下:
使用逻辑派FPGA-Z1开发板作为微控制器, 首先我们向 HC-SR04 模块的 trig 引脚发送一个持续 10us 的触发信号, HC-SR04 收到我们给出的触发信号之后, 会使用超声波探头发射 8 个 40khz 的脉冲。 当测距结束后, HC-SR04 会通过 echo 引脚发送一个高电平信号, 高电平持续时 间就是声波往返物体经历的时间。 我们利用这段时间就可以计算出物体到超声波模块的距离。
4、实战任务
使用逻辑派FPGA-G1开发板编写 HC_SR04 超声波测距模块驱动代码,最终使用开发板按键触发超声波测距模块的测距操作。,并将测距得到的距离使用串口发送到上位机。
5、系统框图
6、程序编写
`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;
//串口发射使能,没发送完work_en为1
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;
//串口发送counter
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 ),// 发送使能请求
.tx_in_data (uart_tx_data ),// UART要发送的数据
.uart_txd (uart_txd ),// UART发送端口
.uart_tx_done (uart_tx_done ),//8bit数据发送完成
.uart_tx_busy (uart_tx_busy ) // 发送忙
);
endmodule2
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
157
158
`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 ;//10us计数器
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
//capfag来了给cnten置1
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;
//cnt_en为1时,cnt_trig计数到10us(触发信号)
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;
//1us计时器,capfag来了清零,开始计数
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;
//计算echo高电平持续了多少us
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
endmodule2
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
`timescale 1ns / 1ps
//--------------------port----------------------------
module uart_tx (
input clk,
input rst_n,
input uart_tx_req,
input [7:0] tx_in_data,
output reg uart_txd,
output reg uart_tx_done,
output reg uart_tx_busy
);
//----------------------parameter-------------------
//串口有起始和停止位,还有波特率
parameter CLK_FREQ = 26'd50_000_000, // 系统时钟频率 50MHz
UART_BPS = 17'd115200; // UART 波特率 115200
parameter START_BIT = 1'b0; // 起始位
parameter END_BIT = 1'b1; // 停止位
localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS; //每434个时钟周期发一位数据
reg [12:0]baud_cnt; //434计数器
reg [3:0] bit_cnt; //8位计数器
reg [7:0] data_reg;
//使能信号锁存,发完一个字节(8位)清零
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
uart_tx_busy <= 0;
end
else if (uart_tx_req) begin
uart_tx_busy <= 1'd1;end
else if ((bit_cnt == 4'd09) && (baud_cnt == 'd1)) begin
uart_tx_busy <= 0;
end
else
uart_tx_busy <= uart_tx_busy;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
baud_cnt <= 13'd0;
end
else if (baud_cnt == (BAUD_CNT_MAX-'d1)) begin
baud_cnt <= 0;
end
else if (uart_tx_busy) begin
baud_cnt <= baud_cnt + 'd1;
end
//baud_cnt <= baud_cnt?
end
//bit_cnt counter
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
bit_cnt <= 0;
end
else if(baud_cnt == 13'd1)begin
if ((bit_cnt == 4 'd9) )
bit_cnt <=0;
else if (uart_tx_busy )
bit_cnt <= bit_cnt +1;
end
//else
//bit_cnt <= bit_cnt;
end
//数据寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_reg<= 0;
end
else if(uart_tx_req)
data_reg <= tx_in_data;
end
//串口发送模块
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
uart_txd <= 1'd1; //空闲时为高电平
end
else if(baud_cnt == BAUD_CNT_MAX - 1)
case (bit_cnt)
4'd0: uart_txd <= START_BIT; // 起始位
4'd1: uart_txd <= data_reg[0]; // 数据最低位
4'd2: uart_txd <= data_reg[1];
4'd3: uart_txd <= data_reg[2];
4'd4: uart_txd <= data_reg[3];
4'd5: uart_txd <= data_reg[4];
4'd6: uart_txd <= data_reg[5];
4'd7: uart_txd <= data_reg[6];
4'd8: uart_txd <= data_reg[7]; // 数据最高位
4'd9: uart_txd <= END_BIT; // 停止位
default: uart_txd <= 1'b1; // 默认状态
endcase
end
// 发送就绪信号逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
uart_tx_done <= 1'b0; // 初始化为未就绪
else if ( baud_cnt == 1'd1 && bit_cnt == 4'd9)
uart_tx_done <= 1'b1; // 发送完成,设置发送就绪信号为有效
else
uart_tx_done <= 1'b0; // 发送中
end
endmodule2
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
代码解释
driver.v
作用:发 10 微秒脉冲给超声波模块,等回波,量回波持续多久,换成距离。
核心就两行:
assign trig = cnt_en; // 触发脉冲
distance <= (cnt_us_num * 17) / 1000; // 微秒数换成厘米2
最后把距离拆成个十百千四位(s_g/s_s/s_b/s_q)输出。
uart.v
作用:把一个字节,按"起始位 + 8 位数据 + 停止位",一位一位从 uart_txd 线吐出去。
核心:
- baud_cnt 数 434 下 = 一位的时间
- 数满一位,bit_cnt 加一,换下一位发
- 数据进来先锁存到 data_reg,发的时候一位位取
top.v —— 顶层,把上面两个连起来
作用:
1. 按键消抖)
2. 拿距离的数字,查表转成字符
3. 按顺序一个字一个字喂给 uart_tx,发出去
7、仿真编写
接下来我们对软件进行仿真
`timescale 1ns / 1ps
module HC_SR04_top_tb;
reg clk;
reg rst_n;
reg key_in;
reg echo;
wire trig;
wire uart_txd;
HC_SR04_top u_HC_SR04_top(
.clk (clk ),
.rst_n (rst_n ),
.key_in (key_in ),
.echo (echo ),
.trig (trig ),
.uart_txd (uart_txd )
);
initial clk = 1'b0;
always #10 clk = ~clk;
initial begin
$dumpfile("HC_SR04_top_tb.vcd");
$dumpvars(0, HC_SR04_top_tb);
end
initial begin
rst_n = 1'b0; // 先复位
key_in = 1'b1; // 按键松开
echo = 1'b0; // echo 空闲
#200;
rst_n = 1'b1; // 释放复位
#200;
// 模拟按键按下: 拉低 25ms(超过 20ms 消抖时间, 才能被认作有效按键)
key_in = 1'b0;
#25_000_000;
key_in = 1'b1;
#10_000_000;
$finish;
end
initial begin
forever begin
@(posedge trig);
#100_000; // 延迟 100us(模拟 HC-SR04 响应时间)
echo = 1'b1; // echo 拉高
#2_000_000; // 保持 2ms
echo = 1'b0; // echo 拉低
end
end
endmodule2
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
可以看到按键按下后,trig信号短暂脉冲,然后echo开始拉高。
随后串口开始发送信息
8、I/O绑定
引脚绑定如图
约束文件源工程中给出
此处需要杜邦线将模块和拓展排针相连
9、程序下载
将程序烧录进板子后,连接串口助手,设置波特率,按下按键即可看到串口打印距离信息