今天学习了按键的消抖。上午用了简单的延时和标志位进行消抖。 其实现相对快捷,但实则逻辑,和有用程度没有下午的有限状态机清晰,更加适合临时开发,从后期维护和长期稳定,以及复用能力来看,还是有限状态机更胜一筹。
产生疑惑及解决:
今天的疑惑产生倒是几乎没什么,如果有,那就是后面实现有限状态机的时候,一些代码没看懂,但后来静下心来理清实现的逻辑,一步步看懂了。一会儿会贴出相关收获以及实现逻辑的思维导图。
接下来做的事:
进一步回顾,理清一些程序的实现逻辑,以及模块化架构思维,学会复用,对于后期学习的进一步调整,相关侧重点的增强。
今天收获:
对于延时消抖,收获不多。但对于有限状态机,收获很多,因为一方面其比较重要,另一方面,其逻辑很清晰,而且前几天有接触,有一定总结,更有兴趣去进一步专研总结。
下面我将从宏观,逐步进行具体,实例化。(也相当于对状态机实现的进一步回顾总结)
有限状态机实现 的大体框架:
程序实现包括三大板块:状态转移——状态描述(重点)——状态输出
提到有限状态机,则 必有两个变量。 当前状态,下一个状态。
然后再是对于状态转移规律:即几个状态的切换关系。
下面分别是四个状态:空闲状态——按下消抖状态——按下稳定状态——弹起消抖状态
明白几个状态后。 再理清各个状态之间的条件转移关系。
tips:在实现的时候,直接宏观的一步步写,然后再逐步细化,补充,学会 层次化,模块化的思维。如下方判断条件的书写:(即类似伪代码一样。后续一步步扩充,补全就行)
//第二段,描述状态转移规律
always@(*)begin
case(state_c)
IDLE:begin
if(idle2filter_down_start)
state_n = FILTER_DOWN;
else
state_n = state_c;
end
具体过程不再详述,以下为实现的导图:
(可能不太清晰,后续会贴代码,或有需要清晰图的,可以找我要Q:1354087583)
在用状态机实现消抖过程中,还学到个很巧妙的东西。就是上下升沿的判断(边沿检测)
当然,其实也很简单,但对于我是从 之前一直是静态状态判断(高,低电平),到动态状态(升降电平)的判断的跳变,还是觉得蛮有意思。
//对按键输入信号打两拍,从而实现边沿检测
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_in_r0 <= 1'b1;
key_in_r1 <= 1'b1;
end
else begin
key_in_r0 <= key_in;
key_in_r1 <= key_in_r0;
end
end
assign n_edge = ~key_in_r0 & key_in_r1;//下降沿
assign p_edge = ~key_in_r1 & key_in_r0;//上升沿
其很巧妙的利用了fpga的非阻塞赋值 ,即 第8,9行代码。
解释:当这两条语句进行后,key_in_r1实至是获取的key_in_r0 执行第8行之前的值,而key_in_r0获取的是现在(此时此刻)传入的key_in的值(key_in是动态的外部输入的实时电平)。
今天差不多就这些,接下来就是今天相关的代码。
(注:以下代码均以抬起按键认为有效 ,即 不具有连续输入功能, 按一下 松开,变化一下)
普通延时消抖
顶层:
module top_key_beep(
input clk,
input rst_n,
input key,
output beep
);
wire flag;
wire key_value;
key_debounce u_key_debounce(
.clk(clk),
.rst_n(rst_n),
.key_in(key),
.flag(flag),
.key_value(key_value)
);
beep_control u_beep_control(
.clk(clk),
.rst_n(rst_n),
.flag(flag),
.key_value(key_value),
.beep(beep)
);
endmodule
消抖:
module key_debounce(
input clk,
input rst_n,
input key_in,
output reg flag,//按键消抖完成
output reg key_value//消抖后的按键值
);
parameter DELAY = 20'd100_0000;
reg [19:0] delay_cnt ;
reg key_reg;//保存上一时刻按键的值
//延时消抖 逻辑代码
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
delay_cnt <= 20'd0;
key_reg <= 1'b1;
end
else begin
key_reg <= key_in;
if (key_reg != key_in) begin
delay_cnt <= DELAY;
end
else begin
if (delay_cnt > 20'd0) begin
delay_cnt <= delay_cnt -1'd1;
end
else begin
delay_cnt <= 20'd0;
end
end
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 1'b0;
key_value <= 1'b1;
end
else if(delay_cnt == 1'd1)begin //20ms没有抖动,输出当前按键值
flag <= 1'b0;
key_value <= key_in;
end
else begin
flag <= 1'b0;
key_value <= key_value;
end
end
endmodule
蜂鸣器:
module beep_control(
input clk,
input rst_n,
input key_value,
input flag,
output reg beep
);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
beep <= 1'b1;
end
else begin
if(!key_value && flag) begin
beep <= ~beep;//低电平有效
end
else begin
beep <= beep;
end
end
end
endmodule
有限状态机实现消抖
顶层文件:top_key_beep.v
module top_key_beep(
input clk,
input rst_n,
input [1:0]key,
output [1:0]led
);
wire [1:0]key_value;
key_keep u_key_keep(
.clk(clk),
.rst_n(rst_n),
.key_in(key[0]),
.key_down(key_value[0])
);
key_keep u_key1_keep(
.clk(clk),
.rst_n(rst_n),
.key_in(key[1]),
.key_down(key_value[1])
);
beep_control u_beep_control(
.clk(clk),
.rst_n(rst_n),
.key_value(key_value),
.led(led)
);
endmodule
响应文件(led):
module beep_control(
input clk,
input rst_n,
input [1:0]key_value,
output reg [1:0]led
);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led <= 2'b11;
end
else if(key_value[0]) begin
led[0] <= ~led[0];//低电平有效
end
else if(key_value[1]) begin
led[1] <= ~led[1];//低电平有效
end
else begin
led <= led;
end
end
endmodule
按键消抖文件
module key_keep(//消抖模块
input clk,
input rst_n,
input key_in,//未消抖的按键
output reg key_down//消抖后的按键值
);
/****************************************************************
main
按键消抖用状态机方法实现
***************************************************************/
//定义按键按下的四个 状态
localparam IDLE = 4'b0001;
localparam FILTER_DOWN = 4'b0010;//按键按下过程
localparam HOLD_DOWN = 4'b0100 ;
localparam FILTER_UP = 4'b1000; //按键松开过程
parameter TIME_DELAY = 20'd100_0000;
wire add_delay_cnt;//开始计时的标志
wire end_delay_cnt;//结束及时的标志
reg [19:0] cnt;
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态
reg key_in_r0;
reg key_in_r1;
wire n_edge;//按键按下
wire p_edge;//按键松开
wire idle2filter_down_start;
wire filter_down2hold_down_start;
wire filter_down2idle_start;
wire hold_down2filter_up_start;
wire filter_up2idle_start;
//接收按键信号
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_in_r0 <= 1'b1;
key_in_r1 <= 1'b1;
end
else begin
key_in_r0 <= key_in;
key_in_r1 <= key_in_r0;
end
end
assign n_edge = ~key_in_r0 & key_in_r1;
assign p_edge = ~key_in_r1 & key_in_r0;
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
if(!rst_n)begin
state_n = IDLE;
end
else begin
case (state_c)
IDLE:begin
if(idle2filter_down_start)
state_n <=FILTER_DOWN;
else
state_n <=state_c;
end
FILTER_DOWN:begin
if(filter_down2hold_down_start)begin
state_n <= HOLD_DOWN;
end
else if (filter_down2idle_start) begin
state_n <=IDLE;
end
else
state_n <= state_c;
end
HOLD_DOWN: begin
if (hold_down2filter_up_start) begin
state_n <= FILTER_UP;
end
else
state_n <= state_c;
end
FILTER_UP: begin
if (filter_up2idle_start)begin
state_n <= IDLE;
end
else
state_n <= state_c;
end
default: ;
endcase
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_down <= 0;
end
else if(hold_down2filter_up_start)begin
key_down <= ~key_in_r1;//如果不取反,按键按下就输出0,取反,按键按下就输出1
end
else begin
key_down <= 1'b0;
end
end
assign idle2filter_down_start = (state_c == IDLE)&&(n_edge);
assign filter_down2idle_start = (state_c == FILTER_DOWN)&&(add_delay_cnt && p_edge);
assign filter_down2hold_down_start = (state_c == FILTER_DOWN)&&(end_delay_cnt && !p_edge);
assign hold_down2filter_up_start = (state_c == HOLD_DOWN)&&(p_edge);
assign filter_up2idle_start = (state_c == FILTER_UP)&&(end_delay_cnt);
/*计数条件与结束条件*/
assign add_delay_cnt = (state_c==FILTER_DOWN) || (state_c==FILTER_UP);
assign end_delay_cnt = add_delay_cnt && (cnt == TIME_DELAY-1);
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 20'd0;
end
else if(add_delay_cnt)begin
if (end_delay_cnt) begin
cnt <= 20'd0;
end
else begin
cnt <= cnt +1'b1;
end
end
else begin
cnt <= cnt;
end
end
endmodule
(这里再贴个老师的,因为也附有自己的添的一些注释,好理解写,同时也工整不少,但注意 这个文件的变量名和上面的并不通用)
状态机实现消抖:
module key_debounce (
input clk,
input rst_n,
input key_in,
output reg key_down
);
//独热码方式定义状态机状态参数
localparam IDLE =4'b0001;
localparam FILTER_DOWN =4'b0010;
localparam HOLD_DOWN =4'b0100;
localparam FILTER_UP =4'b1000;
parameter TIME_DELAY = 20'd100_0000;
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态
reg [19:0] counter;//计数器
reg key_in_r0;
reg key_in_r1;
wire n_edge;//下降沿检测信号
wire p_edge;//上升沿检测信号
wire add_delay_flag;
wire end_delay_flag;
wire idle2filter_down_start; //空闲状态进入按下消抖状态标志,即检测到下降沿
wire filter_down2idle_start;
wire filter_down2hold_down_start;
wire hold_down2filter_up_start;
wire filter_up2idle_start;
/***************************************************************
main
按键消抖用状态机方法实现
***************************************************************/
//对按键输入信号打两拍,从而实现边沿检测
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_in_r0 <= 1'b1;
key_in_r1 <= 1'b1;
end
else begin
key_in_r0 <= key_in;
key_in_r1 <= key_in_r0;
end
end
//更简便方法
assign n_edge = ~key_in_r0 & key_in_r1;//下降沿
assign p_edge = ~key_in_r1 & key_in_r0;//上升沿
//三段式状态机实现
//第一段,描述状态转移
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(idle2filter_down_start)
state_n = FILTER_DOWN;
else
state_n = state_c;
end
FILTER_DOWN:begin
if(filter_down2idle_start)
state_n = IDLE;
else if(filter_down2hold_down_start)
state_n = HOLD_DOWN;
else
state_n = state_c;
end
HOLD_DOWN:begin
if(hold_down2filter_up_start)
state_n = FILTER_UP;
else
state_n = state_c;
end
FILTER_UP:begin
if(filter_up2idle_start)
state_n = IDLE;
else
state_n = state_c;
end
default:state_n = IDLE;
endcase
end
//第三段,描述各个状态的输出
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_down <= 1'b0;
end
else if( hold_down2filter_up_start ) begin//按键抬起的时候切换输出
key_down <= ~key_in_r1;//把按下时候的0 变成1(为了便于后续判断,这里输出这个 就相当于是 输出的flag。输出1表示有效,0表示无效)
end
else
key_down <= 1'b0;
end
assign idle2filter_down_start = (state_c == IDLE) && (n_edge);
assign filter_down2idle_start = (state_c == FILTER_DOWN) && (add_delay_flag && p_edge);
assign filter_down2hold_down_start = (state_c == FILTER_DOWN) && (end_delay_flag && (!p_edge));
assign hold_down2filter_up_start = (state_c == HOLD_DOWN) && (p_edge);
assign filter_up2idle_start = (state_c == FILTER_UP)&&(end_delay_flag);
/*计数条件与结束条件*/
assign add_delay_flag = (state_c==FILTER_DOWN) || (state_c==FILTER_UP);
assign end_delay_flag = add_delay_flag && (counter == TIME_DELAY-1);
//延时计时模块
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
counter<=20'd0;
end
else if(add_delay_flag)begin
if(end_delay_flag)begin
counter<=20'd0;
end
else begin
counter <= counter + 1'b1;
end
end
else begin
counter <= counter;
end
end
endmodule