04 初探按键消抖与仿真模型
我是先把计数器拆分:加法和减法计数器。
先做加法计数器,再把加法计数器拆成按键控制和led控制部分,在本子上写下他们的大致原理再,写下他们的输入输出端口模块,最终按照模块端口和工作原理及各小功能模块,照图施工就行。
总体——拆分——各模块(仿真验证)(对于一个模块先写模块的端口,和它的工作原理,大概由那几个部分构成,及各部分的联系)
一 设计定义
第一部分设计功能:实现按键的按下是真正的按下,滤除掉机械抖动。(原因是按键动作会有一段稳定的状态,而机械抖动会给出错误的电平信号且不稳定)
第二部分设计原理:按键按下可以分为,下降沿和上升沿。而完整的按键按下与释放,由四个状态组成,分别是空闲状态,按下滤波状态(下降沿),按下稳定状态(低电平),按键释放滤波状态(上升沿)。状态如下图:
通过上面对按键按下与释放的过程,分析设计方法可以为状态机,各个状态转移的关系。如下图:状态触发条件----从抖动到稳定需要10ms的延时,才能进入下一个状态。
总结:按键按下与释放,首先得把按键分为下降沿和上升沿,下降沿与上升沿都得维持20ms/10ms, 才能看作真正的按下与释放。否则为抖动。
二 设计输入
整体模块框图及设计代码:
module key_filter ( clk, rst_n, key_in, pin_out ); input clk; input rst_n; input key_in; // output pin_out; //the output of key,when the key_in==1(means press) //the output of key,when the key_in==0(means release) //此处省略下面的边沿检测和状态机部分
endmodule
|
按键消抖设计,分为两部分,一是按键边沿检测模块(下降沿和上升沿),二是状态机模块实现整个按下与释放过程。
第一部分边沿检测:边沿检测就是检测按键的下降沿和上升沿,而实现按键的按下与释放的原理:使用两级D触发器,记忆按键当前状态的电平值和上一个状态的电平值,通过比较这两个状态的电平,就能判断下降沿和上升沿。电路结构图如下图:
第二部分边沿检测代码:
reg H2L_F1; //first reg(store the value of key from 1 to 0,press) reg H2L_F2; reg L2H_F1; //first reg(store the value of key from 0 to 1,press) reg L2H_F2; //the clasic edge detection(边沿检测) //注意小梅哥的边缘检测,优良的不同 //他只用了两个寄存器,并且定义了按键的上升沿和下降沿(assign) //
always@(posedge clk or negedge rst_n) if(!rst_n) begin H2L_F1<=1'b1; H2L_F2<=1'b1; L2H_F1<=1'b0; L2H_F2<=1'b0; end else begin H2L_F1<=key_in; H2L_F2<=H2L_F1; L2H_F1<=key_in; L2H_F2<=L2H_F1; end
|
第二部分按键按下与释放状态:
设计原理:照图施工,是按键按下与释放,就如设计定义的状态原理图和状态转移图所示。分为四个状态,未按下的空闲状态,按下的滤波状态,按下稳定状态,释放滤波状态。触发条件,分别是,下降沿到来,满足10ms的延迟,上升沿到来,满足10ms的延迟。思路如下面代码所示。
//be careful the binary(二进制) localparam IDEL = 4'b0001, //the idel state FILTER0 = 4'b0010, //first filter(key from 1 to 0) DOWN = 4'b0100, //when the key_in==0 FILTER1 = 4'b1000; //second filter(key from 0 to 1) parameter T10MS = 20'd500_000-1; reg [19:0]delay_cnt; reg [3:0]state; reg pin_out; //他把计数器的10MS单独写在一个模块 //小梅哥滤波的原理:按键的下降沿与上升沿的间隔时间大于10MS //还有按键的输出信号,即表示按键按下或释放的信号,只有保持一个时钟周期 always@(posedge clk or negedge rst_n) if(!rst_n)begin state<=IDEL; delay_cnt<=20'd0; pin_out<=1'b0; end else begin case(state) IDEL:begin if(!H2L_F1 & H2L_F2) state<=FILTER0; else state<=IDEL; end FILTER0:begin if(key_in==1'b0)begin if((delay_cnt==T10MS) )begin state<=DOWN; pin_out<=1'b1; delay_cnt<=20'd0; end else delay_cnt<=delay_cnt+1'b1; end else state<=IDEL; end DOWN:begin if(L2H_F1 & !L2H_F2) state<=FILTER1; else state<=DOWN; end FILTER1:begin if(key_in==1'b1)begin if((delay_cnt==T10MS) )begin state<=IDEL; pin_out<=1'b0; delay_cnt<=20'd0; end else delay_cnt<=delay_cnt+1'b1; end else state<=DOWN; end
default:state<=IDEL; endcase
end
|
三 仿真验证
我对仿真的理解就是,给一个系统,加激励,然后观察输出的波形。作用有两个:一是验证设计是否实现功能。二是定位问题。
仿真模型:我的理解就是在tb文件里产生时钟与复位信号,在仿真模型文件里产生(除时钟和复位信号的)其他激励如按键信号key,最终在tb文件调用仿真文件key_model即可。(类似与顶层模块与各个功能模块之间的调用)
![]()
|
第一步部分 Task函数调用与随机函数的用法:
抖动是人为的设计了一些固定值抖动,不具有随机性且编写出来的文件太长, 这里可以采用随机数发生函数来产生抖动
Task函数格式:
task press_key;
begin
//中间部分为仿真具体实现功能,自己设计
end
endtask
//在仿真主程序中,直接调用Task函数名press_key;
A:下面表格为仿真模型(key_model)的代码:
注,这是单独的一个设计模块,类似于功能模块(部分)。
//Task函数与随机函数 reg [15:0]myrand;
|
B:下面表格为仿真顶层(key_ filter_tb)的代码:
注:这个仿真设计相当于顶层,可以直接调用仿真模型。
//仿真主程序调用Task函数 initial begin key_in = 1'b1;
|
C: 收获有三:
A写好一个部分的程序,其他的在此基础上复制修改(如把按键按下的写好,写按键释放时,复制一段只需要把按键的值改成(0-1即可)) |
|
B 模拟真实仿真时,若时间较长可以把时钟信号删除掉,再 进行仿真(缩小仿真时间)
|
|
C对于信号输入的值很多情况,最好调用一个随机函数$random。。且把仿真程序分成几个部分,在一个主要的仿真顶层程序直接调用。比如再key_filter_tb 仿真程序中调用key_model程序(类似于模块划分,设置顶层程序与子程序。) |
|
D学习语法可以看IEEE—2005语法标准 |
从仿真波形看出,能够实现把小于十毫秒的抖动滤除,实现按键消抖。
我学到了,边沿检测,仿真模型,随机函数等等。至此,一个按键消抖模块完成勒,很开心。