按键消抖及按键控制 LED 灯
创作时间:
作者:
@小白创作中心
按键消抖及按键控制 LED 灯
引用
CSDN
1.
https://m.blog.csdn.net/weixin_50628347/article/details/139118170
按键是人机交互中常见的元素,常用于系统复位或控制设置等。然而,当按键被按下时,其产生的信号波形并不稳定,会出现抖动现象。为了解决这个问题,本实验使用状态机技术来消除按键抖动,并通过稳定的按键信号来控制LED灯的亮灭。
前言
按键是人机交互中常见的元素,常用于系统复位或控制设置等。然而,当按键被按下时,其产生的信号波形并不稳定,会出现抖动现象。如果直接检测波形的下降沿作为按键按下的标志,可能会因为抖动而产生多个错误标志。
为了解决这个问题,实验中使用状态机技术来消除按键抖动。状态机能够监测按键保持某一电平状态(高电平或低电平)的时间,当按键稳定在一个电平状态超过20ms时,即可判断按键已被按下或释放。最后,通过稳定的按键信号来控制LED灯的亮灭。
一、程序设计
本实验设计了一个状态机,通过状态机实现了按键消抖,然后再使用这个按键消抖模块来控制 led 灯的亮灭。
主要用了两个模块,key_debuounce 模块用于按键信号进行消除抖动,当按键按下的时候,key_down信号产生一个高电平信号用来控制 led的翻转,实现按键按一下 led 灯的亮灭状态发生改变。
IDLE 状态表明处于空闲状态,FILTER0 表明按键按下的抖动状态,DOWN 表明按键按下的稳定状态,FILTTER1 表明按键处于抬起的抖动状态。按键还没按下的时候处于 IDLE 空闲状态,按检测到按键按下的下降沿的时候进入 FILTER0状态,在 FILTER0检测到按键信号稳定 20ms后进入 DOWN状态,检测到按键抬起的上升沿的时候进入 FILTER1 状态,检测到按键抬起稳定 20ms 后进入 IDLE 状态,表明一次按键按下完成。
二、Verilog代码编写
1.按键消抖模块代码
// 定义一个名为 key_debounce 的模块,用于消抖按键输入信号
module key_debounce(
input clk, // 时钟信号输入
input rst_n, // 复位信号输入,低电平有效
input key_in, // 按键输入信号
output key_down, // 按键按下事件输出
output key_up // 按键释放事件输出
);
// 定义状态机的状态编码
localparam
IDLE=4'b0001, // 空闲状态
FILTER0=4'b0010, // 首次滤波状态
DOWN=4'b0100, // 按键按下状态
FILTER1=4'b1000; // 按键释放滤波状态
// 定义20毫秒计数器的最大计数值,用于消抖延时
localparam CNT_20MS=20_000_000/20-1;
// 定义内部寄存器和信号
reg[31:0]cnt; // 用于消抖延时的计数器
reg[1:0]key_filter; // 按键滤波寄存器,用于消除按键输入的抖动
reg[1:0]key_in_d; // 按键输入的双缓存寄存器
reg[3:0]state; // 状态机当前状态
wire nedge; // 按键从未按下变为按下的边沿信号
wire pedge; // 按键从未按下变为释放的边沿信号
wire end_cnt; // 计数器达到20毫秒的信号
// key_filter 寄存器,用于存储按键输入的抖动
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
key_filter<=2'b11; // 复位时清零
else
key_filter<={key_filter[0],key_in}; // 非复位时,实现按键输入的双缓冲
end
// key_in_d 寄存器,用于存储 key_filter 的第二位
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
key_in_d<=2'b11; // 复位时清零
else
key_in_d<={key_in_d[0],key_filter[1]}; // 非复位时,实现key_filter 的第二位的双缓冲
end
// 检测按键从未按下变为按下的边沿信号 nedge
assign nedge=(!key_in_d[0])&key_in_d[1];
// 检测按键从未释放变为按下的边沿信号 pedge
assign pedge=key_in_d[0]&(!key_in_d[1]);
// 计数器逻辑,用于实现20毫秒的消抖延时
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt<='d0;
else if(state==FILTER0)begin
if(end_cnt|pedge) // 如果计数器达到20毫秒或检测到按键释放,计数器清零
cnt<='d0;
else
cnt<=cnt+1'b1; // 否则计数器递增
end
else if(state==FILTER1)begin
if(end_cnt|nedge) // 如果计数器达到20毫秒或检测到按键按下,计数器清零
cnt<='d0;
else
cnt<=cnt+1'b1; // 否则计数器递增
end
else
cnt<='d0; // 其他状态下计数器清零
end
// 判断计数器是否达到20毫秒
assign end_cnt=cnt==CNT_20MS;
// 状态机逻辑,用于处理按键事件
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
state<=IDLE; // 复位时,状态机置为空闲状态
else begin
case(state) // 根据当前状态和输入信号进行状态转换
IDLE:begin
if(nedge) // 如果检测到按键按下边沿,进入 FILTER0 状态
state<=FILTER0;
else
state<=IDLE; // 否则保持空闲状态
end
FILTER0:begin
if(end_cnt) // 如果计数器达到20毫秒,进入 DOWN 状态
state<=DOWN;
else
state<=FILTER0; // 否则保持 FILTER0 状态
end
DOWN:begin
if(pedge) // 如果检测到按键释放边沿,进入 FILTER1 状态
state<=FILTER1;
else
state<=DOWN; // 否则保持 DOWN 状态
end
FILTER1:begin
if(end_cnt) // 如果计数器达到20毫秒,返回 IDLE 状态
state<=IDLE;
else
state<=FILTER1; // 否则保持 FILTER1 状态
end
default:state<=IDLE; // 任何未定义的状态都视为空闲状态
endcase
end
end
// 定义按键按下事件输出信号,当状态机处于 FILTER0 状态且计数器达到20毫秒时输出
assign key_down=state==FILTER0&&end_cnt;
// 定义按键释放事件输出信号,当状态机处于 FILTER1 状态且计数器达到20毫秒时输出
assign key_up=state==FILTER1&&end_cnt;
endmodule
2.顶层代码
// 定义一个名为 key_led 的模块,用于控制LED灯随按键输入信号进行闪烁
module key_led(
input clk, // 时钟信号输入
input rst_n, // 复位信号输入,低电平有效
input key_in, // 按键输入信号
output reg led // LED输出信号,用于指示按键操作
);
// 定义一个内部信号 key_down,用于接收消抖后的按键按下事件
wire key_down;
// 实例化消抖模块 key_debounce,用于稳定按键输入信号
key_debounce key_debounce(
.clk(clk), // 连接时钟信号
.rst_n(rst_n), // 连接复位信号
.key_in(key_in), // 连接原始按键输入信号
.key_down(key_down) // 连接消抖后的按键按下事件输出
);
// 定义一个 always 块,用于处理LED灯的状态
always@(posedge clk or negedge rst_n) begin
if(!rst_n) begin
// 当复位信号为低时,将LED灯设置为初始状态(这里为高电平)
led<=1'b1;
end else if(key_down) begin
// 当检测到消抖后的按键按下事件时,切换LED灯的状态
led<=~led;
end else begin
// 如果没有检测到按键按下事件,保持LED灯当前状态不变
led<=led;
end
end
endmodule
3.仿真代码
`timescale 1ns / 1ps // 定义仿真时间单位为1纳秒,时间精度为1皮秒
module tb_key_debounce(); // 定义测试模块 tb_key_debounce
reg clk; // 定义时钟信号 clk
reg rst_n; // 定义复位信号 rst_n,低电平有效
reg key_in; // 定义按键输入信号 key_in
reg[15:0]my_rand; // 定义一个16位的随机数发生器寄存器 my_rand
wire key_down; // 定义按键按下事件输出信号 key_down
wire key_up; // 定义按键释放事件输出信号 key_up
// 实例化待测试的消抖模块 key_debounce
key_debounce key_debounce(
.clk(clk),
.rst_n(rst_n),
.key_in(key_in),
.key_down(key_down),
.key_up(key_up)
);
// 生成时钟信号的 initial 块
initial begin
clk=1; // 初始化时钟信号为高电平
forever begin // 创建一个永久循环,以产生连续的时钟信号
#10 clk=~clk; // 每10纳秒翻转一次时钟信号
end
end
// 生成复位信号的 initial 块
initial begin
rst_n=0; // 初始化复位信号为低电平
repeat(20) @(posedge clk); // 等待20个时钟周期
rst_n=1; // 将复位信号置为高电平,结束复位
end
// 定义一个任务 bounce,用于模拟按键的抖动
task bounce;
begin
repeat(50)begin
my_rand=5_000_000+({$random}%5_000_000); // 生成随机数,模拟按键抖动时间
#my_rand; // 等待随机时间
key_in=~key_in; // 翻转按键输入信号,模拟按键抖动
end
end
endtask
// 定义一个任务 press_key,用于模拟按键的按下和释放
task press_key;
begin
key_in=1; // 将按键输入信号置为高电平,模拟按键按下
#25_000_000; // 等待25毫秒
bounce; // 调用 bounce 任务,模拟按键抖动
key_in=0; // 将按键输入信号置为低电平,模拟按键释放
#50_000_000; // 等待50毫秒
bounce; // 再次调用bounce 任务,模拟按键抖动
key_in=1; // 将按键输入信号置回高电平,准备下一次按键事件
#25_000_000; // 等待25毫秒
end
endtask
// 主要的 initial 块,用于初始化信号并启动测试
initial begin
my_rand=0; // 初始化随机数发生器寄存器
key_in=1; // 将按键输入信号置为高电平
@(posedge rst_n); // 等待复位信号变为高电平
repeat(50)@(posedge clk) begin // 进行50次循环,每次循环等待一个时钟周期
press_key; // 调用 press_key 任务,模拟一次按键按下和释放
press_key; // 再次调用 press_key 任务
#50_000_000; // 等待50毫秒
$stop; // 停止仿真
end
end
endmodule
热门推荐
【图朴解决方案】蒸汽管道上为什么常常要弯一段?
耒阳举办元宵晚会
长江干线航道概况
横琴:拨动大湾区旅游发展之弦
BBD指标的计算方法和应用范围是什么?BBD指标的局限性有哪些?
口腔微生物组研究:解读健康和疾病中的宏基因组数据
猫咪吃化毛膏有用吗?科学解析+使用指南
发育迟缓的康复治疗
小儿发育迟缓症状识别与早期干预的重要性
广绣传承人王新元:让广绣走出画框
工地项目经理如何高效开会?十大核心要点全解析
治疗耳鸣脑鸣的6个中成药,一文总结
黄金市场高频监测体系:租赁利率、EFP与远期曲线指标解析
如何健康长寿?中医角度的长寿秘诀
适合带爸妈自驾游的目的地推荐!
三包政策规定有哪些
炉石传说卡牌设计深度解析:数值与机制的辩证关系
软件开发如何写好文档
燕窝30克等于多少毫升?一文详解燕窝重量与体积换算
如何避免婚姻中出现冷漠、不沟通、不关心等现象
什么是核能?从原理到应用的全面解析
5G革命:当技术隐入尘烟,世界正在发生这些静默巨变
一包祝福寄温情(新春走基层)
快递单价已低至0.85元 业内人士:价格过低将致快递服务质量成疑
什么是编程攻击
鸟类也有自己的“方言”!来听大自然的声音→
乌尔姆和达姆施塔特赛事关注3.28前瞻
新鲜木耳怎样吃才安全?这几点要记住!
紫罗兰琥珀的功效与作用及禁忌价值
找律师代理需要注意什么事项