文章目录
- 前言
- 一、超声波模块介绍
- 1、产品特点
- 2、超声波模块的时序图
- 二、系统设计
- 1、系统框图
- 2、源码
- 3、RTL视图
- 4、效果
- 三、总结
- 四、参考资料
前言
环境:
1、Quartus18.1
2、vscode
3、板子型号:EP4CE6F17C8N
4、超声波模块:HC_SR04
要求:
使用 EP4CE6F17C8开发板驱动 超声波检测模块(HC_SR04 ),并将所测得数据显示到开发板上的数码管上
一、超声波模块介绍
1、产品特点
HC-SR04超声波测距模块可提供2cm-400cm的非接触式距离感测功能,测距精度可达高到3mm;模块包括超声波发射器、接收器与控制电路。
基本工作原理:
(1)采用IO口 TRIG触发测距,给最少10us的高电平信呈。
(⑵)模块自动发送8个40khz的方波,自动检测是否有信号返回;
(3)有信号返回,通过IO口ECHO输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。测试距离=(高电平时间*声速(340M/S))/2;
2、超声波模块的时序图
以上时序图表明你只需要提供一个10uS 以上脉冲触发信号,该模块内部将发出8个40kHz周期电平并检测回波。一旦检测到有回波信号则输出回响信号。回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。
二、系统设计
1、系统框图
2、源码
- HC_SR04_TOP顶层文件:
module HC_SR04_TOP(
input clk ,
input rstn ,
input echo , // 距离信号
output trig , // 触发测距信号
output wire [5:0] sel ,
output wire [7:0] seg
);
wire [18:00] data_o ;
wire clk_us ;
seg_driver u_seg_driver(
.clk (clk ),
.rstn (rstn ),
.data_in (data_o ), //待显示数据
.sel (sel ), // 我这里是8位段选,可以换6位,但是要自己改代码
.seg (seg )
);
clk_div u_clk_div(
.clk (clk ),
.rstn (rstn ),
.clk_us (clk_us )
);
trig_driver u_trig_driver(
.clk_us (clk_us ),
.rstn (rstn ),
.trig (trig )
);
echo_driver u_echo_driver(
.clk (clk ),
.clk_us (clk_us ),
.rstn (rstn ),
.echo (echo ),
.data_o (data_o )
);
//Logic Description
endmodule
- trig_driver超声波驱动模块:
module trig_driver(
input wire clk_us ,
input wire rstn ,
output wire trig //触发测距信号
);
parameter CYCLE_MAX = 19'd29_9999;
reg [18:00] cnt ;
// 10毫秒持续电平输出
always @(posedge clk_us or negedge rstn) begin
if(!rstn) begin
cnt <= 19'd0;
end
else if(cnt == CYCLE_MAX) begin
cnt <= 19'd0;
end
else begin
cnt <= cnt + 19'd1;
end
end
assign trig = cnt < 15 ? 1'b1 : 1'b0;
endmodule
- echo_driver测距模块:
module echo_driver(
input wire clk ,
input wire clk_us ,
input wire rstn ,
input wire echo ,
output wire [18:00] data_o //检测距离,保留3位小数,*1000实现
);
parameter T_MAX = 16'd5_9999;//510cm 对应计数值
reg r1_echo,r2_echo; //边沿检测
wire echo_pos,echo_neg; //
reg [15:00] cnt ;
reg [18:00] data_r ;
//如果使用clk_us 检测边沿,延时2us,差值过大
always @(posedge clk or negedge rstn)begin
if(!rstn)begin
r1_echo <= 1'b0;
r2_echo <= 1'b0;
end
else begin
r1_echo <= echo;
r2_echo <= r1_echo;
end
end
assign echo_pos = r1_echo & ~r2_echo;
assign echo_neg = ~r1_echo & r2_echo;
always @(posedge clk_us or negedge rstn) begin
if(!rstn) begin
cnt <= 16'd0;
end
else if(echo) begin
if(cnt == T_MAX) begin
cnt <= 16'd0;
end
else begin
cnt <= cnt + 16'd1;
end
end
else begin
cnt <= 16'd0;
end
end
always @(posedge clk or negedge rstn)begin
if(!rstn)begin
data_r <= 'd2;
end
else if(echo_neg)begin
data_r <= (cnt << 4) + cnt;
end
else begin
data_r <= data_r;
end
end
assign data_o = data_r >> 1;
endmodule
- 时钟分频模块:
module clk_div(
input wire clk ,
input wire rstn ,
output wire clk_us //
);
parameter CNT_MAX = 19'd49;//1us的计数值为 50 * Tclk(20ns)
reg [5:0] cnt ;
wire add_cnt ;
wire end_cnt ;
// 时钟分频
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
cnt <= 6'd0;
end
else if(cnt == CNT_MAX) begin
cnt <= 6'd0;
end
else begin
cnt <= cnt + 6'd1;
end
end
assign clk_us = cnt >= CNT_MAX ;
endmodule
- seg_driver数码管驱动模块:
module seg_driver(
input wire clk ,
input wire rstn ,
input wire [18:0] data_in , //待显示数据
output reg [5:0] sel , // 我这里是8位段选,可以换6位,但是要自己改代码
output reg [7:0] seg
);
//parameter define
localparam NUM_0 = 8'b1100_0000,
NUM_1 = 8'b1111_1001,
NUM_2 = 8'b1010_0100,
NUM_3 = 8'b1011_0000,
NUM_4 = 8'b1001_1001,
NUM_5 = 8'b1001_0010,
NUM_6 = 8'b1000_0010,
NUM_7 = 8'b1111_1000,
NUM_8 = 8'b1000_0000,
NUM_9 = 8'b1001_0000,
NUM_A = 8'b1000_1000,
NUM_B = 8'b1000_0011,
NUM_C = 8'b1100_0110,
NUM_D = 8'b1010_0001,
NUM_E = 8'b1000_0110,
NUM_F = 8'b1000_1110,
ALL_LIGHT = 8'b0000_0000,
LIT_OUT = 8'b1111_1111,
LINE = 8'b1011_1111;
localparam MAX_10us = 10'd999 ;
//reg 、wire define
reg [3:0] cm_hund ;//100cm
reg [3:0] cm_ten ;//10cm
reg [3:0] cm_unit ;//1cm
reg [3:0] point_1 ;//1mm
reg [3:0] point_2 ;//0.1mm
reg [3:0] point_3 ;//0.01mm
reg [9:0] cnt_10us ;
reg [7:0] num ;// 段选输出判断
always @(posedge clk or negedge rstn)begin
if(!rstn)begin
cm_hund <= 'd0;
cm_ten <= 'd0;
cm_unit <= 'd0;
point_1 <= 'd0;
point_2 <= 'd0;
point_3 <= 'd0;
end
else begin
cm_hund <= data_in / 10 ** 5;
cm_ten <= data_in / 10 ** 4 % 10;
cm_unit <= data_in / 10 ** 3 % 10;
point_1 <= data_in / 10 ** 2 % 10;
point_2 <= data_in / 10 ** 1 % 10;
point_3 <= data_in / 10 ** 0 % 10;
end
end
// 修改后 段选
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
cnt_10us <= 10'd0;
end
else if(cnt_10us == MAX_10us) begin
cnt_10us <= 10'd0;
end
else begin
cnt_10us <= cnt_10us + 10'd1;
end
end
// 数码管位移
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
sel <= 6'b111_110;
end
else if(cnt_10us == MAX_10us) begin
sel <= {sel[0],sel[5:1]};
end
else begin
sel <= sel;
end
end
// 确定输出数字
always @(*) begin
case (sel)
6'b01_1111 : num = hex_data(point_3);
6'b10_1111 : num = hex_data(point_2);
6'b11_0111 : num = hex_data(point_1);
6'b11_1011 : num = hex_data(cm_unit);
6'b11_1101 : num = hex_data(cm_ten) ;
6'b11_1110 : num = hex_data(cm_hund);
// 6'b11_1111 : num = LINE;
// 6'b11_1111 : num = LIT_OUT;
default : num = NUM_0;
endcase
end
// 位选输出
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
seg <= LINE;
end
else begin
case (num)
NUM_0 : seg <= NUM_0 ;
NUM_1 : seg <= NUM_1 ;
NUM_2 : seg <= NUM_2 ;
NUM_3 : seg <= NUM_3 ;
NUM_4 : seg <= NUM_4 ;
NUM_5 : seg <= NUM_5 ;
NUM_6 : seg <= NUM_6 ;
NUM_7 : seg <= NUM_7 ;
NUM_8 : seg <= NUM_8 ;
NUM_9 : seg <= NUM_9 ;
LINE : seg <= LINE ;
LIT_OUT : seg <= LIT_OUT ;
ALL_LIGHT : seg <= ALL_LIGHT;
endcase
end
end
// 函数,4位输入,7位输出,判断要输出的数字
function [7:0] hex_data; //函数不含时序逻辑相关
input [03:00] data_i;//至少一个输入
begin
case(data_i)
4'd0:hex_data = NUM_0;
4'd1:hex_data = NUM_1;
4'd2:hex_data = NUM_2;
4'd3:hex_data = NUM_3;
4'd4:hex_data = NUM_4;
4'd5:hex_data = NUM_5;
4'd6:hex_data = NUM_6;
4'd7:hex_data = NUM_7;
4'd8:hex_data = NUM_8;
4'd9:hex_data = NUM_9;
default:hex_data = ALL_LIGHT;
endcase
end
endfunction
endmodule
3、RTL视图
4、效果
超声波测距
三、总结
数码管的前三位表示百、十、个的厘米单位,后三位为保留小数位,总体来说测距模块还是相对准确的。通过这次的操作,基本理解了超声波模块的使用驱动原理,对数码管的操作更加熟练。
四、参考资料
基于DE2 115开发板驱动HC_SR04超声波测距模块【附源码】