注册 登录
电子工程世界-论坛 返回首页 EEWORLD首页 频道 EE大学堂 下载中心 Datasheet 专题
TT小哥的个人空间 https://home.eeworld.com.cn/space-uid-640101.html [收藏] [复制] [分享] [RSS]
日志

基于FPGA的按键实验

已有 3786 次阅读2015-8-10 13:09 |个人分类:FPGA学记| FPGA, 按键消抖, 多功能按键

基于FPGA的按键实验

1设计需求

实现按键的触发驱动编写,可完成按键的单击、双击以及长按方式的触发,反馈信号。

2设计内容 2.1设计原理

通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动。

1 按键抖动示意图

通常抖动的时间长度大约是10ms左右,在控制系统使用按键时,为避免一次按键信号的抖动影响到触发信号的多次触发,大多需要对按键进行消抖处理。按键消抖的处理方法主要采用等待延时的方式对抖动期进行忽略处理。

2.2参考文档

参考文档主要参考《verilog那些事儿-驱动篇I》。

2.3开发环境考虑

本次设计采用Altera公司提供的FPGA开发环境QuartusII14.1,并安装modelsim-Altera作为FPGA开发过程中的逻辑仿真工具。

2.4设计思路

设计分为三个实验分布进行,分别是1、单点击按键与长按键2、单点击按键与双击按键3、单点击按键、长按键与双击按键综合实验。设计采用分模块设计,建模分为控制模块与按键驱动功能模块。

 

3设计的实现 3.1单点击按键与长按键实现

首先编写按键驱动模块,其采用状态机思想,具体状态机状态转移图如图2单点击按键与长按键状态机转移图所示:

2 单点击按键与长按键状态机转移图

按键驱动代码:


module key_function(

                            clk,

                            rst_n,

                           

                            key_in,

                            isSclick,

                            isLclick

                            );

                               

    parameter delay10ms   =   500_000;

    parameter delay2s     =   100_000_000;

    input clk;

    input rst_n;

    input key_in;

 

    output isSclick,isLclick;

    //check key input

    reg F1,F2;

    always@(posedge clk , negedge rst_n )begin

        if(!rst_n)begin

            F1 <= 1'b1;

            F2 <= 1'b1;

        end else

        begin

            {F1 , F2} <= {key_in , F1};

        end

    end

 

    //FSM

    reg [3:0]i;

    reg [27:0]cnt;

    reg isSclick,isLclick;

    reg [1:0]mode;

    wire H2L,L2H;

    always@(posedge clk , negedge rst_n)begin

        if(!rst_n)begin

            i <= 4'd0;

            cnt <= 28'd0;

            isSclick <= 1'b0;

            isLclick <= 1'b0;

            mode <= 2'b00;

        end else

        begin

            case(i)

            4'd0    :   if( H2L ) i <= i +1'b1;

            4'd1    :   if(cnt == delay10ms)begin i <= i + 1'd1;cnt <= 28'd0;end

                        else cnt <= cnt + 1'b1;

            4'd2    :   if( L2H ) begin i <= i + 1'b1; cnt <= 28'd0;mode <= 2'd1;end

                        else    if( {F1 , F2} == 2'b00 && cnt == delay2s)

                                    begin i <= i + 1'b1;cnt <= 28'd0;mode <= 2'd2;end

                                else cnt <= cnt + 1'b1;

            4'd3    :   if( mode == 2'd2 )begin i <= i + 1'b1;isLclick <= 1'b1;end

                        else if( mode == 2'd1 )begin i <= i + 1'b1;isSclick <= 1'b1;end

            4'd4    :   begin i <= i + 1'b1;{ isSclick , isLclick } <= 2'b00;end

            4'd5    :   if( mode == 2'd2 )i <= i + 1'b1;

                        else if( mode == 2'd1 )i <= 4'd8;

            4'd6    :   if( L2H ) i <= i + 1'b1;

            4'd7    :   if(cnt == delay10ms)begin i <= i + 1'd1;cnt <= 28'd0;end

                        else cnt <= cnt + 1'b1;

            4'd8    :   i <= 4'd0;

            default :i <= 4'd0;

            endcase

        end

    end

   

    assign H2L = ~F1 && F2;

    assign L2H = F1 && ~F2;

 

endmodule


1~14行:表示代码驱动的端口定义:

端口名称

属性(位宽)

描述

clk

input(1)

工作时钟输入

rst_n

input(1)

异步复位

key_in

input(1)

机械按键信号输入端口

isSclick

output(1)

消抖后单点击触发信号输出

isLclick

output(1)

消抖后长点击触发信号输出

18~27行:通过两个寄存器F1F2缓存机械按键信号输入端口的电平信号判断高低电平的边沿信号。

29~75行:为具体的状态机实现代码:

i=0:不断检测按键输入信号,等待下降沿触发信号,下降沿发生立刻跳转到下一个状态;

i=1:计数10ms消抖延时,跳转下一个状态;

i=2:等待上升沿信号,并计数,若计数值达到100_000_0002s)则认为为长按键信号,置位模式寄存器相关位跳转到下一个状态;若在2s内等待到上升沿则认为是单击按键信号,置位模式寄存器相关位,跳转到下一状态

i=3:根据模式寄存器置位单点击按键(isSclick)和长按键触发信号(isLclick),跳转到下一个状态

i=4:复位所有按键触发信号,跳转到下一个状态;

i=5:根据模式寄存器判断,如果是长按键则跳转到下一个状态,否则跳转到i=8;

i=6:长按键等待按键上升沿,松手检测,若发生上升沿,跳转到下一个状态;

i=7:计数10ms消抖延时,跳转下一个状态;

i=8:状态机结束跳转到i=0状态。

顶层控制模块,通过按键驱动的反馈信号,完成按键的功能:


module keytop(

                    clk,

                    rst_n,

                   

                    LED,

                    key

                    );

 

    input clk;

    input rst_n;

    input key;

 

    output LED;

   

    wire isSclick;

    wire isLclick;

 

    key_function key2(

                            .clk(clk),

                            .rst_n(rst_n),

                           

                            .key_in(key),

                            .isSclick(isSclick),

                            .isLclick(isLclick)

                            );

                           

    reg LEDr;

    always@(posedge clk , negedge rst_n)begin

        if(!rst_n)begin

            LEDr <= 1'b0;

        end else

        begin

            if(isLclick)

                LEDr <= 1'b0;

            else

                if(isSclick)

                    LEDr <= 1'b1;

        end

    end

   

    assign LED = LEDr;

 

endmodule


控制模块逻辑简单,只是实现了长按键触发信号关闭LED,单击按键打开LED的功能。不做解释。最后编写testbench对代码的逻辑功能进行逻辑测试,由于软件模拟出来按键的抖动比较麻烦,只是简单的测试一下逻辑的功能测试,与实际应用有较大差别。


`timescale 1ns/1ns

 

module testbench;

 

reg clk;

reg rst_n;

wire LED;

reg key_in;

 

initial

begin

    clk <= 1'b1;

    rst_n <= 1'b0;

    key_in <= 1'b1;

    #100 rst_n <= 1'b1;

    #100 key_in <= 1'b0;

    #1200 key_in <= 1'b1;

    #1200 key_in <= 1'b0;

    #240000 key_in <= 1'b1;

    #1200 $stop;

end

 

always #10 clk <= ~clk;

keytop keytop(

                .clk(clk),

                .rst_n(rst_n),

               

                .LED(LED),

                .key(key_in)

                );

 

endmodule

 


10~21行:产生了一个复位信号,以及一个单击按键和一个长按键信号;

23行:产生时钟激励。

(为方便仿真,将按键消抖的延时时间缩短了,在按键驱动代码1011行进行了修改

    parameter delay10ms   =   500_000;

    parameter delay2s     =   100_000_000;

改为:

    parameter delay10ms   =   50;

    parameter delay2s     =   100;

在实际应用应改回原值。)

逻辑仿真图如下图3所示:

3 单点击按键与长按键逻辑仿真图

3.2单点击按键与双击按键实现

首先编写按键驱动模块,其采用状态机思想,具体状态机状态转移图如图4单点击按键与双击按键状态机转移图所示:

4 单点击按键与双击按键状态机转移图

按键驱动代码:


module key_function(

                            clk,

                            rst_n,

                           

                            key_in,

                            isSclick,isDclick

                            );

                               

    parameter delay10ms =   500_000;

    parameter delay90ms =   4_500_000;

    input clk;

    input rst_n;

    input key_in;

 

    output isSclick,isDclick;

 

    //check key input

    reg F1,F2;

    always@(posedge clk , negedge rst_n )begin

        if(!rst_n)begin

            F1 <= 1'b1;

            F2 <= 1'b1;

        end else

        begin

            {F1 , F2} <= {key_in , F1};

        end

    end

 

    //FSM

    reg [3:0]i;

    reg [27:0]cnt;

    reg isSclick,isDclick;

    reg [1:0]mode;

    wire H2L,L2H;

    always@(posedge clk , negedge rst_n)begin

        if(!rst_n)begin

            i <= 4'd0;

            cnt <= 28'd0;

            isSclick <= 1'b0;

            isDclick <= 1'b0;

            mode <= 2'b00;

        end else

        begin

            case(i)

            4'd0    :   if( H2L ) i <= i + 1'b1;

            4'd1    :   if( cnt == delay10ms )begin i <= i + 1'd1;cnt <= 28'd0;end

                        else cnt <= cnt + 1'd1;

            4'd2    :   if( L2H ) i <= i + 1'b1;

            4'd3    :   if( cnt == delay10ms )begin i <= i + 1'd1;cnt <= 28'd0;end

                        else cnt <= cnt + 1'd1;

            4'd4    :   if( H2L ) begin i <= i + 1'd1;cnt <= 28'd0;mode <= 2'd2;end

                        else  if( cnt == delay90ms) begin i <= 4'd8;cnt <= 28'd0;mode <= 2'd1;end

                                else cnt <= cnt + 1'd1;

            4'd5    :   if( cnt == delay10ms )begin i <= i + 1'd1;cnt <= 28'd0;end

                        else cnt <= cnt + 1'd1;

            4'd6    :   if( L2H ) i <= i + 1'b1;

            4'd7    :   if( cnt == delay10ms )begin i <= i + 1'd1;cnt <= 28'd0;end

                        else cnt <= cnt + 1'd1;

            4'd8    :   if( mode == 2'd1) begin i <= i + 1'd1;isSclick <= 1'b1;end

                        else    if( mode == 2'd2) begin i <= i + 1'b1; isDclick <= 1'b1;end

            4'd9    :   begin i <= i + 1'b1; { isSclick , isDclick } <= 2'b00;end

            4'd10   :   i <= 4'd0;

            default :i <= 4'd0;

            endcase

        end

    end

   

    assign H2L = ~F1 && F2;

    assign L2H = F1 && ~F2;

 

endmodule

 


1~14行:表示代码驱动的端口定义:

端口名称

属性(位宽)

描述

clk

input(1)

工作时钟输入

rst_n

input(1)

异步复位

key_in

input(1)

机械按键信号输入端口

isSclick

output(1)

消抖后单点击触发信号输出

isDclick

output(1)

消抖后双击触发信号输出

18~27行:通过两个寄存器F1F2缓存机械按键信号输入端口的电平信号判断高低电平的变化。

29~78行:为具体的状态机实现代码:

i=0:不断检测按键输入信号,等待下降沿触发信号,下降沿发生立刻跳转下一个状态;

i=1:计数10ms消抖延时,跳转下一个状态;

i=2:等待上升沿,跳转到下一个状态

i=3计数10ms消抖延时,跳转下一个状态;

i=4:检测100ms计数内有无再次下降沿触发,若有下降沿触发则认为是双击按键,置位模式寄存器相关位,跳转到下一个状态;若100ms之内无下降沿触发认为是单击按键,置位模式寄存器相关位,跳转到i=8状态;

i=5:计数10ms消抖延时,跳转下一个状态;

i=6:等待上升沿,进入下一个状态;

i=7:计数10ms消抖延时,跳转下一个状态;

i=8:根据模式寄存器,置位单击按键触发信号(isSclick)和双击按键触发信号(isDclick),跳转到下一个状态;

i=9:复位所有按键触发信号,跳转倒下一个状态;

i=10:状态机结束跳转到i=0状态。

顶层控制模块,通过按键驱动的反馈信号,完成按键的功能:


module keytop(

                    clk,

                    rst_n,

                   

                    LED,

                    key

                    );

input clk;

input rst_n;

input key;

 

output LED;

 

wire isSclick,isDclick;

 

key_function key2(

                        .clk(clk),

                        .rst_n(rst_n),

                       

                        .key_in(key),

                        .isSclick(isSclick),

                        .isDclick(isDclick)

                        );

 

 

    reg LEDr;

    always@(posedge clk , negedge rst_n)begin

        if(!rst_n)begin

            LEDr <= 1'b0;

        end else

        begin

            if(isDclick)

                LEDr <= 1'b0;

            else

                if(isSclick)

                    LEDr <= 1'b1;

        end

    end

   

    assign LED = LEDr;

   

endmodule

 


控制模块逻辑简单,只是实现了双击按键触发信号关闭LED,单击按键打开LED的功能。不做解释。最后编写testbench对代码的逻辑功能进行逻辑测试,由于软件模拟出来按键的抖动比较麻烦,只是简单的测试一下逻辑的功能测试,与实际应用有较大差别。


`timescale 1ns/1ns

 

module testbench;

 

reg clk;

reg rst_n;

wire LED;

reg key_in;

 

//if you want to use this testbench,you should defparam;

//parameter delay10ms   =   50;

//parameter delay90ms   =   4_50;

initial

begin

    clk <= 1'b1;

    rst_n <= 1'b0;

    key_in <= 1'b1;

    #100 rst_n <= 1'b1;

    #100 key_in <= 1'b0;

    #1200 key_in <= 1'b1;

    #1200 key_in <= 1'b0;

    #1200 key_in <= 1'b1;

    #1200

    #12000 key_in <= 1'b0;

    #1200 key_in <= 1'b1;

    #12000

    $stop;

end

 

always #10 clk <= ~clk;

keytop keytop(

                .clk(clk),

                .rst_n(rst_n),

               

                .LED(LED),

                .key_in(key_in)

                );

 

endmodule

 


13~28行:产生了一个复位信号,以及一个单击按键和一个双击按键信号;

30行:产生时钟激励。

(为方便仿真,将按键消抖的延时时间缩短了,在按键驱动代码1011行进行了修改

    parameter delay10ms =   500_000;

    parameter delay90ms =   4_500_000;

改为:

    parameter delay10ms =   50;

    parameter delay90ms =   4_50;

实际应用中应改回原值)

逻辑仿真图如下图5所示:

5 单点击按键与双击按键逻辑仿真图

3.3单点击按键、长按键与双击按键综合实验实现

首先编写按键驱动模块,其采用状态机思想,具体状态机状态转移图如图6单点击按键与双击按键状态机转移图所示:

6 单点击按键与双击按键状态机转移图

按键驱动代码:


module key_function(

                            clk,

                            rst_n,

                           

                            key_in,

                            isSclick,isDclick,isLclick

                            );

                               

    parameter delay10ms =   50;

    parameter delay90ms =   4_50;

    parameter delay2s       =   100_00;

   

    input clk;

    input rst_n;

    input key_in;

 

    output isSclick,isDclick,isLclick;

 

    //check key input

    reg F1,F2;

    always@(posedge clk , negedge rst_n )begin

        if(!rst_n)begin

            F1 <= 1'b1;

            F2 <= 1'b1;

        end else

        begin

            {F1 , F2} <= {key_in , F1};

        end

    end

 

    //FSM

    reg [3:0]i;

    reg [27:0]cnt;

    reg isSclick,isDclick,isLclick;

    reg [1:0]mode;

    wire H2L,L2H;

    always@(posedge clk , negedge rst_n)begin

        if(!rst_n)begin

            i <= 4'd0;

            cnt <= 28'd0;

            isSclick <= 1'b0;

            isDclick <= 1'b0;

            isLclick <= 1'b0;

            mode <= 2'b00;

        end else

        begin

            case(i)

            4'd0    :   if( H2L ) i <= i + 1'b1;

            4'd1    :   if( cnt == delay10ms )begin i <= i + 1'd1;cnt <= 28'd0;end

                        else cnt <= cnt + 1'd1;

            4'd2    :   if( L2H ) begin i <= i + 1'd1;cnt <= 28'd0;end

                        else    if( cnt == delay2s) begin i <= 4'd6;cnt <= 28'd0;mode <= 2'd3;end

                                else cnt <= cnt + 1'd1;

            4'd3    :   if( cnt == delay10ms )begin i <= i + 1'd1;cnt <= 28'd0;end

                        else cnt <= cnt + 1'd1;

            4'd4    :   if( H2L && cnt <= delay90ms) begin i <= i + 1'd1;cnt <= 28'd0;mode <= 2'd2;end

                        else    if( cnt >= delay90ms ) begin i <= 4'd6;cnt <= 28'd0;mode <= 2'd1;end

                                else cnt <= cnt + 1'd1;

            4'd5    :   if( cnt == delay10ms )begin i <= i + 1'd1;cnt <= 28'd0;end

                        else cnt <= cnt + 1'd1;

            4'd6    :   if( mode == 2'd3 ) begin i <= i + 1'd1;isLclick <= 1'b1;end

                        else    if( mode == 2'd2 ) begin i <= i + 1'd1;isDclick <= 1'b1;end

                                else if( mode == 2'd1 ) begin i <= i + 1'd1; isSclick <= 1'b1;end

            4'd7    :   begin { isSclick , isLclick , isDclick } <= 3'b000;i <= i + 1'd1;end

            4'd8    :   if( mode == 2'd3 ) begin i <= i + 1'd1;end

                        else    if( mode == 2'd2 ) begin i <= i + 1'd1;end

                                else    if( mode == 2'd1 ) i <= 4'd11;

            4'd9    :   if( L2H ) i <= i + 1'd1;

            4'd10   :   if( cnt == delay10ms )begin i <= i + 1'd1;cnt <= 28'd0;end

                        else cnt <= cnt + 1'd1;

            4'd11   :   i <= 4'd0;

            default :i <= 4'd0;

            endcase

        end

    end

   

 

    assign H2L = ~F1 && F2;

    assign L2H = F1 && ~F2;

 

endmodule

 


1~14行:表示代码驱动的端口定义:

端口名称

属性(位宽)

描述

clk

input(1)

工作时钟输入

rst_n

input(1)

异步复位

key_in

input(1)

机械按键信号输入端口

isSclick

output(1)

消抖后单点击触发信号输出

isLclick

output(1)

消抖后长点击触发信号输出

isDclick

output(1)

消抖后双击触发信号输出

18~27行:通过两个寄存器F1F2缓存机械按键信号输入端口的电平信号判断高低电平的边沿信号。

29~75行:为具体的状态机实现代码:

i=0:不断检测按键输入信号,等待下降沿触发信号,下降沿发生立刻跳转到下一个状态;

i=1:计数10ms消抖延时,跳转下一个状态;

i=2:等待上升沿信号,并计数,若计数值达到100_000_0002s)则认为为长按键信号置位模式寄存器相关位,跳转到i=6个状态;若在2s内等待到下降沿则认为是非长按键信号,跳转到下一状态

i=3:计数10ms消抖延时,跳转下一个状态;

i=4:检测100ms计数内有无再次下降沿触发,若有下降沿触发则认为是双击按键,置位模式寄存器相关位,跳转到下一个状态;若100ms之内无下降沿触发认为是单击按键,置位模式寄存器相关位,跳转到i=6状态;

i=5:计数10ms消抖延时,跳转下一个状态;

i=6:根据模式寄存器,置位单击按键触发信号(isSclick)、双击按键触发信号(isDclick)以及长按键触发信号(isLclick),跳转到下一个状态;

i=7:复位所有按键触发信号,跳转到下一个状态;

i=8:根据模式寄存器判断是否需要消抖处理,长按键与双击按键需要按键消抖处理,跳转到下一个状态,短按键直接跳转到i=11

i=9:等待上升沿,跳转到下一个状态;

i=10:计数10ms消抖延时,跳转下一个状态;

i=11:状态机结束跳转到i=0状态。

顶层控制模块,通过按键驱动的反馈信号,完成按键的功能:


module keytop(

                    clk,

                    rst_n,

                   

                    key,

                    LED

                    );

input clk;

input rst_n;

input key;

 

output LED;

wire isSclick2,isDclick2,isLclick2;

//key function

reg LED;

always@(posedge clk , negedge rst_n)begin

    if(!rst_n)begin

        LED <= 1'b0;

    end else

    begin

        case( { isSclick2 , isDclick2 , isLclick2 } )

        3'b100  :   LED <= 1'b1;

        3'b010  :   LED <= 1'b0;

        3'b001  :   LED <= 1'b1;

        default :   ;

        endcase

    end

end

 

key_function key2(

                        .clk(clk),

                        .rst_n(rst_n),

                       

                        .key_in(key),

                        .isSclick(isSclick2),

                        .isDclick(isDclick2),

                        .isLclick(isLclick2)

                        );

 

endmodule


控制模块逻辑简单,只是实现了单击按键触发信号开LED,双击按键关闭LED,长按键开LED的功能。不做解释。最后编写testbench对代码的逻辑功能进行逻辑测试,由于软件模拟出来按键的抖动比较麻烦,只是简单的测试一下逻辑的功能测试,与实际应用有较大差别。


`timescale 1ns/1ns

 

module testbench;

 

reg clk;

reg rst_n;

wire LED;

reg key_in;

 

//if you want to use this testbench,you should defparam;

//parameter delay10ms   =   50;

//parameter delay90ms   =   4_50;

//parameter delay90ms   =   100_00;

initial

begin

    clk <= 1'b1;

    rst_n <= 1'b0;

    key_in <= 1'b1;

    #100 rst_n <= 1'b1;

    #100 key_in <= 1'b0;

    #1200 key_in <= 1'b1;

    #12000 key_in <= 1'b0;

    #1200 key_in <= 1'b1;

    #1200 key_in <= 1'b0;

    #1200 key_in <= 1'b1;

    #1200 ;

    #1200 key_in <= 1'b0;

    #240000 key_in <= 1'b1;

    #1200 $stop;

    $stop;

end

 

always #10 clk <= ~clk;

keytop keytop(

                    .clk( clk ),

                    .rst_n( rst_n ),

 

                    .key( key_in ),

                    .LED( LED )

                  );

 

endmodule


13~28行:产生了一个复位信号和一个单击按键以及一个长按键信号;

30行:产生时钟激励。

(为方便仿真,将按键消抖的延时时间缩短了,在按键驱动代码91011行进行了修改

    parameter delay10ms =   500_000;

    parameter delay90ms =   4_500_000;

    parameter delay2s       =   100_000_000;

改为:

    parameter delay10ms =   50;

    parameter delay90ms =   4_50;

    parameter delay2s       =   100_00;

逻辑仿真图如下图7所示:

 

4设计的验证

 

5设计总结

<1>Error (10149): Verilog HDL Declaration error at keytop.v(25): identifier "key" is already declared in the present scope                                                       变量已被声明;

该错误出现在变量重定义或者是某变量和例化名称相同,如:

key_function key(

                                     .clk(clk),

                                     .rst_n(rst_n),

                                    

                                     .key_in(key),

                                     .isSclick(isSclick),

                                     .isLclick(isLclick)

                                     );

key”与“key_inkey)”连接的输入,名称重复;

 

<2>Error (176310): Can't place multiple pins assigned to pin location Pin_F16 (IOPAD_X34_Y18_N21)

         Info (176311): Pin LED[1] is assigned to pin location Pin_F16 (IOPAD_X34_Y18_N21)

         Info (176311): Pin ~ALTERA_nCEO~ is assigned to pin location Pin_F16 (IOPAD_X34_Y18_N21)

无法分配该引脚;由于FPGA有部分引脚有编程作用,如果需要使用期作为普通IO时需要预先设置,如下

 

 

全部作者的其他最新日志
评论 (0 个评论)

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 注册

热门文章