EBAZ4205 第三个工程的PL部分按键功能演示(IO输入功能)

本章介绍如果读取按键的操作,来演示PL部分的IO输入功能

本文在 vivado2018.3版本上 演示, 其他版本请自行研究

1. 硬件介绍

先看原理图,由原理图可以看出板子上接了五个按键,并且默认是通过上拉电阻上拉到3.3V的,也就是当按键没有按下的时候 按键KEY 的信号脚是3.3V ,当按键按下后,该信号脚被拉低到GND也就是0V 同样也能看出 两个按键分别连接到了 FPGA芯片的 T19 P19 U20 U19 和V20上

为了按键的功能,这里再引入指示灯,转接板上一共有3个指示灯如下图所示,将LED的驱动脚拉高,指示灯亮,拉低指示灯熄灭,指示灯分别接在主芯片的 H18 K17和E19脚

2.代码的编写

完整的工程创建前面已经详细图文描写了,这里略过,直接介绍程序。

正常的按键操作,在按下瞬间 电平会产生抖动,虽说只是一个按下的操作,但是实际可能电压跳变了无数次,所以正常的按键代码是需要消抖的,消抖会稍微麻烦点 这里放到本文的最后再介绍

2.1 为了方便理解输入输出的概念这里先简单写一个不带消抖的按键,去控制LED灯的程序(一般芯片间的通讯是不需要做消抖功能的,只有按键才需要做消抖)

首先 原先 LED 的驱动程序,我们用的是output ,output代表输出,所以这里我们的按键要设置成input ,代表输入

完整代码比较简单 就不详细描述了,详细见下面(功能上 我们按下上中下三个按钮中的任意一个,上中下三个灯中对应的灯被点亮)

程序是通过时钟信号同步的时序逻辑的方式来运行,其中LED灯的变化 和 KEY的变化仅在clk 的上升沿完成,受时钟影响

`timescale 1ns / 1ps
module KEY_TEST(
    input clk,
    output  LED1,
    output  LED2,
    output  LED3,
    input KEY1,
    input KEY2,
    input KEY3
);

reg LED1_r=0;
reg LED2_r=0;
reg LED3_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;
    
    if(KEY3==0)LED3_r<=1'b1;
    else LED3_r<=1'B0;
end

assign LED1=LED1_r;
assign LED2=LED2_r;
assign LED3=LED3_r;

endmodule

约束文件如下

set_property PACKAGE_PIN H18 [get_ports LED1]
set_property PACKAGE_PIN K17 [get_ports LED2]
set_property PACKAGE_PIN E19 [get_ports LED3]
set_property PACKAGE_PIN T19 [get_ports KEY1]
set_property PACKAGE_PIN U20 [get_ports KEY2]
set_property PACKAGE_PIN U19 [get_ports KEY3]
set_property PACKAGE_PIN N18 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports LED1]
set_property IOSTANDARD LVCMOS33 [get_ports LED2]
set_property IOSTANDARD LVCMOS33 [get_ports LED3]
set_property IOSTANDARD LVCMOS33 [get_ports KEY1]
set_property IOSTANDARD LVCMOS33 [get_ports KEY2]
set_property IOSTANDARD LVCMOS33 [get_ports KEY3]
set_property IOSTANDARD LVCMOS33 [get_ports clk]

保存后进行编译和综合,最后下载到板子上进行运行,可以看到当按下左边的按键后,左边的灯被点亮,当按下右边的按键后,右边的灯被点亮了

到这里简单的输入功能介绍完毕了。

完整工程:

也可以是组合逻辑(不推荐使用) ,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 H18 [get_ports LED]
set_property PACKAGE_PIN T19 [get_ports KEY]
set_property PACKAGE_PIN N18 [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}]

这里只演示一个按键,如果要做多个按键,可以用模块例化的方式 设计多个按键,这里请自行尝试

如果需要增加按键释放消抖,原理上相通,可自行研究

以下是带消抖功能的完整工程:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注