实验 07:拨码开关控制 LED
🛒 开发板: EK301 GW5A FPGA 开发板 芯片: GW5A-LV25UG324C1/I0 (Arora V 系列)
一、实验目的
- 掌握拨码开关作为 FPGA 输入信号的使用方法,包括两级同步消除亚稳态。
- 理解多模式切换的设计思路——用按键在多种输出模式之间轮转。
- 学会复用已有模块(
key_debounce)实现按键消抖,降低开发复杂度。 - 掌握滑动开关的 CST 约束写法(
PULL_MODE=DOWN、IO_TYPE=LVCMOS33)。
二、实验过程
2.1 硬件连接
| 信号 | 方向 | FPGA 引脚 | Bank | 说明 |
|---|---|---|---|---|
| CLK | 输入 | T9 | Bank 4 | 50MHz 有源晶振 |
| SW1~SW8 | 输入 | F10/F9/A15/B14/A14/A13/A12/B11 | Bank 0/1 | 滑动开关,默认下拉(ON=1) |
| KEY1 | 输入 | C10 | Bank 1 | 轻触按键,模式切换 |
| LED1~7 | 输出 | G9/E7/E6/A5/A4/B4/A3/C5 | Bank 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 行为 | 说明 |
|---|---|---|---|
| A | 0 | led = sw | 直接映射:开关 ON → LED 亮 |
| B | 1 | led = ~sw | 取反映射:开关 ON → LED 灭 |
| C | 2 | led = 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
端口列表:
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk | input | 1 | 50MHz 系统时钟 |
sw | input | [7:0] | 8 路滑动开关 (ON=1) |
key0 | input | 1 | 模式切换按键 (按下=0) |
led | output | [7:0] | 8 路 LED 输出 (高亮) |
内部信号:
| 信号 | 位宽 | 说明 |
|---|---|---|
sw_sync1, sw_sync2 | [7:0] | 两级同步寄存器 |
key0_press | 1 | 消抖后的按键脉冲 |
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=1→led=1→ LED 亮。 - 模式 B:开关 ON →
sw=1→led=0→ LED 灭(逻辑取反)。 default分支保证综合器不会推断出锁存器。
四、实验现象
| 操作 | 模式 | LED 现象 |
|---|---|---|
| 上电 | A(0) | 拨动 K1 |
| 按 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_sync1、sw_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