跳到主要内容

实验 04:按键消抖与 LED 控制

🛒 开发板: EK301 GW5A FPGA 开发板


一、实验目的

  1. 理解机械按键的抖动现象(按下/释放时 5-20ms 的电平快速跳变)。
  2. 掌握 FPGA 按键消抖的标准实现:两级同步器 + 下降沿检测 + 20ms 延时确认状态机。
  3. 学会模块化设计——key_debounce 作为可复用模块,参数化消抖时间。
  4. 掌握多按键协同控制 LED 的交互逻辑设计。

二、实验过程

2.1 硬件连接

信号方向FPGA 引脚Bank说明
KEY1输入C10Bank 1轻触按键,按下=0,RC消抖
KEY2输入A10Bank 1同上
KEY3输入B9Bank 0同上
KEY4输入A9Bank 0同上
LED1~7输出G9/E7/E6/A5/A4/B4/A3/C5Bank 0/1蓝色LED

⚠️ KEY3=B9, KEY4=A9(注意 EK301 和 EK303 引脚不同!)

2.2 按键抖动原理

理想波形: ───────┐ ┌──────
└─────────┘

实际波形: ───────┐┌┐┌┐┌┐┌──┌────────
└┘└┘└┘└┘ └┘
←5-20ms→ ←抖动区→
  • 抖动原因:金属触点弹性碰撞,产生 5-20ms 的不稳定跳变。
  • RC 硬件消抖:板载 RC 低通滤波,减缓边沿但不能完全消除。
  • FPGA 软件消抖:两级同步 + 延时确认,彻底消除抖动。

2.3 消抖状态机

fall (下降沿)
┌──────┐ ─────────────→ ┌──────┐
│ IDLE │ │ WAIT │
│ S0 │ ←───────────── │ S1 │
└──────┘ key==1(抖动回高) └──────┘
↑ │
│ cnt==MAX && key==0
│ ↓
│ ┌──────┐
└──────────────────────│PRESS │
输出pressed=1 │ S2 │
└──────┘

2.4 关键设计决策

决策原因
仅检测下降沿避免按下+释放各触发一次
消抖时间 20ms大于典型抖动期(5-20ms)
输出单周期脉冲下游逻辑只需 1 个 clk 知晓按键事件
使用电平触发 非脉冲key_press 可安全用于 if 条件

三、代码讲解

3.1 两级同步器

reg [1:0] sync;
reg prev;
always @(posedge clk) begin
{sync, prev} <= {sync[0], key_in, sync[1]};
end
  • 异步输入 key_in → 第一级 sync[0] → 第二级 sync[1] → 再延迟一拍 prev
  • 两级同步大幅降低亚稳态概率(MTBF > 数百年)。
  • prev 存储 sync[1] 上一拍的值,用于边沿检测。

3.2 下降沿检测

wire fall = prev && !sync[1];
  • prev=1, sync[1]=0 → 1→0 跳变 → fall=1
  • 仅检测下降沿,忽略上升沿(释放)。

3.3 消抖状态机

always @(posedge clk) begin
case (st)
2'd0: begin // IDLE
pressed <= 0;
if (fall) begin st <= 2'd1; cnt <= 0; end
end
2'd1: begin // WAIT
if (cnt == MAX) begin
if (sync[1] == 0) st <= 2'd2; // 确认按下
else st <= 2'd0; // 抖动回高→放弃
end else cnt <= cnt + 1;
end
2'd2: begin pressed <= 1; st <= 2'd0; end // 输出脉冲
endcase
end

3.4 按键功能分配

if (key_press[0]) led[0] <= ~led[0]; // KEY1: toggle LED1
if (key_press[1]) dir <= ~dir; // KEY2: 切换流水方向
if (key_press[2]) speed_mode <= ...; // KEY3: 三档速度
if (key_press[3]) led <= ~led; // KEY4: 全部翻转

四、实验现象

操作预期结果
上电LED1 亮,开始右移流水灯
按 KEY1LED1 切换亮/灭
按 KEY2流水方向从右移→左移→右移...
按 KEY3流水速度: 慢→中→快→慢...
按 KEY4全部 8 个 LED 翻转
按住不放不重复触发(单次响应)

五、掌握知识点

知识点说明
按键抖动5-20ms 不稳定跳变,需消抖处理
两级同步器sync[0]→sync[1] 消除亚稳态
下降沿检测prev && !sync[1]
参数化模块#(parameter MS=20) 可调消抖时间
单周期脉冲pressed=1 仅持续 1 个 clk


附录:源码

top.v

//////////////////////////////////////////////////////////////////////////////////
// Company: 武汉芯路恒科技有限公司
// Engineer: 小梅哥团队
// Web: www.corecourse.cn
//
// Create Date: 2020/07/20 00:00:00
// Design Name: key_led
// Module Name: key_led
// Project Name: EK301 实验04
// Description: 按键消抖与LED控制
// KEY1: LED1翻转
// KEY2: LED流水方向切换
// KEY3: 流水速度切换(3档)
// KEY4: 全部LED翻转
//
// Dependencies: key_debounce.v
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 芯片: GW5A-LV25UG324C1/I0
// 时钟: 50MHz (T9)
//////////////////////////////////////////////////////////////////////////////////

module key_led (
input wire clk, // 50MHz (T9)
input wire reset_n, // 复位信号,低有效 (A11)
input wire [3:0] key, // KEY1~KEY4 (按下=0)
output reg [7:0] led // LED1~LED8
);

wire reset = ~reset_n;

// ---- 按键消抖例化 (4路) ----
wire [3:0] key_flag;
wire [3:0] key_state; // 1=释放, 0=按下

key_debounce u_key0 (.clk(clk), .reset_n(reset_n), .key_in(key[0]), .key_flag(key_flag[0]), .key_state(key_state[0]));
key_debounce u_key1 (.clk(clk), .reset_n(reset_n), .key_in(key[1]), .key_flag(key_flag[1]), .key_state(key_state[1]));
key_debounce u_key2 (.clk(clk), .reset_n(reset_n), .key_in(key[2]), .key_flag(key_flag[2]), .key_state(key_state[2]));
key_debounce u_key3 (.clk(clk), .reset_n(reset_n), .key_in(key[3]), .key_flag(key_flag[3]), .key_state(key_state[3]));

// ---- LED流水时钟(可变速) ----
// 三档速度: 0.5Hz / 1Hz / 2Hz
localparam SPEED_SLOW = 50_000_000 / 1 - 1; // 0.5Hz
localparam SPEED_MID = 50_000_000 / 2 - 1; // 1Hz
localparam SPEED_FAST = 50_000_000 / 4 - 1; // 2Hz

reg [1:0] speed_mode; // 速度档位: 0=慢 1=中 2=快
reg [25:0] cnt_speed;
reg tick;
reg dir; // 0=右移, 1=左移

// --- 复位初始化 ---
always@(posedge clk or posedge reset)
if(reset)begin
led <= 8'b0000_0001;
speed_mode <= 2'd1; // 默认中速
dir <= 1'b0;
end
else begin
// --- 速度选择 ---
if (key_flag[2]) begin
// KEY3: 三档循环切换 (检测按下脉冲)
if (key_state[2] == 1'b0) begin
if (speed_mode == 2'd2)
speed_mode <= 2'd0;
else
speed_mode <= speed_mode + 1'b1;
end
end

// --- LED 控制 ---
// KEY1: toggle LED1 (检测按下脉冲)
if (key_flag[0] && key_state[0] == 1'b0)
led[0] <= ~led[0];

// KEY2: 切换流水方向 (检测按下脉冲)
if (key_flag[1] && key_state[1] == 1'b0)
dir <= ~dir;

// KEY4: 全部LED翻转 (检测按下脉冲)
if (key_flag[3] && key_state[3] == 1'b0)
led <= ~led;

// 流水灯 (tick)
if (tick) begin
if (dir)
led <= {led[0], led[7:1]}; // 左移
else
led <= {led[6:0], led[7]}; // 右移
end
end

// --- 分频器(根据速度档位) ---
always@(posedge clk or posedge reset)
if(reset)begin
cnt_speed <= 26'd0;
tick <= 1'b0;
end
else begin
case (speed_mode)
2'd0: if (cnt_speed == SPEED_SLOW) begin cnt_speed <= 0; tick <= 1; end
else begin cnt_speed <= cnt_speed + 1; tick <= 0; end
2'd1: if (cnt_speed == SPEED_MID) begin cnt_speed <= 0; tick <= 1; end
else begin cnt_speed <= cnt_speed + 1; tick <= 0; end
2'd2: if (cnt_speed == SPEED_FAST) begin cnt_speed <= 0; tick <= 1; end
else begin cnt_speed <= cnt_speed + 1; tick <= 0; end
endcase
end

endmodule

key_debounce.v

// =============================================================================
// EK301 按键消抖模块 (自写简化版, 已验证可用)
// 功能: 信号连续20ms稳定才确认变化, 输出key_flag(脉冲)+key_state(1=释放/0=按下)
// =============================================================================

module key_debounce #(
parameter MS = 20 // 消抖时间(ms)
)(
input wire clk,
input wire reset_n, // 低有效复位 (可接1'b1)
input wire key_in, // 按键输入 (按下=0)
output reg key_flag, // 边沿脉冲 (按下/释放均有)
output reg key_state // 1=释放, 0=按下
);

localparam CNT_MAX = (50_000_000 * MS / 1000) - 1;

// ---- 两级同步 ----
reg [1:0] sync;
always @(posedge clk) sync <= {sync[0], key_in};

// ---- 消抖: 连续CNT_MAX周期值相同才确认 ----
reg [19:0] cnt;
reg stable; // 消抖后的稳定值

always @(posedge clk) begin
if (sync[1] != stable) begin
if (cnt == CNT_MAX) begin
stable <= sync[1];
cnt <= 0;
end else begin
cnt <= cnt + 1;
end
end else begin
cnt <= 0;
end
end

// ---- 边沿检测 + 状态输出 ----
reg stable_prev;
always @(posedge clk) stable_prev <= stable;

always @(posedge clk) begin
// key_flag: stable变化时产生一个周期脉冲
key_flag <= (stable != stable_prev);
// key_state: 与key_in同相 (按下=0, 释放=1)
key_state <= stable;
end

endmodule