||
基于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单点击按键与长按键状态机转移图所示:
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行:通过两个寄存器F1与F2缓存机械按键信号输入端口的电平信号判断高低电平的边沿信号。
29~75行:为具体的状态机实现代码:
i=0:不断检测按键输入信号,等待下降沿触发信号,下降沿发生立刻跳转到下一个状态;
i=1:计数10ms消抖延时,跳转下一个状态;
i=2:等待上升沿信号,并计数,若计数值达到100_000_000(2s)则认为为长按键信号,置位模式寄存器相关位跳转到下一个状态;若在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行:产生时钟激励。
(为方便仿真,将按键消抖的延时时间缩短了,在按键驱动代码10、11行进行了修改
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行:通过两个寄存器F1与F2缓存机械按键信号输入端口的电平信号判断高低电平的变化。
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行:产生时钟激励。
(为方便仿真,将按键消抖的延时时间缩短了,在按键驱动代码10、11行进行了修改
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行:通过两个寄存器F1与F2缓存机械按键信号输入端口的电平信号判断高低电平的边沿信号。
29~75行:为具体的状态机实现代码:
i=0:不断检测按键输入信号,等待下降沿触发信号,下降沿发生立刻跳转到下一个状态;
i=1:计数10ms消抖延时,跳转下一个状态;
i=2:等待上升沿信号,并计数,若计数值达到100_000_000(2s)则认为为长按键信号置位模式寄存器相关位,跳转到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行:产生时钟激励。
(为方便仿真,将按键消抖的延时时间缩短了,在按键驱动代码9、10、11行进行了修改
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_in(key)”连接的输入,名称重复;
<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时需要预先设置,如下