文章目录
实验环境
开发板:野火征途-ep4ce10
开发环境:quartus II
实验目的及要求
掌握HC_SR04超声波测距模块的原理和使用方法,通过fpga开发板对HC_SR04模块进行驱动,进行距离测量,将测量到的数据显示到fpga开发板的数码管上,通过串口发送到上位机。
一、超声波测距原理
HC-SR04超声波测距模块可提供 2cm-400cm的非接触式距离感测功能,测距精度可达高到 3mm;模块包括超声波发射器、接收器与控制电路。图1为HC-SR04外观,其基本工作原理为给予此超声波测距模块一触发信号后模块发射超声波,当超声波投射到物体而反射回来时,模块输出一回响信号,以触发信号和回响信号间的时间差,来判定物体的距离。图2为模块时序图。
以上时序图表明只需要提供一个10uS 以上脉冲触发信号,该模块内部将发出8 个40kHz 周期电平并检测回波。一旦检测到有回波信号则输出回响信号。回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。
公式:uS/58=厘米或者uS/148=英寸;或是:距离=高电平时间*声速(340M/S)/2;建议测量周期为60ms 以上,以防止发射信号对回响信号的影响
FPGA各模块代码编写
本次实验中包含的FPGA模块包括了hc_sr_echo、hc_sr_trig、filter、FPGA_UART、bcd_8421、hc595_ctrl、seg_595_dynamic、seg_dynamic、hc_sr_top.
超声波测距部分
- HC-SR04超声波测距模块进行一次距离测量就需要一个trig信号对模块进行触发控制。因此模块hc_sr_trig的作用就是每隔100ms就产生一10us的高电平trig信号。
模块代码如下图所示:
module hc_sr_trig(
input wire clk ,
input wire rst_n ,
output wire trig //触发测距信号
);
//trig:超声波触发信号,高电平至少为10us,同时考虑到信号不要重叠,所以当距离为5000mm时的来回时间做为超声波触发信号的周期T
//T=(5/340) * 1000ms = 14.7ms,来回就是29.4ms,所以就让触发脉冲的周期至少为30ms,高电平时间为10us
//为了防止发射信号对回响信号产生影响,这里直接设定为100ms的周期,高电平为10us
parameter CNT_10US_MAX = 9'd500;
parameter CNT_100MS_MAX = 23'd5000_000;
reg [22:0] cnt_100ms ;
wire cnt_100ms_flag ;
reg trig_r;
//cnt_100ms and cnt_100ms_flag
assign cnt_100ms_flag = cnt_100ms==CNT_100MS_MAX - 1'b1;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_100ms<=23'd0;
end
else if(cnt_100ms_flag) begin
cnt_100ms<=23'd0;
end
else begin
cnt_100ms<=cnt_100ms+1'b1;
end
end
//trig
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
trig_r<=1'b0;
end
else if(cnt_100ms<CNT_10US_MAX) begin
trig_r<=1'b1;
end
else begin
trig_r<=1'b0;
end
end
assign trig = trig_r;
endmodule
- HC_SR04超声波模块距离测量,输入测距使能信号echo,输出下降沿标志的拍打信号和测得的距离数据
具体代码如下:
module hc_sr_echo(
input wire clk ,
input wire rst_n ,
input wire echo ,
output reg fall_flag_r1, //下降沿标志的打拍信号
output wire [12:0] data_o //检测距离,保留3位小数,*1000实现
);
/*计算:s=340*t/2 m = 340_000 mm * t /2
记脉冲个数:N
s=N*20*340_000/1000_000_000/2 mm = N*0.0034 mm = N*34/10000 ;
5000/0.0034=1470588 21bit ; 34 6bit
*/
reg echo_r1 ;
reg echo_r2 ;
wire raise_flag ;//上升沿标志信号
wire fall_flag ;//下降沿标志信号
reg [20:0] cnt_pulse ;//考虑的范围25~4000mm
reg [20:0] temp_pulse ;
reg [12:0] data_bin ;
//raise_flag
assign raise_flag = ((~echo_r2) && (echo_r1)) ? 1'b1 : 1'b0 ;
assign fall_flag = ((echo_r2) && (~echo_r1)) ? 1'b1 : 1'b0 ;
//beat time
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
echo_r1<=1'b0;
echo_r2<=1'b0;
end
else begin
echo_r1<=echo;
echo_r2<=echo_r1;
end
end
//cnt_pulse
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cnt_pulse<=21'd0;
end
else if(raise_flag) begin
cnt_pulse<=21'd1;//直接从1开始计数,计算的时候就不用进行加1操作了
end
else if(echo_r2) begin
cnt_pulse<=cnt_pulse+1'b1;
end
else begin
cnt_pulse<=cnt_pulse;
end
end
//temp_pulse
always @ (posedge clk or negedge rst_n ) begin
if(!rst_n) begin
temp_pulse<=21'd0;
end
else if(fall_flag) begin
temp_pulse<=cnt_pulse;
end
else begin
temp_pulse<=temp_pulse;
end
end
//计算距离,以mm为单位,s=N*34/10000
always @ (posedge clk or negedge rst_n ) begin
if(!rst_n) begin
fall_flag_r1<=1'b0;
end
else begin
fall_flag_r1<=fall_flag;
end
end
//data_bin
always @ (posedge clk or negedge rst_n ) begin
if(!rst_n) begin
data_bin<=1'b0;
end
else if(fall_flag_r1) begin
data_bin<=temp_pulse*34/10000;
end
else begin
data_bin<=data_bin;
end
end
assign data_o = data_bin;
endmodule
数据滤波
- filter模块这里采用均值滤波的方式,每采集到八个距离信息就进行一次滤波,输入测距模块采集到的数据,取八个数据的平均值作为采集到的距离数据进行输出。代码如下:
//均值滤波算法
module filter
(
input wire clk ,
input wire rst_n ,
input wire [12:0] data_bin ,
input wire fall_flag_r1 ,
output wire [12:0] data_ave
);
reg [12:0] data_temp [7:0];
reg [15:0] data_sum ;
//data_temp:移位暂存8次测距的数值
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
data_temp[0]<=13'd0;
data_temp[1]<=13'd0;
data_temp[2]<=13'd0;
data_temp[3]<=13'd0;
data_temp[4]<=13'd0;
data_temp[5]<=13'd0;
data_temp[6]<=13'd0;
data_temp[7]<=13'd0;
end
else if(fall_flag_r1) begin
data_temp[0]<=data_bin ;
data_temp[1]<=data_temp[0] ;
data_temp[2]<=data_temp[1] ;
data_temp[3]<=data_temp[2] ;
data_temp[4]<=data_temp[3] ;
data_temp[5]<=data_temp[4] ;
data_temp[6]<=data_temp[5] ;
data_temp[7]<=data_temp[6] ;
end
end
//data_sum:暂存的8个数据之和
always @ (posedge clk or negedge rst_n ) begin
if(!rst_n) begin
data_sum<=1'd0;
end
else begin
data_sum<=data_temp[0]+data_temp[1]+data_temp[2]+data_temp[3]+data_temp[4]+data_temp[5]+data_temp[6]+data_temp[7];
end
end
//data_ave
assign data_ave = data_sum[15:3] ;//截取高13位,相当于右移3位,即除以8的效果。
endmodule
串口模块代码编写
- 串口采用RS232 UART IP核,代码如下:
Quartus RS232 UART IP核 使用 Verilog
module FPGA_UART (
input wire clk ,
input wire rst_n ,
input wire uart_rx ,
input wire [16:00] data_o ,
output wire uart_tx
);
parameter state_wait = 4'd0;
parameter state_send = 4'd1;
reg [7:0] uart_send_data = 8'd0;
reg uart_send_valid;
wire uart_send_ready;
reg [31:0] counter = 32'd0;
reg [3:0] state = state_wait;
reg send_flag = 0; // 这个标志位的作用应该是表示移出数据标志位,
reg end_flag = 0;
wire [15:00] data_buf; // 测试数据
assign data_buf = data_o[15:0];
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
uart_send_valid <= 0;
counter <= 32'd0;
uart_send_data <= 8'd0;
state <= state_wait;
send_flag = 0;
end
else begin
case (state)
state_wait: begin
counter <= counter + 32'd1;
if(counter >= 32'd24999999) begin
counter <= 32'd0;
state <= state_send;
uart_send_data <= data_buf[15:8]; // 提前移出第一个字节
end
end
state_send: begin
if(end_flag) begin
end_flag <= 0;
uart_send_valid <= 0;
send_flag <= 0;
state <= state_wait;
end
else begin
uart_send_valid <= 1;
send_flag <= 1;
if(send_flag) begin // 这里是为了让uart_send_valid维持一个周期后等第一个字节发送完成后移出后面的字节
uart_send_data <= data_buf[7:0]; // 模拟字节移出
end_flag = 1; // 强行退出发送状态,实际运用中会判断数据长度
end
end
end
endcase
end
end
uart u0 (
.clk_clk (clk), // clk.clk
.reset_reset_n (rst_n), // reset.reset_n
// .rs232_0_from_uart_ready (<connected-to-rs232_0_from_uart_ready>), // rs232_0_avalon_data_receive_source.ready
// .rs232_0_from_uart_data (<connected-to-rs232_0_from_uart_data>), // .data
// .rs232_0_from_uart_error (<connected-to-rs232_0_from_uart_error>), // .error
// .rs232_0_from_uart_valid (<connected-to-rs232_0_from_uart_valid>), // .valid
.rs232_0_to_uart_data (uart_send_data), // rs232_0_avalon_data_transmit_sink.data
// .rs232_0_to_uart_error (<connected-to-rs232_0_to_uart_error>), // .error
.rs232_0_to_uart_valid (uart_send_valid), // .valid
.rs232_0_to_uart_ready (uart_send_ready), // .ready
.rs232_0_UART_RXD (uart_rx), // rs232_0_external_interface.RXD
.rs232_0_UART_TXD (uart_tx) // .TXD
);
endmodule
数码管模块代码编写
bcd_8421转码模块
- bcd_8421模块的作用是输入二进制的距离数据,并将其转化为数码管显示所需的bcd8421码进行输出,该模块代码如下:
`timescale 1ns/1ns
module bcd_8421
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [19:0] data , //输入需要转换的数据
output reg [3:0] unit , //个位BCD码
output reg [3:0] ten , //十位BCD码
output reg [3:0] hun , //百位BCD码
output reg [3:0] tho , //千位BCD码
output reg [3:0] t_tho , //万位BCD码
output reg [3:0] h_hun //十万位BCD码
);
//reg define
reg [4:0] cnt_shift ; //移位判断计数器
reg [43:0] data_shift ; //移位判断数据寄存器
reg shift_flag ; //移位判断标志信号
//cnt_shift:从0到21循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_shift <= 5'd0;
else if((cnt_shift == 5'd21) && (shift_flag == 1'b1))
cnt_shift <= 5'd0;
else if(shift_flag == 1'b1)
cnt_shift <= cnt_shift + 1'b1;
else
cnt_shift <= cnt_shift;
//data_shift:计数器为0时赋初值,计数器为1~20时进行移位判断操作
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_shift <= 44'b0;
else if(cnt_shift == 5'd0)
data_shift <= {24'b0,data};
else if((cnt_shift <= 20) && (shift_flag == 1'b0))
begin
data_shift[23:20] <= (data_shift[23:20] > 4) ? (data_shift[23:20] + 2'd3) : (data_shift[23:20]);
data_shift[27:24] <= (data_shift[27:24] > 4) ? (data_shift[27:24] + 2'd3) : (data_shift[27:24]);
data_shift[31:28] <= (data_shift[31:28] > 4) ? (data_shift[31:28] + 2'd3) : (data_shift[31:28]);
data_shift[35:32] <= (data_shift[35:32] > 4) ? (data_shift[35:32] + 2'd3) : (data_shift[35:32]);
data_shift[39:36] <= (data_shift[39:36] > 4) ? (data_shift[39:36] + 2'd3) : (data_shift[39:36]);
data_shift[43:40] <= (data_shift[43:40] > 4) ? (data_shift[43:40] + 2'd3) : (data_shift[43:40]);
end
else if((cnt_shift <= 20) && (shift_flag == 1'b1))
data_shift <= data_shift << 1;
else
data_shift <= data_shift;
//shift_flag:移位判断标志信号,用于控制移位判断的先后顺序
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
shift_flag <= 1'b0;
else
shift_flag <= ~shift_flag;
//当计数器等于20时,移位判断操作完成,对各个位数的BCD码进行赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
unit <= 4'b0;
ten <= 4'b0;
hun <= 4'b0;
tho <= 4'b0;
t_tho <= 4'b0;
h_hun <= 4'b0;
end
else if(cnt_shift == 5'd21)
begin
unit <= data_shift[23:20];
ten <= data_shift[27:24];
hun <= data_shift[31:28];
tho <= data_shift[35:32];
t_tho <= data_shift[39:36];
h_hun <= data_shift[43:40];
end
endmodule
seg_dynamic模块
- seg_dynamic模块的作用是输入表示距离的bcd_8421数据,小数点位置数据,使能信号,正负符号信号。在使能信号seg_en为高电平的情况下,通过输入的信息产生数码管显示所需的段选信号和位选信号,该模块的代码如下:
`timescale 1ns/1ns
module seg_dynamic
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低有效
input wire [19:0] data , //数码管要显示的值
input wire [5:0] point , //小数点显示,高电平有效
input wire seg_en , //数码管使能信号,高电平有效
input wire sign , //符号位,高电平显示负号
output reg [5:0] sel , //数码管位选信号
output reg [7:0] seg //数码管段选信号
);
//parameter define
parameter CNT_MAX = 16'd49_999; //数码管刷新时间计数最大值
//wire define
wire [3:0] unit ; //个位数
wire [3:0] ten ; //十位数
wire [3:0] hun ; //百位数
wire [3:0] tho ; //千位数
wire [3:0] t_tho ; //万位数
wire [3:0] h_hun ; //十万位数
//reg define
reg [23:0] data_reg ; //待显示数据寄存器
reg [15:0] cnt_1ms ; //1ms计数器
reg flag_1ms ; //1ms标志信号
reg [2:0] cnt_sel ; //数码管位选计数器
reg [5:0] sel_reg ; //位选信号
reg [3:0] data_disp ; //当前数码管显示的数据
reg dot_disp ; //当前数码管显示的小数点
//data_reg:控制数码管显示数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_reg <= 24'b0;
//若显示的十进制数的十万位为非零数据或需显示小数点,则六个数码管全显示
else if((h_hun) || (point[5]))
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
//若显示的十进制数的万位为非零数据或需显示小数点,则值显示在5个数码管上
//打比方我们输入的十进制数据为20’d12345,我们就让数码管显示12345而不是012345
else if(((t_tho) || (point[4])) && (sign == 1'b1))//显示负号
data_reg <= {4'd10,t_tho,tho,hun,ten,unit};//4'd10我们定义为显示负号
else if(((t_tho) || (point[4])) && (sign == 1'b0))
data_reg <= {4'd11,t_tho,tho,hun,ten,unit};//4'd11我们定义为不显示
//若显示的十进制数的千位为非零数据或需显示小数点,则值显示4个数码管
else if(((tho) || (point[3])) && (sign == 1'b1))
data_reg <= {4'd11,4'd10,tho,hun,ten,unit};
else if(((tho) || (point[3])) && (sign == 1'b0))
data_reg <= {4'd11,4'd11,tho,hun,ten,unit};
//若显示的十进制数的百位为非零数据或需显示小数点,则值显示3个数码管
else if(((hun) || (point[2])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd10,hun,ten,unit};
else if(((hun) || (point[2])) && (sign == 1'b0))
data_reg <= {4'd11,4'd11,4'd11,hun,ten,unit};
//若显示的十进制数的十位为非零数据或需显示小数点,则值显示2个数码管
else if(((ten) || (point[1])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd11,4'd10,ten,unit};
else if(((ten) || (point[1])) && (sign == 1'b0))
data_reg <= {4'd11,4'd11,4'd11,4'd11,ten,unit};
//若显示的十进制数的个位且需显示负号
else if(((unit) || (point[0])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd10,unit};
//若上面都不满足都只显示一位数码管
else
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd11,unit};
//cnt_1ms:1ms循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1ms <= 16'd0;
else if(cnt_1ms == CNT_MAX)
cnt_1ms <= 16'd0;
else
cnt_1ms <= cnt_1ms + 1'b1;
//flag_1ms:1ms标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
flag_1ms <= 1'b0;
else if(cnt_1ms == CNT_MAX - 1'b1)
flag_1ms <= 1'b1;
else
flag_1ms <= 1'b0;
//cnt_sel:从0到5循环数,用于选择当前显示的数码管
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_sel <= 3'd0;
else if((cnt_sel == 3'd5) && (flag_1ms == 1'b1))
cnt_sel <= 3'd0;
else if(flag_1ms == 1'b1)
cnt_sel <= cnt_sel + 1'b1;
else
cnt_sel <= cnt_sel;
//数码管位选信号寄存器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel_reg <= 6'b000_000;
else if((cnt_sel == 3'd0) && (flag_1ms == 1'b1))
sel_reg <= 6'b000_001;
else if(flag_1ms == 1'b1)
sel_reg <= sel_reg << 1;
else
sel_reg <= sel_reg;
//控制数码管的位选信号,使六个数码管轮流显示
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_disp <= 4'b0;
else if((seg_en == 1'b1) && (flag_1ms == 1'b1))
case(cnt_sel)
3'd0: data_disp <= data_reg[3:0] ; //给第1个数码管赋个位值
3'd1: data_disp <= data_reg[7:4] ; //给第2个数码管赋十位值
3'd2: data_disp <= data_reg[11:8] ; //给第3个数码管赋百位值
3'd3: data_disp <= data_reg[15:12]; //给第4个数码管赋千位值
3'd4: data_disp <= data_reg[19:16]; //给第5个数码管赋万位值
3'd5: data_disp <= data_reg[23:20]; //给第6个数码管赋十万位值
default:data_disp <= 4'b0 ;
endcase
else
data_disp <= data_disp;
//dot_disp:小数点低电平点亮,需对小数点有效信号取反
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
dot_disp <= 1'b1;
else if(flag_1ms == 1'b1)
dot_disp <= ~point[cnt_sel];
else
dot_disp <= dot_disp;
//控制数码管段选信号,显示数字
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
seg <= 8'b1111_1111;
else
case(data_disp)
4'd0 : seg <= {dot_disp,7'b100_0000}; //显示数字0
4'd1 : seg <= {dot_disp,7'b111_1001}; //显示数字1
4'd2 : seg <= {dot_disp,7'b010_0100}; //显示数字2
4'd3 : seg <= {dot_disp,7'b011_0000}; //显示数字3
4'd4 : seg <= {dot_disp,7'b001_1001}; //显示数字4
4'd5 : seg <= {dot_disp,7'b001_0010}; //显示数字5
4'd6 : seg <= {dot_disp,7'b000_0010}; //显示数字6
4'd7 : seg <= {dot_disp,7'b111_1000}; //显示数字7
4'd8 : seg <= {dot_disp,7'b000_0000}; //显示数字8
4'd9 : seg <= {dot_disp,7'b001_0000}; //显示数字9
4'd10 : seg <= 8'b1011_1111 ; //显示负号
4'd11 : seg <= 8'b1111_1111 ; //不显示任何字符
default:seg <= 8'b1100_0000;
endcase
//sel:数码管位选信号赋值
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sel <= 6'b000_000;
else
sel <= sel_reg;
bcd_8421 bcd_8421_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.data (data ), //输入需要转换的数据
.unit (unit ), //个位BCD码
.ten (ten ), //十位BCD码
.hun (hun ), //百位BCD码
.tho (tho ), //千位BCD码
.t_tho (t_tho ), //万位BCD码
.h_hun (h_hun ) //十万位BCD码
);
endmodule
hc595_ctrl 模块
- hc595_ctrl 模块的作用是输入段选信号和位选信号,输出控制数码管显示的两个芯片所需要的四个信号,stcp(数据存储器时钟)、shcp(移位寄存器时钟)、ds(串行数据输入)、oe(使能信号,低有效)。通过该模块就能够直接控制数码管显示数据。该模块代码如下:
`timescale 1ns/1ns
module hc595_ctrl
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低有效
input wire [5:0] sel , //数码管位选信号
input wire [7:0] seg , //数码管段选信号
output reg stcp , //数据存储器时钟
output reg shcp , //移位寄存器时钟
output reg ds , //串行数据输入
output wire oe //使能信号,低有效
);
//reg define
reg [1:0] cnt_4 ; //分频计数器
reg [3:0] cnt_bit ; //传输位数计数器
//wire define
wire [13:0] data ; //数码管信号寄存
//将数码管信号寄存
assign data = {seg[0],seg[1],seg[2],seg[3],seg[4],seg[5],seg[6],seg[7],sel};
//将复位取反后赋值给其即可
assign oe = ~sys_rst_n;
//分频计数器:0~3循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_4 <= 2'd0;
else if(cnt_4 == 2'd3)
cnt_4 <= 2'd0;
else
cnt_4 <= cnt_4 + 1'b1;
//cnt_bit:每输入一位数据加一
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_bit <= 4'd0;
else if(cnt_4 == 2'd3 && cnt_bit == 4'd13)
cnt_bit <= 4'd0;
else if(cnt_4 == 2'd3)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
//stcp:14个信号传输完成之后产生一个上升沿
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
stcp <= 1'b0;
else if(cnt_bit == 4'd13 && cnt_4 == 2'd3)
stcp <= 1'b1;
else
stcp <= 1'b0;
//shcp:产生四分频移位时钟
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
shcp <= 1'b0;
else if(cnt_4 >= 4'd2)
shcp <= 1'b1;
else
shcp <= 1'b0;
//ds:将寄存器里存储的数码管信号输入即
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
ds <= 1'b0;
else if(cnt_4 == 2'd0)
ds <= data[cnt_bit];
else
ds <= ds;
endmodule
seg_595_dynamic数码管动态显示模块
- seg_595_dynamic数码管显示顶层模块,将以上几个数码管显示,模块进行实例化,使之协同工作,代码如下:
`timescale 1ns/1ns
module seg_595_dynamic
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低有效
input wire [19:0] data , //数码管要显示的值
input wire [5:0] point , //小数点显示,高电平有效
input wire seg_en , //数码管使能信号,高电平有效
input wire sign , //符号位,高电平显示负号
output wire stcp , //数据存储器时钟
output wire shcp , //移位寄存器时钟
output wire ds , //串行数据输入
output wire oe //使能信号
);
//wire define
wire [5:0] sel; //数码管位选信号
wire [7:0] seg; //数码管段选信号
//---------- seg_dynamic_inst ----------
seg_dynamic seg_dynamic_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低有效
.data (data ), //数码管要显示的值
.point (point ), //小数点显示,高电平有效
.seg_en (seg_en ), //数码管使能信号,高电平有效
.sign (sign ), //符号位,高电平显示负号
.sel (sel ), //数码管位选信号
.seg (seg ) //数码管段选信号
);
//---------- hc595_ctrl_inst ----------
hc595_ctrl hc595_ctrl_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低有效
.sel (sel ), //数码管位选信号
.seg (seg ), //数码管段选信号
.stcp (stcp ), //输出数据存储寄时钟
.shcp (shcp ), //移位寄存器的时钟输入
.ds (ds ), //串行数据输入
.oe (oe )
);
endmodule
蜂鸣器模块代码编写
- beep_driver模块的作用是输入蜂鸣器使能信号en和单次鸣叫时长TIME。以下是该模块的代码
`timescale 1ns/1ns
module beep_driver
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //系统复位,低有效
input wire [28:0] TIME ,
input wire en , //蜂鸣器使能,高有效
input wire long_flag , //长响标志
output reg beep //输出蜂鸣器控制信号
);
parameter MI = 18'd151514 ;//"咪"音调分频计数值(频率330)
//reg define
reg [24:0] cnt ; //0.5s计数器
reg [17:0] freq_cnt ; //音调计数器
reg [1:0] cnt_500ms ; //0.5s个数计数
reg [17:0] freq_data ; //音调分频计数值
//wire define
wire [16:0] duty_data ; //占空比计数值
//设置50%占空比:音阶分频计数值的一半即为占空比的高电平数
assign duty_data = freq_data >> 1'b1;
//cnt:0.5s循环计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 25'd0;
else if(cnt == TIME )
cnt <= 25'd0;
else
cnt <= cnt + 1'b1;
//cnt_500ms:对500ms个数进行计数,每个音阶鸣叫时间0.5s,7个音节一循环
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_500ms <= 1'd0;
else if(long_flag == 1'd1 && cnt_500ms == 1'd1)//长响标志
cnt_500ms <= 1'd1;
else if(cnt == TIME && cnt_500ms == 1'd1 && long_flag == 1'd0)
cnt_500ms <= 1'd0;
else if(cnt == TIME)
cnt_500ms <= 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
freq_data <= 17'b0;
else case(cnt_500ms)
0: freq_data <= 17'b0;
1: freq_data <= MI;
default: freq_data <= 17'b0;
endcase
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
freq_cnt <= 18'd0;
else if(freq_cnt == freq_data || cnt == TIME)
freq_cnt <= 18'd0;
else
freq_cnt <= freq_cnt + 1'b1;
//beep:输出蜂鸣器波形
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
beep <= 1'b0;
else if(freq_cnt >= duty_data && en == 1'b1)
beep <= 1'b1;
else
beep <= 1'b0;
endmodule
- 蜂鸣器鸣叫时长设定部分代码,通过不同的距离数据改变蜂鸣器鸣叫时长TIME_500MS_S的值。
//蜂鸣器模块
//控制蜂鸣器使能
wire [19:0] distance_max;//27'b11001000
wire [19:0] distance_min;//27'b101
reg beep_en ;//蜂鸣器使能
reg [28:0] TIME_500MS ;
wire long_flag_s;//长响标志
reg long_flag;
wire [28:0] TIME_500MS_S;
wire en;
assign distance_max = 27'b11001000;
assign distance_min = 27'b00110010;
assign TIME_500MS_S= TIME_500MS;
assign long_flag_s = long_flag;
assign en = beep_en;
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
beep_en <= 1'b0;
TIME_500MS <= 28'd24999_999;
end
else if(data_ave > distance_min && data_ave < distance_max)begin
beep_en <= 1'b1;
long_flag <= 1'b0;
if(data_ave-distance_min < 19'd200 && data_ave-distance_min > 19'd180)
TIME_500MS <= 28'd24999_999;
else if(data_ave-distance_min < 19'd180 && data_ave-distance_min > 19'd160)
TIME_500MS <= 28'd21999_999;
else if(data_ave-distance_min < 19'd160 && data_ave-distance_min > 19'd130)
TIME_500MS <= 28'd18999_999;
else if(data_ave-distance_min < 19'd130 && data_ave-distance_min > 19'd100)
TIME_500MS <= 28'd15999_999;
else if(data_ave-distance_min < 19'd100 && data_ave-distance_min > 19'd70)
TIME_500MS <= 28'd12999_999;
else if(data_ave-distance_min < 19'd70 && data_ave-distance_min > 19'd40)
TIME_500MS <= 28'd9999_999;
else if(data_ave-distance_min < 19'd40 && data_ave-distance_min > 19'd0)
TIME_500MS <= 28'd7999_999;
end
else if(data_ave > distance_max)begin
beep_en <= 1'b0;
long_flag <= 1'b0;
end
else if (data_ave < distance_min)begin
TIME_500MS <= 28'd24999_999;
long_flag <= 1'b1;
beep_en <= 1'b1;
end
end
上板验证结果、、
总结
本次项目实实现了用hc_sr04超声波测距模块进行距离数据的采集和显示。完成的功能包括通过串口传输距离数据,数码管动态显示距离数据,蜂鸣器鸣叫间隔随距离变化而变化。
参考文献
基于FPGA的HC_SR04超声波测距
Quartus RS232 UART IP核 使用 Verilog
基于FPGA的超声波测距