跳到主要内容

实验 06:按键计数器

🛒 开发板购买: EK303 GW1N FPGA核心板


一、实验目的

  1. 学习边沿检测电路——将消抖后的单周期脉冲转化为计数触发。
  2. 理解二进制计数在 FPGA 中的实现方式。
  3. 用 LED 以二进制形式直观显示计数值,加深对二进制数制的理解。
  4. 掌握长按/短按识别——用计时器区分单次点击和长按操作。

二、实验过程

2.1 硬件连接

外设引脚
KEY1计数键
LED1~3Pin 8/9/10(显示计数低 3 位)

2.2 设计思路

核心功能

KEY1 短按 ──→ 计数器 +1 ──→ LED 以二进制显示低 3 位
KEY1 长按 ──→ 计数器清零 ──→ LED 全灭 + 快闪两次确认

长按/短按判断

KEY1 按下

按键保持低电平 → 计时器开始计数

释放时:
计时 < 3 秒 → 判定为"短按" → 计数器 +1
计时 ≥ 3 秒 → 判定为"长按" → 计数器清零

LED 二进制显示

计数值二进制LED3LED2LED1
0000
1001
2010
3011
4100
5101
...............
7111

2.3 Verilog 模块设计

模块名key_counter

端口

端口方向位宽说明
clk_50minput1时钟
key_ininput1按键输入
ledoutput3计数值低 3 位显示

内部信号

信号位宽说明
key_pressed1消抖后的按键脉冲
counter8按键计数值 (0~255)
hold_timer28按下持续时间计数器 (>3s = 150M 周期)
key_stable1同步后的按键稳态值

逻辑描述

  1. 例化消抖模块,产生 key_pressed 脉冲。
  2. 检测按键状态:当 key_stable == 0(按下)时,hold_timer 递增;当 key_stable == 1(释放)时,判断:
  • hold_timer < 3秒 → 短按 → counter <= counter + 1
  • hold_timer ≥ 3秒 → 长按 → counter <= 0,同时触发清零闪烁。
  1. led <= counter[2:0],直接输出计数器低 3 位。

三、实验现象

操作现象
短按 KEY1 × 1LED: ○○●(计数值 = 1)
短按 KEY1 × 3LED: ○●●(计数值 = 3)
短按 KEY1 × 7LED: ●●●(计数值 = 7)
短按 KEY1 × 8LED: ○○○(计数值 = 8,低 3 位 = 000)
长按 KEY1 3 秒LED 全灭并快闪两次(计数器归零确认)

直观展示了二进制计数规律:每按一次键 LED 亮灭组合变化一次,按 8 次恰好一个循环。


四、掌握知识点

知识点说明
二进制数表示3 bit 可表示 07,N bit 可表示 0(2^N − 1)
边沿检测检测按键脉冲触发计数
长按/短按识别hold_timer 计时 + 释放时刻判断
计数器溢出8 位计数器 255→0 自动回绕
状态指示用 LED 闪烁反馈操作确认

五、思考题

  1. 如果要显示完整的 8 位计数值(0~255),只有 3 个 LED 怎么设计?
  2. 如何增加"双击"检测——0.5 秒内连续按两次?
  3. 如果长按清空后希望不闪烁,而是 LED 保持全灭,怎么改?

附录:源码清单

top.v

// =============================================================================
// exp_06_key_counter - 按键计数器 (Key Counter)
// 芯片: GW1N-LV9LQ144C6/I5, 时钟: 50MHz (Pin 98)
//
// 功能:
// KEY1 短按 → 计数器+1, LED[2:0] 以二进制显示计数低3位
// KEY1 长按≥3秒 → 计数器清零, LED快闪两次确认
// =============================================================================

module top (
input wire clk_50m, // 50MHz 系统时钟 (Pin 98)
input wire key1, // 计数/清零按键 (Pin 69, PULL_UP)
output reg [2:0] led // LED[2:0] 二进制计数显示 (Pin 8/9/10)
);

// =========================================================================
// 参数定义
// =========================================================================
// 消抖周期: 20ms = 1,000,000 cycles @ 50MHz
localparam DEBOUNCE_MS = 20;
localparam DEBOUNCE_MAX = 50_000_000 / 1000 * DEBOUNCE_MS - 1; // 999,999
localparam DB_CNT_W = 20; // 2^20 > 1,000,000

// 长按阈值: 3s = 150,000,000 cycles @ 50MHz
localparam LONG_PRESS_S = 3;
localparam LONG_PRESS_MAX = 50_000_000 * LONG_PRESS_S - 1; // 149,999,999
localparam LP_CNT_W = 28; // 2^28 > 150,000,000

// 确认闪烁参数: 200ms周期, 闪2次 = 400ms亮 + 400ms灭
localparam BLINK_HALF = 50_000_000 / 5 - 1; // 200ms = 10,000,000
localparam BLINK_CNT_W = 26; // for 4 blinks * 200ms = 800ms

// =========================================================================
// 内部信号
// =========================================================================
reg [DB_CNT_W-1:0] db_cnt; // 消抖计数器
reg key_sync0; // 同步寄存器0
reg key_sync1; // 同步寄存器1 (两级同步)
reg key_stable; // 消抖后的稳态值
reg key_prev; // 上一时钟的稳态值
wire key_pressed; // 下降沿检测 (按下瞬间脉冲)
wire key_released; // 上升沿检测 (释放瞬间脉冲)

reg [LP_CNT_W-1:0] hold_timer; // 按下持续时间计数器
reg [7:0] counter; // 按键计数值 0~255
wire long_press; // 长按标志

// 清零确认闪烁
reg [BLINK_CNT_W-1:0] blink_cnt; // 闪烁计数器
reg [2:0] blink_phase; // 闪烁阶段: 0=不闪, 1-4=闪烁
reg blink_led; // 闪烁LED输出

// =========================================================================
// 1. 按键同步 + 消抖
// =========================================================================
always @(posedge clk_50m) begin
key_sync0 <= key1;
key_sync1 <= key_sync0;
end

// 消抖: 连续20ms稳定才更新 key_stable
always @(posedge clk_50m) begin
if (key_sync1 != key_stable) begin
// 与当前稳态不一致,开始/继续计数
if (db_cnt == DEBOUNCE_MAX) begin
key_stable <= key_sync1;
db_cnt <= {DB_CNT_W{1'b0}};
end else begin
db_cnt <= db_cnt + 1'b1;
end
end else begin
db_cnt <= {DB_CNT_W{1'b0}};
end
end

// 边沿检测
always @(posedge clk_50m) begin
key_prev <= key_stable;
end

assign key_pressed = key_prev && !key_stable; // 高→低 = 按下
assign key_released = !key_prev && key_stable; // 低→高 = 释放

// =========================================================================
// 2. 按下持续时间计时器
// =========================================================================
always @(posedge clk_50m) begin
if (!key_stable) begin
// 按键按下中,持续计时
if (hold_timer < LONG_PRESS_MAX)
hold_timer <= hold_timer + 1'b1;
end else begin
hold_timer <= {LP_CNT_W{1'b0}};
end
end

assign long_press = (hold_timer >= LONG_PRESS_MAX);

// =========================================================================
// 3. 计数器逻辑 (短按+1, 长按清零)
// =========================================================================
always @(posedge clk_50m) begin
if (key_released) begin
if (long_press) begin
// 长按: 清零计数器, 触发闪烁确认
counter <= 8'd0;
end else begin
// 短按: 计数器+1
counter <= counter + 1'b1;
end
end
end

// =========================================================================
// 4. 清零确认闪烁 (快闪两次)
// =========================================================================
always @(posedge clk_50m) begin
if (key_released && long_press) begin
// 触发闪烁序列
blink_phase <= 3'd1;
blink_cnt <= {BLINK_CNT_W{1'b0}};
end else if (blink_phase != 3'd0) begin
if (blink_cnt == BLINK_HALF) begin
blink_cnt <= {BLINK_CNT_W{1'b0}};
blink_phase <= blink_phase + 1'b1; // 进入下一阶段
end else begin
blink_cnt <= blink_cnt + 1'b1;
end
end
end

// 闪烁LED信号: 阶段1/3亮, 阶段2/4灭, 阶段5停止
always @(*) begin
case (blink_phase)
3'd1, 3'd3: blink_led = 1'b1; // 亮
3'd2, 3'd4: blink_led = 1'b0; // 灭
3'd5: blink_led = 1'b0; // 结束,全灭
default: blink_led = 1'b0;
endcase
end

// =========================================================================
// 5. LED 输出: 闪烁时显示闪烁, 否则显示计数器低3位
// =========================================================================
always @(posedge clk_50m) begin
if (blink_phase != 3'd0)
led <= {3{blink_led}};
else
led <= counter[2:0];
end

endmodule