本帖最后由 UT发布 于 2025-4-9 09:13 编辑
软件版本: TD_ 5.6.4 _Release_ 97693
操作系统: WIN11 64bit
硬件平台:适用安路 (Anlogic)FPGA
1概述 按键是最为常见的电子元器件之一,在电子设计中应用广泛。按键的消抖,是指按键在闭合或松开的瞬间伴随着一连串的抖动,这样的抖动将直接影响设计系统的稳定性,降低响应灵敏度。因此,必须对抖动进行处理,即消除抖动的影响。本章节中,我们根据机械按键的构造与原理,设计并实现按键消抖。
在完成本实验前,请确保已经完成前面的实验,包括已经掌握以下能力:
1.1 按键消抖简介 按键开关,最早出现在日本,称之为敏感型开关, 我们开发板上使用的机械按键是按键的一种,特点是:接触电阻小,按键按下或弹起时有滴答 的 清脆声 。 但由于其构造和原理,在按键闭合及断开的瞬间均伴随有一连串的抖动 。
按键结构示意图
从按键结构示意图中可以看到按键存在一个反作用弹簧, 其内部结构是靠金属弹片受力变化来实现通断向按键开关操作方向施压时开关功能闭合接通,当撤销压力时开关即断开。 当按下或者松开时,由于弹簧的物理特性,不会立马闭合或者断开,往往会在闭合或断开的短时间内产生机械抖动,机械抖动便会产生电平的抖动。按键从按下再到松开的过程中,其变化如下图所示 :
按键按下到松开的电平变化图
按键的机械特性,决定着按键的抖动时间, 产生的抖动次数以及间隔时间均是不可预期的, 一般抖动时间在 5ms~10ms。 通常检测到输入信号状态为低电平时表示按键按下,当 FPGA的晶振频率为 25 MHz,即系统时钟 4 0ns时,以 4 0ns的周期去采样信号的状态,由于采样时间远小于按键抖动时间, FPGA 在检测信号状态时会认为按键多次被按下,这就需要通过消除抖动,以确保按键被按下时只检测到一次低电平, 意味着每次在按键闭合或松开期间,跳过这段抖动时间,再检测按键的状态。实际工程中,有很多消抖方案, 按键消抖可分为硬件消抖和软件消抖。硬件消抖主要使用 RS 触发器或电容等方法在硬件电路上实现消抖,一般在按键较少时使用。软件消抖 可以 通过简单的延时实现按键的消抖动 ,也可以通过状态机的方式完成。 本章利用 FPGA内部来设计消抖,即采取软件消抖。
延时方法:
一般机械按键在被按下和释放时,产生的抖动不会超过 20ms,因此可以通过简单的延时来检测,当检测到输入信号为低电平时,开始计时 20ms ,计时结束后检测此时按键信号状态,当信号状态为高电平时,表示按键抖动过程,等待下一次低电平到来后再次开始计时检测按键信号状态;当信号为低电平时,表示按键为按下状态,产生一个脉冲,表示按键按下状态。当检测到输入信号为高电平时,开始计时 20ms ,计时结束后检测此时按键信号状态,当信号状态为低电平时,表示按键抖动过程,等待下一次高电平到来后再次开始计时检测按键信号状态;当信号为高电平时,产生一个脉冲,表示按键释放状态。
按键消抖原理图
状态机方法:
将按键按下到释放的整个过程分为 4个过程 : 按键未被按下或释放按键后的空闲状态;按键按下前的抖动状态;按下按键并保持低电平的稳定状态;按键释放前的抖动状态。
· 空闲状态 (IDEL):该状态一直处于高电平,当检测到下降沿,状态跳转到 FILTER0 。
· 抖动状态 (FILTER0):该状态下需要根据是否计满数和是否有上升沿来做出不同的反应。当低电平保持时间超过计时时间,表示按键按下,状态跳转至 DOWN ;当计时未满且检测到上升沿,表示此时为抖动,状态跳转回 IDEL 。
· 稳定状态 (DOWN):该状态一直处于低电平,当检测到上升沿,状态跳转到 FILTER1 ,否则保持 DOWN 。
· 释放状态 (FILTER1):该状态与 FILTER0 类似,高电平保持时间超过计时时间,表示按键释放,状态跳转至 IDLE ;当计时未满且检测到下降沿,表示此时为抖动,状态跳转回 DOWN 。
按键消抖状态转换图
触发器内部的数据形成需要一定的时间, 在同步系统中,输入信号总 与 系统时钟同步,能达到寄存器的时序要求,亚稳态 就 不会发生。只要系统中有异步元件,亚稳态就无法避免,亚稳态主要发生在异步信号检测、跨时钟域信号传输以及复位电路等常用设计中。
亚稳态产生示意
建立时间 Tsu:上升沿到来之前,数据要维持一定时间的稳定状态,数据稳定不变的一个最小时间。
保持时间 Th:触发器时钟上升沿到来以后,在一定的时间内,数据也要保持不变,数据保持不变的最小时间。
寄存器延迟 (Tco): 上升沿以后到产生震荡 的过程 叫做寄存器延迟。
决断时间 (Tmet): 输出信号 稳定前,信号震荡的时间 ,这段时间是不确定的。
亚稳态就是不稳定的状态,是指触发器无法在某个规定时间段内达到一个可确认的状态。 如上图,在 CLK的 Tus 和 Th 时间内,输入数据 D 发生改变,不是稳定在 0 或者 1 ,这会导致采集数据时在 0 和 1 之间振荡,这段时间称为决断时间,经过这段时间后,采集的数据 将随机稳定到 0或 1 上, 输出 与输入没有必然的关系,会 导致 对后续电路判断 不准 , 因此在 FPGA设计 中要 降低亚稳态发生的概率。
概率 = (建立时间 + 保持时间) / 采集时钟周期
当系统采用 25 M时钟对一个外部信号进行采集,采集时钟周期为 4 0ns,亚稳态产生的概率为: 1ns/ 4 0ns = 2.5 %。这一概率会随时钟频率升高而升高。若有亚稳态产生,我们就要对 其进行消除,常用对亚稳态消除有三种方式:
( 2)采用 FIFO 对跨时钟域数据通信进行缓冲设计;
1.2 硬件电路分析
4 个按键,其中 1路硬件复位按键,按下按键就可以给系统实现一个复位,系统部分恢复默认状态,需要重新加载 FPGA 。 3 路 PL按键,通过软件给系统一个高 / 低电平信号来实现复位操作,程序部分状态恢复到默认。由原理图可以看出,按键未按下时 ,线路断开,没有电流通过按键,那么 IO 和 VCC_ADJ 是等电位,此时 IO 为高电平;按键按下时, VCC_ADJ 经过电阻和按键,接到 GND ,形成一条通路,此时 IO 为低电平,因此系统即可通过检测 IO 的电平来判断按键的状态。
按键未按下时的等效电路 按键按下时的等效电路
2按键消抖程序设计 2. 1 系统框图 本次实验通过按键实现 LED左移的效果,采用延时方法消除按键抖动,该方法代码简单使用方便,不占用其他资源。本实验共有两个模块,分别为顶层模块 key_top 和按键消抖模块 key ,通过调用 key 模块实现按键去抖动的功能。顶层模块 key_top 需要三个输入的端口,分别为系统时钟、系统复位、按键,效果输出为 4 位的 LED 端口。
按键消抖模块需要三个输入的端口,分别为系统时钟、系统复位、按键,输出两个端口,分别为按键按下和释放。判断按键状态是否变化,需要使用计时器模块对前后信号进行对比,计时器计时需要基于系统时钟 I_sysclk来计数,除此之外,添加系统复位使程序恢复至默认状态,按键输入信号 I_key 消抖后的输出结果表示为 O_key_down (按键按下)和 O_key_up (按键释放)。
2. 2 状态机流程图 10ms定时器模块: MLK-S200 FPGA开发板上晶振产生的系统时钟频率是 25 MHz,周期为 4 0ns, 10ms 则需要计数( 10ms/ 4 0ns=25 0_000-1=24 9_999)次,计数 24 9_999时,产生一个脉冲信号并置 1 。
消抖 状态机 模块: 状态机共有四个状态,使用 10ms计 时模块 产生的脉冲信号来 触发 消 抖状态机 模块, 每间隔 10ms判断一次按键状态。 当检测到按键信号为低电平,下一个 10ms检测到按键信号不为低电平时表示此时为按键抖动;再次检测到按键信号为低电平,下一个 10ms 检测到按键信号为低电平时判断此时按键按下;当检测到按键信号为高电平,下一个 10ms 检测到按键信号不为高电平时表示此时为按键抖动;再次检测到按键信号为高电平,下一个 10ms 检测到按键信号为高电平时判断此时按键释放。
寄存 器 亚稳态模块 : 我们采用 对异步信号进行同步处理 的方式去除亚稳态的影响, 对 key进行异步多次寄存, 让数据 在 下一个 时钟沿之后 保持稳定,从而 消除亚稳态 。 寄存器 具有消除亚稳态功能,使用多级寄存器消除亚稳态的危害,单 Bit数据从低速到高速,一般采取打两拍进行对亚稳态的消除,打一拍的话, 如果决断时间过长导致下一个时钟沿时数据还处于决断时间,此时 我们数据正常输出的概率是 70-80%。 因此可以通过再打一拍的方式降低亚稳态发生的概率, 第二拍以后数据正常输出的概率是 99%以上 。
2. 3 程序源码
module key #
(
parameter REF_CLK = 32'd25000000 //设置时钟为参数,方便上层调用和修改
)
(
input I_sysclk,
input I_rstn,
input I_key,
output O_key_down,
output O_key_up
);
//设置10MS的时钟分频计数
localparam T10MS = (REF_CLK/100 - 1'b1);
//设置按键状态机的状态
localparam KEY_S0 = 2'd0;
localparam KEY_S1 = 2'd1;
localparam KEY_S2 = 2'd2;
localparam KEY_S3 = 2'd3;
reg [24:0] t10ms_cnt = 25'd0;
reg [3:0] key_r = 4'd0;
reg [1:0] key_s = 2'b0;
reg [1:0] key_s_r = 2'b0;
wire t10ms_done ;
assign t10ms_done = (t10ms_cnt == T10MS);//10ms计数是否完成
assign O_key_down = (key_s == KEY_S2)&&( key_s_r == KEY_S1);//是否按下
assign O_key_up = (key_s == KEY_S0)&&( key_s_r == KEY_S3);//是否弹起
//10ms timer counter
always @(posedge I_sysclk or negedge I_rstn)begin
if(I_rstn == 1'b0)begin
t10ms_cnt <= 25'd0;//复位
end
else if(t10ms_cnt < T10MS)
t10ms_cnt <= t10ms_cnt + 1'b1;//未计满则+1
else
t10ms_cnt <= 25'd0;//计满则置0,开始新一轮计数
end
always @(posedge I_sysclk)begin
key_s_r <= key_s;//将key_s的状态缓存一拍
end
always @(posedge I_sysclk)begin
key_r <= {key_r[2:0],I_key};//将I_key的状态缓存一拍
end
always @(posedge I_sysclk or negedge I_rstn)begin
if(I_rstn == 1'b0)begin
key_s <= KEY_S0;
end
else if(t10ms_done)begin //每10ms进行一次状态判断和转移
case(key_s)
KEY_S0:begin
if(!key_r[3]) //收到第一个按键的低电平信号,不能判断是否为毛刺
key_s <= KEY_S1; //转到状态S1
end
KEY_S1:begin//recheck key done //第二次判断按键是否按下
if(!key_r[3])
key_s <= KEY_S2; //按下转入S2状态
else
key_s <= KEY_S0; //没按下,判断为毛刺,转入S0状态,重新等待触发
end
KEY_S2:begin//wait key up //确定按键按下后
if(key_r[3]) //等待按键松开,接收到按键的高电平信号
key_s <= KEY_S3; //不能确定是否为毛刺,转状态S3
end
KEY_S3:begin//recheck key up
if(key_r[3]) //第二次判断按键是否松开
key_s <= KEY_S0; //依然检测到按键是松开的状态,转入S0状态,等待触发
end
endcase
end
end
endmodule 复制代码
以上代码中,首先把系统时钟做分频,产生 10ms 的分配时钟使能信号。在设计的状态机中,分 4 个状态
KEY_S0:判断按键是否按下,如果是,转移到状态 KEY_S1 ;
KEY_S1: 10ms 后再次判断按键是否按下,如果是,转移状态到 KEY_S2 ,否则继续回到 KEY_S0 ;
KEY_S2:判断按键是否抬起,如果是,转移状态到 KEY_S3 ;
KEY_S3: 10ms 后再次判断按键是否抬起,如果是,转移状态到 KEY_S0 ,否则继续回到 KEY_S2 ;
当状态从 KEY_S1 转到 KEY_S2 代表依次按钮按下 key 输出一次高电平。
演示代码中通过按键按下调整 LED的点亮顺序,确认按键工作正常
module key_top #
(
parameter REF_CLK = 32'd25000000
)
(
input I_sysclk,
input I_rstn,
input I_key1,
input I_key2,
output [3:0]O_LED
);
reg [3:0] LED_r;//存储LED灯的状态
wire key1_down,key2_down;
wire lock,clk0;
assign O_LED = LED_r;//将存储的LED灯的状态输出
pll pll_inst(
.refclk(I_sysclk),
.reset(~I_rstn),
.extlock(lock),
.clk0_out (clk0)
);
always @(posedge I_sysclk or negedge I_rstn)begin
if(I_rstn == 1'b0)
LED_r <= 4'b0111; //设置LED的初始状态,低电平有效
else if(key1_down)
LED_r<={LED_r[2:0],LED_r[3]};//LED灯右移
else if(key2_down)
LED_r<={LED_r[0],LED_r[3:1]};//LED灯左移
end
//例化两个按键,分别控制LED左右移动
key#(
.REF_CLK(REF_CLK)
)
key_u1
(
.I_sysclk(I_sysclk),
.I_rstn(I_rstn),
.I_key(I_key1),
.O_key_down(key1_down),
.O_key_up()
);
key#(
.REF_CLK(REF_CLK)
)
key_u2
(
.I_sysclk(I_sysclk),
.I_rstn(I_rstn),
.I_key(I_key2),
.O_key_down(key2_down),
.O_key_up()
);
endmodule 复制代码
3 FPGA工程 fpga工程的创建过程不再重复,如有不清楚的请看前面实验,具体的 FPGA 型号以对应的开发板上芯片为准
米联客的代码管理规范 ,在对应的 FPGA 工程路径下创建 uisrc 路径,并且创建以下文件夹
04_pin:放 fpga 的 pin 脚约束文件或者时序约束文件
05_boot:放编译好的 bit 或者 bin 文件 ( 一般为空 )
4 Modelsim仿真 4. 1 准备工作 Modelsim仿真的创建过程不再重复,如有不清楚的请看前面实验
仿真测试文件源码如下,对仿真精度没什么要求的,可以设置为 1ns,另外仿真阶段可以把间隔时间设置小一些,这样仿真速度就快了:
module tb_key();
reg I_sysclk,I_rstn,I_key1,I_key2;
wire [3:0]O_LED;
key_top#
(
.REF_CLK(25000000)
)
key_top_inst
(
.I_sysclk(I_sysclk),
.I_rstn(I_rstn),
.I_key1(I_key1),
.I_key2(I_key2),
.O_LED(O_LED)
);
initial
begin
// Initialize Inputs
I_sysclk = 0;
I_rstn = 0;
#100;
I_rstn =1;
I_key1 = 1;
I_key2 = 1;
#10000;
forever
begin
I_key1 = 0;
// Wait 100 ns for global reset to finish
#100;
I_key1=1; #1000;
I_key1=0; #1000;
I_key1=1; #2000;
I_key1=0; #5000;
#20000000;
I_key1=1;
I_key1=0; #1000;
I_key1=1; #2000;
I_key1=0; #1000;
I_key1=1; #2000;
#20000000;
I_key2=1; #1000;
I_key2=0; #1000;
I_key2=1; #2000;
I_key2=0; #5000;
#20000000;
I_key2=1;
I_key2=0; #1000;
I_key2=1; #2000;
I_key2=0; #1000;
I_key2=1; #2000;
I_key2=0; #1000;
#20000000;
I_key2=1; #1000;
end
end
always #20 I_sysclk=~I_sysclk;
endmodule 复制代码
4.2 启动 modelsim仿真 设置运行 100ms(如果运行时间太长可以修改小一些 )
可以看到按键状态, key_down和 key_up 的情况,以及成功滤除了按键的抖动
5下载演示 5. 1 硬件连接
(该教程为通用型教程,教程中仅展示一款示例开发板的连接方式,具体连接方式以所购买的开发板型号以及结合配套代码管脚约束为准。)
请确保下载器和开发板已经正确连接,并且开发板已经上电 (注意 JTAG 端子不支持热插拔,而 USB 接口支持,所以在不通电的情况下接通好 JTAG 后,再插入 USB 到电脑,之后再上电,以免造成 JTAG IO 损坏 )
5. 2 运行结果 (该教程为通用型教程,教程中仅展示一款示例开发板的上板现象,具体现象以所购买的开发板型号以及配套代码上板现象为准。)
每按一次底板上的 KEY2 按键, LED 灯会依次右移;按 KEY3 键, LED 灯依次左移