当前位置:网站首页>基于 FPGA 按键控制呼吸灯原理、仿真及验证全过程
基于 FPGA 按键控制呼吸灯原理、仿真及验证全过程
2022-07-27 09:17:00 【可乐有点好喝】
目录
内容简介:基于FPGA实现两个按键控制不同频率的呼吸灯,按键①按下后,1秒钟频率的呼吸灯亮灭;按键②按下后,3秒钟频率的呼吸灯亮灭
说明:本文中按键的使用涉及到按键消抖的原理,关于按键消抖的原理,本文不再赘述,可以参考博客:【入门学习一】基于 FPGA 使用 Verilog 实现按键点灯代码及原理讲解
一、呼吸灯原理
- 本文呼吸灯主要采用 PWM 脉冲宽度调制的方式实现,而蜂鸣器就可以采用 PWM 方式实现,可以参考博客:【入门学习二】基于 FPGA 使用 Verilog 实现蜂鸣器响动的代码及原理讲解
- 文章末尾有 FPGA 开发板上实现的效果,可以事先看看
- 通过让 LED 输出如下图所示的波形即可实现呼吸灯的效果

- 灭—>亮的过程中,初始状态的一个区间全是低电平,也就是LED不亮,随着时间的推移,高电平的持续时间不断在增加,而低电平的持续时间不断的减少,最后的一个区间全为高电平,这样就可以实现从灭到亮的一个呼吸效果
亮—>灭的过程中,初始状态的一个区间全是高电平,也就是LED亮,随着时间的推移,低电平的持续时间不断在增加,而高电平的持续时间不断的减少,最后的一个区间全为低电平,这样就可以实现从亮到灭的一个呼吸效果
- 这里可以通过两个计数器 cnt_0、cnt_1 来实现上面图所示的效果
- cnt_0:用来计数 1 秒所需的时钟周期数,FPGA 开发板晶振为 50 MHz 频率,所以可以计算得出
一个时钟周期时长 = 1 秒 50 M H z = 1 秒 50 _ 000 _ 000 H z = 20 n s (纳秒) 1 秒对应的时钟周期数 = 1 s 20 n s = 1 _ 000 _ 000 _ 000 n s 20 n s = 50 _ 000 _ 000 个时钟周期 一个时钟周期时长=\frac{1秒}{50MHz}=\frac{1秒}{50\_000\_000Hz}=20ns(纳秒)\\ 1秒对应的时钟周期数=\frac{1s}{20ns}=\frac{1\_000\_000\_000ns}{20ns}=50\_000\_000个时钟周期 一个时钟周期时长=50MHz1秒=50_000_000Hz1秒=20ns(纳秒)1秒对应的时钟周期数=20ns1s=20ns1_000_000_000ns=50_000_000个时钟周期
我们让 cnt_0 累加 50_000_000 次就达到计数 1 秒的效果,累加值为 1 个时钟周期
cnt_1:在一个小区间中从 100_000 开始累加,累加值为 100_000 个时钟周期,最大值也是 50_000_000
这里的 100_000 累加值可以自己设置,如果设得比较小,那么 LED 输出波形就比较稀疏,会出现闪烁的现象,如果设置得比较大,那么 LED 输出波形会比较稠密一些 - 这两个计数器之间,互不干扰!
- 如何实现上图中的波形效果???
- 有了这两个计数器,就可以实现啦
- 左边灭—>亮的过程我们就可以让 cnt_1 <= cnt_0 时,LED 输出高电平,不满足条件则输出低电平
- 右边亮—>灭的过程我们就可以让 cnt_1 > cnt_0 时,LED 输出高电平,不满足条件则输出低电平
- 这里会发现一个小问题,就是右边的为什么没有等于 “ = ”,这里我亲测了的,当等于时,波形输出就不会那么整齐,但是大体上不怎么影响呼吸灯的效果
二、系统设计
2.1 系统框架图
- 如下所示:

- 这里我们采用三个模块,一个按键模块,一个 LED 模块,一个顶层模块,顶层模块调用按键模块和 LED 模块
- 这里的按键模块有现成的,就是我的另外一篇博客:【入门学习一】基于 FPGA 使用 Verilog 实现按键点灯代码及原理讲解
- 其中详细讲解了按键消抖的原理,但由于我们这里使用的两个按键,所以是在这篇博客的基础上增加了位宽,仅此而已,但后面还是会贴上代码的
2.2 信号定义
- 信号定义如下:


2.3 波形分析
- 按键模块波形:

其中 key_in 为按键输入信号,key_0 和 key_1 配合为打拍器,用于检测 key_in 下降沿,然后 add_flag 使能信号拉高,计数器 cnt_delay 开始计数,计数到最大值时,输出一个矩形脉冲信号 press - LED 模块波形:

当 press 信号来到后,设置 MAX_S 最大值,当 MAX_S 有值后,计数器开始计数,cnt_0 从 0 开始计数,计数到 50_000_000 个时钟周期后,再初始化重新开始计数,而 cnt_1 以 100_000 个时钟周期为单位进行计数,而这样就可以使用 cnt_0 与 cnt_1 进行比较,得到最上面图所示的波形了,然后再赋值给 LED 即可,当 cnt_0 计数到最大值后 led_flag 取反,当 led_flag 为 0 时,就是呼吸灯由灭到亮的过程,当 led_flag 为 1 时,就是呼吸灯由亮到灭的过程
三、代码实现
3.1 顶层模块
- 以下代码我就不详细讲解具体逻辑流程了,也比较简单,逐句阅读,逐句理解就可以了
- 注意事项:在代码中模块名后面添加了全局参数,不建议删除,因为后面仿真时调用模块需要改变全局参数的值
- 文件名:key_led_top.v
/*========================================*\ filename : key_led_top.v description : 按键控制呼吸灯顶层模块 up file : reversion : v1.0:2022-7-23 13:32:26 author : 张某某 \*========================================*/
module key_led_top #(parameter MS_20 = 20'd1000_000,
S_1 = 28'd50_000_000,
S_3 = 28'd150_000_000,
LED_HZ = 28'd100_000)(
input clk , // 系统时钟50MHz
input rst_n , // 复位信号
input [1:0] key_in , // 按键输入
output [3:0] led // LED灯输出
);
// 信号定义
wire [1:0] press ; // 连接press信号
// 模块调用
// 按键模块
key_filter #(.MS_20(MS_20)) U_key_filter(
/*input */ .clk (clk ),
/*input */ .rst_n (rst_n ),
/*input [1:0]*/ .key_in (key_in ),
/*output reg [1:0]*/ .press (press )
);
// LED模块调用
breathe_led #(.S_1(S_1), .S_3(S_3), .LED_HZ(LED_HZ)) U_breathe_led(
/*input */ .clk (clk ),
/*input */ .rst_n (rst_n ),
/*input [1:0]*/ .press (press ),
/*output reg [3:0]*/ .led (led )
);
endmodule
3.2 按键消抖模块
- 文件名:key_filter.v
/*========================================*\ filename : key_filter.v description : 按键消抖 up file : key_led_top.v reversion : v1.0:2022-7-23 13:26:58 author : 张某某 \*========================================*/
module key_filter #(parameter MS_20 = 20'd1000_000)(
input clk , // 50MHz时钟信号
input rst_n , // 复位按键信号输入
input [1:0] key_in , // 按键输入
output reg [1:0] press // 按键使能信号输出
);
// 信号定义
reg [ 1:0] key_0 ; // key_in现态
reg [ 1:0] key_1 ; // key_in次态
wire [ 1:0] key_nedge ; // key_in下降沿使能信号
reg add_flag ; // 计数使能信号
reg [19:0] cnt_delay ; // 延时计数器
// 模块功能
// key_0、key_1:打拍器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_0 <= 'b1;
key_1 <= 'b1;
end
else begin
key_0 <= key_in;
key_1 <= key_0;
end
end
// key_nedge:检测下降沿
assign key_nedge = ~key_0 & key_1;
// add_flag:计数使能信号,高电平计数,低电平不计数
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
add_flag <= 'b0;
end
else if (key_nedge) begin // key_in下降沿到来,则使能信号拉高
add_flag <= 'b1;
end
else if (cnt_delay >= MS_20 - 1) begin // 计数器计满,则使能信号拉低
add_flag <= 'b0;
end
else begin
add_flag <= add_flag;
end
end
// cnt_delay:计数20ms
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_delay <= 20'd0;
end
else if (add_flag) begin // 使能信号高电平时,则开始计数
if (cnt_delay >= MS_20 - 1) begin // 计数器计满后清零
cnt_delay <= 20'd0;
end
else begin // 未达到最大值,则累加
cnt_delay <= cnt_delay + 1;
end
end
else begin
cnt_delay <= 20'd0;
end
end
// press:输出一个时钟周期的矩形脉冲信号
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
press <= 2'b0;
end
else if (cnt_delay >= MS_20 - 1) begin // 计数到最大值后,输出脉冲信号
press <= ~key_in;
end
else begin
press <= 2'b0;
end
end
endmodule
3.3 呼吸灯模块
- 文件名:breathe_led.v
/*========================================*\ filename : breathe_led.v description : 呼吸灯模块 up file : key_led_top.v reversion : v2.0:2022-7-24 15:06:46 author : 张某某 \*========================================*/
module breathe_led #(parameter S_1 = 28'd50_000_000,
S_3 = 28'd150_000_000,
LED_HZ = 28'd100_000)(
input clk , // 系统时钟50MHz
input rst_n , // 复位信号
input [1:0] press , // 按键按下的使能信号
output reg [3:0] led // 输出LED
);
// 信号定义
reg [27:0] MAX_S ; // 最大时钟周期数,1秒或3秒
reg [27:0] cnt_0 ; // 以1个时钟周期为单位进行累加,计满MAX_S个时钟周期
reg [27:0] cnt_1 ; // 以LED_HZ个时钟周期为单位进行累加,计满MAX_S个时钟周期
reg led_flag ; // LED亮灭使能信号:0为灭到亮,1为亮到灭
// 逻辑描述
// MAX_S:根据按键输入选择计数最大值
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
MAX_S <= 28'd0;
end
else begin
case (press)
2'd1 : MAX_S <= S_1; // 设置计数1秒最大值:50_000_000
2'd2 : MAX_S <= S_3; // 设置计数3秒最大值:150_000_000
//2'd3 :
default: MAX_S <= MAX_S;
endcase
end
end
// cnt_0:以1个时钟周期为单位进行累加,计满MAX_S个时钟周期
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_0 <= 28'd0;
end
else if (cnt_0 >= MAX_S) begin
cnt_0 <= 28'd1;
end
else begin
cnt_0 <= cnt_0 + 28'd1;
end
end
// cnt_1:以100_000个时钟周期为单位进行累加,计满MAX_S个时钟周期
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt_1 <= 28'd10;
end
else if (cnt_1 >= MAX_S) begin
cnt_1 <= LED_HZ;
end
else begin
cnt_1 <= cnt_1 + LED_HZ;
end
end
// led_flag:LED亮灭使能信号:0为灭到亮,1为亮到灭
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
led_flag <= 1'b0;
end
else if (MAX_S && cnt_0 >= MAX_S) begin // 使能信号取反
led_flag <= ~led_flag;
end
else begin
led_flag <= led_flag;
end
end
// led:LED灯输出呼吸灯波形
always @(*) begin
if(led_flag) begin // 亮--->灭
if (cnt_1 > cnt_0) begin
led = 4'b1111;
end
else begin
led = 4'b0000;
end
end
else begin // 灭--->亮
if (cnt_1 <= cnt_0) begin
led = 4'b1111;
end
else begin
led = 4'b0000;
end
end
end
endmodule
四、仿真流程
4.1 仿真代码
- 自己新建一个工程,然后将上面三个模块代码添加到工程中
- 然后再新建一个 .v 文件,复制粘贴以下仿真代码,也是 Verilog HDL 文件
- 文件名:tb_key_led_top.v
/*========================================*\ filename : tb_key_led_top.v description : 按键控制呼吸灯仿真文件 up file : reversion : v1.0:2022-7-23 15:57:23 author : 张某某 \*========================================*/
`timescale 1ns/1ns
module tb_key_led_top;
// 激励信号
reg tb_clk ;
reg tb_rst_n ;
reg [1:0] tb_key_in ;
// 观测信号
wire [3:0] tb_led ;
// 模块调用
// 为了仿真更快一点,每一个参数我都将其抹了四个零
key_led_top #(.MS_20(20'd100),
.S_1(28'd50_00),
.S_3(28'd150_00),
.LED_HZ(28'd10)) U_key_led_top(
/*input */ .clk (tb_clk ),
/*input */ .rst_n (tb_rst_n ),
/*input [1:0]*/ .key_in (tb_key_in ),
/*output [3:0]*/ .led (tb_led )
);
// 系统初始化
// 时钟信号生成
initial tb_clk = 1'b0;
always #10 tb_clk = ~tb_clk;
// 系统初始化
initial begin
// 复位信号、按键信号初始化
tb_rst_n = 1'b0;
tb_key_in = 2'b11;
#(10) tb_rst_n = 1'b1;
#(100) tb_key_in = 2'b10;
#(200 * 20) tb_key_in = 2'b11;
// 循环10次延时40000ns
repeat(10) begin
#(40000);
end
// 停止仿真
$stop(2);
end
endmodule
4.2 仿真流程
- 仿真流程可以参考博客:Quartus 与 ModelSim 联合仿真详细步骤
- 可以看到三个模块文件在 rtl 文件夹下,仿真文件在 tb 下,这个没什么影响,自己也可以放在一个文件夹下

- 仿真不用综合(也就是编译 debug 的意思),我这里左下角显示绿色√,是因为之前做的过程中综合的,当然想综合也可以,现在直接设置一些相关的仿真就行
- 双击【Files】,点击【Simulation】,选择安装的仿真工具,然后勾选【Compile test bench】,再点击【Test Benches…】

- 这里我之前添加过了的,所以会有一个 test bench,新打开的设置是空白的,需要点击【New…】

- 输入 test bench 的名称,要与仿真文件名一模一样,否则找不到文件,然后下面选择仿真文件 tb_key_led_top.v,再点击【Add】,最后一直点击【OK】即可设置完成

- 点击【RTL Simulation】开始仿真

- Quartus 可以将这个 RTL Simulation 按钮设置成快捷键,参考博客:Quartus 在工具栏设置功能快捷方式
- 在【sim】窗口内,可以将按键模块和呼吸灯模块添加到 Wave 窗口中观测它们内部信号的波形图

- 可以看到我的 ModelSim 工具栏只有一行,比较简洁,其实是有很多东西用不上的,可以参考博客进行设置:ModelSim 相关实用设置
- 看下图理解波形窗口

- 点击【Restart】按钮清除之前的仿真数据,弹窗点击【OK】

- 点击【Run -All】开始仿真

- 这三个放大镜放大/缩小波形图,先点黑色那个,这个窗口就充满整个的波形图

4.3 仿真结果
- 可以看到使能信号周期性亮灭,LED 的波形很明显可以看到两个高低电平的宽度不停再变化


- cnt_1 计数器如下所示:

当 cnt_1 取到最大值时,就改变 LED 的值,这里没有一个时钟周期的延时,主要取决于在最后 LED 的输入采用的是组合逻辑
如果是时序逻辑电路的话,就会有一个时钟周期的延时,如下图所示:
五、板上验证
5.1 配置管脚
- 在开发板上面验证就必须要综合了

- 然后配置管脚

- 如果是和我一样型号的开发板的话,管脚可以按照下图所示配置,如果不是的话,自己查开发板原理图进行配置

5.2 下载程序
- 开发板连接到电脑上后,点击【Programmer】下载代码到开发板

- 电脑需要配置 USB-Blaster 才能下载,安装 USB-Blaster 驱动可以参考博客:Quartus-II 13.1 详细安装、注册、配置步骤
- 点击【Hardware Setup…】
- 选择 USB-Blaster [USB-0]
- 点击【Start】开始下载

5.3 验证结果
- 结果如下:
按键控制呼吸灯
- 当下载完代码后,LED 是灭的,这是因为代码中初始化 LED 为低电平,只有当按下两个控制按键后,才会开始呼吸灯
- 当按下按键 KEY2 后,显示 1 秒呼吸灯的效果
当按下按键 KEY3 后,切换为 3 秒呼吸灯的效果
当按下按键 KEY2 后,又切换为 1 秒呼吸灯的效果
当按下复位键 KEY1 后,又初始化了,也就是 LED 灭了
边栏推荐
- [C language - zero foundation lesson 11] rotate the pointer of the big turntable
- Music experience ceiling! Emotional design details of 14 Netease cloud music
- C# 窗体应用常用基础控件讲解(适合萌新)
- js call和apply
- Is the operation of assigning values to int variables atomic?
- NCCL (NVIDIA Collective Communications Library)
- 【微服务~Sentinel】Sentinel之dashboard控制面板
- You haven't heard of such 6 question brushing websites, have you? Are you out?
- HBuilder 微信小程序中运行uni-app项目
- IDL reads HSD data of sunflower 8 (himawari-8)
猜你喜欢

拍卖行做VC,第一次出手就投了个Web3

Pytorch custom CUDA operator tutorial and runtime analysis

1344. 时钟指针的夹角

Antdesign a-modal user-defined instruction realizes dragging and zooming in and out

Music experience ceiling! Emotional design details of 14 Netease cloud music

Apple cut its price by 600 yuan, which was almost a devastating blow to the collapse of its domestic flagship mobile phone

linux安装和远程连接mysql记录

645. Wrong set

音乐体验天花板!14个网易云音乐的情感化设计细节

C# 窗体应用常用基础控件讲解(适合萌新)
随机推荐
Some practical, commonly used and increasingly efficient kubernetes aliases
C language takes you to tear up the address book
The fourth day of learning C language
1344. 时钟指针的夹角
[C language - zero foundation lesson 14] variable scope and storage class
IDL reads HSD data of sunflower 8 (himawari-8)
NCCL 集合通信--Collective Operations
【每日算法Day 96】腾讯面试题:合并两个有序数组
[micro service ~sentinel] sentinel dashboard control panel
Mangodb simple to use
NPM and yarn update dependent packages
NPM install error forced installation
ctfshow 终极考核
BOM的常用操作和有关获取页面/窗口高度、宽度及滚动的兼容性写法
[C language _ review _ learn Lesson 2] what is base system? How to convert between hexadecimals
Summary of traversal methods
Music experience ceiling! Emotional design details of 14 Netease cloud music
C language exercise 2
Common operations of BOM and compatible writing methods for obtaining page / window height, width and scrolling
Principle of flex:1