按键简介
按键开关是一种电子开关,属于电子元器件类。常见的按键开关有两种,第一种是轻触式按键开关(简称轻触开关),使用时以向开关的操作方向施加压力使内部电路闭合接通,当撤销压力时开关断开,其内部结构是靠金属弹片受力后发生形变来实现通断的;第二种是自锁按键,自锁按键第一次按下后保持接通,即自锁,第二次按下后,开关断开,同时开关按钮
弹出来。
硬件原理图
LED原理图
LED0 到 LED3 这 4 个发光二极管的阴极分别连到 S8050(NPN 三极管)的集电极上,阳极都与 3.3V 电压相连,三极管的基极分别与 FPGA 相连,这是由于 FPGA 的 IO 口的电压只有 1.5V,电压较低,所以此处连接三极管是为了起到放大电压的作用。这样就可以通过改变三极管的状态来控制 LED 的亮灭。当 FPGA 输出到为高电平时,三极管导通,LED 灯亮;当 FPGA 输出到为低电平时,三极管截止,LED 灯灭。
按键原理图
每个按键都连接了一个 1K 电阻(起到限流的作用,以防止按键被按下时电源直接接地造成电路短路),当按键未按下时,为高电平,按下后,为低电平。
程序功能
使用 4 个按键来控制 4 颗 LED 灯。没有按键被按下时,4 颗 LED 保持常灭;如果按键 KEY0 被按下, LED 灯从低位到高位流水;如果按键 KEY1 被按下,LED 灯从高位到低位流水;如果按键 KEY2 被按下,LED 灯交替闪烁;KEY3 被按下,LED 灯常亮。
系统框图
代码编写
c
`timescale 1ns / 1ns //仿真单位/仿真精度
module key_led #(
//参数列表
parameter COUNT_WIDTH = 25, //内部计数器宽度
parameter COUNT_PERIOD = 25_000_000 //计数器最大周期,决定LED多久变化依次
)
(
input sys_clk, //时钟
input sys_rst_n, //复位
input [3:0] key, //按键
output reg [3:0] led //led
);
//周期计数器,当计数到COUNT_PERIOD-1时LED进行输出状态切换
reg [COUNT_WIDTH-1:0] count;
//LED等控制标志
//流水灯模式时记录当前点亮的是那个LED
//闪烁模式时led_flag[0]用于亮灭控制
reg [1:0] led_flag;
//周期计数器,当计数到COUNT_PERIOD-1时LED进行输出状态切换
always @(posedge sys_clk) begin
if(!sys_rst_n)
count <= 0;
else if(count < (COUNT_PERIOD - 1))
count <= count+ 1;
else
count <= 0;
end
//LED等控制标志
//流水灯模式时记录当前点亮的是那个LED
//闪烁模式时led_flag[0]用于亮灭控制
always @(posedge sys_clk) begin
if(!sys_rst_n)
led_flag <= 2'h0;
else if(count == (COUNT_PERIOD - 1))
led_flag <= led_flag + 2'h1;
end
//根据按键状态和流水灯控制标志控制LED输出
always @(posedge sys_clk) begin
if(!sys_rst_n)
led <= 4'b0000;
else if(key == 4'b1111) //全灭
led <= 4'b0000;
else if(key == 4'b1110) begin //从led0到led3流水
if(led_flag == 2'd0)
led <= 4'b0001;
else if(led_flag == 2'd1)
led <= 4'b0010;
else if(led_flag == 2'd2)
led <= 4'b0100;
else
led <= 4'b1000;
end
else if(key == 4'b1101) begin //从led3到led0流水
if(led_flag == 2'd0)
led <= 4'b1000;
else if(led_flag == 2'd1)
led <= 4'b0100;
else if(led_flag == 2'd2)
led <= 4'b0010;
else
led <= 4'b0001;
end
else if(key == 4'b1011) begin //闪烁
if(led_flag[0] == 1'b0)
led <= 4'b1111;
else
led <= 4'b0000;
end
else if(key == 4'b0111) //全亮
led <= 4'b1111;
end
endmodule
仿真验证激励程序
c
`timescale 1ns/1ns //仿真的单位/仿真的精度
module tb_key_led();
reg sys_clk; //时钟
reg sys_rst_n; //复位
reg [3:0] key; //按键
wire [3:0] led; //led
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
key <= 4'b1111;
#180
sys_rst_n = 1'b1;
#2000
key <= 4'b1110; //按下 KEY0
#2000
key <= 4'b1111; //释放 KEY0
#2000
key <= 4'b1101; //按下 KEY1
#2000
key <= 4'b1111; //释放 KEY1
#2000
key <= 4'b1011; //按下 KEY2
#2000
key <= 4'b1111; //释放 KEY2
#2000
key <= 4'b0111; //按下 KEY3
#2000
key <= 4'b1111; //释放 KEY3
end
always #10 sys_clk = ~sys_clk;
key_led #(
//参数列表
.COUNT_WIDTH(25),
.COUNT_PERIOD(25)
)
tb_key_led_inst(
.sys_clk(sys_clk), //时钟
.sys_rst_n(sys_rst_n), //复位
.key(key), //按键
.led(led) //led
);
endmodule
时序约束
时序约束(Timing Constraints)用来描述设计人员对时序的要求,比如时钟频率,输入输出的延时等,以满足设计的时序要求。对时序约束最简单的理解就是,设计者告诉 EDA 工具设计中所使用的时钟信号的参数(如频率等),然后 EDA 工具按照所要求的时钟参数去优化布局布线,使设计能够在要求的时钟下正常工作
一般情况下对于简单的设计,即使不对工程做时序约束,也不影响最终的功能。但是当设计变得复杂起来,或者输入的时钟频率比较高的时候,如果不添加时序约束,那么就有可能导致功能异常。
添加时序约束
-
对时钟进行约束前,需要先对代码进行综合,即点击 Vivado 左侧"Flow Navigator"窗口下"Run Synthesis"按钮来对代码进行综合
在弹出的窗口中我们直接点击"OK"
-
综合完成后,在弹出的"Synthesis Completed"窗口中我们直接点击"X"即可
-
点击 Vivado 左侧"Flow Navigator"窗口下的"Edit Timing Constraints"按钮了
-
点击"Edit Timing Constraints"按钮后,Vivado 就会打开"Timing Constraints"界面,我们点击该界面下的"+"号按键就可以添加时钟约束了
-
点击"+"号后,弹出的"Create Clock"界面
"Create Clock"界面中各参数定义如下:
Clock name:时钟名称,用于为所创建的时钟约束命名。为了可以一眼看出该时钟约束的约束源,通常情况下其命名与被约束的时钟信号名相同
Source objects:源对象,用于指定被约束的时钟对象
Waveform:波形,用于设置时钟的周期(Period),上升沿(Rise at)、下降沿(Fall at)以及勾选 Add this clock to the existing clock(是否将该时钟添加到现有时钟中)
Command:命令,即通过我们上述的配置后,vivado 自动生成的约束命令,该命令是可以直接复制粘贴到 XDC 文件中使用的
-
点击"Source objects"框后的"..."指定时钟源对象
-
点击"..."后弹出"Specify Clock Source Objects",在"Specify Clock Source Objects"界面中"Find"之上的各个选项主要用于设置筛选条件,因为系统时钟来自于 IO 口,所以"Find names of type(寻找名称类型)"选择"I/O Port",其余选项保持默认。
-
在找到的信号中,我们选择需要约束的系统时钟信号(sys_clk)然后点击"→"按钮将其添加到右侧窗口。
-
之后点击"Set"即可将该信号定义为源对象
-
点击 "Create Clock" 的"OK"即可添加约束并关闭"Create Clock"界面
-
此时的 "Timing Constraints"界面如下
-
点击 Vivado 软件左上方的保存图标来将其保存到 XDC 文件中,如果弹出"Out of Date Design"则点击 "OK"
-
在弹出的"Save Constraints"界面中会默认保存到现有的 XDC 文件中,所以直接点击"OK"
管脚约束
管脚约束方法1:
直接在约束文件中增加如下内容,用于约束IO引脚
c
#IO 引脚约束
#------------------------------系统时钟和复位-----------------------------------
set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS15} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U7 IOSTANDARD LVCMOS15} [get_ports sys_rst_n]
#----------------------------------按键-----------------------------------------
set_property -dict {PACKAGE_PIN T4 IOSTANDARD LVCMOS15} [get_ports {key[0]}]
set_property -dict {PACKAGE_PIN T3 IOSTANDARD LVCMOS15} [get_ports {key[1]}]
set_property -dict {PACKAGE_PIN R6 IOSTANDARD LVCMOS15} [get_ports {key[2]}]
set_property -dict {PACKAGE_PIN T6 IOSTANDARD LVCMOS15} [get_ports {key[3]}]
#-----------------------------------LED-----------------------------------------
set_property -dict {PACKAGE_PIN V9 IOSTANDARD LVCMOS15} [get_ports {led[0]}]
set_property -dict {PACKAGE_PIN Y8 IOSTANDARD LVCMOS15} [get_ports {led[1]}]
set_property -dict {PACKAGE_PIN Y7 IOSTANDARD LVCMOS15} [get_ports {led[2]}]
set_property -dict {PACKAGE_PIN W7 IOSTANDARD LVCMOS15} [get_ports {led[3]}]
管脚约束方法2:
- 需要先对代码进行综合,即点击 Vivado 左侧"Flow Navigator"窗口下"Run Synthesis"按钮来对代码进行综合
- 综合完成后选择 Vivado 左侧"Flow Navigator"窗口下"SYNTHESIS"
- 在右上角的窗口布局中选择"I/O Planning"
- 选中下面的"I/O Ports"页面
Name:工程中顶层端口的名称。
Direction:说明管脚是输入还是输出。
Neg Diff Pair:负差分对,差分信号在 I/O Ports 窗口中只显示在一行里(只会显示 P 端信号,N 端信号显示在 Neg Diff Pair 属性栏中)。
Package Pin:配置管脚封装。
Fixed:每一个端口都有 Fixed 属性,表明该逻辑端口是由用户赋值的。端口必须保持锁定状态,才能避免生成比特流时不会发生错误。
Bank:显示管脚所在的 Bank。
I/O Std:配置管脚的电平标准,常用电平标准有 LVTTL 和 LVCMOS、SSTL、LVDS 与 HSTL 等。
Vcco:选择的管脚的电压值。
Vref:在我们的设计中,硬件上 VREF 引脚悬空。
Drive Strength:驱动强度,默认 12mA。
Slew Type:指上升下降沿的快慢,设置快功耗会高一点,默认设置慢(slow)。
Pull Type:管脚上下拉设置,有上拉、下拉、保持与不设置。
Off-Chip Termination:终端阻抗,默认 50Ω。
IN-TERM:是用于 input 的串联电阻。
提示 :
如果页面被不小心关掉了可以选择右上角的"Reset Layout"
- 在I/O Ports中根据原理图完成引脚约束
- 点击 Vivado 软件左上方的保存图标来将其保存到 XDC 文件中,如果弹出"Out of Date Design"则点击 "OK"
- 在弹出的"Save Constraints"界面中会默认保存到现有的 XDC 文件中,所以直接点击"OK"