实验 09:按键调速流水灯
🛒 开发板购买: EK303 GW1N FPGA核心板
一、实验目的
- 综合运用按键消抖、状态机和 LED 动态驱动,实现多维度交互控制。
- 掌握多输入状态机设计——3 个按键分别控制方向、速度、暂停。
- 理解参数化速度控制——通过
case选择不同的分频系数实现多档速度。 - 学习移位方向的动态切换(左移 vs 右移)。
二、实验过程
2.1 硬件连接
| 外设 | 引脚 | 功能 |
|---|---|---|
| KEY1 | 69 | 方向切换(左移 ↔ 右移) |
| KEY2 | 70 | 速度切换(快/中/慢) |
| KEY3 | 71 | 暂停/恢复 |
| LED1~3 | Pin 8/9/10 | 流水灯显示 |
2.2 设计思路
系统架构:
KEY1 ──→ [消抖] ──→ 方向控制 (dir)
KEY2 ──→ [消抖] ──→ 速度控制 (speed[1:0])
KEY3 ──→ [消抖] ──→ 暂停控制 (pause)
dir, speed, pause
↓
[流水灯核心模块]
↓
LED[2:0]
方向控制:
| dir | 方向 | 移位逻辑 |
|---|---|---|
| 0 | 左移(LED1→LED2→LED3) | led <= {led[1:0], led[2]} |
| 1 | 右移(LED3→LED2→LED1) | led <= {led[0], led[2:1]} |
速度档位:
| 档位 | speed | 移位间隔 | 周期值 (50MHz) |
|---|---|---|---|
| 快 | 00 | 0.1s | 5,000,000 |
| 中 | 01 | 0.3s | 15,000,000 |
| 慢 | 10 | 0.8s | 40,000,000 |
暂停控制:pause = 1 时计数器停止、移位停止,LED 保持当前状态。
2.3 Verilog 模块设计
模块名:led_shift_ctrl
端口:
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk_50m | input | 1 | 时钟 |
key_dir, key_spd, key_pause | input | 1 | 三个按键 |
led | output | 3 | LED 输出 |
内部信号:
| 信号 | 位宽 | 说明 |
|---|---|---|
dir | 1 | 方向:0=左移, 1=右移 |
speed | 2 | 速度档位 0/1/2 |
pause | 1 | 暂停标志 |
shift_en | 1 | 移位使能脉冲 |
shift_cnt | 26 | 分频计数器 |
逻辑描述:
- 例化 3 个消抖模块处理三个按键。
- 按键响应:
- KEY1 脉冲 →
dir <= ~dir - KEY2 脉冲 →
speed <= speed + 1(循环 0→1→2→0) - KEY3 脉冲 →
pause <= ~pause
- 分频计数器:根据
speed选择不同的比较阈值,!pause时正常计数。 - 移位逻辑:
shift_en时根据dir执行左移或右移。
2.4 引脚约束
同前,3 个 LED + 3 个按键。
三、实验现象
| 操作 | 现象 |
|---|---|
| 上电默认 | 三个 LED 快速(0.1s)从左向右流水 |
| 按下 KEY1 | 流水方向反转,从右向左流动 |
| 按下 KEY2(1 次) | 速度变为中速(0.3s/步) |
| 按下 KEY2(2 次) | 速度变为慢速(0.8s/步) |
| 按下 KEY2(3 次) | 回到快速 |
| 按下 KEY3 | 流水暂停,LED 静止保持当前状态 |
| 再按 KEY3 | 恢复流水 |
三个按键独立控制方向/速度/暂停,互不干扰,操作即时响应。
四、掌握知识点
| 知识点 | 说明 |
|---|---|
| 多输入状态机 | 3 个按键信号驱动不同的状态寄存器 |
case(speed) 参数化 | 根据速度档位选择分频阈值 |
| 动态方向控制 | dir 选通左移或右移表达式 |
| 暂停实现 | pause 门控计数器使能 |
| 模块复用 | 3 次例化同一个消抖模块 |
五、思考题
- 如何增加一个自动模式——流水方向每 5 秒自动反转?
- 如果 KEY2 要支持 5 档速度,
speed位宽需要多少? - 暂停时能否让 LED 闪烁提示"已暂停"?
附录:源码清单
top.v
// =============================================================================
// exp_09_shift_ctrl - 按键调速流水灯
// 芯片: GW1N-LV9LQ144C6/I5, 时钟: 50MHz (Pin 98)
//
// 功能:
// KEY1 (Pin 69): 方向切换 (左移 ↔ 右移)
// KEY2 (Pin 70): 速度切换 (快→中→慢→快, 3档循环)
// KEY3 (Pin 71): 暂停/恢复
// LED[2:0] (Pin 8/9/10): 流水灯显示
// =============================================================================
module top (
input wire clk_50m, // 50MHz 系统时钟 (Pin 98)
input wire key_dir, // 方向切换键 (Pin 69)
input wire key_spd, // 速度切换键 (Pin 70)
input wire key_pause, // 暂停/恢复键 (Pin 71)
output reg [2:0] led // LED 输出 (Pin 8/9/10)
);
// =========================================================================
// 参数定义
// =========================================================================
// 消抖: 20ms = 1,000,000 cycles
localparam DEBOUNCE_MAX = 1_000_000 - 1;
localparam DB_CNT_W = 20;
// 流水速度: 3 档
localparam SPEED_FAST = 26'd5_000_000; // 0.1s / 步
localparam SPEED_MID = 26'd15_000_000; // 0.3s / 步
localparam SPEED_SLOW = 26'd40_000_000; // 0.8s / 步
// =========================================================================
// 内部信号 - 按键消抖
// =========================================================================
// 3 路独立消抖信号
reg kd_sync0, kd_sync1, kd_stable, kd_prev;
reg ks_sync0, ks_sync1, ks_stable, ks_prev;
reg kp_sync0, kp_sync1, kp_stable, kp_prev;
reg [DB_CNT_W-1:0] db_cnt_dir;
reg [DB_CNT_W-1:0] db_cnt_spd;
reg [DB_CNT_W-1:0] db_cnt_pause;
// =========================================================================
// 内部信号 - 控制状态
// =========================================================================
reg dir; // 0=左移, 1=右移
reg [1:0] speed; // 0=快, 1=中, 2=慢
reg pause; // 0=运行, 1=暂停
// =========================================================================
// 内部信号 - 流水灯计时
// =========================================================================
reg [25:0] shift_cnt; // 分频计数器
reg [25:0] speed_threshold; // 当前速度阈值
// =========================================================================
// 1. 按键消抖模块 × 3
// =========================================================================
// ---- KEY1: 方向 ----
always @(posedge clk_50m) begin
kd_sync0 <= key_dir;
kd_sync1 <= kd_sync0;
end
always @(posedge clk_50m) begin
if (kd_sync1 != kd_stable) begin
if (db_cnt_dir == DEBOUNCE_MAX) begin
kd_stable <= kd_sync1;
db_cnt_dir <= {DB_CNT_W{1'b0}};
end else begin
db_cnt_dir <= db_cnt_dir + 1'b1;
end
end else begin
db_cnt_dir <= {DB_CNT_W{1'b0}};
end
end
always @(posedge clk_50m) kd_prev <= kd_stable;
wire key_dir_pressed = kd_prev && !kd_stable; // 下降沿
// ---- KEY2: 速度 ----
always @(posedge clk_50m) begin
ks_sync0 <= key_spd;
ks_sync1 <= ks_sync0;
end
always @(posedge clk_50m) begin
if (ks_sync1 != ks_stable) begin
if (db_cnt_spd == DEBOUNCE_MAX) begin
ks_stable <= ks_sync1;
db_cnt_spd <= {DB_CNT_W{1'b0}};
end else begin
db_cnt_spd <= db_cnt_spd + 1'b1;
end
end else begin
db_cnt_spd <= {DB_CNT_W{1'b0}};
end
end
always @(posedge clk_50m) ks_prev <= ks_stable;
wire key_spd_pressed = ks_prev && !ks_stable;
// ---- KEY3: 暂停 ----
always @(posedge clk_50m) begin
kp_sync0 <= key_pause;
kp_sync1 <= kp_sync0;
end
always @(posedge clk_50m) begin
if (kp_sync1 != kp_stable) begin
if (db_cnt_pause == DEBOUNCE_MAX) begin
kp_stable <= kp_sync1;
db_cnt_pause <= {DB_CNT_W{1'b0}};
end else begin
db_cnt_pause <= db_cnt_pause + 1'b1;
end
end else begin
db_cnt_pause <= {DB_CNT_W{1'b0}};
end
end
always @(posedge clk_50m) kp_prev <= kp_stable;
wire key_pause_pressed = kp_prev && !kp_stable;
// =========================================================================
// 2. 按键响应逻辑
// =========================================================================
// KEY1: 方向翻转
always @(posedge clk_50m) begin
if (key_dir_pressed)
dir <= ~dir;
end
// KEY2: 速度循环 0→1→2→0
always @(posedge clk_50m) begin
if (key_spd_pressed) begin
if (speed == 2'd2)
speed <= 2'd0;
else
speed <= speed + 1'b1;
end
end
// KEY3: 暂停翻转
always @(posedge clk_50m) begin
if (key_pause_pressed)
pause <= ~pause;
end
// =========================================================================
// 3. 速度阈值选择 (组合逻辑)
// =========================================================================
always @(*) begin
case (speed)
2'd0: speed_threshold = SPEED_FAST; // 0.1s
2'd1: speed_threshold = SPEED_MID; // 0.3s
2'd2: speed_threshold = SPEED_SLOW; // 0.8s
default: speed_threshold = SPEED_FAST;
endcase
end
// =========================================================================
// 4. 流水灯分频计数器
// =========================================================================
wire shift_en;
always @(posedge clk_50m) begin
if (!pause) begin
if (shift_cnt >= speed_threshold - 1) begin
shift_cnt <= 26'd0;
end else begin
shift_cnt <= shift_cnt + 1'b1;
end
end
end
assign shift_en = (!pause) && (shift_cnt >= speed_threshold - 1);
// =========================================================================
// 5. 流水灯移位逻辑
// =========================================================================
always @(posedge clk_50m) begin
if (shift_en) begin
if (dir == 1'b0)
led <= {led[1:0], led[2]};
else
led <= {led[0], led[2:1]};
end
end
// =========================================================================
// 6. 上电初始值
// =========================================================================
initial begin
led = 3'b001;
dir = 1'b0;
speed = 2'd0;
pause = 1'b0;
shift_cnt = 26'd0;
end
endmodule