软件:PC、Quartus Prime 18.1、Modelsim 10.5b
硬件:Altera FPGA开发板(EP4CE6E22F17C8)
使用经过消除抖动后的按键信号控制LED呈现两种不同的状态
a) 流水灯
b) 闪烁
根据开发板的原理图,可得到以下资料
这类机械按键由于弹簧片的存在,默认处于高电平,按下和释放时都会有一定的抖动时间,在抖动时,其电平状态为不定值,故需要在稳定时再读取信号,通常抖动时间在5~10ms,故设置一个20ms的计数器,用于计时低电平时长。
根据硬件原理图所示,8颗发光二极管,所有的阳极都接通3.3V的正电压,也即————高电平,所以如果我们想要
发光二极管导通的话,需要在阴极接通低电平,就可以让LED亮起来。
根据系统要求,可以得到以下框架分布
根据系统构建,可得到以下模块
信号名 | 端口类型 | 数据位宽 | 信号说明 |
---|---|---|---|
Clk | i | 1 | 输入时钟信号,50MHz |
Rst_n | i | 1 | 输入复位信号,低电平有效 |
key | i | 1 | 输入按键信号,低电平有效 |
flag | o | 1 | 输出按键消抖信号,低电平有效 |
/*================================================*\
Filename ﹕key_filter.v
Author ﹕Adolph
Description ﹕计数器实现按键消抖
Called by ﹕key_top.v
Revision History ﹕ 2022-5-9
Revision 1.0
Email﹕ [email protected]
\*================================================*/
module key_filter(
input clk ,//system clock ,50MHz
input rst_n ,//system reset ,low valid
input key_in ,//key input,high valid
output key_flag //filter out,high valid
);
parameter CNT_20MS = 20'd100_0000;//20ms counter value
//20ms counter
reg [19:0] cnt_delay;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt_delay <= 20'd0;
else if(!key_in)begin
if(cnt_delay >= CNT_20MS)
cnt_delay <= cnt_delay;//just valid once
else
cnt_delay <= cnt_delay + 20'd1;
end
else
cnt_delay <= 20'd0;
end
assign key_flag = (cnt_delay == CNT_20MS - 20'd1);
endmodule
/*================================================*\
Filename ﹕tb_key_filter.v
Author ﹕Adolph
Description ﹕按键消抖测试文件
Called by ﹕No file
Revision History ﹕ 2022-5-9
Revision 1.0
Email﹕ [email protected]
\*================================================*/
`timescale 1ns/1ns
module tb_key_filter();
reg tb_clk;
reg tb_rst_n;
reg tb_key;
wire tb_flag;
parameter clk_period = 20;
defparam U_key_filter.CNT_20MS = 1400; //
key_filter U_key_filter(
/*input */.clk (tb_clk ),//system clock ,50MHz
/*input */.rst_n (tb_rst_n ),//system reset ,low valid
/*input */.key_in (tb_key ),//key input,high valid
/*output*/.key_flag (tb_flag ) //filter out,high valid
);
initial tb_clk = 1'b0;
always #(clk_period / 2) tb_clk = ~tb_clk;
integer i; //整形信号,32-bit
initial begin
tb_rst_n = 1'b0;
tb_key = 1'b1;
#(clk_period * 20 + 3);
tb_rst_n = 1'b1;
press_key; //调用任务
#(clk_period * 4000);
press_key;
$stop;
end
task press_key;
begin
tb_key = 1'b1;
repeat(48)begin //模拟前抖动
i = {$random} % 300;
#(i * clk_period); tb_key = ~tb_key;
end
tb_key = 1'b0;
#(1500 * clk_period);
repeat(55)begin //模拟后抖动
i = {$random} % 300;
#(i * clk_period); tb_key = ~tb_key;
end
tb_key = 1'b1;
#(3000 * clk_period);
end
endtask
endmodule
信号名 | 端口类型 | 数据位宽 | 信号说明 |
---|---|---|---|
Clk | i | 1 | 输入时钟信号,50MHz |
Rst_n | i | 1 | 输入复位信号,低电平有效 |
key1 | i | 1 | 输入按键消抖信号,高电平有效 |
key2 | i | 1 | 输入按键消抖信号,高电平有效 |
led_o | o | 8 | 输出按键消抖信号,低电平有效 |
/*================================================*\
Filename ﹕led_driver.v
Author ﹕Adolph
Description ﹕按键消抖——LED驱动部分
Called by ﹕key_top.v
Revision History ﹕ 2022-5-10
Revision 1.0
Email﹕ [email protected]
\*================================================*/
module led_driver(
input wire Clk , //system clock 50MHz
input wire Rst_n , //reset ,low valid
input wire key_in1 , //消抖后的按键信号输入
input wire key_in2 , //消抖后的按键信号输入
output reg [07:00] led_o //LED信号输出
);
//Parameter Declarations
parameter CNT_MAX = 24'd1000_0000; //50MHz主频下200ms计数值
//Interrnal wire/reg declarations
reg [23:00] cnt ; //200ms Counter
wire add_cnt ; //Counter Enable
wire end_cnt ; //Counter Reset
reg flag_1 ; //key_in1 ctrl blink led
reg [07:00] r1_led ; //key_in1 ctrl blink led
reg flag_2 ; //key_in2 ctrl water led
reg [07:00] r2_led ; //key_in2 ctrl water led
//Logic Description
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
cnt <= 24'd0;
end
else if(add_cnt)begin
if(end_cnt)begin
cnt <= 24'd0;
end
else begin
cnt <= cnt + 1'b1;
end
end
else begin
cnt <= 24'd0;
end
end
assign add_cnt = flag_1 || flag_2;
assign end_cnt = add_cnt && cnt >= CNT_MAX - 24'd1;
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
flag_1 <= 1'b0;
flag_2 <= 1'b0;
end
else begin
case({key_in1,key_in2})
2'b10:begin flag_1 <= 1'b1; flag_2 <= 1'b0; end
2'b01:begin flag_1 <= 1'b0; flag_2 <= 1'b1; end
default: ;//当两个按键同时按下,则维持不变
endcase
end
end //always end
//闪烁
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
r1_led <= 8'd0;
end
else if(key_in1)begin
r1_led <= 8'd255;
end
else if(end_cnt)begin
r1_led <= ~r1_led;
end
else begin
r1_led <= r1_led;
end
end //always end
//流水
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
r2_led <= 8'd1;
end
else if(key_in2)begin
r2_led <= 8'd1;
end
else if(end_cnt)begin
r2_led <= {r2_led[6:0],r2_led[7]};
end
else begin
r2_led <= r2_led;
end
end //always end
always @(posedge Clk or negedge Rst_n)begin
if(!Rst_n)begin
led_o <= 8'd0;
end
else if(flag_1)begin
led_o <= r1_led;
end
else if(flag_2)begin
led_o <= r2_led;
end
else begin
led_o <= led_o;
end
end //always end
endmodule
/*================================================*\
Filename ﹕tb_led_driver.v
Author ﹕Adolph
Description ﹕按键消抖——LED驱动部分 测试文件
Called by ﹕No file
Revision History ﹕ 2022-5-10
Revision 1.0
Email﹕ [email protected]
\*================================================*/
`timescale 1ns/1ns //仿真系统时间尺度定义
`define clk_period 20 //时钟周期参数宏定义
module tb_led_driver();
//参数重定义
defparam U_led_driver.CNT_MAX = 200;
//激励信号定义
reg Clk ;
reg Rst_n ;
reg key_in1 ;
reg key_in2 ;
//响应信号定义
wire [07:00] led_o ; //
//实例化
led_driver U_led_driver(
/*input wire */.Clk (Clk ), //system clock 50MHz
/*input wire */.Rst_n (Rst_n ), //reset ,low valid
/*input wire */.key_in1 (key_in1), //消抖后的按键信号输入
/*input wire */.key_in2 (key_in2), //消抖后的按键信号输入
/*output reg [07:00]*/.led_o (led_o ) //LED信号输出
);
//产生时钟
initial Clk = 1'b0;
always #(`clk_period / 2) Clk = ~Clk;
//产生激励
initial begin
Rst_n = 1'b0;
key_in1 = 1'b0;
key_in2 = 1'b0;
#(`clk_period * 10 + 3);
Rst_n = 1'b1;
#(`clk_period * 10);
key_in1 = 1'b1; //闪烁灯
#`clk_period;
key_in1 = 1'b0;
#(10 * `clk_period * U_led_driver.CNT_MAX);
key_in2 = 1'b1; //流水灯
#`clk_period;
key_in2 = 1'b0;
#(10 * `clk_period * U_led_driver.CNT_MAX);
key_in1 = 1'b1; //闪烁灯
#`clk_period;
key_in1 = 1'b0;
#(10 * `clk_period * U_led_driver.CNT_MAX);
key_in2 = 1'b1; //流水灯
#`clk_period;
key_in2 = 1'b0;
#(10 * `clk_period * U_led_driver.CNT_MAX);
$stop(2);
end
endmodule
顶层文件在此不作讲解,根据下列RTL视图,相信读者可以很轻易的完成相应代码设计
基于前面的步骤的结束,我们开始上板验证
在quartus的Pin planner 中进行引脚绑定
后面补上
然后进行全编译,待到全编译通过后,连接好开发板,电源线和下载都要连接好,然后打开电源
下载编程文件
如果是第一次使用开发板的童鞋,参看这里更新驱动,切记,前提条件是开发板正确和PC连接,并且已经通电!!!
驱动更新成功后,点击“Start”进行编程,右上角的Progress为下载进度,成功后会有“100% Successful”提示字样,然后在开发板上可以看到相应的效果——流水灯。
到这里基本上就结束了,给大家提几点在学习过程中可能会出现的错误
1、我们的文件名和Module后面的模块名要保持一致,不然在仿真的时候会找不到文件的
2、reg和wire信号的使用规则一定要分清楚
3、任何信号在使用之前一定要先声明
...
然后大家可以在提供的基础代码上进行创新,比如:
1、使用状态机实现按键消抖
2、实现一个文件同时进行多位按键消抖
3、修改LED驱动部分显示效果
...