使用System-Verilog实现FPGA基于DE2-115开发板驱动HC_SR04超声波测距模块实现蜂鸣器,led和vga提示功能
环境:
1.硬件 DE2-115 HC-SR04超声波传感器
2.软件 Quartus 18.1
参考资料:使用System-Verilog实现FPGA基于DE2-115开发板驱动HC_SR04超声波测距模块|集成蜂鸣器,led和vga提示功能-CSDN博客
目录
1.10 属性(Properties)和序列(Sequences)
基于STM32的HC-SR04超声波测距(滤波算法+数据上云)_哔哩哔哩_bilibili
一.SystemVerilog——下一代硬件设计语言
SystemVerilog 是一种用于硬件设计和验证的高级硬件描述语言(HDL),它在传统的 Verilog 语言的基础上提供了一系列的增强和新特性。以下是从不同方面对 SystemVerilog 的介绍:
1.1 语言基础
SystemVerilog 保留了 Verilog 的基本语法和语义,因此它与 Verilog 代码兼容。这意味着现有的 Verilog 代码可以不经修改地在 SystemVerilog 环境中使用。
2.2 面向对象编程
SystemVerilog 引入了面向对象的编程特性,包括:
类(Classes):可以包含数据成员和方法,支持封装和继承。
继承(Inheritance):允许新类从现有类中派生,并扩展或修改现有类的行为。
多态(Polymorphism):通过虚拟方法和接口实现,允许以统一的方式处理不同类型的对象。
1.3 接口(Interfaces)
接口提供了一种定义模块之间通信的方法,它们可以被实例化并用于连接不同的模块。接口可以包含信号、方法和时钟域,支持复杂和动态的通信协议。
1.4 程序包(Packages)
程序包是一组可以在整个项目中重用的常量、类型定义、类和接口。它们类似于其他编程语言中的命名空间,有助于代码的组织和模块化。
1.5 数据结构
SystemVerilog 增加了对复杂数据结构的支持,包括:
字符串(Strings):提供了丰富的字符串操作功能。
队列(Queues):动态大小的数组,适用于实现缓冲区和动态数据结构。
多维数组和联合数组:增强了数组的使用,支持多维和基于条件的数组。
1.6 随机化(Randomization)
SystemVerilog 提供了随机化机制,允许随机生成测试向量和约束求解,这对于测试和验证尤其重要。
1.7 并发性和时序控制
SystemVerilog 增强了并发性和时序控制的能力,包括:
always_ff 和 initial_final 块:提供了更清晰和直观的方式来描述时序逻辑和初始化代码。
**时钟门控:**允许基于时钟边沿的并发执行。
1.8 功能增强
函数(Functions) 和 任务(Tasks):增强了函数和任务的定义和使用,支持返回多个值,以及更复杂的参数传递。
覆盖率(Coverage):支持代码覆盖率的收集,帮助验证过程更加完整。
1.9 断言(Assertions)
SystemVerilog 支持断言,这是一种验证设计是否满足特定属性的方法。断言可以在设计中嵌入检查,以确保设计行为的正确性。
1.10 属性(Properties)和序列(Sequences)
属性(Properties):定义需要在仿真中观察的特定事件或信号模式。
序列(Sequences):定义事件的特定顺序,用于测试和验证设计。
1.11 综合和验证
SystemVerilog 不仅适用于 RTL 综合,还特别强调了设计验证。它提供了丰富的特性来帮助设计者创建测试环境,检查设计的正确性,并生成测试报告。
1.12 工具和生态系统
SystemVerilog 得到了主要的 EDA 工具供应商的支持,包括综合器、仿真器、调试器和验证工具。这些工具提供了对 SystemVerilog 特性的全面支持,使得设计和验证过程更加高效。
SystemVerilog 的这些特性使其成为复杂集成电路和电子系统设计的强大工具,它帮助设计者以更高的抽象层次工作,提高了生产力,同时降低了设计和验证的复杂性。随着硬件设计要求的不断提高,SystemVerilog 预计将继续发展和扩展,以满足不断变化的行业需求。
二、实验原理
2.1 传感器概述:
HC-SR04超声波距离传感器的核心是两个超声波传感器。一个用作发射器,将电信号转换为40 KHz超声波脉冲。接收器监听发射的脉冲。如果接收到它们,它将产生一个输出脉冲,其宽度可用于确定脉冲传播的距离。就是如此简单!该传感器体积小,易于在任何机器人项目中使用,并提供2厘米至400厘米(约1英寸至13英尺)之间出色的非接触范围检测,精度为3mm。
2.2 传感器引脚
-
VCC 是HC-SR04超声波距离传感器的电源
-
Trig 引脚用于触发超声波脉冲
-
Echo 回声当接收到反射信号时,引脚产生一个脉冲。脉冲的长度与检测发射信号所需的时间成正比
-
GND 用于接地
2.3 传感器工作原理
当持续时间至少为10 µS(10微秒)的脉冲施加到触发引脚时,一切就开始了。响应于此,传感器以40 KHz发射八个脉冲的声音脉冲。这种8脉冲模式使设备的“超声特征”变得独一无二,从而使接收器能够将发射模式与环境超声噪声区分开。八个超声波脉冲通过空气传播,远离发射器。同时,回声引脚变为高电平,开始形成回声信号的开始。如果这些脉冲没有被反射回来,则回波信号将在38毫秒后超时并返回低电平。因此38 ms的脉冲表示在传感器范围内没有阻塞。
如果这些脉冲被反射回去,则在收到信号后,Echo引脚就会变低。这会产生一个脉冲,其宽度在150 µS至25 mS之间变化,具体取决于接收信号所花费时间。
HC-SR04的时序图如下:
通过时序图我们可以知道,我们给HC-SR04发送长达 10us 的TTL脉冲,然后模块就会进行测距,测距的结果通过回响信号传达,回响的TTL电平信号时间即是超声波从HC-SR04模块发出,触碰到障碍物后返回到HC-SR04模块的时间总和。
TTL是逻辑电平标准,当电压达到2.4V-5V之间,那么为逻辑1(高电平),电压在0V~0.4V之间,那么为逻辑0(低电平)。所以我们可以直接通过GPIO口来输出以及输入时序所需的电平信号。
然后,将接收到的脉冲的宽度用于计算到反射物体的距离。这可以通过我们在初中学到的简单的距离-速度-时间方程来解决。
距离=速度x时间 ,当然温度,以及环境噪声等对实验结果都有影响,因此公式应在不同环境下进行修改
总所周知,声音的速度为340m/s,因此我们将回响电平的时间除340再除2之后得到的就是单位为米的测距结果。
三、System-Verilog文件
3.1 时钟分频
(1)clk_div.sv
定义时钟分频模块,产生周期为1us的时钟信号clk_div.sv
// //产生一个以微秒为周期的时钟信号clk_us,该信号可用于驱动一些需要精确时间控制的电路
module clk_div(
input logic Clk, // 输入系统时钟,50MHz
input logic Rst_n, // 输入复位信号,低电平有效
output logic clk_us // 输出微秒级时钟信号
);
// 参数声明 1us = 1000ns = 50个时钟周期
parameter int CNT_MAX = 19'd50; //1us的计数值为 50 * Tclk(20ns)
// 内部线网/寄存器声明
logic [18:0] cnt; // 定义一个19位的计数器
logic add_cnt; // 计数器使能信号
logic end_cnt; // 计数器结束信号,达到最大值时有效
// 计数器的寄存器逻辑
always_ff @(posedge Clk, negedge Rst_n) begin
if (!Rst_n) begin // 如果复位信号有效,则计数器清零
cnt <= '0;
end
else if (add_cnt) begin // 如果计数器达到最大值,则计数器重置
if (end_cnt) begin
cnt <= '0;
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
3.2 超声波测距
- 实现HC-SR04超声波传感器的触发模块,用于生成触发测距信号(trig)
- 定义HC-SR04超声波传感器的回声模块,用于测量距离并输出检测距离数据(data_o)
(1)hc_sr_trig.sv
hc_sr_trig.sv
:
// hc_sr_trig 模块定义开始,用于生成超声波触发信号
// Description ﹕超声波触发测距模块
// 波形周期 300ms,前 15us 高电平
module hc_sr_trig (
input logic clk_us, // 输入 1MHz 系统时钟
input logic Rst_n, // 输入复位信号,低电平有效
output logic trig // 输出触发测距信号
);
// 参数声明 300_000*1_000ns = 3 *10^8ns = 0.3s = 300ms
// 波形周期 300ms,前 10us 高电平
// 建议测量周期为 60ms 以上,以防止发射信号对回响信号的影响。
parameter int CYCLE_MAX = 19'd300_000; // 定义触发信号的一个周期计数,基于 1MHz 时钟
// 内部线网/寄存器声明
logic [18:0] cnt; // 计数器,用于生成触发信号的时间控制
logic add_cnt; // 计数器使能信号
logic end_cnt; // 计数器结束信号,达到预定义周期时有效
// 计数器逻辑,用于控制触发信号的产生
always_ff @(posedge clk_us, negedge Rst_n) begin
if (!Rst_n) begin // 如果复位信号有效,则计数器清零
cnt <= '0;
end else if (add_cnt) begin // 如果计数器使能
if (end_cnt) begin // 如果计数器达到预定义的最大周期
cnt <= '0; // 计数器重置
end else begin
cnt <= cnt + 1'b1; // 否则计数器递增
end
end else begin
cnt <= cnt; // 如果计数器未使能,则保持当前值
end
end
assign add_cnt = 1'b1; // 赋值计数器使能信号,始终使计数器有效
assign end_cnt = add_cnt && (cnt == CYCLE_MAX - 9'd1); // 赋值计数器结束信号,当计数器值达到 CYCLE_MAX - 1 时有效
// 赋值触发信号,当计数器值小于 15 时,输出高电平,作为触发
// cnt < 15 置为高电平,表示前 15us 为高电平,作为触发信号
// 此逻辑基于 HC-SR04 模块的触发信号需求,通常为 10 微秒的高电平
assign trig = (cnt < 15) ? 1'b1 : 1'b0;
/*
计数器 cnt 用于生成持续一定时间的触发信号 trig。当计数器小于 15 时,trig 为高电平,表示触发信号是活跃的。
计数器在每个 1MHz 时钟的上升沿递增,当计数器达到设定的最大周期 CYCLE_MAX 时,计数器重置,重新开始计数。
这样,trig 信号就会周期性地输出高电平脉冲,以满足 HC-SR04 超声波传感器的触发需求。
*/
endmodule
(2)hc_sr_echo.sv
hc_sr_echo.sv
// 处理HC-SR04超声波传感器的回声信号,并计算距离
// Description ﹕超声波检测距离模块
// 本模块理论测试距离 2cm~510cm
// 输出结果保留两位小数
module hc_sr_echo
(
input logic Clk, // 输入50MHz时钟信号
input logic clk_us, // 输入1MHz系统时钟信号
input logic Rst_n, // 输入复位信号,低电平有效
input logic echo, // 输入超声波回声信号
output logic [18:0] data_o // 输出检测到的距离,以厘米为单位,保留三位小数
);
/* S(um) = 17 * t --> x.abc cm */
//Parameter Declarations
parameter T_MAX = 16'd60_000; // 定义计数器的最大值,对应510厘米
logic r1_echo, r2_echo; // 用于边沿检测的寄存器
logic echo_pos, echo_neg; // 回声信号的上升沿和下降
logic [15:0] cnt; // 1MHz时钟下的计数器,用于测量回声脉冲宽度
logic add_cnt; // 计数器使能信号
logic end_cnt; // 计数器结束信号
logic [18:0] data_r; // 距离数据的中间寄存器
// 逻辑描述
// 使用50MHz时钟检测回声信号的边沿,以避免使用1MHz时钟导致的2us延时
always_ff @(posedge Clk or negedge Rst_n) begin
if (!Rst_n) begin
r1_echo <= 1'b0;
r2_echo <= 1'b0;
end
else begin
r1_echo <= echo;
r2_echo <= r1_echo;
end
end
// 产生上升沿和下降沿信号
assign echo_pos = r1_echo & ~r2_echo; // 回声信号上升沿
assign echo_neg = ~r1_echo & r2_echo; // 回声信号下降沿
// 计数器逻辑,用于测量回声脉冲宽度
always_ff @(posedge clk_us or negedge Rst_n) begin
if (!Rst_n) begin
cnt <= '0;
end
else if (add_cnt) begin
if (end_cnt) begin
cnt <= cnt; // 如果达到最大测量范围,则保持当前计数值
end
else begin
cnt <= cnt + 1'b1; // 否则计数器递增
end
end
else begin // 如果回声信号低电平,计数器归零
cnt <= '0;
end
end
assign add_cnt = echo; // 赋值计数器使能信号,当回声信号为高电平时使能计数器
assign end_cnt = add_cnt && cnt >= T_MAX - 1; //赋值计数器结束信号,当计数器达到最大值T_MAX时有效 超出最大测量范围则保持不变,极限
// 测试距离=(高电平时间*声速(340M/S))/2;
// 距离数据处理逻辑,将计数值转换为距离
always_ff @(posedge Clk or negedge Rst_n) begin
if (!Rst_n) begin
data_r <= 'd2; // 复位时中间寄存器置为2,用于小数点后三位的计算
end
else if (echo_neg) begin
// 当回声信号下降沿到来时,将计数值左移四位并加上自身,实现小数点后三位的计算
//t = cnt*1000ns = cnt*10-6s
//s = 340*t m
data_r <= (cnt << 4) + cnt;
end
else begin
data_r <= data_r; // 否则保持当前值
end
end
// 将中间寄存器的数据右移一位,实现除以2的操作,得到最终的距离数据
assign data_o = data_r >> 1;
endmodule
3.3 数码管驱动
(1)seg_driver.sv
seg_driver
:
// seg_driver模块用于驱动七段显示器,显示数字或特定的符号。
module seg_driver(
input logic Clk, // 输入的时钟信号。
input logic Rst_n, // 低电平有效的复位信号。
input logic [18:0] data_o, // 输入的数字数据,这里假设是测得的距离数据。
output logic [6:0] hex1, // 第1个七段显示器的段选信号输出。
output logic [6:0] hex2, // 第2个...
output logic [6:0] hex3,
output logic [6:0] hex4,
output logic [6:0] hex5,
output logic [6:0] hex6,
output logic [6:0] hex7,
output logic [6:0] hex8
);
// 参数定义区,定义了特殊显示值和小数点的编码,以及计数器的最大值。
parameter NOTION = 4'd10, // 定义数字"10"用于消隐的编码。
FUSHU = 4'd11, // 定义数字"11"用作小数点的编码。
MAX20us = 10'd1000; // 定义20微秒计数器的最大值。
// 寄存器声明区,声明了用于控制和显示数字的内部寄存器。
logic [9:0] cnt_20us; // 用于动态扫描定时的20微秒计数器。
logic [7:0] sel_r; // 动态扫描控制的片选信号寄存器。
logic [3:0] number; // 要显示的数字,范围0-9或特殊编码。
logic [6:0] seg_r; // 根据number解析得到的七段显示器段选编码。
// 每个七段显示器的段选编码寄存器,用于存储最终输出到显示器的段选编码。
logic [6:0] hex1_r, hex2_r, hex3_r, hex4_r, hex5_r, hex6_r, hex7_r, hex8_r;
// 20微秒计数器始终块,用于周期性地重置计数器来实现动态扫描。
always_ff @(posedge Clk or negedge Rst_n) begin
if (!Rst_n) begin
cnt_20us <= 0; // 复位时计数器清零。
end
else if (cnt_20us == (MAX20us - 1)) begin
cnt_20us <= 0; // 计数器达到最大值时重置。
end
else begin
cnt_20us <= cnt_20us + 1; // 否则计数器递增。
end
end
// 动态扫描控制始终块,用于生成选择当前激活的七段显示器的片选信号。
always_ff @(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)) begin
sel_r <= {sel_r[6:0], sel_r[7]}; // 计数器达到最大值时,片选信号左移循环。
end
else begin
sel_r <= sel_r; // 否则保持当前片选信号不变。
end
end
// 组合逻辑块,根据片选信号sel_r获取要显示的数字。
always_comb begin
case (sel_r)
// 根据sel_r的值选择对应的数字或特殊编码。
// 这些编码对应于输入数据data_o的不同部分。
// ...(此处省略了部分case语句)
default: number = 4'd0; // 默认情况下不显示任何数字。
endcase
end
// 组合逻辑块,根据数字解析出对应的七段显示器段选值seg_r。
always_comb begin
case (number)
// 对应数字0-9的七段显示器编码。
// ...(此处省略了部分case语句)
NOTION: seg_r = 7'b111_1111; // 消隐编码,所有段都不亮。
FUSHU: seg_r = 7'b011_1111; // 小数点编码,只点亮小数点部分。
default: seg_r = 7'b111_1111; // 默认消隐。
endcase
end
// 组合逻辑块,根据片选信号sel_r将seg_r值赋给对应的七段显示器寄存器。
always_comb begin
// 初始化所有寄存器为消隐状态。
hex1_r = 7'b111_1111;
hex2_r = 7'b111_1111;
hex3_r = 7'b111_1111;
hex4_r = 7'b111_1111;
hex5_r = 7'b111_1111;
hex6_r = 7'b111_1111;
hex7_r = 7'b111_1111;
hex8_r = 7'b111_1111;
// 根据当前选中的显示器,将seg_r的值赋给对应的寄存器。
case (sel_r)
8'b11_11_11_10: hex1_r = seg_r;
8'b11_11_11_01: hex2_r = seg_r;
// ...(此处省略了部分case语句)
default: ;
endcase
end
// 将寄存器的值通过assign语句输出到端口,连接到外部的七段显示器硬件。
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.4 VGA驱动
(1)vga_dirve.sv
vga_dirve.sv
:
module vga_dirve (
input logic clk, // 系统时钟
input logic rst_n, // 复位
input logic [23:0] rgb_data, // 16位RGB对应值
output logic vga_clk, // vga时钟 25M
output logic h_sync, // 行同步信号
output logic v_sync, // 场同步信号
output logic [11:0] addr_h, // 行地址
output logic [11:0] addr_v, // 列地址
output logic [7:0] rgb_r, // 红基色
output logic [7:0] rgb_g, // 绿基色
output logic [7:0] rgb_b // 蓝基色
);
// 640 * 480 60HZ
localparam int H_FRONT = 16; // 行同步前沿信号周期长
localparam int H_SYNC = 96; // 行同步信号周期长
localparam int H_BLACK = 48; // 行同步后沿信号周期长
localparam int H_ACT = 640; // 行显示周期长
localparam int V_FRONT = 11; // 场同步前沿信号周期长
localparam int V_SYNC = 2; // 场同步信号周期长
localparam int V_BLACK = 31; // 场同步后沿信号周期长
localparam int V_ACT = 480; // 场显示周期长
// 800 * 600 72HZ (已注释,使用640*480)
// ...
localparam int H_TOTAL = H_FRONT + H_SYNC + H_BLACK + H_ACT; // 行周期
localparam int V_TOTAL = V_FRONT + V_SYNC + V_BLACK + V_ACT; // 列周期
logic [11:0] cnt_h; // 行计数器
logic [11:0] cnt_v; // 场计数器
logic [23:0] rgb; // 对应显示颜色值
// 对应计数器开始、结束、计数信号
logic flag_enable_cnt_h, flag_clear_cnt_h, flag_enable_cnt_v, flag_clear_cnt_v, flag_add_cnt_v, valid_area;
// 25M时钟 行周期*场周期*刷新率 = 800 * 525* 60
logic clk_25;
// 50M时钟 1040 * 666 * 72
// ...
// PLL实例化生成时钟
pll pll_inst (
.areset(~rst_n),
.inclk0(clk),
.c0(clk_50), // 50M
.c1(clk_25) // 25M
);
// 根据不同分配率选择不同频率时钟
assign vga_clk = clk_25;
// 行计数
always_ff @(posedge vga_clk or negedge rst_n) begin
if (!rst_n) begin
cnt_h <= 0;
end else if (flag_enable_cnt_h) begin
cnt_h <= flag_clear_cnt_h ? 0 : cnt_h + 1;
end
end
// 行同步信号
always_ff @(posedge vga_clk or negedge rst_n) begin
if (!rst_n) begin
h_sync <= 0;
end else if (cnt_h == H_SYNC - 1) begin
h_sync <= 1;
end else if (flag_clear_cnt_h) begin
h_sync <= 0;
end
end
// 场计数
always_ff @(posedge vga_clk or negedge rst_n) begin
if (!rst_n) begin
cnt_v <= 0;
end else if (flag_enable_cnt_v) begin
cnt_v <= flag_clear_cnt_v ? 0 : cnt_v + flag_add_cnt_v;
end
end
// 场同步信号
always_ff @(posedge vga_clk or negedge rst_n) begin
if (!rst_n) begin
v_sync <= 0;
end else if (cnt_v == V_SYNC - 1) begin
v_sync <= 1;
end else if (flag_clear_cnt_v) begin
v_sync <= 0;
end
end
// 对应有效区域行地址 1-640
always_ff @(posedge vga_clk or negedge rst_n) begin
if (!rst_n) begin
addr_h <= 0;
end else if (valid_area) begin
addr_h <= cnt_h - H_SYNC - H_BLACK + 1;
end
end
// 对应有效区域列地址 1-480
always_ff @(posedge vga_clk or negedge rst_n) begin
if (!rst_n) begin
addr_v <= 0;
end else if (valid_area) begin
addr_v <= cnt_v - V_SYNC - V_BLACK + 1;
end
end
// 有效显示区域
assign valid_area = cnt_h >= H_SYNC + H_BLACK && cnt_h <= H_SYNC + H_BLACK + H_ACT &&
cnt_v >= V_SYNC + V_BLACK && cnt_v <= V_SYNC + V_BLACK + V_ACT;
// 显示颜色
always_ff @(posedge vga_clk or negedge rst_n) begin
if (!rst_n) begin
rgb <= 24'b0;
end else if (valid_area) begin
rgb <= rgb_data;
end
end
assign rgb_r = rgb[23:16];
assign rgb_g = rgb[15:8];
assign rgb_b = rgb[7:0];
endmodule // vga_dirve
四、实现过程
4.1 模块说明
这里要求超声波模块的正负极分别接入5V和GND,其余trigger和echo自由接线,使用的是GPIO[0]和GPIO[1]