1、实验平台
软件:PC、Quartus Prime 18.1、Modelsim 10.5b
硬件:Altera FPGA开发板(EP4CE6E22F17C8)
2、实验目的
- 1、通过延时方式实现按键消抖
- 2、状态机demo熟悉
- 3、状态机实现按键消抖(多位宽)
2.1、实验要求
使用经过消除抖动后的按键信号控制LED呈现两种不同的状态
a) 流水灯
b) 闪烁
3、实验流程
3.1、实验原理
根据开发板的原理图,可得到以下资料
独立按键
这类机械按键由于弹簧片的存在,默认处于高电平,按下和释放时都会有一定的抖动时间,在抖动时,其电平状态为不定值,故需要在稳定时再读取信号,通常抖动时间在5~10ms,故设置一个20ms的计数器,用于计时低电平时长。
LED:
根据硬件原理图所示,8颗发光二极管,所有的阳极都接通3.3V的正电压,也即————高电平,所以如果我们想要
发光二极管导通的话,需要在阴极接通低电平,就可以让LED亮起来。
3.2、系统架构
根据系统要求,可以得到以下框架分布
3.3、功能模块划分
根据系统构建,可得到以下模块
3.3.1、按键消抖模块
模块框图
信号定义
信号名 | 端口类型 | 数据位宽 | 信号说明 |
---|
Clk | i | 1 | 输入时钟信号,50MHz | Rst_n | i | 1 | 输入复位信号,低电平有效 | key | i | 1 | 输入按键信号,低电平有效 | flag | o | 1 | 输出按键消抖信号,低电平有效 |
时序信号图
设计文件
module key_filter(
input clk ,
input rst_n ,
input key_in ,
output key_flag
);
parameter CNT_20MS = 20'd100_0000;
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;
else
cnt_delay <= cnt_delay + 20'd1;
end
else
cnt_delay <= 20'd0;
end
assign key_flag = (cnt_delay == CNT_20MS - 20'd1);
endmodule
仿真文件
`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(
.clk (tb_clk ),
.rst_n (tb_rst_n ),
.key_in (tb_key ),
.key_flag (tb_flag )
);
initial tb_clk = 1'b0;
always #(clk_period / 2) tb_clk = ~tb_clk;
integer i;
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
仿真图
3.3.2、LED驱动模块
模块框图
信号定义
信号名 | 端口类型 | 数据位宽 | 信号说明 |
---|
Clk | i | 1 | 输入时钟信号,50MHz | Rst_n | i | 1 | 输入复位信号,低电平有效 | key1 | i | 1 | 输入按键消抖信号,高电平有效 | key2 | i | 1 | 输入按键消抖信号,高电平有效 | led_o | o | 8 | 输出按键消抖信号,低电平有效 |
信号时序图
设计文件
module led_driver(
input wire Clk ,
input wire Rst_n ,
input wire key_in1 ,
input wire key_in2 ,
output reg [07:00] led_o
);
parameter CNT_MAX = 24'd1000_0000;
reg [23:00] cnt ;
wire add_cnt ;
wire end_cnt ;
reg flag_1 ;
reg [07:00] r1_led ;
reg flag_2 ;
reg [07:00] r2_led ;
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 @(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 @(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 @(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
endmodule
仿真文件
`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(
.Clk (Clk ),
.Rst_n (Rst_n ),
.key_in1 (key_in1),
.key_in2 (key_in2),
.led_o (led_o )
);
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
仿真图
3.3.3、顶层文件
顶层文件在此不作讲解,根据下列RTL视图,相信读者可以很轻易的完成相应代码设计
RTL视图
3.4、上板验证
基于前面的步骤的结束,我们开始上板验证
在quartus的Pin planner 中进行引脚绑定
后面补上
然后进行全编译,待到全编译通过后,连接好开发板,电源线和下载都要连接好,然后打开电源
下载编程文件
如果是第一次使用开发板的童鞋,参看这里更新驱动,切记,前提条件是开发板正确和PC连接,并且已经通电!!! 驱动更新成功后,点击“Start”进行编程,右上角的Progress为下载进度,成功后会有“100% Successful”提示字样,然后在开发板上可以看到相应的效果——流水灯。
4、总结
到这里基本上就结束了,给大家提几点在学习过程中可能会出现的错误
1、我们的文件名和Module后面的模块名要保持一致,不然在仿真的时候会找不到文件的
2、reg和wire信号的使用规则一定要分清楚
3、任何信号在使用之前一定要先声明
...
然后大家可以在提供的基础代码上进行创新,比如:
1、使用状态机实现按键消抖
2、实现一个文件同时进行多位按键消抖
3、修改LED驱动部分显示效果
...
|