2. 按键控制LED

任务目标

使用按键作为输入,控制开发板上LED的亮灭状态。每次按下按键时,LED状态翻转(按下前灭则按下后亮,反之亦然)。核心学习点在于:按键存在机械抖动,必须进行消抖处理;使用简单状态机(或等效的边沿检测逻辑)确保每次按键只触发一次状态翻转。

硬件环境与依赖

  • 开发板:任意主流FPGA开发板(如Altera DE10-Lite、Xilinx Artix-7系列)
  • 外设:1个按键(按下为低电平,即低有效)、1个LED(高电平点亮)
  • 时钟:50MHz系统时钟(可根据实际板卡调整分频参数)

核心原理与设计思路

按键消抖的常用方法是“延时采样”:检测到按键电平变化后,等待约10~20ms,再次读取电平,若与变化后状态一致,则确认按键状态稳定。本设计采用状态机实现:空闲态等待按键按下,进入消抖态计时,计时结束后若按键仍为按下状态,则翻转LED并回到空闲态等待释放。该状态机同时天然完成了“按下沿检测”,避免一次按压过程中多次触发。

关键完整代码

module key_led (
    input       clk,        // 50MHz
    input       rst_n,      // 低电平复位
    input       key_in,     // 按键输入,按下为0
    output reg  led_out     // LED输出,1点亮
);

// 参数定义
parameter CNT_20MS = 1_000_000;  // 50MHz时钟下20ms计数

// 状态编码
localparam IDLE = 2'b00;
localparam PRESS_DETECT = 2'b01;
localparam RELEASE_WAIT = 2'b10;

reg [1:0] state, next_state;
reg [19:0] cnt;
reg key_reg0, key_reg1;  // 两级同步寄存器

// 按键同步(消除亚稳态)
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        key_reg0 <= 1'b1;
        key_reg1 <= 1'b1;
    end else begin
        key_reg0 <= key_in;
        key_reg1 <= key_reg0;
    end
end

// 状态寄存器
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        state <= IDLE;
    else
        state <= next_state;
end

// 计数器
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        cnt <= 20'd0;
    else if (next_state != state)  // 状态变化时清零
        cnt <= 20'd0;
    else if (state == PRESS_DETECT || state == RELEASE_WAIT)
        cnt <= cnt + 1'b1;
end

// 状态转移组合逻辑
always @(*) begin
    next_state = state;
    case (state)
        IDLE: begin
            if (key_reg1 == 1'b0)  // 检测到按下
                next_state = PRESS_DETECT;
        end
        PRESS_DETECT: begin
            if (cnt >= CNT_20MS) begin
                if (key_reg1 == 1'b0)  // 消抖后确认按下
                    next_state = RELEASE_WAIT;
                else
                    next_state = IDLE;  // 抖动,回到空闲
            end
        end
        RELEASE_WAIT: begin
            if (key_reg1 == 1'b1)  // 等待释放
                next_state = IDLE;
        end
    endcase
end

// LED输出控制
always @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        led_out <= 1'b0;
    else if (state == PRESS_DETECT && next_state == RELEASE_WAIT)
        led_out <= ~led_out;  // 确认按下时翻转
end

endmodule

运行与验证

  1. 引脚约束:根据板卡原理图,将clk连接50MHz时钟引脚,key_in连接按键引脚,led_out连接LED引脚,rst_n连接复位按键(或直接接高电平)。
  2. 编译烧录:在Quartus/Vivado中新建工程,添加上述代码,完成综合、布局布线后下载到FPGA。
  3. 功能验证:
    • 上电后LED处于熄灭状态(若复位信号有效则更明确)。
    • 快速按一下按键(按下时间大于20ms),LED状态翻转(灭→亮)。
    • 再次按下,LED翻转(亮→灭)。
    • 快速连续按多次,每次按下LED都翻转一次,不会出现一次按压多次翻转或状态错乱。

通过判定标准:每次完整按压(按下并释放)LED恰好翻转一次;快速连按时翻转次数与按压次数一致;按住不放时LED只翻转一次,不会持续闪烁。

常见问题

  • LED不响应:检查按键是否低有效(按下为0),若为高有效需反转判断条件;检查时钟频率与CNT_20MS参数是否匹配。
  • 一次按压多次翻转:说明消抖时间不足或状态机逻辑有误,确认CNT_20MS值是否正确(50MHz下20ms = 1_000_000)。
  • LED状态上电不确定:确保复位信号有效,或在初始化时给led_out赋确定初值。