天天看點

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