跳到主要内容

实验 07:拨码开关控制 LED

🛒 开发板: EK301 GW5A FPGA 开发板 芯片: GW5A-LV25UG324C1/I0 (Arora V 系列)


一、实验目的

  1. 掌握拨码开关作为 FPGA 输入信号的使用方法,包括两级同步消除亚稳态。
  2. 理解多模式切换的设计思路——用按键在多种输出模式之间轮转。
  3. 学会复用已有模块key_debounce)实现按键消抖,降低开发复杂度。
  4. 掌握滑动开关的 CST 约束写法(PULL_MODE=DOWNIO_TYPE=LVCMOS33)。

二、实验过程

2.1 硬件连接

信号方向FPGA 引脚Bank说明
CLK输入T9Bank 450MHz 有源晶振
SW1~SW8输入F10/F9/A15/B14/A14/A13/A12/B11Bank 0/1滑动开关,默认下拉(ON=1)
KEY1输入C10Bank 1轻触按键,模式切换
LED1~7输出G9/E7/E6/A5/A4/B4/A3/C5Bank 0/1蓝色LED

2.2 拨码开关原理

EK301 板载 8 路滑动开关(SW1SW8,丝印 K1K8),通过 10KΩ 下拉电阻到 GND。当开关拨到 ON 位置时,引脚接 VCC(3.3V),FPGA 读入高电平;OFF 时引脚被下拉到 GND,读入低电平。

与轻触按键的区别

特性轻触按键(KEY1~3)滑动开关(K1~K8)
操作方式瞬时按下拨动保持
默认电平上拉(高)下拉(低)
有抖动?是(5-20ms)否(静态电平)
需消抖?仅需两级同步

⚠️ 开关虽然没有机械抖动,但仍是异步输入信号,必须经过两级同步器消除亚稳态。

2.3 设计思路

本实验实现 3 种开关→LED 映射模式:

模式编号LED 行为说明
A0led = sw直接映射:开关 ON → LED 亮
B1led = ~sw取反映射:开关 ON → LED 灭
C2led = sw编码显示模式(当前同直接映射,预留扩展)

KEY1 每按一次,mode 在 0→1→2→0 之间循环切换。KEY1 经过 key_debounce 模块消抖(20ms),确保一次按键只触发一次模式切换。

2.4 模块设计

系统框图

top.v (switch_led)
├─ key_debounce.v ← KEY1 消抖(复用 exp_06)
├─ 两级同步器(sw) ← 消除开关亚稳态
├─ 模式状态机(mode) ← 3 种模式轮转
└─ LED 输出逻辑 ← case(mode) 选择映射

模块名switch_led

端口列表

端口方向位宽说明
clkinput150MHz 系统时钟
swinput[7:0]8 路滑动开关 (ON=1)
key0input1模式切换按键 (按下=0)
ledoutput[7:0]8 路 LED 输出 (高亮)

内部信号

信号位宽说明
sw_sync1, sw_sync2[7:0]两级同步寄存器
key0_press1消抖后的按键脉冲
mode[1:0]当前模式 (0-2)

三、代码逐段讲解

3.1 开关两级同步

reg [7:0] sw_sync1, sw_sync2;
always @(posedge clk) begin
sw_sync1 <= sw;
sw_sync2 <= sw_sync1;
end
  • 异步输入 sw → 第一级 sw_sync1 → 第二级 sw_sync2
  • 两级同步后,sw_sync2 的亚稳态概率极低(MTBF > 数百年)。
  • 虽然开关是静态电平,但上电瞬间和拨动瞬间仍可能产生亚稳态,两级同步是最低成本的防护。

3.2 按键消抖例化

wire key0_press;
key_debounce #(.MS(20)) u_key0 (.clk(clk), .key_in(key0), .pressed(key0_press));
  • key0 是异步输入,使用 key_debounce 模块完成:两级同步 → 下降沿检测 → 20ms 延时确认。
  • key0_press单周期脉冲,仅在确认按键按下时持续 1 个时钟周期。
  • #(.MS(20)) 参数化消抖时间,可根据需要调整。

3.3 模式切换状态机

reg [1:0] mode;

always @(posedge clk) begin
if (key0_press) begin
if (mode == 2) mode <= 0;
else mode <= mode + 1;
end
end
  • mode 是 2-bit 寄存器,取值为 0/1/2。
  • 每收到 key0_press 脉冲,mode 加 1。
  • mode==2 时置 0,实现 0→1→2→0 循环。
  • 如果改为 mode <= mode + 1(利用 2-bit 自动溢出),则 0→1→2→3→0,模式 3 对应 default 分支。

3.4 LED 输出逻辑

always @(posedge clk) begin
case (mode)
2'd0: led <= sw_sync2; // A: 直接映射
2'd1: led <= ~sw_sync2; // B: 取反映射
2'd2: led <= sw_sync2; // C: 编码模式
default: led <= sw_sync2;
endcase
end
  • 使用 sw_sync2(两级同步后的稳定值)而非原始 sw
  • 模式 A:开关 ON → sw=1led=1 → LED 亮。
  • 模式 B:开关 ON → sw=1led=0 → LED 灭(逻辑取反)。
  • default 分支保证综合器不会推断出锁存器。

四、实验现象

操作模式LED 现象
上电A(0)拨动 K1K8,对应 LED17 同步亮灭
按 KEY1 一次B(1)拨动 K1~K8,对应 LED 取反:ON→灭,OFF→亮
按 KEY1 两次C(2)同模式 A(编码显示,预留后续扩展)
按 KEY1 三次A(0)回到直接映射模式

验证要点

  • ✅ 模式 A:全部 8 个开关独立控制 8 个 LED,互不干扰。
  • ✅ 模式 B:开关全部 ON 时 LED 全灭,全部 OFF 时 LED 全亮。
  • ✅ 模式切换时 LED 立即响应,无延迟无闪烁。
  • ✅ 按住 KEY1 不放,仅触发一次模式切换(消抖生效)。
  • ✅ 快速拨动开关,LED 响应跟随无异常(两级同步生效)。

五、掌握知识点

知识点说明在本实验中的体现
拨码开关静态电平输入,ON=高/OFF=低8 路滑动开关直接映射 LED
两级同步器sync1→sync2 消除异步输入亚稳态sw_sync1sw_sync2
模块复用例化已有模块减少重复代码复用 key_debounce 消抖
多模式设计case(mode) + 按键轮转3 种模式循环切换
PULL_MODE约束文件中设置内部弱上下拉开关用 DOWN,按键用 UP
取反逻辑~ 运算符翻转全部 8 位模式 B 的 led <= ~sw_sync2


附录:源码

top.v

// =============================================================================
// EK301 实验05: 拨码开关+按键控制LED
// 芯片: GW5A-LV25UG324C1/I0 | 时钟: 50MHz (T9)
// SW1~SW8 控制 LED, KEY1 切换 3 种模式 (自写消抖)
// =============================================================================

module switch_led (
input wire clk, // 50MHz (T9)
input wire [7:0] sw, // SW1~SW8 (ON=1)
input wire key0, // KEY1 (模式切换, 按下=0)
output reg [7:0] led // LED1~LED8
);

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

// ---- 简单消抖: 连续20ms为低才算按下 ----
localparam DEBOUNCE_MS = 20;
localparam CNT_MAX = (50_000_000 * DEBOUNCE_MS / 1000) - 1; // 999_999

reg [19:0] cnt;
reg key_stable; // 消抖后的稳定值
reg key_prev; // 上一拍值, 用于边沿检测

always @(posedge clk) begin
if (key_sync[1] != key_stable) begin
// 当前值与稳定值不同, 开始计数
if (cnt == CNT_MAX) begin
key_stable <= key_sync[1]; // 确认变化
cnt <= 0;
end else begin
cnt <= cnt + 1;
end
end else begin
cnt <= 0; // 相同则清零
end
key_prev <= key_stable;
end

// ---- 检测下降沿 (按下) ----
wire key_press = key_prev && !key_stable;

// ---- 两级同步 (开关) ----
reg [7:0] sw_sync1, sw_sync2;
always @(posedge clk) begin
sw_sync1 <= sw;
sw_sync2 <= sw_sync1;
end

// ---- 模式切换 ----
reg [1:0] mode; // 0=直接, 1=取反, 2=编码

always @(posedge clk) begin
if (key_press) begin
if (mode == 2) mode <= 0;
else mode <= mode + 1;
end
end

// ---- LED 输出 ----
always @(posedge clk) begin
case (mode)
2'd0: led <= sw_sync2;
2'd1: led <= ~sw_sync2;
2'd2: led <= sw_sync2;
default: led <= sw_sync2;
endcase
end

endmodule

key_debounce.v

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