天天看点

5、SCM 按键消抖 vs FPGA 按键消抖

;SCM(使用的锐志实验板) 时钟周期为11.0592Mhz FPGA (Altrea BJ-EPM240)时钟周期为50Mhz(C语言和Verilog语言的语法不做详细讲解,可以查看相关资料以下基础实验都是基于两个原理图(http://pan.baidu.com/s/1sl56yc9))

5、SCM 按键消抖 vs FPGA 按键消抖
  • 按键在闭合和断开时,触点会存在抖动现象。在按键按下或者是释放的时候都会出现一个不稳定的抖动时间,如果不处理好这个抖动时间,我们就无法处理好按键编码;首先介绍串行代码的描述思想,然后介绍硬件并行代码的思想

因为 指令周期>(~4) 机器周期>状态周期>(~12) 时钟周期,因此对于按键的延迟要根据执行一条指令的执行时间;理论的计算是 一条指令执行时间=m个机器周期xn1个时钟周期+p个状态状态周期xn2个机器周期

  • 时钟周期,是晶振频率的倒数。
  • 状态周期,是时钟周期的二倍。
  • 机器周期,是时钟周期的 12 倍。
  • 指令周期,执行一条指令所需要的时间,一般由若干个机器周期组成。指令不同,所需的机器周期也不同。
#include<reg52.h>
#define  uint unsigned int
sbit clk_div=P3^5; //按键
sbit led=P0^7;
void delay(uint div_delay)
{
 uint i,j;
  for(i=div_delay;i>0;i--)
   for(j=110;j>0;j--);  //延迟1ms  
}

void main()
{ 
 led=1;//可省略
while(1)
{
  if(clk_div==0)
  { delay(20);  //按键延迟20ms
  if(clk_div==0) 
    
   while(!clk_div); 
   led=~led;
  }}}      

上述代码为一个简单的SCM按键点亮小灯;

  • (个人见解,还望前辈批评)首先FPGA 的硬件编程语言是并行执行的,但是每一个always

    语句的触发的逻辑操作一般都是有逻辑的制约关系的,即下一个always

    语句的逻辑操作成立条件,一般都是建立在上一个always语句的逻辑操作的寄存器的数值变化,这是阻碍初学者晦涩难懂的串行代码转并行硬件电路代码思维主要障碍。verilog

    的基本语法不做讲解,可以查看相关资料;还是以源码促进学习;

`timescale 1ps/1ns 
module div
( clk,
  rst_n,
  sw,
  led 
  
);

input clk;
input rst_n;
input [2:0] sw;
output [2:0] led ;


reg [2:0]clk_div;
reg [2:0]clk_div_t;

always@(posedge clk or negedge rst_n)
if(!rst_n) clk_div<=3'b111;
else clk_div<={sw[0],sw[1],sw[2]};         
                                           

always@(posedge clk or negedge rst_n)
if(!rst_n) clk_div_t<=3'b111;
else  clk_div_t<=clk_div;

wire [2:0] key_an=(~clk_div) & clk_div_t;

reg [20:0] cnt;

always@(posedge  clk or negedge rst_n)  
if(!rst_n) cnt<=20'h0;
else if(key_an) cnt<=20'h0;
else cnt<=cnt+1'b1;

reg [2:0] key_clk_div;
reg [2:0] key_clk_div_t;


always@(posedge clk or negedge rst_n)
if(!rst_n) key_clk_div<=3'b111;
else if(cnt==20'hfffff)  key_clk_div<={sw[0],sw[1],sw[2]};
 

 always@(posedge clk or negedge rst_n)
if(!rst_n) key_clk_div_t<=3'b111;
else key_clk_div_t<=key_clk_div;
 
 
wire [2:0] key_an_clk= (~key_clk_div) &(key_clk_div_t);  

reg [2:0] d;

always@(posedge clk or negedge rst_n)
if(!rst_n) 
begin
d[0]<=1'b0;
d[1]<=1'b0;
d[2]<=1'b0;
end 
else 
begin
if(key_an_clk[0]) d[0]<=~d[0];
if(key_an_clk[1]) d[1]<=~d[1];
if(key_an_clk[2]) d[2]<=~d[2]; 
end 
  
   
assign led[0]=d[0]?1'b1:1'b0;
assign led[1]=d[1]?1'b1:1'b0;
assign led[2]=d[2]?1'b1:1'b0;

endmodule      

上面是源代码,下面详细分析一下代码的含义;

reg [2:0]clk_div;
reg [2:0]clk_div_t;

always@(posedge clk or negedge rst_n)
if(!rst_n) clk_div<=3'b111;
else clk_div<={sw[0],sw[1],sw[2]};        
                                          

always@(posedge clk or negedge rst_n)
if(!rst_n) clk_div_t<=3'b111;
else  clk_div_t<=clk_div;

wire [2:0] key_an=(~clk_div) & clk_div_t;      

首先假设在按键未按下状态时,sw[0],sw[1],sw[2] 都为高电平 , 在每一个时钟周期上升沿过程中,clk_div 和clk_div_t 、key_an寄存器的数值分别会有下面的状态(每一列的一组数据代表一个上升沿的此刻寄存器中的数值)

reg posedge clk posedge clk posedge clk posedge clk posedge clk
clk_div 111 111 111 111 111
clk_div_t 111 111 111 111 111
key_an 000 000 000 000 000

当按键在第二个时钟周期上升沿之前按下sw[1]按键的时候,各个寄存器的状态如下

reg posedge clk posedge clk posedge clk posedge clk posedge clk
clk_div 111 101 101 101 101
clk_div_t 111 111 101 101 101
key_an 000 000 010 000 000

当在第三个时钟周期时候检测到有按键按下去(主要是因为非阻塞电路),即key_an 状态为 010

reg [20:0] cnt;

always@(posedge  clk or negedge rst_n)  
if(!rst_n) cnt<=20'h0;
else if(key_an) cnt<=20'h0;
else cnt<=cnt+1'b1;

reg [2:0] key_clk_div;
reg [2:0] key_clk_div_t;


always@(posedge clk or negedge rst_n)
if(!rst_n) key_clk_div<=3'b111;
else if(cnt==20'hfffff)  key_clk_div<={sw[0],sw[1],sw[2]};


always@(posedge clk or negedge rst_n)
if(!rst_n) key_clk_div_t<=3'b111;
else key_clk_div_t<=key_clk_div;


wire [2:0] key_an_clk= (~key_clk_div) &(key_clk_div_t);  
      

首先因为按键的抖动原因 ,需要延迟20ms 在检测一下按键 是否真呈按下状态; 因为假设时钟周期为50Mhz ; 1/50Mhz=0.02us 若要延迟20ms 则首先让让寄存器cnt数值累加到20’HFFFFF 也就等同于累计了20’HFFFFF 时钟周期;计算为 0.02220=0.021000000=20ms (代码里面的20’HFFFFFF ${\approx}$220) 这样就完成了延迟20ms 时间,然后在锁定此时的按键状态,进行按键小灯点亮或者关闭;

reg [2:0] d;

always@(posedge clk or negedge rst_n)
if(!rst_n) 
begin
d[0]<=1'b0;
d[1]<=1'b0;
d[2]<=1'b0;
end 
else 
begin
if(key_an_clk[0]) d[0]<=~d[0];
if(key_an_clk[1]) d[1]<=~d[1];
if(key_an_clk[2]) d[2]<=~d[2]; 
end 
  
   
assign led[0]=d[0]?1'b1:1'b0;
assign led[1]=d[1]?1'b1:1'b0;
assign led[2]=d[2]?1'b1:1'b0;      
set_property PACKAGE_PIN K17 [get_ports clk]
set_property PACKAGE_PIN E17 [get_ports rst_n]
set_property PACKAGE_PIN M15 [get_ports {led[0]}]
set_property PACKAGE_PIN G14 [get_ports {led[1]}]
set_property PACKAGE_PIN M17 [get_ports {led[2]}]
set_property PACKAGE_PIN G15 [get_ports {led[3]}]
set_property PACKAGE_PIN M19 [get_ports {key[0]}]
set_property PACKAGE_PIN M20 [get_ports {key[1]}]
set_property PACKAGE_PIN L16 [get_ports {key[2]}]
set_property PACKAGE_PIN F16 [get_ports {key[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {key[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {key[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {key[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {key[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
      
`timescale 1ns / 1ps
module led_demo(

input  clk ,
input  rst_n ,
input  [3:0]key,
output [3:0]led 

);

//50MHZ 1/50MHZ=0.02us  1s=1000000/0.02us=1000 000 00/2=50 000 000
//key 20ms/0.02us=2000000/2=1000000

reg [3:0] key_first;

always@(posedge clk or negedge rst_n)
if(!rst_n)
   key_first<=4'b1111;
else
   key_first<=key;

wire [3:0]key_an=key_first&~key;


reg [20:0] cnt ;
always@(posedge clk or negedge rst_n)
  if(!rst_n)
     cnt <= 0 ;
else if(key_an)
    cnt <= 0 ;
else
    cnt<=cnt+1'b1;// 当加法超过24位位数之后,又变成0

reg [3:0] key_second;  

always@(posedge clk or negedge rst_n)
if(!rst_n)
   key_second<=4'b1111;
else if(cnt==20'd1_000_000)
   key_second<=key;
   
reg [3:0] key_three;
always@(posedge clk or negedge rst_n)
if(!rst_n)
   key_three<=4'b1111;
else 
   key_three<=key_second;


wire [3:0]key_an_real=key_three&~key_second;
//wire [3:0]key_an_real=key_second&~key; //不能这样写 这样写会在按键之后, 这里会立刻记录按键bit

reg [3:0] ledreg;
always@(posedge clk or negedge rst_n)
if(!rst_n)
   ledreg <=4'b1111 ;
else 
begin
if(key_an_real[0])  ledreg[0] <= ~ledreg[0];     
if(key_an_real[1])  ledreg[1] <= ~ledreg[1];       
if(key_an_real[2])  ledreg[2] <= ~ledreg[2];   
if(key_an_real[3])  ledreg[3] <= ~ledreg[3];   
end
 
  
assign led[0]=ledreg[0]?1'b1:1'b0;
assign led[1]=ledreg[1]?1'b1:1'b0;
assign led[2]=ledreg[2]?1'b1:1'b0;
assign led[3]=ledreg[3]?1'b1:1'b0;
endmodule