FPGA_WebLab/public/doc/02_key/key.md

258 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 基础-2-按键检测与消抖
## 2.1 章节导读
在数字电路中,按键是最常用的人机交互输入方式。然而,机械式按键在按下或释放过程中会产生抖动信号,直接读取会引起误触发。本章我们将实现一个可靠的按键检测模块,完成信号的消抖和下降沿检测,以便为更复杂的模块如状态机切换、模式转换等提供稳定的触发信号。
## 2.2 理论学习
由于机械结构的限制按键在触发的一瞬间其接触点会发生数次抖动导致输出信号在0和1之间反复跳变。这种现象称为“抖动”。为避免系统错误响应需要对按键信号进行“消抖”处理。
常见的软件消抖方法包括定时器延时,而在软件中通常使用计数器。在本实验中,采用对输入信号进行采样判断,当其状态发生变化时开始计数,若持续稳定一定时长后,才认为按键真正改变。
在此基础上,若需检测按键的“按下事件”,则还需进一步提取其上升沿(或下降沿)作为一个单周期的“有效触发”信号。
## 2.3 实战演练
### 2.3.1 实验目标
实现一个具有消抖功能的按键检测模块,并进一步提取其下降沿触发信号,输出一个单时钟周期宽度的 `btn_flag` 信号用于后级逻辑判断。同时为了使实验现象更加明显设置8位的IO输出连接led当检测到 `btn_flag` 信号后8位信号`led`会自加1。
### 2.3.2 硬件资源
本实验使用试验箱上普通按键输入资源,输入信号经过电平转换后进入 FPGA 芯片,输出信号可连接状态指示灯以观察效果。
根据原理图可知实验板的按键按下是低电平,不按为高电平。
<div> <!--块级封装-->
<center> <!--将图片和文字居中-->
<img src="./images/1.png"
alt="无法显示图片时显示的文字"
style="zoom:30%"/>
<br> <!--换行-->
图1.实验板的按键资源 <!--标题-->
</center>
</div>
<div> <!--块级封装-->
<center> <!--将图片和文字居中-->
<img src="./images/2.png"
alt="实验板按键原理图"
style="zoom:60%"/>
<br> <!--换行-->
图2.实验板按键原理图 <!--标题-->
</center>
</div>
<div> <!--块级封装-->
<center> <!--将图片和文字居中-->
<img src="./images/xx.png"
alt="数字孪生"
style="zoom:60%"/>
<br> <!--换行-->
图3.远程实验界面按键 <!--标题-->
</center>
</div>
### 2.3.3 程序设计
为了实现稳定的按键检测逻辑,设计流程如下:
1. 对输入 `btn` 进行采样,形成 `btn_temp`
2. 若检测到 `btn_temp` 与当前 `btn` 状态不一致,则开始计数;
3. 若计数器 `cnt` 达到设定阈值如255则认为按键状态稳定更新 `btn_ggle`
4. 实验板的按键按下是低电平,不按为高电平。所以对 `btn_ggle` 打两拍形成 `btn_flag_d0``btn_flag_d1`,再判断其下降沿,输出一个时钟周期的`btn_flag`
5. 检测到信号`btn_flag`后,信号`led <= led + 1`。
该模块的参考代码如下(`btn_ggle.v`
```verilog
module btn_ggle(
input wire clk,
input wire rstn,
input wire btn,
output wire btn_flag,
output reg [7:0] led
);
reg btn_ggle;
reg btn_flag_d0,btn_flag_d1;
reg [7:0] cnt;
reg btn_temp;
//检测按键状态
always @(posedge clk) btn_temp <= btn;
//按键状态改变时开始计数
always @(posedge clk) begin
if(~rstn) cnt <= 0;
else if(btn_temp != btn) cnt <= 1;
else if(cnt != 0) cnt <= cnt + 1;
else cnt <= 0;
end
//计数到255时认为按键值稳定
always @(posedge clk) begin
if(~rstn) btn_ggle <= btn;
else if(cnt == 8'hFF) btn_ggle <= btn_temp;
else btn_ggle <= btn_ggle;
end
//对btn_ggle信号延迟打拍
always @(posedge clk) begin
if(~rstn) begin
btn_flag_d0 <= 0;
btn_flag_d1 <= 0;
end
else begin
btn_flag_d0 <= btn_ggle;
btn_flag_d1 <= btn_flag_d0;
end
end
//btn_flag检测btn_ggle的下降沿
assign btn_flag = ~btn_flag_d0 && btn_flag_d1;
//检测到按键按下的标志位btn_flagled会加1
always @(posedge clk) begin
if(~rstn) led <= 0;
else if(btn_flag) led <= led + 1;
else led <= led;
end
endmodule
```
### 2.3.4 仿真验证
为验证功能的正确性,设计测试平台(`btn_ggle_tb.v`),代码如下:
```verilog
`timescale 1ns/1ns
module btn_ggle_tb;
reg clk;
reg rstn;
reg btn;
wire btn_flag;
wire [7:0] led;
btn_ggle btn_ggle_inst (
.clk(clk),
.rstn(rstn),
.btn(btn),
.btn_flag(btn_flag),
.led(led)
);
// 27MHz 时钟周期约为 37.037ns取37ns近似
always #(500/27) clk = ~clk; // 半周期18.5ns ≈ 27MHz
initial begin
// 初始化
clk = 0;
rstn = 0;
btn = 1; // 按键默认未按下,高电平有效
// 释放复位
#200;
rstn = 1;
// 模拟带抖动的按下过程
#1000 btn = 0;
#100 btn = 1; // 抖动
#100 btn = 0;
#100 btn = 1;
#100 btn = 0;
// 稳定按下
#100000 btn = 0;
// 模拟抖动松开过程
#300000 btn = 1;
#100 btn = 0;
#100 btn = 1;
#100 btn = 0;
#100 btn = 1;
// 稳定松开
#100000 btn = 1;
// 第二次按下
#300000 btn = 0;
#100000 btn = 0;
#300000 $finish;
end
endmodule
```
利用ModuleSim进行仿真部分仿真波形如下图所示
<div> <!--块级封装-->
<center> <!--将图片和文字居中-->
<img src="./images/3.png"
alt="仿真波形(一)"
style="zoom:60%"/>
<br> <!--换行-->
图4.仿真波形(一) <!--标题-->
</center>
</div>
<div> <!--块级封装-->
<center> <!--将图片和文字居中-->
<img src="./images/4.png"
alt="仿真波形(二)"
style="zoom:60%"/>
<br> <!--换行-->
图5.仿真波形(二) <!--标题-->
</center>
</div>
<div> <!--块级封装-->
<center> <!--将图片和文字居中-->
<img src="./images/5.png"
alt="仿真波形(三)"
style="zoom:60%"/>
<br> <!--换行-->
图6.仿真波形(三) <!--标题-->
</center>
</div>
从仿真波形二和三中我们可以看到当我们模拟按键按下1 ----> 0当按键抖动`btn`在0和1之间来回跳转`cnt`的值会变回1重新开始计数直到按键稳定按下`btn`的值稳定不变为0`cnt`稳定增加,当`cnt`的值增加到`8hFF`时,认为按键按下,`btn_ggle`存储此时的按键状态,同时`btn_flag`检测到下降沿,拉高一个时钟周期。`led`信号也加一。
<div> <!--块级封装-->
<center> <!--将图片和文字居中-->
<img src="./images/6.png"
alt="仿真波形(四)"
style="zoom:60%"/>
<br> <!--换行-->
图7.仿真波形(四) <!--标题-->
</center>
</div>
<div> <!--块级封装-->
<center> <!--将图片和文字居中-->
<img src="./images/7.png"
alt="仿真波形(五)"
style="zoom:60%"/>
<br> <!--换行-->
图8.仿真波形(五) <!--标题-->
</center>
</div>
从波形三和四中我们可以看到当模拟按键抬起时0 ----> 1按键的抖动也会使`cnt`重新计数,直到稳定,`cnt`计数到`8hFF`时,更新`btn_ggle`,由于按键是抬起,`btn_flag`不变,`led`不变。
### 2.3.5 上板验证
完成仿真后,可进行上板验证。端口连接如下表所示:
| 端口名称 | 类型 | 管脚 |说明 |
| -------- | ------ | ------ | ---------- |
| clk | Input | | 27MHz 时钟 |
| rstn | Input | | 低电平复位 |
| btn | Input | | 外部按钮 |
| btn_flag | Output | | 上升沿标志 |
| led[0] | Output | | 驱动led |
| led[1] | Output | | 驱动led |
| led[2] | Output | | 驱动led |
| led[3] | Output | | 驱动led |
| led[4] | Output | | 驱动led |
| led[5] | Output | | 驱动led |
| led[6] | Output | | 驱动led |
| led[7] | Output | | 驱动led |
将`.sbit`文件上传至平台并下载到实验板多次按下按键观察led灯跳转如果按下1次按键led只跳转一次那么说明达成实验目标。
## 2.4 章末总结
本实验通过一个典型的按键检测例子,介绍了数字系统中常用的消抖和边沿检测方法,掌握了如何利用计数器和触发器组合进行抖动抑制与事件捕捉。在更复杂的设计中,这类基础模块可作为控制逻辑的可靠触发信号源,具有广泛应用价值。