基于FPGA的HC_SR04超声波测距

实验环境

开发板:野火征途-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

串口模块代码编写

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的超声波测距

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值