文章目录
- 前言
- 一、实验原理
- 二、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 , // 输入系统时钟,50MHzinput 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 , // 辅助时钟信号,1Mhzinput 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; //最大距离 4mparameter 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
// endalways @(posedge clk or negedge rst_n) beginif(!rst_n) beginecho_r0 <= 0;echo_r1 <= 0;endelse beginecho_r0 <= echo;echo_r1 <= echo_r0;end
end
always @(posedge clk or negedge rst_n) beginif(!rst_n) begins_current <= s_idle; endelse begins_current <= s_next;end
endalways @(*) begincase (s_current)s_idle : beginif(idle_sig) begins_next = s_send;endelse begins_next = s_idle;endends_send : beginif(send_sig) begins_next = s_wait;endelse begins_next = s_send;endends_wait : beginif(accept_start_sig) begins_next = s_accept;endelse begins_next = s_wait;endends_accept : beginif(accept_stop_sig) begins_next = s_accept_wait;endelse begins_next = s_accept;endends_accept_wait : beginif(accept_wait_sig) begins_next <= s_idle;endelse begins_next <= s_accept_wait;endenddefault: s_next = s_idle;endcase
end//距离
always @(posedge clk or negedge rst_n) beginif(!rst_n) begindistance_data_r <= 0;endelse if(accept_stop_sig) begindistance_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) beginif(!rst_n) begincnt_distance <= 0;endelse if(accept_start_sig) begincnt_distance <= 0;endelse if(s_current == s_accept) begincnt_distance <= cnt_distance + 1;endelse begincnt_distance <= 0;end
end//发送触发信号
always @(posedge clk_1 or negedge rst_n) begincase (s_current)s_idle : begintrig <= 0;ends_send : begintrig <= 1;ends_wait : begintrig <= 0;ends_accept : begintrig <= 0;ends_accept_wait : begintrig <= 0;enddefault: begintrig <= 0;endendcase
end
//等待发送玩脉冲
always @( posedge clk_1 or negedge rst_n ) beginif ( !rst_n ) begincnt <= 0;endelse if ( s_current == s_send ) beginif ( flag_clear_cnt == 9 ) begincnt <= 0;endelse begincnt <= cnt + 1;endendelse begincnt <= 0;end
end
assign flag_clear_cnt = cnt == 9;//延时计数器
always @( posedge clk_1 or negedge rst_n ) beginif ( !rst_n ) begincnt_wait <= 0;endelse if ( s_current == s_accept_wait ) beginif ( flag_clear_cnt_wait ) begincnt_wait <= 0;endelse begincnt_wait <= cnt_wait + 1;endendelse begincnt_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) beginif (!Rst_n) begincnt_20us <= 10'd0;endelse if (cnt_20us == MAX20us - 1'd1) begincnt_20us <= 10'd0;endelse begincnt_20us <= cnt_20us + 1'd1;end
end// 单个信号sel_r位 片选 拼接约束,实现动态扫描
always @(posedge Clk or negedge Rst_n) beginif (!Rst_n) beginsel_r <= 8'b11_11_11_10;endelse if (cnt_20us == MAX20us - 1'd1) beginsel_r <= {sel_r[6:0],sel_r[7]};//向左循环移动endelse beginsel_r <= sel_r;end
end// 根据选择信号sel_r获取要显示的数字
always @(*) begincase (sel_r)//片选信号8'b11_11_11_10: number = NOTION ;//18'b11_11_11_01: number = data_o/10_0000 ;//28'b11_11_10_11: number = (data_o%10_0000)/1_0000 ;//38'b11_11_01_11: number = ((data_o%10_0000)%1_0000)/1000 ;//48'b11_10_11_11: number = FUSHU ;//58'b11_01_11_11: number = (((data_o%10_0000)%1_0000)%1000)/100 ;//68'b10_11_11_11: number = ((((data_o%10_0000)%1_0000)%1000)%100)/10 ;//78'b01_11_11_11: number = ((((data_o%10_0000)%1_0000)%1000)%100)%10 ;//8default: number = 4'd0 ;endcase
end// 根据数字解析出对应的七段显示器段选值seg_r, 低电平点亮,高电平熄灭
always @(*) begincase (number) //654_3210 4'd0 : seg_r = 7'b100_0000;//_ 7 段数码管的点在 DE2-115 开发板上是不可用的。4'd1 : seg_r = 7'b111_1001;//14'd2 : seg_r = 7'b010_0100;//24'd3 : seg_r = 7'b011_0000;//34'd4 : seg_r = 7'b001_1001;//44'd5 : seg_r = 7'b001_0010;//54'd6 : seg_r = 7'b000_0010;//64'd7 : seg_r = 7'b111_1000;//74'd8 : seg_r = 7'b000_0000;//84'd9 : seg_r = 7'b001_0000;//9NOTION : 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 @(*) begincase (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超声波测距模块