🎉欢迎来到FPGA专栏~按键消抖模块设计与验证
- ☆* o(≧▽≦)o *☆嗨~我是小夏与酒🍹
- ✨博客主页:小夏与酒的博客
- 🎈该系列文章专栏:FPGA学习之旅
- 文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
- 📜 欢迎大家关注! ❤️
一、效果演示
🥝模块设计:
🥝按键消抖模块的完整代码,可直接使用:
//
//模块:按键消抖模块
//key_state:输出消抖之后按键的状态
//key_flag:按键消抖结束时产生一个时钟周期的高电平脉冲
//
module KeyFilter(
input Clk,
input Rst_n,
input key_in,
output reg key_flag,
output reg key_state
);
//按键的四个状态
localparam
IDLE = 4'b0001,
FILTER1 = 4'b0010,
DOWN = 4'b0100,
FILTER2 = 4'b1000;
//状态寄存器
reg [3:0] curr_st;
//边沿检测输出上升沿或下降沿
wire pedge;
wire nedge;
//计数寄存器
reg [19:0]cnt;
//使能计数寄存器
reg en_cnt;
//计数满标志信号
reg cnt_full;//计数满寄存器
//------<边沿检测电路的实现>------
//边沿检测电路寄存器
reg key_tmp0;
reg key_tmp1;
//边沿检测
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
key_tmp0 <= 1'b0;
key_tmp1 <= 1'b0;
end
else begin
key_tmp0 <= key_in;
key_tmp1 <= key_tmp0;
end
end
assign nedge = (!key_tmp0) & (key_tmp1);
assign pedge = (key_tmp0) & (!key_tmp1);
//------<状态机主程序>------
//状态机主程序
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(curr_st)
IDLE:begin
key_flag <= 1'b0;
if(nedge)begin
curr_st <= FILTER1;
en_cnt <= 1'b1;
end
else
curr_st <= IDLE;
end
FILTER1:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b0;
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else if(pedge)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER1;
end
DOWN:begin
key_flag <= 1'b0;
if(pedge)begin
curr_st <= FILTER2;
en_cnt <= 1'b1;
end
else
curr_st <= DOWN;
end
FILTER2:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b1;
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else if(nedge)begin
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER2;
end
default:begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
end
//------<20ms计数器>------
//20ms计数器
//Clk 50_000_000Hz
//一个时钟周期为20ns
//需要计数20_000_000 / 20 = 1_000_000次
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 20'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt_full <= 1'b0;
else if(cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
end
endmodule
🥝RTL视图:
🥝状态转移:
🥝仿真结果:
二、模块设计
🥝模块设计:
信号 | 作用 |
---|---|
clk | 时钟信号输入 |
rst_n | 复位信号输入 |
key_in | 按键信号输入 |
key_flag | 消抖结束之后的标志位 |
key_state | 消抖结束之后按键的状态 |
🥝上升沿检测电路:
🥝下降沿检测电路:
🥝边沿检测电路的实现:
检测到下降沿,nedge输出高电平;检测到上升沿,pedge输出高电平。
//------<边沿检测电路的实现>------
//边沿检测电路寄存器
reg key_tmp0;
reg key_tmp1;
//边沿检测
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
key_tmp0 <= 1'b0;
key_tmp1 <= 1'b0;
end
else begin
key_tmp0 <= key_in;
key_tmp1 <= key_tmp0;
end
end
assign nedge = (!key_tmp0) & (key_tmp1);//检测到下降沿,nedge输出高电平
assign pedge = (key_tmp0) & (!key_tmp1);//检测到上升沿,pedge输出高电平
🥝一段式状态机设计:
按键的四种状态:
//按键的四个状态
localparam
IDLE = 4'b0001,
FILTER1 = 4'b0010,
DOWN = 4'b0100,
FILTER2 = 4'b1000;
计数器:
//------<20ms计数器>------
//20ms计数器
//Clk 50_000_000Hz
//一个时钟周期为20ns
//需要计数20_000_000 / 20 = 1_000_000次
//计数寄存器
reg [19:0]cnt;
//使能计数寄存器
reg en_cnt;
//计数满标志信号
reg cnt_full;//计数满寄存器
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt <= 20'd0;
else if(en_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 20'd0;
end
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)
cnt_full <= 1'b0;
else if(cnt == 999_999)
cnt_full <= 1'b1;
else
cnt_full <= 1'b0;
end
状态机主程序:
//------<状态机主程序>------
//状态机主程序
always@(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(curr_st)
IDLE:begin
key_flag <= 1'b0;
if(nedge)begin
curr_st <= FILTER1;
en_cnt <= 1'b1;
end
else
curr_st <= IDLE;
end
FILTER1:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b0;
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else if(pedge)begin
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER1;
end
DOWN:begin
key_flag <= 1'b0;
if(pedge)begin
curr_st <= FILTER2;
en_cnt <= 1'b1;
end
else
curr_st <= DOWN;
end
FILTER2:begin
if(cnt_full)begin
key_flag <= 1'b1;
key_state <= 1'b1;
curr_st <= IDLE;
en_cnt <= 1'b0;
end
else if(nedge)begin
curr_st <= DOWN;
en_cnt <= 1'b0;
end
else
curr_st <= FILTER2;
end
default:begin
curr_st <= IDLE;
en_cnt <= 1'b0;
key_flag <= 1'b0;
key_state <= 1'b1;
end
endcase
end
end
三、仿真测试
3.1 常规编写
`timescale 1ns/1ns
`define clock_period 20
module KeyFilter_tb;
reg Clk;
reg Rst_n;
reg key_in;
wire key_flag;
wire key_state;
KeyFilter KeyFilter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
initial Clk = 1;
always#(`clock_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
key_in = 1'b1;
#(`clock_period*10);
Rst_n = 1'b1;
#(`clock_period*10 + 1);
key_in = 0;#1000;
key_in = 1;#2000;
key_in = 0;#1400;
key_in = 1;#2600;
key_in = 0;#1300;
key_in = 1;#200;
key_in = 0;#20000100;
#50000000;
key_in = 1;#2600;
key_in = 0;#1000;
key_in = 1;#2000;
key_in = 0;#1400;
key_in = 1;#2600;
key_in = 0;#1300;
key_in = 1;#200;
key_in = 1;#20000100;
#50000000;
$stop;
end
endmodule
仿真结果:
3.2 task编写
`timescale 1ns/1ns
`define clock_period 20
module KeyFilter_tb;
reg Clk;
reg Rst_n;
reg key_in;
wire key_flag;
wire key_state;
KeyFilter KeyFilter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
initial Clk = 1;
always#(`clock_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
key_in = 1'b1;
#(`clock_period*10);
Rst_n = 1'b1;
#(`clock_period*10 + 1);
#30000;
PressKey; #10000;
PressKey; #10000;
PressKey; #10000;
$stop;
end
reg [15:0]myrand;
task PressKey;
begin
//50次随机时间按下抖动
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key_in = ~key_in;
end
key_in = 0;
#50_000_000;//按下稳定
//50次随机时间释放抖动
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key_in = ~key_in;
end
key_in = 1;
#50_000_000;//释放稳定
end
endtask
endmodule
注意$random
随机函数的用法:
$random
这一系统函数可以产生一个有符号的32bit
随机整数。一般的用法是$random%b
,其中b>0
。这样就会生成一个范围在(-b+1):(b-1)
中的随机数。如果只得到正数的随机数,可采用{$random}%b
来产生。
myrand = {$random}%65536;//0~65535
上述语句的作用即是产生了0~65535
之间的随机数。
通过repeat语句
循环50
次,就产生了50次不同的延时效果:
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key_in = ~key_in;
end
仿真结果:
四、仿真模型
编写key_model并添加到测试激励文件中:
`timescale 1ns/1ns
module key_model(key);
output reg key;
reg [15:0]myrand;
initial begin
key = 1'b1;
PressKey; #10000;
PressKey; #10000;
PressKey; #10000;
$stop;
end
task PressKey;
begin
//50次随机时间按下抖动
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key = ~key;
end
key = 0;
#50_000_000;//按下稳定
//50次随机时间释放抖动
repeat(50)begin
myrand = {$random}%65536;//0~65535
#myrand key = ~key;
end
key = 1;
#50_000_000;//释放稳定
end
endtask
endmodule
修改KeyFilter_tb:
`timescale 1ns/1ns
`define clock_period 20
module KeyFilter_tb;
reg Clk;
reg Rst_n;
wire key_in;
wire key_flag;
wire key_state;
KeyFilter KeyFilter0(
.Clk(Clk),
.Rst_n(Rst_n),
.key_in(key_in),
.key_flag(key_flag),
.key_state(key_state)
);
key_model key_model0(.key(key_in));
initial Clk = 1;
always#(`clock_period/2) Clk = ~Clk;
initial begin
Rst_n = 1'b0;
#(`clock_period*10);
Rst_n = 1'b1;
#(`clock_period*10 + 1);
end
endmodule
整个激励文件的内部结构:
仿真结果:
文章来源:https://www.toymoban.com/news/detail-658798.html
🧸结尾文章来源地址https://www.toymoban.com/news/detail-658798.html
- ❤️ 感谢您的支持和鼓励! 😊🙏
- 📜您可能感兴趣的内容:
- 【FPGA】串口通信讲解-状态机判断数据值
- 【Python】串口通信-与FPGA、蓝牙模块实现串口通信(Python+FPGA)
- 【Arduino TinyGo】【最新】使用Go语言编写Arduino-环境搭建和点亮LED灯
- 【全网首发开源教程】【Labview机器人仿真与控制】Labview与Solidworks多路支配关系-四足爬行机器人仿真与控制
到了这里,关于【FPGA零基础学习之旅#10】按键消抖模块设计与验证(一段式状态机实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!