实验 06:按键计数器
🛒 开发板购买: EK303 GW1N FPGA核心板
一、实验目的
- 学习边沿检测电路——将消抖后的单周期脉冲转化为计数触发。
- 理解二进制计数在 FPGA 中的实现方式。
- 用 LED 以二进制形式直观显示计数值,加深对二进制数制的理解。
- 掌握长按/短按识别——用计时器区分单次点击和长按操作。
二、实验过程
2.1 硬件连接
| 外设 | 引脚 |
|---|---|
| KEY1 | 计数键 |
| LED1~3 | Pin 8/9/10(显示计数低 3 位) |
2.2 设计思路
核心功能:
KEY1 短按 ──→ 计数器 +1 ──→ LED 以二进制显示低 3 位
KEY1 长按 ──→ 计数器清零 ──→ LED 全灭 + 快闪两次确认
长按/短按判断:
KEY1 按下
↓
按键保持低电平 → 计时器开始计数
↓
释放时:
计时 < 3 秒 → 判定为"短按" → 计数器 +1
计时 ≥ 3 秒 → 判定为"长按" → 计数器清零
LED 二进制显示:
| 计数值 | 二进制 | LED3 | LED2 | LED1 |
|---|---|---|---|---|
| 0 | 000 | ○ | ○ | ○ |
| 1 | 001 | ○ | ○ | ● |
| 2 | 010 | ○ | ● | ○ |
| 3 | 011 | ○ | ● | ● |
| 4 | 100 | ● | ○ | ○ |
| 5 | 101 | ● | ○ | ● |
| ... | ... | ... | ... | ... |
| 7 | 111 | ● | ● | ● |
2.3 Verilog 模块设计
模块名:key_counter
端口:
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk_50m | input | 1 | 时钟 |
key_in | input | 1 | 按键输入 |
led | output | 3 | 计数值低 3 位显示 |
内部信号:
| 信号 | 位宽 | 说明 |
|---|---|---|
key_pressed | 1 | 消抖后的按键脉冲 |
counter | 8 | 按键计数值 (0~255) |
hold_timer | 28 | 按下持续时间计数器 (>3s = 150M 周期) |
key_stable | 1 | 同步后的按键稳态值 |
逻辑描述:
- 例化消抖模块,产生
key_pressed脉冲。 - 检测按键状态:当
key_stable == 0(按下)时,hold_timer递增;当key_stable == 1(释放)时,判断:
hold_timer < 3秒→ 短按 →counter <= counter + 1。hold_timer ≥ 3秒→ 长按 →counter <= 0,同时触发清零闪烁。
led <= counter[2:0],直接输出计数器低 3 位。
三、实验现象
| 操作 | 现象 |
|---|---|
| 短按 KEY1 × 1 | LED: ○○●(计数值 = 1) |
| 短按 KEY1 × 3 | LED: ○●●(计数值 = 3) |
| 短按 KEY1 × 7 | LED: ●●●(计数值 = 7) |
| 短按 KEY1 × 8 | LED: ○○○(计数值 = 8,低 3 位 = 000) |
| 长按 KEY1 3 秒 | LED 全灭并快闪两次(计数器归零确认) |
直观展示了二进制计数规律:每按一次键 LED 亮灭组合变化一次,按 8 次恰好一个循环。
四、掌握知识点
| 知识点 | 说明 |
|---|---|
| 二进制数表示 | 3 bit 可表示 0 |
| 边沿检测 | 检测按键脉冲触发计数 |
| 长按/短按识别 | hold_timer 计时 + 释放时刻判断 |
| 计数器溢出 | 8 位计数器 255→0 自动回绕 |
| 状态指示 | 用 LED 闪烁反馈操作确认 |
五、思考题
- 如果要显示完整的 8 位计数值(0~255),只有 3 个 LED 怎么设计?
- 如何增加"双击"检测——0.5 秒内连续按两次?
- 如果长按清空后希望不闪烁,而是 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