按键消抖及按键控制 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
热门推荐
小孩有白头发的原因及应对方法
宝宝也会失眠吗?如何判断和应对儿童行为性失眠?
武汉特色旅游路线:从晴川阁到南岸嘴,领略江城独特魅力
慢性根尖周炎是怎么回事
除小猪佩奇,还有另四部有绘本又有动画片的经典推荐,适合3-6岁英语启蒙进阶使用
刻意“穷养”或“富养”长大的孩子,一辈子为钱所困
2025年哪家银行存款利率最高?深度解析帮你选对银行!
在北京买首套房首付比例是多少
阳宅风水朝向怎么看:提升家居舒适度的方法
舌头上面有裂纹怎么回事
js怎么判断世纪闰年
吃维生素B尿黄是怎么回事
丙氨酸氨基转移酶试剂盒检测原理与操作步骤详解
你家的房子能扛住几级地震?
UV固化技术中最优波长选择指南:如何选择最适合的UV固化波长?
国补红利掀起我市数码产品"换新潮"
黑芝麻有哪些吃法?可以泡水喝吗?如何辨别黑芝麻真假?早做了解
痛风是怎么引起的?
电梯检验员怎么考? 有什么作用?报考政策条件要求
睡觉时总是在凌晨三四点醒来?医生:当心身体可能出这些问题了!
哮喘的诊断方法和相关检查
新车交车三大步骤:检查、核对、教学,这些细节不容忽视
海龟汤题目全集及解析
软件源码如何封装
中西医结合治疗肝病:全面调理与局部治疗的完美融合
【科普营养】每天要吃多少卡路里?2种算法带你入门
缴费记录可以作为证据吗?——电子支付凭证的法律效力与适用
麦琳“拧巴”的爱: 在牺牲与控制中找寻位置,在反复验证中追索安稳
壶关羊汤——乡村工匠精神与传统美味的传承与创新
文献检索全攻略:编辑视角下的高效检索指南