文章目录
一、按键消抖概述
1、为何要进行按键消抖
2、消抖的方式
二、系统设计
1、系统模块划分
按键和LED是不同的外设,这里划分模块时,可以将按键消抖设计为一个模块,LED控制设计为一个模块。当然,这个工程比较简单,可以将两者划分为一个模块。
2、系统时序图
三、代码实现
文件结构:
1、按键消抖模块(key_debounce)
/**
*
* 按键消抖模块
*/
module key_debounce(
input wire clk,
input wire rst_n,
input wire key_in, // 按键输入
output reg key_out // 消抖后的按键, 1为按下
);
parameter TIME_DELAY = 1000_000; // 延迟时间,20ms
reg [19:0] cnt_delay; // 计数器
wire add_cnt; // 是否计数
wire end_cnt; // 是否完成一次计数
reg key_before; // 前一个状态电平
reg key_now; // 当前按键电平
wire flag_fall; // 按下(开始抖动)标志
reg flag_timing; // 在计时标志,1表示在(可)计时
// 当前按键电平设置
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
key_now <= 1'b1;
else
key_now <= key_in;
end
// 前一个按键状态电平设置
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
key_before <= 1'b1;
else
key_before <= key_now;
end
// 前一状态为1,后一状态为0,开始抖动
assign flag_fall = key_before & (~key_now);
// 计时标志判断
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
flag_timing <= 1'b0;
else if (flag_fall) // 开始计时
flag_timing <= 1'b1;
else if (end_cnt) // 计时结束
flag_timing <= 1'b0;
end
// 计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt_delay <= 20'd0;
else if (add_cnt) begin // 计数
if (end_cnt)
cnt_delay <= 20'd0;
else
cnt_delay <= cnt_delay + 1'd1;
end
end
assign add_cnt = flag_timing; // 在计时状态 计时
assign end_cnt = ((cnt_delay==TIME_DELAY) && add_cnt); // 计时结束标志
// 消抖后的按键
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
key_out <= 1'b0;
else if (end_cnt)
key_out <= ~key_now;
else
key_out <= 1'b0;
end
endmodule
2、呼吸灯模块(led_breath)
/**
*
* 呼吸灯
*/
module led_breath(
input wire clk,
input wire rst_n,
input wire flag_state_change, // 状态切换标志
output reg[3:0] led_b
);
parameter TIME_US = 50; // 1us (微秒)
parameter TIME_MS = 1000; // 1000次us --- 1ms
parameter TIME_S = 1000; // 1000次毫秒 --- 1s
// 信号定义
reg [5:0] cnt_us; // 微秒计数器
reg [9:0] cnt_ms; // 毫秒计数器
reg [9:0] cnt_s; // 秒计数器
wire add_cnt_us;
wire end_cnt_us;
wire add_cnt_ms;
wire end_cnt_ms;
wire add_cnt_s;
wire end_cnt_s;
reg flag; // 满1s时,flag取反, flag 为0 时由灭变亮,flag为1时由亮变灭
// us计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt_us <= 6'd0;
else if (flag_state_change)
cnt_us <= 6'd0;
else if (add_cnt_us) begin
if (end_cnt_us)
cnt_us <= 6'd0;
else
cnt_us <= cnt_us + 1'd1;
end
end
assign add_cnt_us = 1'b1;
assign end_cnt_us = ((cnt_us==TIME_US-1) && add_cnt_us);
// ms 计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt_ms <= 10'd0;
else if (flag_state_change)
cnt_ms <= 6'd0;
else if (add_cnt_ms) begin
if (end_cnt_ms)
cnt_ms <= 10'd0;
else if(end_cnt_us) // 每1us,+1
cnt_ms <= cnt_ms + 1'd1;
end
end
assign add_cnt_ms = 1'b1;
assign end_cnt_ms = ((cnt_ms==TIME_MS-1) && add_cnt_ms);
// s 计数器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt_s <= 10'd0;
else if (flag_state_change)
cnt_s <= 6'd0;
else if (add_cnt_s) begin
if (end_cnt_s)
cnt_s <= 10'd0;
else if(end_cnt_ms) // 每1ms,+1
cnt_s <= cnt_s + 1'd1;
end
end
assign add_cnt_s = 1'b1;
assign end_cnt_s = ((cnt_s==TIME_S-1) && add_cnt_s);
// flag判断
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
flag <= 1'b0;
else if (flag_state_change)
flag <= 1'b0;
else if(end_cnt_s)
flag <= ~flag;
end
// led判断
// flag 为0 时由灭变亮,flag为1时由亮变灭
integer i;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
led_b <= 4'b0000;
else if (flag_state_change)
led_b <= 4'b0000;
else begin
led_b <= {4{(cnt_s > cnt_ms) ? ~flag : flag}};
end
end
endmodule
3、流水灯模块(led_waterfall)
/**
*
* 流水灯模块
*/
module led_waterfall(
input wire clk,
input wire rst_n,
input wire flag_state_change, // 状态切换标志
output reg[3:0] led_w
);
parameter T_WATER = 25'd24_999_999; // 一个变化时间--0.5s
reg [24:0] cnt_period; // 周期计时器
wire add_cnt;
wire end_cnt;
// 计时器
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt_period <= 25'd0;
else if (flag_state_change) // 状态切换时,从头开始
cnt_period <= 25'd0;
else if (add_cnt) begin
if (end_cnt)
cnt_period <= 25'd0;
else
cnt_period <= cnt_period + 1'd1;
end
end
assign add_cnt = 1'b1;
assign end_cnt = ((T_WATER==cnt_period) && add_cnt);
// 流水灯输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
led_w <= 4'b0001;
else if (flag_state_change) // 切换状态,从头开始
led_w <= 4'b0001;
else if (end_cnt)
led_w <= {led_w[2:0], led_w[3]};
else
led_w <= led_w;
end
endmodule
4、按键控制模块
/**
*
* 按键控制模块
*/
module led_control(
input wire clk,
input wire rst_n,
input wire[1:0] key, // 1为按下
output reg[3:0] led // LED, 1为亮
);
// LED两种状态
parameter WATERFALL_LIGHT = 0; // 流水灯
parameter BREATH_LIGHT = 1; // 呼吸灯
reg led_state = WATERFALL_LIGHT; // LED状态,初始为流水灯
reg flag_state_change; // 状态切换标志
/* 按键改变LED状态,
key[0] 为流水灯
key[1] 为呼吸灯
*/
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
led_state <= WATERFALL_LIGHT;
else if (key[0]) begin
led_state <= WATERFALL_LIGHT;
flag_state_change = 1'b1;
end
else if (key[1]) begin
led_state <= BREATH_LIGHT;
flag_state_change = 1'b1;
end
else begin
led_state <= led_state;
flag_state_change <= 1'b0;
end
end
wire[3:0] led_w;
wire[3:0] led_b;
led_waterfall u_led_waterfall(
.clk (clk),
.rst_n (rst_n),
.flag_state_change (flag_state_change), // 状态切换标志
.led_w (led_w)
);
led_breath u_led_breath(
.clk (clk),
.rst_n (rst_n),
.flag_state_change (flag_state_change), // 状态切换标志
.led_b (led_b)
);
// led输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
led <= 4'b1111;
else if (led_state == WATERFALL_LIGHT)
led <= led_w;
else if (led_state == BREATH_LIGHT)
led <= led_b;
else
led <= led;
end
5、顶层文件
/**
*
* 顶层文件
*/
module led_control_top(
input wire clk,
input wire rst_n,
input wire[1:0] key_in,
output wire[3:0] led
);
wire [1:0] key; // 消抖后的key
// 按键消抖模块实例化
key_debounce k0(
.clk (clk),
.rst_n (rst_n),
.key_in (key_in[0]), // 按键输入
.key_out (key[0]) // 消抖后的按键, 1为按下
);
key_debounce k1(
.clk (clk),
.rst_n (rst_n),
.key_in (key_in[1]), // 按键输入
.key_out (key[1]) // 消抖后的按键, 1为按下
);
// led控制模块实例化
led_control u_led_control(
.clk (clk),
.rst_n (rst_n),
.key (key), // 1为按下
.led (led) // LED, 1为亮
);
endmodule
四、效果展示
按键消抖控制LED
五、仿真设计
1、仿真文件
/**
*
* 仿真文件
*/
`timescale 1ns/1ps
module led_control_top_tb;
reg tb_clk;
reg tb_rst_n;
reg [1:0] tb_key_in;
wire [3:0] tb_led;
parameter CYCLE = 20; // 时钟周期
// 修改消抖持续时间
defparam test_led_control_top.k0.TIME_DELAY = 10;
defparam test_led_control_top.k1.TIME_DELAY = 10;
// 修改呼吸灯周期
defparam test_led_control_top.u_led_control.u_led_breath.TIME_US = 5;
defparam test_led_control_top.u_led_control.u_led_breath.TIME_MS = 5;
defparam test_led_control_top.u_led_control.u_led_breath.TIME_S = 5;
// 修改流水灯周期
defparam test_led_control_top.u_led_control.u_led_waterfall.T_WATER = 5;
// 时钟变化
always #(CYCLE/2) tb_clk = ~tb_clk;
// 初始化
integer i,j;
initial begin
tb_clk = 1'b1;
tb_rst_n = 1'b0;
tb_key_in = 2'b11;
#(CYCLE * 10);
tb_rst_n = 1'b1;
#(CYCLE * 100); // 默认为流水灯
// 按下key1,切换为呼吸灯
for (i=0 ;i<5; i=i+1) begin
tb_key_in[1] = {$random};
j = {$random}%20; // 按键产生的时差
#(CYCLE * j);
end
tb_key_in = 2'b11;
#(CYCLE *120);
// 按下key0,切换为流水灯
for(i= 0; i<5; i=i+1) begin
tb_key_in[0] = {$random};
j = {$random}%20;
#(CYCLE * j);
end
tb_key_in = 2'b11;
#(CYCLE *120);
$stop;
end
// 模块实例化
led_control_top test_led_control_top(
.clk (tb_clk),
.rst_n (tb_rst_n),
.key_in (tb_key_in),
.led (tb_led)
);
endmodule