【FPGA】状态机写按键消抖

文章目录

一、状态机原理

状态(FSM),又称有限状态机
一段式状态机
一段式状态机似乎是一锅端,把所有逻辑(包括输入,输出,状态)都在一个always里解决了,这种写法看上去好像很简洁,但是往往不利于维护,这种写法不太推荐,但是在一些简单的状态机中还是可以使用的。
两段式状态机
两段式状态机是一种常用的写法,他把时序逻辑和组合逻辑划分开来,时序逻辑里进行当前逻辑和下一逻辑的切换,组合逻辑里实现各个输入输出及状态判断,这种写法相对容易维护,不过组合逻辑输出较容易出现毛刺等常见问题。
三段式状态机
三段式状态机的写法是一种比较推荐的写法,代码容易维护,时序逻辑的输出解决了两段式写法中组合逻辑的毛刺问题,但是从资源消耗上来讲,三段式消耗的资源会多一些,另外,三段式输入从输入到输出比一段式会延迟一个时钟周期。三段式状态机将时序逻辑和组合逻辑分开,状态和输出分开,清晰容易理解。

我推荐就是写三段式状态机

二、设计思路

按键消抖算是一个比较简单的模块,四个状态
1.IDLE(初始状态)
2.K_D(按键按下的状态)
3.H_D(按键按住稳定的值的状态)
4.K_U(按键弹起的状态)(这个可要可不要)

状态图:

【FPGA】状态机写按键消抖

状态转移图:

【FPGA】状态机写按键消抖

然后去想想这几个状态的跳转条件,无非就是边沿的检测和按键消抖的20ms的延时

assign idle2down = (state_c == IDLE) && nedge;//检测到下降沿
assign down2idle = (state_c == DOWN) && (pedge&& end_cnt_20ms);//计时未到20ms时且出现上升沿表示按键意外抖动,回到初始态
assign down2hold = (state_c == DOWN) && (~pedge && end_cnt_20ms);//计时到20ms时没有出现上升沿标志按键按下后保持稳定
assign hold2up   = (state_c == HOLD) && (pedge);//检测到上升沿跳转到上升态
assign up2idle   = (state_c == UP)   && end_cnt_20ms;//计数器计数到20ms跳转到初始态

其余的逻辑就跟平常的按键消抖无异,可以看我之前的博客的代码

【FPGA】实战之按键消抖

三、代码部分

这里代码是借鉴同学的代码,我的代码写的写完的时候太乱了,反正这一个按键消抖的模块,要用的时候代码直接照搬,例化就完事了,用里面的key_out信号就行了。

module fsm_key_debounce # (parameter KEY_W = 3,TIME_20MS = 1_000_000)(
    input 			            clk		,
    input 			            rst_n	,
    input 		[KEY_W - 1:0]	key_in	,

    output 		[KEY_W - 1:0]	key_out	 
);
//参数定义
localparam IDLE  = 4'b0001;//初始状态 
localparam DOWN  = 4'b0010;//按键按下抖动
localparam HOLD  = 4'b0100;//按键按下后稳定
localparam UP    = 4'b1000;//按键上升抖动
//信号定义
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态

//状态转移条件定义
wire idle2down;
wire down2idle;
wire down2hold;
wire hold2up  ;
wire up2idle  ;

reg [KEY_W - 1:0] key_r0;//同步
reg [KEY_W - 1:0] key_r1;//打拍
wire [KEY_W - 1:0] nedge;//下降沿
wire [KEY_W - 1:0] pedge;//上升沿

//20ms计数器
reg [19:0] cnt_20ms;
wire add_cnt_20ms;
wire end_cnt_20ms;

reg [KEY_W - 1:0] key_out_r;//输出寄存

always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        state_c <= IDLE;
    end
    else begin
        state_c <= state_n;
    end
end

always@(*)begin
    case(state_c)
        IDLE:begin
            if(idle2down)begin
                state_n = DOWN;
            end
            else begin
                state_n = state_c;
            end
        end
        DOWN:begin
            if(down2idle)begin
                state_n = IDLE;
            end
            else if(down2hold)begin
                state_n = HOLD;
            end
            else begin
                state_n = state_c;
            end
        end
        HOLD:begin
            if(hold2up)begin
                state_n = UP;
            end
            else begin
                state_n = state_c;
            end
        end
        UP:begin
            if(up2idle)begin
                state_n = IDLE;
            end
            else begin
                state_n = state_c;
            end
        end
        default:state_n = state_c;
    endcase
end

assign idle2down = (state_c == IDLE) && nedge;//检测到下降沿
assign down2idle = (state_c == DOWN) && (pedge&& end_cnt_20ms);//计时未到20ms时且出现上升沿表示按键意外抖动,回到初始态
assign down2hold = (state_c == DOWN) && (~pedge && end_cnt_20ms);//计时到20ms时没有出现上升沿标志按键按下后保持稳定
assign hold2up   = (state_c == HOLD) && (pedge);//检测到上升沿跳转到上升态
assign up2idle   = (state_c == UP)   && end_cnt_20ms;//计数器计数到20ms跳转到初始态
//20ms计数器
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt_20ms <= 0;
    end
    else if(add_cnt_20ms)begin
        if(end_cnt_20ms)begin
            cnt_20ms <= 0;
        end
        else begin
            cnt_20ms <= cnt_20ms + 1'b1;
        end
    end
end
assign add_cnt_20ms = state_c == DOWN || state_c == UP;//当按键按下或上弹时开始计数
assign end_cnt_20ms = add_cnt_20ms && ((cnt_20ms == TIME_20MS - 1) || pedge);//当计数到最大值或检测到上升沿计数器清零



//同步打拍
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        key_r0 <= {KEY_W{1'b1}};
        key_r1 <= {KEY_W{1'b1}};
    end
    else begin
        key_r0 <= key_in;
        key_r1 <= key_r0;
    end
end

assign nedge = ~key_r0 &  key_r1;//检测下降沿
assign pedge =  key_r0 & ~key_r1;//检测上升沿

//按键赋值
always@(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        key_out_r <= {KEY_W{1'b0}};
    end
    else if(state_c == HOLD && hold2up)begin
        key_out_r <= ~key_r1;
    end
    else begin
        key_out_r <= {KEY_W{1'b0}};
    end
end
assign key_out = key_out_r;

endmodule

四、仿真验证

这里就不验证了,还是可以去看我之前的博客仿真,无非是多加了4个状态和4个状态转移的条件。

【FPGA】实战之按键消抖

上一篇:XDS100V3-DIY


下一篇:FPGA经验谈系列文章——写代码心中要有电路?