首页 > 技术文章 > FPGA——基于四段式状态机的按键防抖

cnlntr 2020-03-12 15:37 原文

一、设计思路

nedge:检测到按键出现下降沿

pedge:检测到按键出现上升沿

end_cnt_20ms:计数器计满20ms

parameter   IDLE     =  4'b0001; //空闲状态
parameter   S1       =  4'b0010; //按下
parameter   S2       =  4'b0100; //等待
parameter   S3       =  4'b1000; //释放

二、按键防抖代码

module key_filter(
   clk         ,
   rst_n       ,
   key         ,
   key_state   ,
   key_flag    
);
parameter   IDLE     =  4'b0001; //空闲状态
parameter   S1       =  4'b0010; //按下
parameter   S2       =  4'b0100; //等待
parameter   S3       =  4'b1000; //释放

parameter   CNTF_N   =  1000000;
parameter   CNTF_W   =  20;
parameter   STATE_W  =  4;       //状态机位宽

input                clk;
input                rst_n;
input                key;
output               key_flag;
output               key_state;

reg                  key_p_flag;
reg                  key_r_flag;
wire                 key_flag;
reg                  key_state;

reg   [CNTF_W-1:0]   cnt_filter;
wire                 add_cnt_filter;
wire                 end_cnt_filter;

reg   [STATE_W-1:0]  state_c;
reg   [STATE_W-1:0]  state_n;
reg   [2:0]          key_sync;

wire                 IDLE2S1_start;
wire                 S12IDLE_start;
wire                 S12S2_start  ;
wire                 S22S3_start  ;
wire                 S32S2_start  ;
wire                 S32IDLE_start;

wire                 nedge_flag;
wire                 pedge_flag;

//20ms计数器
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      cnt_filter <= 0;
   else if(add_cnt_filter)begin
      if(end_cnt_filter)
         cnt_filter <= 0;
      else
         cnt_filter <= cnt_filter + 1'b1;
   end
   else
      cnt_filter <= 0;
end
assign add_cnt_filter   =  (state_c == S1) || (state_c == S3);
assign end_cnt_filter   =  add_cnt_filter && cnt_filter == CNTF_N - 1; 

//状态机
//状态转移描述
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      state_c <= IDLE;
   else
      state_c <= state_n;
end

//状态条件转移描述
always @(*)begin
   case(state_c)
      IDLE:begin
         if(IDLE2S1_start)
            state_n = S1;
         else
            state_n = state_c;
      end
      S1:begin
         if(S12IDLE_start)
            state_n = IDLE;
         else if(S12S2_start)
            state_n = S2;
         else
            state_n = state_c;
      end
      S2:begin
         if(S22S3_start)
            state_n = S3;
         else
            state_n = state_c;
      end
      S3:begin
         if(S32S2_start)
            state_n = S2;
         else if(S32IDLE_start)
            state_n = IDLE;
         else
            state_n = state_c;
      end
      default:state_n = IDLE;
   endcase
end

//状态条件
assign IDLE2S1_start =  state_c == IDLE && nedge_flag;
assign S12IDLE_start =  state_c == S1   && pedge_flag;
assign S12S2_start   =  state_c == S1   && end_cnt_filter; 
assign S22S3_start   =  state_c == S2   && pedge_flag; 
assign S32S2_start   =  state_c == S3   && nedge_flag; 
assign S32IDLE_start =  state_c == S3   && end_cnt_filter;

//状态机输出
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      key_p_flag <= 0;
   else if(S12S2_start)
      key_p_flag <= 1;
   else
      key_p_flag <= 0;
end
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      key_r_flag <= 0;
   else if(S32IDLE_start)
      key_r_flag <= 1;
   else
      key_r_flag <= 0;
end
assign key_flag = (key_p_flag) || (key_r_flag);

always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      key_state <= 1;
   else if(S12S2_start)
      key_state <= 0;
   else if(S32IDLE_start)
      key_state <= 1;
end

//边沿检测
always @(posedge clk or negedge rst_n)begin
   if(!rst_n)
      key_sync <= 3'b111;
   else
      key_sync <= {key_sync[1:0],key};
end
assign nedge_flag = key_sync[2:1]==2'b10;
assign pedge_flag = key_sync[2:1]==2'b01;

endmodule

三、按键防抖仿真文件

`timescale 1ns / 1ns

module key_filter_tb();

parameter CYCLE = 20;

reg clk;
reg rst_n;
reg key;
wire key_state;    
wire key_flag;

reg  [31:0] rand;

key_filter key_filter(
   clk         ,
   rst_n       ,
   key         ,
   key_state   ,
   key_flag    
);


initial clk = 1;
always #(CYCLE/2) clk = ~clk;

initial begin
   rst_n = 1;
   #3;
   rst_n = 0;
   #(10 * CYCLE);
   rst_n = 1;
end

initial begin
   key = 1;
   #(15*CYCLE);
   press_key(3);
   $stop;
end


task press_key;
   input [3:0] seed;
   begin
      key = 1;
      #(20_000_000);

      rand = {$random(seed)}%1_000_000;  //0~999999

      repeat(5)begin
         #rand key = ~key;
      end
      key =0;
      #(25_000_000);

      repeat(5)begin
         #rand key = ~key;
      end
      key = 1;
      #(25_000_000);
   end
endtask
endmodule

四、板级验证代码

module key_top(
clk     ,
rst_n   ,
key_in  ,
led
);
//参数定义
parameter           LED_W =             4;
//输入信号定义
input               clk         ;
input               rst_n       ;
input               key_in      ;

//输出信号定义
output[LED_W-1:0]   led         ;
//输出信号reg定义
wire  [LED_W-1:0]   led         ;
wire                key         ;
 //中间信号定义
wire                key_flag    ;
wire                key_state   ;
reg   [LED_W-1:0]   cnt         ;
wire                add_cnt     ;
wire                end_cnt     ;

assign key = (key_flag && !key_state);

always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
      cnt <= 0;
   end
   else if(add_cnt)begin
      if(end_cnt)
            cnt <= 0;
      else
            cnt <= cnt + 1'b1;
   end
end

assign add_cnt = (key == 1);
assign end_cnt = add_cnt && cnt == 16 - 1;


assign led = cnt;

key_filter key_filter(
.clk(clk)               ,//50MHz时钟
.rst_n(rst_n)           ,//复位
.key(key_in)            ,//按键输入
.key_flag(key_flag)     ,//按键计时标志信号
.key_state(key_state)    //按键状态信号
);
endmodule

推荐阅读