文章目录
- 前言
- 一、实验原理
- 二、Verilog文件
- 2.1 时钟分频
- 2.2 超声波测距
- 2.3 超声波驱动
- 三、实现过程
- 3.1 模块说明
- 3.2 引脚分配
- 三、演示视频
- 总结
- 参考
前言
环境
硬件 DE2-115 HC-SR04超声波传感器
软件 Quartus 18.1
目标结果
使用DE2-115开发板驱动HC-SR04模块,并将所测得数据显示到开发板上的数码管。
模拟倒车雷达,集成蜂鸣器,led和vga提示功能
蜂鸣器提示,小于20cm,1s一响
;小于10cm,0.5s一响
;
LED提示,小于20cm, 全亮提示
;
VGA提示,小于20cm ,显示 警告warning 图片
一、实验原理
参考上一篇博客
基于STM32的HC-SR04超声波测距(PWM蜂鸣器+滤波算法+数据上云-标准库实现)
二、Verilog文件
2.1 时钟分频
定义时钟分频模块,产生周期为1us的时钟信号
//产生一个以微秒为周期的时钟信号clk_us,该信号可用于驱动一些需要精确时间控制的电路
module clk_div(
input wire Clk , // 输入系统时钟,50MHz
input wire Rst_n , // 输入复位信号,低电平有效
output wire clk_us // 输出微秒级时钟信号
);
// 参数声明 1us = 1000ns = 50个时钟周期
parameter CNT_MAX = 19'd50 ; //1us的计数值为 50 * Tclk(20ns)
// 内部线网/寄存器声明
reg [18:0] cnt ; // 定义一个19位的计数器
wire add_cnt ; // 计数器使能信号
wire end_cnt ; // 计数器结束信号,达到最大值时有效
// 计数器的寄存器逻辑
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin // 如果复位信号有效,则计数器清零
cnt <= 'd0;
end
else if(add_cnt)begin // 如果计数器达到最大值,则计数器重置
if(end_cnt)begin
cnt <= 'd0;
end
else begin // 否则计数器继续计数
cnt <= cnt + 1'b1;
end
end
else begin
cnt <= cnt; // 如果计数器未使能,则保持当前值
end
end
// 赋值计数器使能信号,始终使计数器有效
assign add_cnt = 1'b1;
// 赋值计数器结束信号,当计数器使能并且计数值达到CNT_MAX - 1时有效
assign end_cnt = add_cnt && cnt >= CNT_MAX - 19'd1;
// 赋值输出时钟信号,当计数器达到最大值时输出一个脉冲
assign clk_us = end_cnt;
endmodule
2.2 超声波测距
- 实现HC-SR04超声波传感器的触发模块,用于生成触发测距信号(trig)
- 定义HC-SR04超声波传感器的回声模块,用于测量距离并输出检测距离数据(data_o)
module distance_drive (
// 输入输出定义
input wire clk , // 主时钟信号
input wire clk_1 , // 辅助时钟信号,1Mhz
input wire rst_n , // 复位信号,低电平有效
input wire echo , // 回声输入,来自超声波传感器
output reg trig , // 发射触发信号,控制超声波传感器发射
output wire data_out_vld , // 数据有效信号,表示距离数据准备好了
output wire [23:0] distance_data // 距离数据输出,24位宽
);
localparam MAX_DISTANCE = 117647; //最大距离 4m
parameter s_idle = 0;//空闲状态
parameter s_send = 1;//发送触发信号
parameter s_wait = 2;//等待内部发送脉冲
parameter s_accept = 3;//检测回响信号
parameter s_accept_wait = 4;//延时等待
reg echo_r0 ;
reg echo_r1 ;
reg [ 2:0 ] s_current ;
reg [ 2:0 ] s_next ;
reg [ 22 :0 ] cnt ;
reg [ 22:0 ] cnt_wait ;
reg [ 22:0 ] cnt_max ;
reg [ 16:0 ] cnt_distance ;
// reg [ 25:0 ] cnt_distance_r1 ;
// reg [ 19:0 ] cnt_distance_r2 ;
wire accept_start_sig ;
wire accept_stop_sig ;
wire idle_sig ;
wire send_sig ;
// wire wait_sig ;
wire flag_clear_cnt ;
wire flag_clear_cnt_wait ;
reg [ 19:0 ] distance_data_r ;
wire [ 23:0 ] distance_data_r1 ;
assign idle_sig = s_current == s_idle;
assign send_sig = s_current == s_send && flag_clear_cnt;
// assign wait_sig = s_current == s_wait && flag_clear_cnt_wait;
assign accept_wait_sig = s_current == s_accept_wait && flag_clear_cnt_wait;
assign accept_start_sig = s_current == s_wait && echo_r0 && ~echo_r1;
assign accept_stop_sig = s_current == s_accept && (~echo_r0 && echo_r1);
// always @(posedge clk or negedge rst_n) begin
// if(!rst_n) begin
// cnt_distance_r1 <= 0;
// // cnt_distance_r2 <= 0;
// end
// else begin
// cnt_distance_r1 <= cnt_distance * 340 / 100;
// // cnt_distance_r2 <= cnt_distance_r1 >> 4;
// end
// end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
echo_r0 <= 0;
echo_r1 <= 0;
end
else begin
echo_r0 <= echo;
echo_r1 <= echo_r0;
end
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
s_current <= s_idle;
end
else begin
s_current <= s_next;
end
end
always @(*) begin
case (s_current)
s_idle : begin
if(idle_sig) begin
s_next = s_send;
end
else begin
s_next = s_idle;
end
end
s_send : begin
if(send_sig) begin
s_next = s_wait;
end
else begin
s_next = s_send;
end
end
s_wait : begin
if(accept_start_sig) begin
s_next = s_accept;
end
else begin
s_next = s_wait;
end
end
s_accept : begin
if(accept_stop_sig) begin
s_next = s_accept_wait;
end
else begin
s_next = s_accept;
end
end
s_accept_wait : begin
if(accept_wait_sig) begin
s_next <= s_idle;
end
else begin
s_next <= s_accept_wait;
end
end
default: s_next = s_idle;
endcase
end
//距离
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
distance_data_r <= 0;
end
else if(accept_stop_sig) begin
distance_data_r <= cnt_distance * 340 / 200;
end
end
//转BCD码
assign distance_data_r1[3:0] = distance_data_r % 10;
assign distance_data_r1[7:4] = distance_data_r / 10 % 10;
assign distance_data_r1[11:8] = distance_data_r / 100 % 10;
assign distance_data_r1[15:12] = distance_data_r / 1000 % 10;
assign distance_data_r1[19:16] = distance_data_r / 10000 % 10;
assign distance_data_r1[23:20] = distance_data_r / 100000 % 10;
assign data_out_vld = accept_wait_sig;
assign distance_data = distance_data_r1;
//回响信号计数器
always @(posedge clk_1 or negedge rst_n) begin
if(!rst_n) begin
cnt_distance <= 0;
end
else if(accept_start_sig) begin
cnt_distance <= 0;
end
else if(s_current == s_accept) begin
cnt_distance <= cnt_distance + 1;
end
else begin
cnt_distance <= 0;
end
end
//发送触发信号
always @(posedge clk_1 or negedge rst_n) begin
case (s_current)
s_idle : begin
trig <= 0;
end
s_send : begin
trig <= 1;
end
s_wait : begin
trig <= 0;
end
s_accept : begin
trig <= 0;
end
s_accept_wait : begin
trig <= 0;
end
default: begin
trig <= 0;
end
endcase
end
//等待发送玩脉冲
always @( posedge clk_1 or negedge rst_n ) begin
if ( !rst_n ) begin
cnt <= 0;
end
else if ( s_current == s_send ) begin
if ( flag_clear_cnt == 9 ) begin
cnt <= 0;
end
else begin
cnt <= cnt + 1;
end
end
else begin
cnt <= 0;
end
end
assign flag_clear_cnt = cnt == 9;
//延时计数器
always @( posedge clk_1 or negedge rst_n ) begin
if ( !rst_n ) begin
cnt_wait <= 0;
end
else if ( s_current == s_accept_wait ) begin
if ( flag_clear_cnt_wait ) begin
cnt_wait <= 0;
end
else begin
cnt_wait <= cnt_wait + 1;
end
end
else begin
cnt_wait <= 0;
end
end
assign flag_clear_cnt_wait = cnt_wait == 250_000;
endmodule //distance
2.3 超声波驱动
查看平台手册,发现DE2-115开发板不涉及位选信号,每个段选信号都有一个单独的引脚。
数码管驱动器模块代码如下,用于将输入的数据(data_o)转换为对应的数码管显示:
// seg_driver模块定义开始,用于驱动七段显示器
module seg_driver(
input wire Clk, // 时钟信号输入
input wire Rst_n, // 复位信号输入,低电平有效
input wire [18:0] data_o, // 输入的数字数据,即测得的距离数据
//第1个七段显示器的段选信号输出
output wire [6:0] hex1 ,
output wire [6:0] hex2 ,
output wire [6:0] hex3 ,
output wire [6:0] hex4 ,
output wire [6:0] hex5 ,
output wire [6:0] hex6 ,
output wire [6:0] hex7 ,
output wire [6:0] hex8
);
/*
通过cnt_20us计数器实现动态扫描定时,sel_r选择寄存器用于选择当前激活的七段显示器。
根据sel_r的选择,从data_o中提取相应的数字,并将其转换为七段显示器的段选编码seg_r。
最后,根据sel_r的选择,将seg_r的值赋给对应的七段显示器输出寄存器,并从这些寄存器输出到实际的七段显示器硬件上。
NOTION和FUSHU参数分别定义了消隐和小数点的编码,用于控制七段显示器的显示。
*/
// 参数定义
parameter NOTION = 4'd10,// 定义数字"10"用于消隐的编码
FUSHU = 4'd11;
parameter MAX20us = 10'd1000;// 定义20微秒计数器的最大值
reg [9:0] cnt_20us ; // 20微秒计数器
reg [7:0] sel_r ; // 选择寄存器,用于动态扫描控制 片选信号
reg [3:0] number ; // 要显示的数字 0-9
reg [6:0] seg_r ; // 七段显示器的段选编码 段选信号
// 第n个七段显示器的段选编码寄存器
reg [6:0] hex1_r ;
reg [6:0] hex2_r ;
reg [6:0] hex3_r ;
reg [6:0] hex4_r ;
reg [6:0] hex5_r ;
reg [6:0] hex6_r ;
reg [6:0] hex7_r ;
reg [6:0] hex8_r ;
// 20微秒计数器逻辑,用于动态扫描定时
always @(posedge Clk or negedge Rst_n) begin
if (!Rst_n) begin
cnt_20us <= 10'd0;
end
else if (cnt_20us == MAX20us - 1'd1) begin
cnt_20us <= 10'd0;
end
else begin
cnt_20us <= cnt_20us + 1'd1;
end
end
// 单个信号sel_r位 片选 拼接约束,实现动态扫描
always @(posedge Clk or negedge Rst_n) begin
if (!Rst_n) begin
sel_r <= 8'b11_11_11_10;
end
else if (cnt_20us == MAX20us - 1'd1) begin
sel_r <= {sel_r[6:0],sel_r[7]};//向左循环移动
end
else begin
sel_r <= sel_r;
end
end
// 根据选择信号sel_r获取要显示的数字
always @(*) begin
case (sel_r)//片选信号
8'b11_11_11_10: number = NOTION ;//1
8'b11_11_11_01: number = data_o/10_0000 ;//2
8'b11_11_10_11: number = (data_o%10_0000)/1_0000 ;//3
8'b11_11_01_11: number = ((data_o%10_0000)%1_0000)/1000 ;//4
8'b11_10_11_11: number = FUSHU ;//5
8'b11_01_11_11: number = (((data_o%10_0000)%1_0000)%1000)/100 ;//6
8'b10_11_11_11: number = ((((data_o%10_0000)%1_0000)%1000)%100)/10 ;//7
8'b01_11_11_11: number = ((((data_o%10_0000)%1_0000)%1000)%100)%10 ;//8
default: number = 4'd0 ;
endcase
end
// 根据数字解析出对应的七段显示器段选值seg_r, 低电平点亮,高电平熄灭
always @(*) begin
case (number) //654_3210
4'd0 : seg_r = 7'b100_0000;//_ 7 段数码管的点在 DE2-115 开发板上是不可用的。
4'd1 : seg_r = 7'b111_1001;//1
4'd2 : seg_r = 7'b010_0100;//2
4'd3 : seg_r = 7'b011_0000;//3
4'd4 : seg_r = 7'b001_1001;//4
4'd5 : seg_r = 7'b001_0010;//5
4'd6 : seg_r = 7'b000_0010;//6
4'd7 : seg_r = 7'b111_1000;//7
4'd8 : seg_r = 7'b000_0000;//8
4'd9 : seg_r = 7'b001_0000;//9
NOTION : seg_r = 7'b111_1111;// 灭 定义消隐的编码
FUSHU : seg_r = 7'b111_0111;// 定义小数点的编码
default : seg_r = 7'b111_1111;// 默认消隐
endcase
end
//根据片选信号sel_r
// 根据选择信号sel_r将seg_r值赋给对应的七段显示器寄存器
always @(*) begin
case (sel_r)//sel_r片选信号
8'b11_11_11_10: hex1_r = seg_r;//seg_r段选信号
8'b11_11_11_01: hex2_r = seg_r;
8'b11_11_10_11: hex3_r = seg_r;
8'b11_11_01_11: hex4_r = seg_r;
8'b11_10_11_11: hex5_r = seg_r;
8'b11_01_11_11: hex6_r = seg_r;
8'b10_11_11_11: hex7_r = seg_r;
8'b01_11_11_11: hex8_r = seg_r;
default: seg_r = seg_r;
endcase
end
// 将寄存器的值赋予输出端口
assign hex1 = hex1_r;
assign hex2 = hex2_r;
assign hex3 = hex3_r;
assign hex4 = hex4_r;
assign hex5 = hex5_r;
assign hex6 = hex6_r;
assign hex7 = hex7_r;
assign hex8 = hex8_r;
endmodule
三、实现过程
3.1 模块说明
这里要求超声波模块的正负极分别接入5V和GND,其余trigger和echo自由接线,我这里使用的是GPIO[0]和GPIO[1]
3.2 引脚分配
首先这里提出引脚配置,其中trig和echo引脚与自己所接线的位置向同即可
三、演示视频
FPGA基于DE2-115开发板驱动HC_SR04超声波测距模块|集成蜂鸣器,led和vga提示功能
总结
本次测试借用了一些学长的代码,也带有自己的一些思考,补全了学长没有写进代码的部分。由于代码中设计的寄存器只有那么多位数,也和驱动本身的下限,决定了这次设计只能测试大概4~247cm左右的距离。能够自己补全一部分代码,对于我来说还是很有成就感的,下次见
参考
FPGA基于DE2-115 开发板板和HC_SR04驱动的超声波测距
基于DE2 115开发板驱动HC_SR04超声波测距模块