文章目录
- 前言
- 一、超声波模块介绍
- 1、产品特点
- 2、超声波模块的时序图
- 二、系统设计
- 1、系统模块框图
- 2、RTL视图
- 三、源码
- 1、div_clk_us(1us的分频)
- 2、产生驱动超声波的信号
- 3、串口发送模块
- 4、HC_SR04_uart(顶层文件)
- 四、效果
- 五、总结
- 六、参考资料
前言
环境:
1、Quartus18.0
2、vscode
3、板子型号:EP4CE10F17C8
4、超声波模块:HC_SR04
要求:
使用 EP4CE10F17C8开发板驱动 超声波检测模块(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、RTL视图
三、源码
1、div_clk_us(1us的分频)
/**************
芯片晶振为50MHZ,HC_SR04需要一个10us的以上脉冲触发信号
所以这里我们需要对系统时钟进行分频,方便我们产生10us的持续电平
**************/
module div_clk_us (
input sys_clk,
input sys_rst_n,
output wire clk_us
);
//根据晶振换算,1us只需要计数50次即可
parameter [5:0] MAX_us = 6'd49;
reg [5:0] cnt;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)begin
cnt <= 6'd0;
end
else if(cnt == MAX_us)begin
cnt <= 6'd0;
end
else begin
cnt <= cnt + 6'd1;
end
end
assign clk_us = cnt >= MAX_us;
endmodule
2、产生驱动超声波的信号
/****************
根据分频的1us时钟,产生一个持续10us的电平用于驱动HC_SR04
最好是稍微大于10us,这样稳妥一些
****************/
module trig_driver(
input sys_us ,//1us时钟
input sys_rst_n ,
output trig //驱动超声波的信号
);
parameter T = 19'd29_9999;//设置触发信号的周期,这里设置得越小,其触发越频繁,应该返回的距离更新更频繁
reg [18:0] cnt;
always @(posedge sys_us) begin// or negedge sys_rst_n
if(!sys_rst_n)begin
cnt <= 19'd0;
end
else if(cnt == T)begin
cnt <= 19'd0;
end
else begin
cnt <= cnt + 1'd1;
end
end
//15us的高电平
assign trig = (cnt <15 ) ? 1'b1 : 1'b0;//正确的,只是时间太短,观察不到,目前应该是串口问题
endmodule
3、串口发送模块
module uart_send
#(
parameter CLK = 26'd50000000 , // 时钟频率
parameter BAUD = 17'd115200 // 波特率
)
(
input wire clk ,
input wire rstn ,
input wire [7 : 0] data_in , // 需要发送的数据
input wire flag_in , // 数据接收标志位,既发送标志位
output wire tx_done ,
output reg UART_tx // 串口输出位
);
localparam Baud_Clk = CLK/BAUD ; // 传输每个 Baud 需要的时钟数
reg tx_en ; // 发送使能
reg flag_bit ; // 比特标志位,采用下降沿发送
reg [8 : 0] cnt_baud ; // 波特率计数器
reg [3 : 0] cnt_bit ; // 比特计数器
assign tx_done = cnt_bit == 4'd9 && flag_bit == 1'b1;
// 发送使能
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
tx_en <= 1'b0;
end
// 已经发送了十位 bit 并且到达下一个下降沿,输入只需要判断到数据位最后一位,输出则需要判断完整输出
else if(cnt_bit == 4'd9 && flag_bit == 1'b1) begin
tx_en <= 1'b0;
end
else if(flag_in == 1'b1) begin
tx_en <= 1'b1;
end
end
// 波特计数器
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
cnt_baud <= 9'd0;
end
// 传输完成所有波特或者使能失效,表示发送结束
else if(cnt_baud == Baud_Clk - 1'b1 || tx_en == 1'b0) begin
cnt_baud <= 9'd0;
end
else begin
cnt_baud <= cnt_baud + 9'd1;
end
end
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
flag_bit <= 1'b0;
end
// 只有刚开始发送的一瞬间会产生一个时钟周期上升沿和下降沿
else if(cnt_baud == 9'd1) begin
flag_bit <= 1'b1;
end
else begin
flag_bit <= 1'b0;
end
end
// 计数10分有效数据位
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
cnt_bit <= 4'd0;
end
// 已经发送了十位 bit 并且到达下一个下降沿
else if(cnt_bit == 4'd9 && flag_bit == 1'b1) begin
cnt_bit <= 4'd0;
end
// 使能有效,下降沿发送数据
else if(flag_bit == 1'b1 && tx_en == 1'b1) begin
cnt_bit <= cnt_bit + 4'd1;
end
else begin
cnt_bit <= cnt_bit;
end
end
// 满足 RS232 协议 起始位为 0,停止位为 1,并按位输出
always @(posedge clk or negedge rstn) begin
if(!rstn) begin
UART_tx <= 1'd1;
end
// 下降沿发送数据
else if(flag_bit == 1'b1) begin
case (cnt_bit)
0: UART_tx <= 1'd0 ;
1: UART_tx <= data_in[0] ;
2: UART_tx <= data_in[1] ;
3: UART_tx <= data_in[2] ;
4: UART_tx <= data_in[3] ;
5: UART_tx <= data_in[4] ;
6: UART_tx <= data_in[5] ;
7: UART_tx <= data_in[6] ;
8: UART_tx <= data_in[7] ;
9: UART_tx <= 1'd1 ;
default: UART_tx <= 1'd1 ;
endcase
end
end
endmodule //UART_send
4、HC_SR04_uart(顶层文件)
module HC_SR04_uart(
input sys_clk ,
input sys_rst_n ,
input echo ,
input uart_rx , // 串口输入
output trig ,
output uart_tx //串口发送端口
);
wire clk_us;
wire [18:0] data_o_r;//待发送的数据
//时钟分频
div_clk_us div_clk_us_inst(
/*input */ .sys_clk (sys_clk ),
/*input */ .sys_rst_n (sys_rst_n),
/*output*/ .clk_us (clk_us)
);
//产生驱动超声波信号
trig_driver trig_driver_inst(
/*input */ .sys_us (clk_us),//1us时钟
/*input */ .sys_rst_n (sys_rst_n),
/*output*/ .trig (trig)//驱动超声波的信号
);
//对返回来的echo信号进行计算得出距离
echo_driver echo_driver_inst(
/*input */ .sys_clk (sys_clk),
/*input */ .sys_us (clk_us),
/*input */ .sys_rst_n (sys_rst_n),
/*input */ .echo (echo),
/*output [18:0]*/ .data_o (data_o_r)//检测距离,保留三位小数,*1000实现
);
//初步想法是使用串口发送模块直接操作,不需要串口回环,否则需要发送到接收,接收模块再发送给发送模块,发送模块再发送给PC
uart_driver2 uart_driver2_inst(
.clk (sys_clk ),
.rstn (sys_rst_n),
.data_in (data_o_r ),
.UART_rx (uart_rx),
.UART_tx (uart_tx )
);
endmodule
四、效果
FPGA串口输出测距信息
五、总结
前面写过FPGA测距的数码管显示,STM32的测距串口输出,其实这篇文章的内容之前完成过。由于前面又学习了一边串口回环,所以又敲了一遍,实现一下FPGA的串口输出。虽然做过,但是还是折腾了一天,仿真、SignalTap II 抓了一下午的信号。但这次比上一次的理解更加深刻,收获更多。
六、参考资料
1、基于FPGA的超声波测距——数码管显示
2、源码:https://github.com/no1jiangjiang/HC-SR04_uart_FPGA