实验 05:按键控制 LED
🛒 开发板购买: EK303 GW1N FPGA核心板
一、实验目的
- 掌握 Verilog 模块例化——将实验 04 的消抖模块作为子模块调用。
- 学习参数传递
#(.PARAM(val))的语法,实现可配置的模块复用。 - 理解 Toggle 翻转逻辑——按一次亮、再按灭。
- 掌握多模式状态机设计——在不同的 LED 工作模式间切换。
二、实验过程
2.1 硬件连接
| 外设 | 引脚 |
|---|---|
| KEY1 | 69 |
| KEY2 | 70 |
| LED1~3 | Pin 8 / 9 / 10 |
2.2 设计思路
系统结构:
KEY1 ──→ [消抖] ──→ key1_pressed ──→ [Toggle逻辑] ──→ led1
KEY2 ──→ [消抖] ──→ key2_pressed ──→ [模式状态机] ──→ led2, led3
功能设计:
| 按键 | 功能 | 详细 |
|---|---|---|
| KEY1 | Toggle | 按一次 LED1 翻转亮灭状态 |
| KEY2 | 模式切换 | 按一次切换 LED2/LED3 工作模式 |
三种模式:
| 模式 | LED 行为 |
|---|---|
| 模式 0 | 全部常灭 |
| 模式 1 | LED2 以 1Hz 闪烁 |
| 模式 2 | LED2 和 LED3 交替闪烁 |
2.3 Verilog 模块设计
顶层模块名:key_ctrl_led
例化的子模块:两个 key_debounce 实例(分别处理 KEY1 和 KEY2)。
顶层端口:
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk_50m | input | 1 | 时钟 |
key1, key2 | input | 1 | 两个按键输入 |
led1, led2, led3 | output | 1 | 三个 LED |
内部信号:
| 信号 | 说明 |
|---|---|
key1_pressed | KEY1 消抖后的脉冲 |
key2_pressed | KEY2 消抖后的脉冲 |
mode (2 bit) | 当前模式编号 (0/1/2) |
led1_state | LED1 当前亮灭状态 |
blink_cnt (26 bit) | 1Hz 闪烁计数器 |
逻辑描述:
- 例化两个消抖模块,分别处理 KEY1、KEY2。
key1_pressed脉冲到来 →led1_state <= ~led1_state(Toggle)。key2_pressed脉冲到来 →mode <= mode + 1(循环 0→1→2→0)。- 根据
mode值驱动 LED2/LED3:
- mode 0:全部输出 0(灭)。
- mode 1:LED2 用 1Hz 计数器翻转。
- mode 2:LED2 和 LED3 以 1Hz 交替(LED2 亮时 LED3 灭,反之亦然)。
2.4 引脚约束
| 信号 | 约束 |
|---|---|
key1, key2 | IO_LOC 对应按键引脚,PULL_MODE=UP |
led1~3 | Pin 8/9/10,IO_TYPE=LVCMOS33 |
三、实验现象
| 操作 | 现象 |
|---|---|
| 按下 KEY1 | LED1 亮;再按,LED1 灭;交替切换 |
| 按下 KEY2(第 1 次) | 进入模式 1:LED2 开始以 1Hz 闪烁 |
| 按下 KEY2(第 2 次) | 进入模式 2:LED2 和 LED3 交替闪烁(你亮我灭) |
| 按下 KEY2(第 3 次) | 进入模式 0:所有 LED 灭 |
| 继续按 KEY2 | 模式循环 0→1→2→0... |
按键响应准确,每个模式切换有即时视觉反馈。
四、掌握知识点
| 知识点 | 说明 |
|---|---|
| 模块例化 | key_debounce #(.DEBOUNCE_MS(20)) u_key1(...) |
| 参数传递 | #(.PARAM(val)) 让模块可配置 |
| Toggle 逻辑 | state <= ~state 一比特翻转 |
| 多模式状态机 | case(mode) 控制不同行为 |
| 交替闪烁 | led2 <= blink; led3 <= ~blink 反向驱动 |
五、思考题
- 如果 KEY2 要支持 5 种模式,需要修改哪些代码?位宽够吗?
- 怎么在模式切换时让 LED 闪烁两次作为"模式切换确认"信号?
- 消抖模块的
DEBOUNCE_MS参数在例化时怎么传入?
附录:源码清单
top.v
// =============================================================================
// top.v - 按键控制LED (Key Control LED)
// 芯片: GW1N-LV9LQ144C6/I5
// 时钟: 50MHz (Pin 98)
//
// 功能:
// KEY1 (Pin 69): Toggle LED1 — 按一次翻转LED1亮灭状态
// KEY2 (Pin 70): 模式切换 — 按一次在三种模式间循环切换
//
// 模式0: 全部LED灭
// 模式1: LED2 以 1Hz 闪烁
// 模式2: LED2 和 LED3 以 1Hz 交替闪烁 (你亮我灭)
//
// 子模块:
// 例化两个 key_debounce 实例 (来自实验04), 分别处理 KEY1 和 KEY2
// =============================================================================
module key_ctrl_led (
input wire clk_50m, // 50MHz 系统时钟
input wire key1, // KEY1 (Pin 69, 按下低电平)
input wire key2, // KEY2 (Pin 70, 按下低电平)
output reg led1, // LED1 (Pin 8, Toggle控制)
output reg led2, // LED2 (Pin 9, 模式控制)
output reg led3 // LED3 (Pin 10, 模式控制)
);
// =========================================================================
// 本地参数
// =========================================================================
localparam BLINK_HALF_MAX = 26'd24_999_999; // 0.5s @ 50MHz → 1Hz闪烁
localparam MODE_MAX = 2'd2; // 最大模式编号
// =========================================================================
// 内部信号
// =========================================================================
wire key1_pressed; // KEY1 消抖后的脉冲
wire key2_pressed; // KEY2 消抖后的脉冲
reg [1:0] mode; // 当前模式: 0/1/2
reg [25:0] blink_cnt; // 1Hz 闪烁计数器
// =========================================================================
// 子模块例化: KEY1 消抖
// 参数 DEBOUNCE_MS=20 → 20ms 消抖
// =========================================================================
key_debounce #(
.DEBOUNCE_MS(20)
) u_key1_debounce (
.clk_50m (clk_50m),
.key_in (key1),
.key_pressed (key1_pressed)
);
// =========================================================================
// 子模块例化: KEY2 消抖
// =========================================================================
key_debounce #(
.DEBOUNCE_MS(20)
) u_key2_debounce (
.clk_50m (clk_50m),
.key_in (key2),
.key_pressed (key2_pressed)
);
// =========================================================================
// KEY1 处理: Toggle LED1
// 每次 key1_pressed 脉冲到来时,翻转 LED1 的亮灭状态
//
// 效果:
// 按一下 → LED1 亮 (led1=1)
// 再按 → LED1 灭 (led1=0)
// 再按 → LED1 亮 (led1=1) ... 如此往复
// =========================================================================
always @(posedge clk_50m) begin
if (key1_pressed)
led1 <= ~led1;
end
// =========================================================================
// KEY2 处理: 模式切换
// 每次 key2_pressed 脉冲到来时,mode 循环递增: 0→1→2→0→1→2...
// =========================================================================
always @(posedge clk_50m) begin
if (key2_pressed) begin
if (mode == MODE_MAX)
mode <= 2'd0; // 超出最大值则回0
else
mode <= mode + 1'b1;
end
end
// =========================================================================
// 1Hz 闪烁计数器
// 自由运行: 0→24,999,999→0 循环
// 半周期 = 0.5s = 25,000,000 个时钟周期
// =========================================================================
reg blink_toggle; // 1Hz 翻转信号
always @(posedge clk_50m) begin
if (blink_cnt == BLINK_HALF_MAX) begin
blink_cnt <= 26'd0;
blink_toggle <= ~blink_toggle;
end else begin
blink_cnt <= blink_cnt + 1'b1;
end
end
// =========================================================================
// 模式输出逻辑
//
// 模式0 (2'd0): 全部LED灭
// led2 = 0, led3 = 0
//
// 模式1 (2'd1): LED2 1Hz闪烁, LED3灭
// led2 = blink_toggle, led3 = 0
//
// 模式2 (2'd2): LED2/LED3 交替闪烁
// led2 = blink_toggle
// led3 = ~blink_toggle (与LED2反相, 你亮我灭)
// =========================================================================
always @(posedge clk_50m) begin
case (mode)
2'd0: begin
led2 <= 1'b0;
led3 <= 1'b0;
end
2'd1: begin
led2 <= blink_toggle;
led3 <= 1'b0;
end
2'd2: begin
led2 <= blink_toggle;
led3 <= ~blink_toggle;
end
default: begin
led2 <= 1'b0;
led3 <= 1'b0;
end
endcase
end
// =========================================================================
// 上电初始值
// =========================================================================
initial begin
led1 = 1'b0; // 初始LED1灭
mode = 2'd0; // 初始模式0 (全灭)
blink_toggle = 1'b0; // 闪烁信号初值
end
endmodule