001-基于FPGA的外部数字键盘输入


前言

最近闲来没事儿,想练习一下FPGA,于是衍生出了做一个外部按键输入的想法。做起来不难,但是对于新手而言是一个很好的练习方法。首先能够学会多重实例化嵌套的思路,其次能够通过testbench验证自己电路的逻辑状态,通过modelsim里面反馈出的各种时序图从而判断如何去修改自己的代码使得达到理想的结果。verilog跟c很大的不同在于是描述硬件的一款语言,在写代码的时候一定要忘记c语言过程中的一些思路,去适应verilog的硬件描述。

实现的具体功能为:通过外部按键按下判断当时输入的按键所代表的数字,按下四次以后得到一个四位数的数字并将其显示在数码管上,在本项目程序中先开始输入的是个位,依次到千位。若想改变成千位先输入可通过改变状态机里面的赋值语句即可实现要求。


一、前期准备工作

必不可少的硬件外部环境,如下图所示:

在这里插入图片描述
按键就采用最普通的轻触按键,一脚连接地,一脚连接到控制的IO口。这里设置了10个IO口,分别代表数字0-9。记住按键的地和FPGA的地一定要连接至一起。建议每一个按键的两端焊接一个104的电容,避免高频抖动带来的影响。

一款FPGA系列的开发板,此处博主选用的是野火的征途mini板,型号为EP4CE10F17C8。用到的板载资源为key_filter和dynamic_seg_595动态数码管例程。

二、软件思路

1.设计流程

我们将整体分为三个流程

1、检测外部按键输入,配置按键滤波器
由于按键按下的时候,会产生一定的抖动,因此要对抖动在软件中进行处理,此处选择20ms的定时作为抖动信号的产生。具体原理是,当我们检测到有按键按下以后,20ms计时器开始计时,计时到20ms后再次判断按键是否按下,若还有按键按下则代表输出一次按键有效信号,用key_flag表示。key_flag将作为按键状态机的一个判断逻辑在后面的状态机.v文件中进行处理。同时,当出现按键有效信号的时候,检测的哪个按键按下就输出key_flag_status,代表当前采集到的按键所对应的数值。因此这一过程,将输出两个有效信号,一个是key_flag(按键触发信号),一个是key_flag_status(按键数值信号)。

2、状态机切换输入状态,实现个位到千位的赋值
状态机主要是切换赋值状态。在状态机的.v文件中能得到按键传输过来的key_flag信号和key_flag_status信号。我们将状态机分为五个状态,分别是IDLE、ONE、TWO、THREE、FOUR五种状态。其中最开始处于IDLE状态,当检测到key_flag按下后,判断如果当时的信号是5’d10,因为在我们的设置中,5‘d10这个按钮是确定按钮,只有按下这个按键的同时,输出的data数据(也即我们要显示的数据)才能够重新归零然后继续进行赋值。状态机通过判断key_flag作为切换状态,依次循环。在ONE状态切换到TWO状态时进行个位赋值,依次类推,最终FOUR状态切换至IDLE状态时将完成千位的赋值。这样最终得到的data数据就是我们想要显示的四位数据。想要改变的话再按下确定键进行下一次的赋值即可。

3、数码管的显示
此处数码管的显示参考野火单片机开发板的动态数码管例程,只需要在data_gen和最终的顶层文件中添加一个data数据即可。因为在第二步状态机内部我们将输出一个data数据,这个data数据将被赋值在动态数码管的data_gen.v文件中,最终数码管将实时显示data的大小。故当我们从个位输出到千位时,数码管会实时显示数据的大小。

2.代码解读

代码如下:

1、key_filter代码

`timescale  1ns/1ns

// Author        : EmbedFire
// Create Date   : 2019/03/15
// Module Name   : key_filter
// Project Name  : key_filter
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : 按键消抖模块
//
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com



module  key_filter
#(
    parameter CNT_MAX = 20'd999_999 //计数器计数最大值
)
(
    input   wire         sys_clk     ,   //系统时钟50Mhz
    input   wire         sys_rst_n   ,   //全局复位
    input   wire [10:0]   key_in      ,   //按键输入信号

    output  reg          key_flag    ,    //key_flag为1时表示消抖后检测到按键被按下
    output  reg  [4:0]   key_flag_status        //key_flag为1时表示消抖后检测到按键被按下
                                    //key_flag为0时表示没有检测到按键被按下
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg   define
reg     [19:0]  cnt_20ms    ;   //计数器
reg             key_flag1   ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'b0;
    else    if(key_in == 11'b11_111_111_111)
        cnt_20ms <= 20'b0;
    else    if(cnt_20ms == CNT_MAX&&key_in != 11'b11_111_111_111)
        begin
        case(key_in)
        11'b11_111_111_110:   key_flag_status    <=  5'd0;  
        11'b11_111_111_101:   key_flag_status    <=  5'd1;  
        11'b11_111_111_011:   key_flag_status    <=  5'd2;  
        11'b11_111_110_111:   key_flag_status    <=  5'd3;  
        11'b11_111_101_111:   key_flag_status    <=  5'd4;  
        11'b11_111_011_111:   key_flag_status    <=  5'd5;
        11'b11_110_111_111:   key_flag_status    <=  5'd6;  
        11'b11_101_111_111:   key_flag_status    <=  5'd7;  
        11'b11_011_111_111:   key_flag_status    <=  5'd8;  
        11'b10_111_111_111:   key_flag_status    <=  5'd9;
        11'b01_111_111_111:   key_flag_status    <=  5'd10;          
        default:key_flag_status    <=  5'd11        ;
        endcase
        cnt_20ms <= cnt_20ms;
        end
    else
        cnt_20ms <= cnt_20ms + 1'b1;

//key_flag:当计数满20ms后产生按键有效标志位
//且key_flag在999_999时拉高,维持一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
        key_flag <= 1'b0;
        key_flag1<= 1'b0;
        end
    else    if(cnt_20ms == CNT_MAX - 1'b1)
        begin
        key_flag1 <= 1'b1;
        key_flag  <= key_flag1;
        end
    else
        begin
        key_flag1 <= 1'b0;
        key_flag  <= key_flag1;
        end
endmodule

2、状态机代码

`timescale  1ns/1ns

// Author        : EmbedFire
// Create Date   : 2019/05/12
// Module Name   : simple_fsm
// Project Name  : simple_fsm
// Target Devices: Altera EP4CE10F17C8
// Tool Versions : Quartus 13.0
// Description   : 简单状态机
//
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com


module  simple_fsm
(
    input   wire        sys_clk     ,   //系统时钟50MHz
    input   wire        sys_rst_n   ,   //全局复位
    input   wire [10:0] key_in  ,


    output  reg  [19:0] data,         //输出最终的数据
    output  reg  [3:0]  led
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//parameter define
//只有五种状态,使用独热码
parameter   IDLE = 5'b00_001;
parameter   ONE  = 5'b00_010;
parameter   TWO  = 5'b00_100;
parameter   THREE  = 5'b01_000;
parameter   FOUR  = 5'b10_000;
wire            key_flag    ;   //按键使能位
wire    [4:0]   key_flag_status;//按键按下状态位
//reg   define
reg     [4:0]   state = IDLE;

//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//第一段状态机,描述当前状态state如何根据输入跳转到下一状态
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        begin
        state <= IDLE;  //任何情况下只要按复位就回到初始状态
        data  <= 1'b0;
        end
    else    case(state)
                IDLE    :   if(key_flag == 1'b1&& key_flag_status == 5'd0)//判断输入情况 
                                begin
                                state <= ONE;
                                data  <= 1'b0;
                                end
                            else
                                begin
                                state <= IDLE;
                                data  <= data;
                                end
                ONE     :   if(key_flag == 1'b1&& key_flag_status != 5'd11)
                                begin
                                state <= TWO;
                                data  <= data+key_flag_status*1;
                                end
                            else
                                begin
                                state <= ONE;
                                data  <= data;
                                end
                TWO     :   if(key_flag == 1'b1&& key_flag_status != 5'd11)
                                begin
                                state <= THREE;
                                data  <= data+key_flag_status*10;
                                end
                            else
                                begin
                                state <= TWO;
                                data  <= data;
                                end
                THREE     :   if(key_flag == 1'b1&& key_flag_status != 5'd11)
                                begin
                                state <= FOUR;
                                data  <= data+key_flag_status*100;
                                end
                            else
                                begin
                                state <= THREE;
                                data  <= data;
                                end
                FOUR      :   if(key_flag == 1'b1&& key_flag_status != 5'd11)
                                begin
                                state <= IDLE;
                                data  <= data+key_flag_status*1000;
                                end
                            else
                                begin
                                state <= FOUR;
                                data  <= data;
                                end
                //如果状态机跳转到编码的状态之外也回到初始状态
                default :       state <= IDLE;
            endcase
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        led <= 4'b1_111;  //任何情况下只要按复位就回到初始状态
    else if(state== IDLE)
        begin
            if(data<20'd1_000)led <= 4'b1_110;
            else if(data>=20'd1_000&&data<20'd2_000)led<=4'b1_101;
            else if(data>=20'd2_000&&data<20'd3_000)led<=4'b1_011;
            else  led<=4'b0_111;
        end
    else
        led <=4'b1_111;

//-------------key_filter_inst--------------            
key_filter    
#(          
    .CNT_MAX     (20'd999_999)
)
key_filter_inst
(
    .sys_clk     (sys_clk  ),   //系统时钟,频率50MHz
    .sys_rst_n   (sys_rst_n),   //复位信号,低电平有效
    .key_in      (key_in   ),
    
    .key_flag        (key_flag     ),   //数码管要显示的值
    .key_flag_status (key_flag_status)   //小数点显示,高电平有效
);

endmodule

3、数码管的代码
代码参考的就是野火例程动态数码管,在其中只需要更改date_gen中的date传输方式即可,如下面所示:

`timescale  1ns/1ns

// Author        : EmbedFire
// Create Date   : 2019/07/10
// Module Name   : data_gen
// Project Name  : top_seg_595
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : 生成数码管显示数据
//
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途Pro_FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com


module  data_gen
(
    input   wire            sys_clk     ,   //系统时钟,频率50MHz
    input   wire            sys_rst_n   ,   //复位信号,低电平有效
    input   wire    [10:0]  key_in      ,

    output  reg     [19:0]  data        ,   //数码管要显示的值
    output  reg     [3:0]   led         ,
    output  wire    [5:0]   point       ,   //小数点显示,高电平有效
    output  reg             seg_en      ,   //数码管使能信号,高电平有效
    output  wire            sign            //符号位,高电平显示负号
);

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//reg   define
reg     [22:0]  cnt_100ms   ;   //100ms计数器
reg             cnt_flag    ;   //100ms标志信号
wire    [19:0]  data1       ;
wire    [3:0]   led1        ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//

//不显示小数点以及负数
assign  point   =   6'b000_000;
assign  sign    =   1'b0;



//数码管使能信号给高即可
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        seg_en  <=  1'b0;
    else
        begin
        seg_en  <=  1'b1;
        data    <=  data1;
        led     <=  led1  ;
        end
simple_fsm    simple_fsm_inst
(
    .sys_clk     (sys_clk  ),   //系统时钟,频率50MHz
    .sys_rst_n   (sys_rst_n),   //复位信号,低电平有效
    .key_in      (key_in   ),
    
    .data        (data1    ),   //数码管要显示的值
    .led         (led1      )
);
endmodule

最终也只需在top的文件内部调用data_gen文件,如下图所示:

`timescale  1ns/1ns
///
// Author        : EmbedFire
// Create Date   : 2019/07/10
// Module Name   : top_seg_595
// Project Name  : top_seg_595
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description   : 数码管动态显示,顶层模块
//
// Revision      : V1.0
// Additional Comments:
// 
// 实验平台: 野火_征途系列FPGA开发板
// 公司    : http://www.embedfire.com
// 论坛    : http://www.firebbs.cn
// 淘宝    : https://fire-stm32.taobao.com


module  top_seg_595
(
    input   wire            sys_clk     ,   //系统时钟,频率50MHz
    input   wire            sys_rst_n   ,   //复位信号,低电平有效
    input   wire   [10:0]   key_in      ,   //按键输入信号

    output  wire            stcp        ,   //输出数据存储寄时钟
    output  wire            shcp        ,   //移位寄存器的时钟输入
    output  wire            ds          ,   //串行数据输入
    output  wire   [3:0]    led         ,
    output  wire            oe              //输出使能信号
);

//********************************************************************//
//******************** Parameter And Internal Signal *****************//
//********************************************************************//
//wire  define
wire    [19:0]  data    ;   //数码管要显示的值
wire    [5:0]   point   ;   //小数点显示,高电平有效top_seg_595
wire            seg_en  ;   //数码管使能信号,高电平有效
wire            sign    ;   //符号位,高电平显示负号
//********************************************************************//
//**************************** Main Code *****************************//
//********************************************************************//
//-------------data_gen_inst--------------
data_gen    data_gen_inst
(
    .sys_clk     (sys_clk  ),   //系统时钟,频率50MHz
    .sys_rst_n   (sys_rst_n),   //复位信号,低电平有效
    .key_in      (key_in   ),   //按键输入信号
    
    .data        (data     ),   //数码管要显示的值
    .led         (led      ),
    .point       (point    ),   //小数点显示,高电平有效
    .seg_en      (seg_en   ),   //数码管使能信号,高电平有效
    .sign        (sign     )    //符号位,高电平显示负号
);

//-------------seg7_dynamic_inst--------------
seg_595_dynamic    seg_595_dynamic_inst
(
    .sys_clk    (sys_clk   ),   //系统时钟,频率50MHz
    .sys_rst_n  (sys_rst_n ),   //复位信号,低有效
    .data       (data      ),   //数码管要显示的值
    .point      (point     ),   //小数点显示,高电平有效
    .seg_en     (seg_en    ),   //数码管使能信号,高电平有效
    .sign       (sign      ),   //符号位,高电平显示负号

    .stcp       (stcp      ),   //输出数据存储寄时钟
    .shcp       (shcp      ),   //移位寄存器的时钟输入
    .ds         (ds        ),   //串行数据输入
    .oe         (oe        )    //输出使能信号
);
endmodule

至此就实现了代码的实现条件


3.仿真波形

在这里插入图片描述
如图所示,依次在进入状态位的时候对输出的data进行了赋值
在这里插入图片描述
单单key_filter最终综合出的RTL视图

总结

本项目实现了外部按键输入赋值并最终通过动态数码管实时显示的效果,源码和文件将在不久后上传。

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值