《按键消抖与LED控制》实验的个⼈思考与总结
2019/01/08,第⼀个判断是否有按键按下的操作好像有问题,有空在修改!
红⾊为修改部分:
问题描述:
当三个独⽴按键的某⼀个被按下后,相应的LED被点亮;再次按下后,LED熄灭,按键控制LED亮灭
下⾯是LED灯的原理图:
可见,LED是低电平亮,⾼电平灭。
事实上,控制LED等的亮灭很简单,不是问题,对应的代码段如下:
reg d1;
reg d2;
reg d3;
always @ (podge clk or negedge rst_n)
if (!rst_n) begin
d1 <= 1'b0;
d2 <= 1'b0;
d3 <= 1'b0;
end
el begin //某个按键值变化时,LED将做亮灭翻转
if ( led_ctrl[0] ) d1 <= ~d1;
if ( led_ctrl[1] ) d2 <= ~d2;
if ( led_ctrl[2] ) d3 <= ~d3;
end
assign led_d3 = d1 ? 1'b1 : 1'b0; //LED翻转输出
assign led_d2 = d2 ? 1'b1 : 1'b0;
assign led_d1 = d3 ? 1'b1 : 1'b0;
我来做出解释:
⾸先,系统复位时候,先把寄存器变量d1、d2、d3清零,由下⾯这三天语句可知,这三个寄存器变量清零后,对应的led灯就是低电平,也就是亮。
assign led_d3 = d1 ? 1'b1 : 1'b0; //LED翻转输出
assign led_d2 = d2 ? 1'b1 : 1'b0;
assign led_d1 = d3 ? 1'b1 : 1'b0;
然后如果某个按键按下,对应的led_ctrl[?]就会变成⾼电平,此时对应的d?就会翻转电平值,也就是说原来led?是低电平,按键按下后,d?就会翻转为⾼电平,此时,对应的led灯⾃然也会由亮变灭了。继续按下按键,led的亮灭⼜会翻转。
(这些分析都是本⼈通过代码以及实验结果证明的,确实如此。)
不得不说,如果仅仅是上⾯的分析那么简单,就省事了,同时这个实验也没啥意思了。
事实的情况我根据理解做如下的描述:
按键按下时候,按键值有⼀段时间的低电平,我们必须在低电平这段时间内的某⼀个瞬间采样按键值来控制led灯的亮灭。
可能不能采样对是⼀个值得研究的问题。
先给出按键的⼤概电路图;
对这个电路图的⼀些解释:
独⽴按键⼀般有2组管脚,这2组管脚在按键未被按下时是断开的,在按键被按下时则是导通的。
基于此原理,我们⼀般会把按键的⼀个管脚接地,另⼀个管脚上拉到VCC,并且也连接到GPIO。这样,在按键未被按下时,GPIO的连接状态为上拉到VCC,则键值为1;按键被按下时,GPIO虽然还是上拉到VCC,但同时被导通的另⼀个管脚拉到地了,所以它的键值实际上是0。
再给出按键的波形图:
如下图是按键按下时候的波形图,⼀个是理想情况下的波形,另⼀个是事实情况的模拟图:
可见,按下以及释放的瞬间都有⼀个抖动。
在按键按下或者释放的时候都会出现⼀个不稳定的抖动时间,如果不处理好这个抖动时间,我们就⽆法正确采集到正确有效的按键值,所以我们的设计中必须有效消除按键抖动。
好了,按键消抖的问题摆在⾯前了,我们怎么来消除抖动呢?这是⼀个有意思的问题。
看了⽹上好多解释,通过画图来解释这个消除抖动的原理,可是总是越看越糊涂,感觉这种不伦不类的图已经对我的理解产⽣⼀些误导了,于是找到了特权同学的视频看了⼀遍,说实话,看的似乎明⽩了,但是不是太清晰,学习这东西必须花时间,⾃⼰动⼿动脑分析才能够理清楚。
我就通过分析代码的⽅式来理解这个消抖的过程。
`timescale 1ns / 1ps
//说明:当三个独⽴按键的某⼀个被按下后,相应的LED被点亮;
// 再次按下后,LED熄灭,按键控制LED亮灭
module sw_debounce(
clk,rst_n,
sw1_n,sw2_n,sw3_n,
led_d1,led_d2,led_d3
);
input clk; //主时钟信号,25MHz
input rst_n; //复位信号,低有效
input sw1_n,sw2_n,sw3_n; //三个独⽴按键,低表⽰按下
output led_d1,led_d2,led_d3; //发光⼆极管,分别由按键控制
//---------------------------------------------------------------------------
reg key_rst;
always @(podge clk or negedge rst_n)
if (!rst_n) key_rst <= 1'b1;
el key_rst <= sw3_n&sw2_n&sw1_n;
reg key_rst_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中
always @ ( podge clk or negedge rst_n )
if (!rst_n) key_rst_r <= 1'b1;
el key_rst_r <= key_rst;
//当寄存器key_rst由1变为0时,led_an的值变为⾼,维持⼀个时钟周期
wire key_an = key_rst_r & (~key_rst);
/*
key_rst 1 1 1 0 0 1
~key_rst 0 0 0 1 1 0
key_rst_r 1 1 1 0 0 1
key_an 0 0 1 0 0
*/
//---------------------------------------------------------------------------
reg[19:0] cnt; //计数寄存器
always @ (podge clk or negedge rst_n)
if (!rst_n) cnt <= 20'd0; //异步复位
el if(key_an) cnt <=20'd0;
el cnt <= cnt + 1'b1;
reg[2:0] low_sw;
always @(podge clk or negedge rst_n)
if (!rst_n) low_sw <= 3'b111;
el if (cnt == 20'hfffff) //满20ms,将按键值锁存到寄存器low_sw中 cnt == 20'hfffff
low_sw <= {sw3_n,sw2_n,sw1_n};
//---------------------------------------------------------------------------
reg [2:0] low_sw_r; //每个时钟周期的上升沿将low_sw信号锁存到low_sw_r中
always @ ( podge clk or negedge rst_n )
if (!rst_n) low_sw_r <= 3'b111;
el low_sw_r <= low_sw;
/*
low_sw 111 111 111 110 110 110
~low_sw 000 000 000 001 001 001
low_sw_r 111 111 111 110 110 110
led_ctrl 000 000 000 001 000 000
*/
//当寄存器low_sw由1变为0时,led_ctrl的值变为⾼,维持⼀个时钟周期
wire[2:0] led_ctrl = low_sw_r[2:0] & ( ~low_sw[2:0]);
reg d1;
reg d2;
reg d3;
always @ (podge clk or negedge rst_n)
if (!rst_n) begin
d1 <= 1'b0;
d2 <= 1'b0;
d3 <= 1'b0;
end
el begin //某个按键值变化时,LED将做亮灭翻转
if ( led_ctrl[0] ) d1 <= ~d1;
if ( led_ctrl[1] ) d2 <= ~d2;
if ( led_ctrl[2] ) d3 <= ~d3;
end
assign led_d3 = d1 ? 1'b1 : 1'b0; //LED翻转输出
assign led_d2 = d2 ? 1'b1 : 1'b0;
assign led_d1 = d3 ? 1'b1 : 1'b0;
endmodule
⾸先点名输⼊输出:
input clk; //主时钟信号,25MHz
input rst_n; //复位信号,低有效
input sw1_n,sw2_n,sw3_n; //三个独⽴按键,低表⽰按下
output led_d1,led_d2,led_d3; //发光⼆极管,分别由按键控制
这⾥需要提出⼀点的是,输⼊输出在开头都声明好,⾄于中间需要⽤到的⼀些⽹线变量(wire)以及寄存器变量(reg),我们在⽤到的位置声明,不失为⼀种便于理解的代码格式。
reg key_rst;
always @(podge clk or negedge rst_n)
if (!rst_n) key_rst <= 1'b1;
el key_rst <= sw3_n&sw2_n&sw1_n;
reg key_rst_r; //每个时钟周期的上升沿将key_rst信号锁存到key_rst_r中
always @ ( podge clk or negedge rst_n )
if (!rst_n) key_rst_r <= 1'b1;
el key_rst_r <= key_rst;
//当寄存器key_rst由1变为0时,led_an的值变为⾼,维持⼀个时钟周期
wire key_an = key_rst_r & (~key_rst);
/*
key_rst 1 1 1 0 0 1
~key_rst 0 0 0 1 1 0
key_rst_r 1 1 1 0 0 1
key_an 0 0 1 0 0
*/
//---------------------------------------------------------------------------
这⾥定义了⼀个1位的寄存器变量(key_rst),⽤来存放三个按键状态值的与值,由于按键值在没有按下的时候,就是拉⾼的状态,所以当复位信号有效时,就把key_rst赋值为1,也就是每⼀位都是⾼电平。
没有复位的时候,就在每个时钟的上升沿到来时,把三个按键值的与状态赋值给key_rst;
这两句的解释对应的代码为:
reg key_rst;
always @(podge clk or negedge rst_n)
if (!rst_n) key_rst <= 1'b1;
el key_rst <= sw3_n&sw2_n&sw1_n};
紧接着,我们讲下⾯这⼀⼩块代码:
reg key_rst_r;
always @ ( podge clk or negedge rst_n )
if (!rst_n) key_rst_r <= 1'b1;
el key_rst_r <= key_rst;
⼜定义了⼀个1位的寄存器变量key_rst_r,这个寄存器变量在复位信号有效时,也都赋值为1;
否则,在每个时钟上升沿都会将key_rst的值赋值给key_rst_r;
这就意味这什么呢?
意味着这两个变量之间增加了⼀个触发器或寄存器,key_rst_r延时⼀拍(延迟了⼀个时钟周期)。
也就是相当于:
key_rst 1 1 1 0 0 1
key_rst_r 1 1 1 0 0 1