一、实验内容
有四个按键,分别控制四个LED灯的亮灭切换。
二、实验说明
1、代码中,使用位宽为4的key_h
作为输入,分别连接着四个按键,也即每一位的高低对应着按键的松按。使用位宽为4的led
作为输出,分别连接着四个LED,也即每一位的高低对应控制LED的亮灭切换。
2、采集按键状态的时候,采用了这篇文章:FPGA中按键消抖的三种方案 中的方案二来对按键进行消抖。
3、在检测下降沿的时候,使用(前一拍数据)&(后一拍数据取反)的方式进行检测。若结果为1,则说明检测到了下降沿。
三、代码解析
1、RTL代码
//4个独立按键S3/S4/S5/S6的按下与否,对应控制LED D24/D25/D26/D27的亮灭切换。
module at7(
input sys_clk_i, //外部输入50MHz时钟信号
input ext_rst_n, //外部输入复位信号,低电平有效
input [3:0] key_h, //4个独立按键输入,未按下为高电平,按下后为低电平
output reg[3:0] led //4个LED指示灯接口
);
/***********************************************************
第一部分:检测按键的上升沿或者下降沿
************************************************************/
wire key; //所有按键值相与的结果,用于按键触发判断
reg[3:0] keyr; //按键值key的缓存寄存器
//四个键中,任何一个被按下,key都会变为0;除非全都未按下,key才为1。
assign key = key_h[0] & key_h[1] & key_h[2] & key_h[3];
always @(posedge sys_clk_i or negedge ext_rst_n)
if (!ext_rst_n) keyr <= 4'b1111;
else keyr <= {keyr[2:0],key};
/*
因为要连续4个时钟才可以将keyr的4位都赋值为出现过的key值,
并且是使用的最后两个key值,也即keyr的3、4两位,
所以必须要按键按下至少四个周期才能检测到上升沿和下降沿,
起到了初步防止误触的效果。
*/
wire key_neg = ~keyr[2] & keyr[3]; //有按键被按下
wire key_pos = keyr[2] & ~keyr[3]; //有按键被释放
/***********************************************************
第二部分:定时计数20ms时间,用于对按键的消抖
************************************************************/
reg[19:0] cnt;
//按键消抖定时计数器
always @ (posedge sys_clk_i or negedge ext_rst_n)
if (!ext_rst_n) cnt <= 20'd0;
//不管上升沿还是下降沿都要重新计数,只有当cnt计数到指定值才开始采集此时的键值
//这么做就是为了实现消抖,只有按键下降沿或者上升沿发生后高低电平稳定20ms,
//才认为此时的键值是有效的,也即此时才开始采集键值。
else if(key_pos || key_neg) cnt <= 20'd0;
else if(cnt < 20'd999_999) cnt <= cnt + 1'b1;
else cnt <= 20'd0;
reg[3:0] key_halue[1:0];
//定时采集按键值
always @(posedge sys_clk_i or negedge ext_rst_n)
if (!ext_rst_n) begin
key_halue[0] <= 4'b1111;
key_halue[1] <= 4'b1111;
end
else begin
key_halue[1] <= key_halue[0];
//因为只有当计时到20ms才可以采集键值,
//所以下一次再次采集键值至少还的是20ms后
if(cnt == 20'd999_999) key_halue[0] <= key_h;//定时键值采集
else ;
end
//对于检测下降沿的信号,(前一clk的数据)&(后一clk的数据取反),结果若为1,
//则表示检测到了下降沿,否则就没有。此时key_press的四个位对应的四个按键,
//当为1时表示按键被按下,为0就表示此时按键没有被按下。
//消抖后按键值变化标志位
wire[3:0] key_press = key_halue[1] & ~key_halue[0];
/***********************************************************
第三部分:LED亮灭切换控制
************************************************************/
always @ (posedge sys_clk_i or negedge ext_rst_n)
if (!ext_rst_n) led <= 8'hff;
else if(key_press[0]) led[0] <= ~led[0];
else if(key_press[1]) led[1] <= ~led[1];
else if(key_press[2]) led[2] <= ~led[2];
else if(key_press[3]) led[3] <= ~led[3];
else ;
endmodule
2、仿真程序
`timescale 1ns/1ps
module sim_at7();
reg sys_clk_i; //50MHz时钟信号
reg ext_rst_n; //复位信号,低电平有效
reg[3:0] key_h; //4个独立按键输入,未按下为高电平,按下后为低电平
wire[3:0] led; //4个LED指示灯接口
at7 uut_at7(
.sys_clk_i(sys_clk_i), //外部输入50MHz时钟信号
.ext_rst_n(ext_rst_n), //外部输入复位信号,低电平有效
.key_h(key_h),
.led(led) //8个LED指示灯接口
);
initial begin
sys_clk_i = 0;
ext_rst_n = 0; //复位中
key_h = 4'b1111;
#1000;
@(posedge sys_clk_i); #2;
ext_rst_n = 1; //复位结束,正常工作
@(posedge sys_clk_i); #2;
//模拟按键抖动(实际未被按下)
key_h[0] = 1'b0;
#1_000_000; //1ms
key_h[0] = 1'b1;
#5_000_000; //5ms
key_h[0] = 1'b0;
#3_000_000; //3ms
key_h[0] = 1'b1;
//模拟按键动作
key_h[0] = 1'b0;
#1_000_000; //1ms
key_h[0] = 1'b1;
#5_000_000; //5ms
key_h[0] = 1'b0;
#3_000_000; //3ms
key_h[0] = 1'b1;
#3_000_000; //3ms
key_h[0] = 1'b0;
#500_000_000; //500ms,按下
key_h[0] = 1'b1;
#3_000_000; //3ms
key_h[0] = 1'b0;
#3_000_000; //3ms
key_h[0] = 1'b1;
#50_000_000;
$finish;
end
always #10 sys_clk_i = ~sys_clk_i; //50MHz时钟产生
endmodule
3、仿真结果
仿真结果不好展示,自己去仿真看一下就行。大致现象就是当按键按下的时间超过20ms,也即key_h[0]=0
的时间超过20ms的时候,led[0]
才会翻转,否则是不会变化的。