通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。按键抖动会引起一次按键被误读多次。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。
抖动时间的长短由按键的机械特性决定,一般为
5
m
s
~
10
m
s
5ms~10ms
5ms~10ms 。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。单片机一般采用延迟重采样的方式进行消抖。当检测到信号为低时,延迟一段时间(一般为
20
m
s
20ms
20ms ),再次检测信号是否为低,如果为低,则证明按键按下,否则认为按键没有按下,继续下一次检查。
最后,通过实现如下功能的电路来验证消抖是否成功。
计数器迭代的流程如图所示:
在 Q u a r t u s Quartus Quartus 中先编写消抖电路程序并封装成一个模块,程序如下所示:
module xiaodou ( input clk , //输入时钟信号,开发板上是50MHz input rst_n , //复位键(低电平触发) input key_in, //对应的机械按键 output reg clk_500hz, //分频出的500Hz时钟脉冲信号 output key_done //按键按下动作完成标志 ); reg [25:0]div_cnt; //分频计数器 always@(posedge clk or negedge rst_n) //获得500Hz时钟脉冲信号 begin if(!rst_n) begin div_cnt <= 0; clk_500hz <= 0; end else if(div_cnt == 99999) //计数十万次反转状态 begin div_cnt <= 0; clk_500hz <= ~clk_500hz; end else begin div_cnt <= div_cnt + 1; clk_500hz <= clk_500hz; end end reg qout; reg key_tmp1,key_tmp2; parameter n = 10; reg [25:0] cnt; always@(posedge clk_500hz or negedge rst_n) begin if(!rst_n) begin cnt <= 0; qout <= 0; end else if(key_in == 0) //按键按下 begin if(cnt == n-1) //持续2ms的话判定按下 begin cnt <= cnt; qout <= 1; end else begin cnt <= cnt+1; qout <= 0; end end else begin qout <= 0; cnt <= 0; end end /*提取前后按键信号*/ always@(posedge clk_500hz or negedge rst_n) begin if(!rst_n) begin key_tmp1 <= 0; key_tmp2 <= 0; end else begin key_tmp1 <= qout; key_tmp2 <= key_tmp1; end end assign key_done = key_tmp1 & (~ key_tmp2); endmodule
然后编写十进制计数器程序,代码如下:
module count ( input clk, input rst_n, input key, output reg[6:0] seg ); reg[3:0]cnt; wire key_done; xiaodou u1 ( .clk (clk ), //需要分频的信号为50Mhz时钟脉冲信号 .rst_n (rst_n ), //复位键 .key_in (key ), //机械按键 .clk_500hz (clk_500hz), //分频得到的500Hz时钟脉冲信号 .key_done (key_done) //按键动作完成标志 ); always@(posedge clk_500hz or negedge rst_n) begin if(!rst_n) cnt <= 0; else if(key_done) //按下动作完成 begin if(cnt == 9) //计数到9后需要清零 cnt <= 0; else cnt <= cnt + 1; end end always@(cnt) //数码管显示模块 begin case(cnt) 0:seg = 7'b0000001; 1:seg = 7'b1001111; 2:seg = 7'b0010010; 3:seg = 7'b0000110; 4:seg = 7'b1001100; 5:seg = 7'b0100100; 6:seg = 7'b0100000; 7:seg = 7'b0001111; 8:seg = 7'b0000000; 9:seg = 7'b0000100; endcase end endmodule
个人认为,这个实验的主要难点在于如何分频出 500 H z 500Hz 500Hz 的时钟脉冲信号。由于临近期末时间紧迫,我没法系统地深入学习 V e r i l o g Verilog Verilog 语言,因此消抖模块的程序也是找的现成的程序进行学习理解。在最初编写的程序里,我的代码是没有以下这段的。
always@(posedge clk_500hz or negedge rst_n) begin if(!rst_n) begin key_tmp1 <= 0; key_tmp2 <= 0; end else begin key_tmp1 <= qout; key_tmp2 <= key_tmp1; end end
我以为,只要计数够 2 m s 2ms 2ms (就是稳定状态)还是低电平就可以了,这段代码没啥存在的必要,但实际验证貌似还是会有不成功的情况。后来才发现,从原理上来说,机械按键按下存在前沿抖动和后沿抖动,因此需要取前后按键信号的按下状态来进行强判定是否按下。
利用开发板上现有的按键、开关和数码管资源实现一个简易篮球比赛计分器。
简易篮球比赛计分器的基本功能要求如下:
篮球计分器主要由以下模块构成
下面以甲队计分的过程为例描述计分器的设计过程:
在 Q u a r t u s Quartus Quartus 中编写程序,总代码如下:
module ball ( input [2:0] o, //表示对应加1,2,3分按键的状态 input m, //甲队加分控制端 input m1, //乙队加分控制端 input clk, //50MHz时钟脉冲信号 input rst_n, //复位键 input key, output reg [6:0] ans1, output reg [6:0] ans2, output reg [6:0] ans3, output reg [6:0] ans4 ); integer i; reg [3:0] l; //甲队分数的十位 reg [3:0] r; //甲队分数的个位 reg [3:0] l1; //乙队分数的十位 reg [3:0] r1; //乙队分数的个位 //reg [3:0] key1; //reg [3:0] key2; //reg [3:0] key3; wire key_done; //有按键按下 xiaodou u1 //消抖模块,代码与子任务3.1中略有不同 ( .clk (clk ), .rst_n (rst_n ), .key_in1 (o[0] ), .key_in2 (o[1] ), .key_in3 (o[2] ), .clk_500hz (clk_500hz), .key_done (key_done) ); always@(posedge clk_500hz or negedge rst_n) begin if (!rst_n) begin //复位 l=0; r=0; l1=0; r1=0; end else begin if (key_done) begin //当有加分按键按下时 for (i=0; i<=2; i=i+1) begin if (!o[i]) begin //判断是哪个按键按下 case (i) 0:begin if (m) begin //给甲队加分 r=r+1; if (r==10) begin r=0; l=l+1; end end if (m1) begin //给乙队加分 r1=r1+1; if (r1==10) begin r1=0; l1=l1+1; end end end 1:begin if (m) begin r=r+2; if (r>=10) begin r=r-10; l=l+1; end end if (m1) begin r1=r1+2; if (r1>=10) begin r1=r1-10; l1=l1+1; end end end 2:begin if (m) begin r=r+3; if (r>=10) begin r=r-10; l=l+1; end end if (m1) begin r1=r1+3; if (r1>=10) begin r1=r1-10; l1=l1+1; end end end endcase end end end end end always @ (l) begin case(l) 0:ans1 = 7'b0000001; 1:ans1 = 7'b1001111; 2:ans1 = 7'b0010010; 3:ans1 = 7'b0000110; 4:ans1 = 7'b1001100; 5:ans1 = 7'b0100100; 6:ans1 = 7'b0100000; 7:ans1 = 7'b0001111; 8:ans1 = 7'b0000000; 9:ans1 = 7'b0000100; endcase end always @ (r) begin case(r) 0:ans2 = 7'b0000001; 1:ans2 = 7'b1001111; 2:ans2 = 7'b0010010; 3:ans2 = 7'b0000110; 4:ans2 = 7'b1001100; 5:ans2 = 7'b0100100; 6:ans2 = 7'b0100000; 7:ans2 = 7'b0001111; 8:ans2 = 7'b0000000; 9:ans2 = 7'b0000100; endcase end always @ (l1) begin case(l1) 0:ans3 = 7'b0000001; 1:ans3 = 7'b1001111; 2:ans3 = 7'b0010010; 3:ans3 = 7'b0000110; 4:ans3 = 7'b1001100; 5:ans3 = 7'b0100100; 6:ans3 = 7'b0100000; 7:ans3 = 7'b0001111; 8:ans3 = 7'b0000000; 9:ans3 = 7'b0000100; endcase end always @ (r1) begin case(r1) 0:ans4 = 7'b0000001; 1:ans4 = 7'b1001111; 2:ans4 = 7'b0010010; 3:ans4 = 7'b0000110; 4:ans4 = 7'b1001100; 5:ans4 = 7'b0100100; 6:ans4 = 7'b0100000; 7:ans4 = 7'b0001111; 8:ans4 = 7'b0000000; 9:ans4 = 7'b0000100; endcase end endmodule
其中,消抖电路模块代码如下:
module xiaodou ( input clk , input rst_n , input key_in1, input key_in2, input key_in3, output reg clk_500hz, output key_done ); reg [25:0]div_cnt; always@(posedge clk or negedge rst_n) begin if(!rst_n) begin div_cnt <= 0; clk_500hz <= 0; end else if(div_cnt == 99999) begin div_cnt <= 0; clk_500hz <= ~clk_500hz; end else begin div_cnt <= div_cnt + 1; clk_500hz <= clk_500hz; end end reg qout; reg key_tmp1,key_tmp2; parameter n = 10; reg [25:0] cnt; always@(posedge clk_500hz or negedge rst_n) begin if(!rst_n) begin cnt <= 0; qout <= 0; end else if(key_in1==0 || key_in2==0 || key_in3==0) //此处与子任务3.1中的条件不同 begin if(cnt == n-1) begin cnt <= cnt; qout <= 1; end else begin cnt <= cnt+1; qout <= 0; end end else begin qout <= 0; cnt <= 0; end end always@(posedge clk_500hz or negedge rst_n) begin if(!rst_n) begin key_tmp1 <= 0; key_tmp2 <= 0; end else begin key_tmp1 <= qout; key_tmp2 <= key_tmp1; end end assign key_done = key_tmp1 & (~ key_tmp2); endmodule
这个实验是一个综合度较高的基础实验,我个人也是遇到了不少的问题,经过不断修改调试,总结了以下几个主要的问题:
主要的解决方法就是: