跳到主要内容

实验 09:按键调速流水灯

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


一、实验目的

  1. 综合运用按键消抖、状态机和 LED 动态驱动,实现多维度交互控制
  2. 掌握多输入状态机设计——3 个按键分别控制方向、速度、暂停。
  3. 理解参数化速度控制——通过 case 选择不同的分频系数实现多档速度。
  4. 学习移位方向的动态切换(左移 vs 右移)。

二、实验过程

2.1 硬件连接

外设引脚功能
KEY169方向切换(左移 ↔ 右移)
KEY270速度切换(快/中/慢)
KEY371暂停/恢复
LED1~3Pin 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)
000.1s5,000,000
010.3s15,000,000
100.8s40,000,000

暂停控制pause = 1 时计数器停止、移位停止,LED 保持当前状态。

2.3 Verilog 模块设计

模块名led_shift_ctrl

端口

端口方向位宽说明
clk_50minput1时钟
key_dir, key_spd, key_pauseinput1三个按键
ledoutput3LED 输出

内部信号

信号位宽说明
dir1方向:0=左移, 1=右移
speed2速度档位 0/1/2
pause1暂停标志
shift_en1移位使能脉冲
shift_cnt26分频计数器

逻辑描述

  1. 例化 3 个消抖模块处理三个按键。
  2. 按键响应:
  • KEY1 脉冲 → dir <= ~dir
  • KEY2 脉冲 → speed <= speed + 1(循环 0→1→2→0)
  • KEY3 脉冲 → pause <= ~pause
  1. 分频计数器:根据 speed 选择不同的比较阈值,!pause 时正常计数。
  2. 移位逻辑: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 次例化同一个消抖模块

五、思考题

  1. 如何增加一个自动模式——流水方向每 5 秒自动反转?
  2. 如果 KEY2 要支持 5 档速度,speed 位宽需要多少?
  3. 暂停时能否让 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