一、概述
在上一篇文章中我们学习了按键的相关消抖及其使用,在这篇文章当中我们就针对通过按键实现LED的控制。
1、按键原理图
2、基本框架
通过我们前面编写的按键消抖的文件和LED文件将按键和LED两个模块进行交互,从而达到按键控制LED的目的。
二、代码编写
1、首先是按键相关设计文件的编写,新建key.v,如下:
cpp
//状态机实现
module key (
input clk ,
input rst_n ,
input key_in , //输入原始的按键信号
output reg key_out //输出处理之后的按键信号
);
//参数定义
localparam IDLE = 4'b0001,//空闲
JITTLE0 = 4'b0010,//滤除第一次抖动
DOWN = 4'b0100,//稳定
JITTLE1 = 4'b1000;//滤除第二次抖动
parameter TIME_20MS = 1_000_000;//需要计数的值,20ms
//内部信号
reg [3:0] state_c;//现态
reg [3:0] state_n;//次态
reg [19:0] cnt_20ms;//计数20ms
wire add_cnt_20ms;
wire end_cnt_20ms;
wire nedge ;//下降沿信号
wire pedge ;//上升沿信号
reg key_in_r1 ;// 同步打拍
//状态转移 同步时序逻辑描述状态转移
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
state_c <= IDLE;
else
state_c <= state_n;
end
//状态转移条件 组合逻辑
always @(*) begin
case (state_c)//一定是case 现态
IDLE:begin
if(nedge)
state_n = JITTLE0;
else
state_n = state_c;
end
JITTLE0:begin
if(end_cnt_20ms)
state_n = DOWN;
else
state_n = state_c;
end
DOWN:begin
if(pedge)
state_n = JITTLE1;
else
state_n = state_c;
end
JITTLE1 :begin
if(end_cnt_20ms)
state_n = IDLE;
else
state_n = state_c;
end
default: state_n = IDLE;
endcase
end
//20ms计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt_20ms <= 0;
else if(add_cnt_20ms)begin
if(end_cnt_20ms)
cnt_20ms <= 0;
else
cnt_20ms <= cnt_20ms + 1;
end
end
assign add_cnt_20ms = (state_c == JITTLE0) || (state_c == JITTLE1);
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == TIME_20MS - 1;
//下降沿 上升沿
//同步 打拍
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_in_r1 <= 2'b11;
end
else begin
key_in_r1 <= key_in; //同步按键输入信号
end
end
//r1当前状态
assign nedge = ~key_in_r1 && key_in;
assign pedge = key_in_r1 && ~key_in;
//key_out
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
key_out <= 0;
else if(end_cnt_20ms &&(state_c== JITTLE1))
key_out <= ~key_in_r1;//有效脉冲 20ns
else
key_out <= 0;
end
endmodule
2、LED相关设计文件编写
新建led_ctrl.v文件
cpp
module led_ctrl(
input clk ,
input rst_n ,
input key_flag ,//输入原始的按键信号
output reg led //输出处理之后的按键信号
);
always @(posedge clk or negedge rst_n )begin
if(!rst_n)
led<=1'b0;
else if(key_flag)
led<=~led;
end
endmodule
3、顶层文件编写
在顶层文件中,使用一个中间变量key_out将按键中的输出赋值给LED模块中的按键标志位输入key_flag。
cpp
module top(
input clk,
input rst_n,
input key_in,
output led
);
wire key_out;
key key_inst(
/*input */ .clk (clk ),
/*input */ .rst_n (rst_n ),
/*input */ .key_in (key_in), //输入原始的按键信号
/*output reg */ .key_out (key_out) //输出处理之后的按键信号
);
led_ctrl led_inst(
/*input */ .clk (clk ),
/*input */ .rst_n (rst_n ),
/*input */ .led (led), //输入原始的按键信号
/*output reg */ .key_flag (key_out) //输出处理之后的按键信号
);
endmodule
4、测试文件编写
cpp
`timescale 1ns/1ns
module top_tb ;
reg clk ;
reg rst_n ;
reg key_in ;
wire led ;
defparam top_inst.key_inst.TIME_20MS = 1000;
top top_inst(
.clk (clk ),
.rst_n (rst_n ),
.key_in (key_in ), //输入原始的按键信号
.led (led ) //输出处理之后的按键信号
);
//激励信号产生
parameter CLK_CLY = 20;
//时钟
initial clk=1;
always #(CLK_CLY/2)clk=~clk;
//复位
initial begin
rst_n= 1'b0;
#(CLK_CLY*3);
#5;//复位结束避开时钟上升沿
rst_n= 1'b1;
end
//激励
integer i;
initial repeat(5)begin
key_in = 1;//模拟按键未按下
i ={$random}%6;//给i赋值0-5
#(CLK_CLY*500);//等待复位时间结束
#3;
repeat (3)begin
key_in = 0;//前按键抖动开始
#(CLK_CLY*1);
//一个5-10ms的抖动时间
repeat ((i+5)*50)begin
key_in = $random;
#(CLK_CLY*1);
end
key_in = 0;//按键稳定
#(CLK_CLY*100*50);
//后抖动开始
key_in = 1;
#(CLK_CLY*1);
repeat ((i+5)*50)begin
key_in = $random;
#(CLK_CLY*1);
end
key_in = 1;//按键稳定
#(CLK_CLY*10*500);
end
//模拟意外抖动
repeat (3)begin
repeat ((i+5)*50)begin
key_in = $random;
#(CLK_CLY*1);
end
key_in = 1;//按键稳定
#(CLK_CLY*500);
end
$stop;
end
endmodule
三、仿真波形图
通过波形图我们可以看见当按键按下一次,LED状态切换一次,实现了按键控制LED的功能实现。