本章介绍如果读取按键的操作,来演示PL部分的IO输入功能
本文在 vivado2018.3版本上 演示, 其他版本请自行研究
1. 硬件介绍
先看原理图,由原理图可以看出板子上接了两个按键,并且默认是通过上拉电阻上拉到3.3V的,也就是当按键没有按下的时候 按键KEY 的信号脚是3.3V ,当按键按下后,该信号脚被拉低到GND也就是0V 同样也能看出 两个按键分别连接到了 FPGA芯片的 G15和F16管脚上
为了演示两个按键的功能,这里再引入两个指示灯,指示灯之前3节的例子里测试过,将LED的驱动脚拉高,指示灯亮,拉低指示灯熄灭,指示灯分别接在主芯片的 D18和F20脚
2.代码的编写
完整的工程创建前面已经详细图文描写了,这里略过,直接介绍程序。
正常的按键操作,在按下瞬间 电平会产生抖动,虽说只是一个按下的操作,但是实际可能电压跳变了无数次,所以正常的按键代码是需要消抖的,消抖会稍微麻烦点 这里放到本文的最后再介绍
2.1 为了方便理解输入输出的概念这里先简单写一个不带消抖的按键,去控制LED灯的程序(一般芯片间的通讯是不需要做消抖功能的,只有按键才需要做消抖)
首先 原先 LED 的驱动程序,我们用的是output ,output代表输出,所以这里我们的按键要设置成input ,代表输入
完整代码比较简单 就不详细描述了,详细见下面
程序是通过时钟信号同步的时序逻辑的方式来运行,其中LED灯的变化 和 KEY的变化仅在clk 的上升沿完成,受时钟影响
`timescale 1ns / 1ps module KEY_TEST( input clk, output LED1, output LED2, input KEY1, input KEY2 ); reg LED1_r=0; reg LED2_r=0; always@(posedge clk)begin if(KEY1==0)LED1_r<=1'b1; else LED1_r<=1'B0; if(KEY2==0)LED2_r<=1'b1; else LED2_r<=1'B0; end assign LED1=LED1_r; assign LED2=LED2_r; endmodule
约束文件如下
set_property PACKAGE_PIN D18 [get_ports LED1] set_property PACKAGE_PIN F20 [get_ports LED2] set_property PACKAGE_PIN G15 [get_ports KEY1] set_property PACKAGE_PIN F16 [get_ports KEY2] set_property PACKAGE_PIN K18 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports LED1] set_property IOSTANDARD LVCMOS33 [get_ports LED2] set_property IOSTANDARD LVCMOS33 [get_ports KEY1] set_property IOSTANDARD LVCMOS33 [get_ports KEY2] set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {clk_IBUF}]
保存后进行编译和综合,最后下载到板子上进行运行,可以看到当按下左边的按键后,左边的灯被点亮,当按下右边的按键后,右边的灯被点亮了
到这里简单的输入功能介绍完毕了。
完整工程:
也可以是组合逻辑(不推荐使用) ,LED灯的状态仅和按键有关和时钟等没有关系,这种方式就相当于FPGA内部拉了个导线并串了个非门到LED和按键之间
LED1=(KEY1==0)?1’b1:1’b0;
这种方式 也不需要创建LED对应的 REG寄存器(LED_r)缺点是各个信号间不同步 不适用于大型系统中
2.2 以上介绍的方法不带按键消抖,适用于不同芯片间传输,和简单的按键应用, 如果要让系统中按键的稳定性更大,那需要在系统中额外增加按键消抖功能
下面来介绍按键消抖
按键消抖的原由:
通常在一个按键开关在闭合时由于机械特性的原因,按键不会立马接通,在断开时也不会一下子断开,而是在闭合及断开的瞬间均伴随有多次抖动,抖动的产生会对按键的信号本身产生干扰。
一般正常按键 抖动过程是小于20ms的, 而正常的一次按键操作,即使是短按时间也都是超过50ms的。 所以我们的按键消抖时间可以以20ms作为界限。
按键消抖的思路:
a.初始状态 计数器为0,跳转到状态b
b.检测到按键按下(即电平为0)计数器开始计时 ,跳转到状态c
c.计数器计数到20ms前不停的检测,检测到电压值不为0,即认为是抖动信号,则系统回到a初始状态,如果20ms内电压值时钟为0,则认为按键有效,跳转到状态d
d.按键有效信号置位 , 当检测到按键电平不为0时, 按键有效信号清0
(以上是判断按键按下的消抖处理思路, 如果释放按键不参与决策,则不需要做按键释放消抖,否则 按键释放也需要做消抖处理)。
接下来开始写消抖的程序:
`timescale 1ns / 1ps module KEY_TEST( input clk, output LED, input KEY ); reg [1:0]debounce_mode=2'd0; reg [19:0]debounce_count=20'd0; reg [1:0]KEY_r=0; always@(posedge clk)begin KEY_r[1]<= KEY_r[0]; KEY_r[0]<= KEY; end wire KEY_NEGEDGE=(KEY_r[1]&~KEY_r[0])?1'b1:1'b0; reg KEY_value=0; always@(posedge clk)begin if(debounce_mode==0)begin debounce_count<=20'd0; debounce_mode<=2'd1; KEY_value<=0; end else if(debounce_mode==1)begin if(KEY_NEGEDGE==1)begin debounce_count<=20'd0; debounce_mode<=2'd2; end end else if(debounce_mode==2)begin if(debounce_count>=20'd1_000_000)debounce_mode<=2'd3; else begin if(KEY==1)debounce_mode<=2'd0; debounce_count<=debounce_count+1'b1; end end else begin if(KEY==1)debounce_mode<=2'd0; KEY_value<=1'b1; end end
代码简单解读
代码里用了两位寄存器来移位KEY的按键状态,然后通过对比前后的按键变化来找到按键的下降沿(上个时刻是高电平,下个时刻为低电平,代表是下降沿,即(KEY_r[1]&~KEY_r[0])),当系统为下降沿的那个时刻KEY_NEGEDGE输出高电平
reg [1:0]KEY_r=0; always@(posedge clk)begin KEY_r[1]<= KEY_r[0]; KEY_r[0]<= KEY; end wire KEY_NEGEDGE=(KEY_r[1]&~KEY_r[0])?1'b1:1'b0;
debounce_mode代表当前的状态机所处的状态,其中的0,1,2,3分别代表前面写的按键消抖思路的a,b,c,d4个过程步骤,这里可以对应着看。 0代表初始化,1模式检测下降沿,如果出现下降沿进入到模式2,2开始计数并检测按键状态是否变成1,如果是则认为触发的是抖动干扰,则重新回到模式0等待下一个下降沿,如果计数器超过20ms按键时钟没变化,则认为按键有效进入模式3,模式3下改变按键的输出值KEY_value
debounce_count是一个20位的计数器,从按键的下降沿开始 计数,直到20ms停止(20ms 对应50mhz时钟的第1000000个脉冲)
KEY_value代表最终消抖后的按键值,KEY_value=1代表按键有效 并且一直持续。 这里将按键的结果通过assign LED=KEY_value; 将KEY_value寄存器的值直接映射到LED指示灯上,可以更好的看到按键按下的结果。
增加约束文件:
set_property PACKAGE_PIN D18 [get_ports LED] set_property PACKAGE_PIN G15 [get_ports KEY] set_property PACKAGE_PIN K18 [get_ports clk] set_property IOSTANDARD LVCMOS33 [get_ports LED] set_property IOSTANDARD LVCMOS33 [get_ports KEY] set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {clk_IBUF}]
这里只演示一个按键,如果要做多个按键,可以用模块例化的方式 设计多个按键,这里请自行尝试
如果需要增加按键释放消抖,原理上相通,可自行研究
以下是带消抖功能的完整工程: