在按键控制 LED中采用直接读取按键电平状态,然后根据电平状态控制LED。虽然直接读取按键电平状态然后执行相应处理程序的方法非常简单,但是这种方式可能存在误判问题,进而有可能导致程序功能异常,这是因为按键按下和松开时存在抖动问题,可能会使程序检测到错误的按下或松开操作。
本章节将以按键控制蜂鸣器为例讲解按键消抖算法的实现。
蜂鸣器简介
蜂鸣器按照结构原理不同可分为压电式蜂鸣器和电磁式蜂鸣器。压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成;电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。压电式蜂鸣器是利用压电效应原理工作的,当对其施加交变电压时它会产生机械振动发声;电磁式蜂鸣器是接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场,振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。
蜂鸣器按照驱动方式不同又可分为有源蜂鸣器和无源蜂鸣器,其主要区别为蜂鸣器内部是否含有震荡源。一般的有源蜂鸣器内部自带了震荡源,只要通电就会发声。而无源蜂鸣器由于不含内部震荡源,需要外接震荡信号才能发声。
按键消抖原理
常见的按键均为机械弹性开关,当按下或松开按键时,由于弹片的物理特性,不能立即闭合或断开,往往会在断开或闭合的过程中产生机械抖动,消除这种抖动的过程即称为按键消抖。
按键消抖可分为硬件消抖和软件消抖;硬件消抖主要使用 RS 触发器或电容等方法在硬件电路上实现消抖抖,一般在按键较少时使用。软件消抖的原理为按键按下或松开后,由处理器延时 5ms 至 20ms再对按键状态进行采样并判断,如下图所示软件消抖的原理图。
软件消抖
在按键被按下和按键被释放时都会产生抖动,而按键的抖动在数字电路中的体现就是不断变化的高低电平,由此可以绘制按键消抖模块输入信号的波形如下图所示。
抖动部分的 key 信号也有低电平的情况,但是因为机械抖动的原因很快又被拉高了,如果我们把每次 key 的低电平都检测到,相当于在极短的时间内按键被按下很多次,而不是我们实际操作的按下一次,所以我们需要把这段抖动的信号给滤除,只有按键值保持 20ms 及以上的稳定状态,我们才确认按键值有效,此时 key 值为高电平则按键被释放,key 值为低电平则按键被按下,所以消抖的过程就是滤除按键值保持时间小于 20ms 的值。
- 如何滤除保持时间小于20ms的值:从 20ms 开始倒计时,如果 20ms 的倒计时还没有完成按键值就再次产生变化,此时需要从头开始 20ms 倒计时,前一次导致按键值变化的操作视为无效操作,将该次变化视为按键抖动消除;如果计完 20ms 按键状态一直没有改变,说明此次按键被按下或者被释放是有效操作。
- 如何判断按键变化:可以给按键值打两拍(一般外部输入信号我们都使用打拍处理消除亚稳态),然后比较按键两次的打拍值,第一次打拍值与第二次打拍值一致说明按键值没有变化,第一次打拍值与第二次打拍值不一致说明按键值产生了变化。
硬件设计
本次实验采用有源蜂鸣器,其原理图如下:
按键原理图参考按键控制 LED章节
程序功能
用 KEY0 按键来控制蜂鸣器发声,初始状态为蜂鸣器鸣叫,按下按键后蜂鸣器停止鸣叫,再次按下开关,蜂鸣器重新鸣叫。
系统框图
系统分为两个模块,分别是按键消抖模块和蜂鸣器控制模块
编写代码
按键消抖代码
`timescale 1ns / 1ns //仿真单位/仿真精度
module key_debounce #(
parameter COUNT_WIDTH = 20, //按键延时消抖计数器宽度
parameter KEY_DELAY = 20'd100_0000, //按键消抖延时
parameter DEFAULT_LEVEL = 1'b1 //按键默认状态
)
(
input sys_clk, //时钟
input sys_rst_n, //复位
input key_in, //外部输入的按键值
output reg key_filter //消抖后的按键值
);
//延时消抖计数器,当按键按下时从KEY_DELAY开始递减计数,当其值为1时锁定按键状态
reg [COUNT_WIDTH-1:0] count;
//按键信号延迟一个时钟周期
//这里将key_debounce作为内部模块,只需要延迟一个时钟周期,用于捕获上升沿和下降沿
//例化时应将外部按键输入再延迟两个时钟周期,用于消除亚稳态(在FPGA中,对于外部输入信号均需要延迟两拍在使用,以消除亚稳态)
reg key_d1;
//按键信号延迟一个时钟周期
always @(posedge sys_clk) begin
if(!sys_rst_n)
key_d1 <= DEFAULT_LEVEL;
else
key_d1 <= key_in;
end
//检测按键跳变,当按键跳变时重置计数器为KEY_DELAY
always @(posedge sys_clk) begin
if(!sys_rst_n)
count <= 0;
else if(key_d1 != key_in)
count <= KEY_DELAY; //按键跳变,重置计数器为KEY_DELAY
else if(count > 0)
count <= count - 1; //递减计数,直到为0
else
count <= 0;
end
//在计数器值为1时(消抖延时结束)锁定按键状态
always @(posedge sys_clk) begin
if(!sys_rst_n)
key_filter <= DEFAULT_LEVEL;
else if(count == 1)
key_filter <= key_in; //计数器递减为1时锁定按键状态
end
endmodule
按键消抖仿真激励代码
`timescale 1ns / 1ns //仿真单位/仿真精度
module tb_key_debounce();
reg sys_clk; //时钟
reg sys_rst_n; //复位
reg key_in; //输入按键
wire key_filter; //消抖后的按键
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
key_in <= 1;
#180
sys_rst_n = 1'b1;
#2000
//按下过程
key_in <= 0;
#8
key_in <= 1;
#8
key_in <= 0;
#8
key_in <= 1;
#2
key_in <= 0;
#2
key_in <= 1;
#20
key_in <= 0;
//稳定保持过程
#2000
//释放过程
key_in <= 1;
#8
key_in <= 0;
#4
key_in <= 1;
#3
key_in <= 0;
#3
key_in <= 1;
end
//产生时钟
always #10 sys_clk = ~sys_clk;
//例化按键消抖模块
key_debounce #(
.COUNT_WIDTH(20), //按键延时消抖计数器宽度
.KEY_DELAY(25), //按键消抖延时
.DEFAULT_LEVEL(1'b1) //按键默认状态
)
tb_key_debounce_inst(
.sys_clk(sys_clk), //时钟
.sys_rst_n(sys_rst_n), //复位
.key_in(key_in), //外部输入的按键值
.key_filter(key_filter) //消抖后的按键值
);
endmodule
蜂鸣器控制代码
`timescale 1ns / 1ns //仿真单位/仿真精度
module key_beep #(
parameter BEEP_DEFAULT_STATE = 1'b1, //蜂鸣器默认状态
parameter KEY_DEFAULT_LEVEL = 1'b1 //按键默认状态
)
(
input sys_clk, //时钟
input sys_rst_n, //复位
input key_filter, //消抖后的按键值
output reg beep //蜂鸣器
);
//消抖后的按键信号延迟一个时钟周期,用于检测边沿跳变
reg key_filter_d1;
//下降沿标志
wire key_negedge;
//捕获按键下降沿,当上一个时钟周期为高电平,当前时钟周期为低电平即为下降沿
assign key_negedge = key_filter_d1 & (~key_filter);
//消抖后的按键信号延迟一个时钟周期,用于检测边沿跳变
always @(posedge sys_clk) begin
if(!sys_rst_n)
key_filter_d1 <= KEY_DEFAULT_LEVEL;
else
key_filter_d1 <= key_filter;
end
//下降沿时刻翻转蜂鸣器状态
always @(posedge sys_clk) begin
if(!sys_rst_n)
beep <= BEEP_DEFAULT_STATE;
else if(key_negedge == 1'b1)
beep <= ~beep;
end
endmodule
蜂鸣器仿真激励代码
`timescale 1ns / 1ns //仿真单位/仿真精度
module tb_key_beep();
reg sys_clk; //时钟
reg sys_rst_n; //复位
reg key_filter; //消抖后的按键状态
wire beep; //蜂鸣器
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
key_filter <= 1;
#200
sys_rst_n = 1'b1;
#200
//按下
key_filter <= 0;
#2000
//释放
key_filter <= 1;
#2000
//按下
key_filter <= 0;
end
//产生时钟
always #10 sys_clk = ~sys_clk;
key_beep #(
.BEEP_DEFAULT_STATE(1'b1), //蜂鸣器默认状态
.KEY_DEFAULT_LEVEL(1'b1) //按键默认状态
)
tb_key_beep_inst (
.sys_clk(sys_clk), //时钟
.sys_rst_n(sys_rst_n), //复位
.key_filter(key_filter), //消抖后的按键值
.beep(beep) //蜂鸣器
);
endmodule
顶层代码
`timescale 1ns / 1ns
module top_key_beep #(
parameter BEEP_DEFAULT_STATE = 1'b1, //蜂鸣器默认状态
parameter KEY_DEFAULT_LEVEL = 1'b1, //按键默认状态
parameter COUNT_WIDTH = 20, //按键延时消抖计数器宽度
parameter KEY_DELAY = 20'd100_0000 //按键消抖延时
)
(
input sys_clk, //时钟
input sys_rst_n, //复位
input key_in, //外部输入的按键值
output beep //蜂鸣器
);
//消抖后的按键
wire key_filter;
//外部输入按键延时两拍,用于消除亚稳态(在FPGA中,对于外部输入信号均需要延迟两拍在使用,以消除亚稳态)
reg key_in_d1;
reg key_in_d2;
//按键延迟两拍
always @(posedge sys_clk) begin
if(!sys_rst_n) begin
key_in_d1 <= KEY_DEFAULT_LEVEL;
key_in_d2 <= KEY_DEFAULT_LEVEL;
end
else begin
key_in_d1 <= key_in;
key_in_d2 <= key_in_d1;
end
end
//例化按键消抖模块
//例化按键消抖模块
key_debounce #(
.COUNT_WIDTH(COUNT_WIDTH), //按键延时消抖计数器宽度
.KEY_DELAY(KEY_DELAY), //按键消抖延时
.KEY_DEFAULT_LEVEL(KEY_DEFAULT_LEVEL) //按键默认状态
)
tb_key_debounce_inst(
.sys_clk(sys_clk), //时钟
.sys_rst_n(sys_rst_n), //复位
.key_in(key_in_d2), //外部输入的按键值
.key_filter(key_filter) //消抖后的按键值
);
//例化蜂鸣器控制模块
key_beep #(
.BEEP_DEFAULT_STATE(BEEP_DEFAULT_STATE), //蜂鸣器默认状态
.KEY_DEFAULT_LEVEL(KEY_DEFAULT_LEVEL) //按键默认状态
)
tb_key_beep_inst (
.sys_clk(sys_clk), //时钟
.sys_rst_n(sys_rst_n), //复位
.key_filter(key_filter), //消抖后的按键值
.beep(beep) //蜂鸣器
);
endmodule
系统仿真激励代码
`timescale 1ns / 1ns //仿真单位/仿真精度
module tb_top_key_beep();
reg sys_clk; //时钟
reg sys_rst_n; //复位
reg key_in; //输入按键
wire beep; //蜂鸣器
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
key_in <= 1;
#180
sys_rst_n = 1'b1;
#2000
//按下过程
key_in <= 0;
#8
key_in <= 1;
#8
key_in <= 0;
#8
key_in <= 1;
#2
key_in <= 0;
#2
key_in <= 1;
#20
key_in <= 0;
//稳定保持过程
#2000
//释放过程
key_in <= 1;
#8
key_in <= 0;
#4
key_in <= 1;
#3
key_in <= 0;
#3
key_in <= 1;
//稳定保持过程
#2000
//按下过程
key_in <= 0;
#18
key_in <= 1;
#5
key_in <= 0;
#5
key_in <= 1;
#2
key_in <= 0;
#2
key_in <= 1;
#10
key_in <= 0;
//稳定保持过程
#3000
//释放过程
key_in <= 1;
#12
key_in <= 0;
#4
key_in <= 1;
#5
key_in <= 0;
#15
key_in <= 1;
end
//产生时钟
always #10 sys_clk = ~sys_clk;
//例化按键消抖模块
top_key_beep #(
.BEEP_DEFAULT_STATE(1'b1), //蜂鸣器默认状态
.KEY_DEFAULT_LEVEL(1'b1), //按键默认状态
.COUNT_WIDTH(20), //按键延时消抖计数器宽度
.KEY_DELAY(25) //按键消抖延时
)
tb_top_key_beep_inst(
.sys_clk(sys_clk), //时钟
.sys_rst_n(sys_rst_n), //复位
.key_in(key_in), //外部输入的按键值
.beep(beep) //消抖后的按键值
);
endmodule
约束输入
管脚分配如下:
的 XDC 约束语句如下:
#时序约束
create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
#IO 管脚约束
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS15} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U7 IOSTANDARD LVCMOS15} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN T4 IOSTANDARD LVCMOS15} [get_ports key_in]
set_property -dict {PACKAGE_PIN V7 IOSTANDARD LVCMOS15} [get_ports beep]