寫在前面:
按鍵去抖:理想波形與實(shí)際波形之間是有區(qū)別的,實(shí)際波形在按下和釋放的瞬間都有抖動(dòng)的現(xiàn)象,抖動(dòng)時(shí)間的長短和按鍵的機(jī)械特性有關(guān),一般為 5~10ms。通常我們手動(dòng)按鍵然后釋放,這個(gè)動(dòng)作中穩(wěn)定閉合的時(shí)間超過了 20ms。因此單片機(jī)在檢測鍵盤是否按下時(shí)都要加上去抖動(dòng)操作,有專用的去抖動(dòng)電路,也有專門的去抖動(dòng)芯片,但通常我們采用軟件延時(shí)的方法就可以解決抖動(dòng)問題。
1、單片機(jī)中按鍵消抖程序
1.1 單片機(jī)中,比如 STM32 中,一般的方法(最簡單的方法)
軟件消抖程序:
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==1)
{
delay_ms(20);// 延時(shí) 20ms 再去檢測按鍵值
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14)==0) // 相當(dāng)于下降沿
{
KEY1 = 1; // 表示 KEY1 被按下
}
}
1.2 比較全面的按鍵消抖程序及按鍵狀態(tài)檢測程序
第一步:初始化全局時(shí)間戳的定時(shí)器,一般采用 SysTick 定時(shí)器來產(chǎn)生,每 ms 一次 tick 即可。
第二步:初始化按鍵對應(yīng)的 IO,復(fù)用為邊沿觸發(fā)的外部中斷。
第三步:在外部中斷函數(shù)中添加按鍵事件處理函數(shù)。
代碼部分:
typedef struct _Key_t
{
u32 last_time;
enum
{
May_Press,
Release,
}private_state;
enum
{
No_Press,
Short_Press,
Long_Press,
}state;
}Key_t;
#define Is_ShortPress_Threshold 1500
簡單定義一個(gè)按鍵狀態(tài)的結(jié)構(gòu)體,用于管理每個(gè)按鍵的狀態(tài)。順便再定義一個(gè)長短按的識別閾值,用于區(qū)分按鍵的長短按。
if(key_state.private_state==Release)
{
if(KEY==0)
{
key_state.private_state=May_Press;
key_state.last_time=course_ms();
}
}
else if(key_state.private_state==May_Press)
{
if(KEY==1)
{
if((course_ms()-key_state.last_time>10)&&(course_ms()-key_state.last_time
{
key_state.state=Short_Press;
key_state.private_state=Release;
}
else if(course_ms()-key_state.last_time>Is_ShortPress_Threshold)
{
key_state.state=Long_Press;
key_state.private_state=Release;
}
else
key_state.private_state=Release;
}
}
以上為需要添加到中斷處理函數(shù)的按鍵事件處理函數(shù),算法的核心是一個(gè)狀態(tài)機(jī)。在本例中,按鍵被默認(rèn)上拉,按下接地。course_ms()為獲取全局時(shí)間戳的函數(shù)。
思路解釋如下:按鍵狀態(tài)結(jié)構(gòu)體有一個(gè)用于識別的狀態(tài)位,默認(rèn)處于 Release,也就是釋放的狀態(tài)。一旦按鍵被按下,中斷觸發(fā),此時(shí)檢查是否是 Relase 狀態(tài),如果是就檢查按鍵是否被拉低,如果是,此時(shí)進(jìn)入 May_Press 狀態(tài),也就是可能是按下的,并且記錄此時(shí)的時(shí)間戳,這一步是消抖的關(guān)鍵。當(dāng)按鍵被釋放,由于是邊沿觸發(fā),會(huì)再次進(jìn)行處理,此時(shí)檢查和上一次觸發(fā)之間的時(shí)間戳之差,如果小于 10ms 我們就認(rèn)為是抖動(dòng),此時(shí)不會(huì)對按鍵輸出狀態(tài)進(jìn)行修改,而是直接將按鍵狀態(tài)置回 Relase 狀態(tài),反之檢查差值和長短按閾值之間的關(guān)系,將 state 置位為對應(yīng)的狀態(tài)。消抖的核心在于記錄時(shí)間戳,而這只是一個(gè)簡單的賦值操作,并不耗費(fèi)時(shí)間。
效率上來說,延時(shí)消抖花費(fèi)時(shí)間在無意義延時(shí)上,而相對較好的定時(shí)輪詢還是不可避免的在輪詢,而現(xiàn)在這種方式完全是中斷性質(zhì)的。唯一多出的開銷(全局時(shí)間戳)并不是只可以用于按鍵消抖,另外在 HAL 庫中存在直接獲取 tick 的函數(shù),這樣實(shí)現(xiàn)就更方便了。經(jīng)實(shí)際測試,消抖效果可以達(dá)到其他兩種消抖算法的水平。
2、FPGA 按鍵消抖程序
首先,做兩個(gè)假定,以方便后面的描述:
假定按鍵的默認(rèn)狀態(tài)為 0,被按下后為 1
假定按鍵抖動(dòng)時(shí)長小于 20ms,也即使用 20ms 的消抖時(shí)間
核心:方案
最容易想到的方案
在按鍵電平穩(wěn)定的情況下,當(dāng)?shù)谝淮螜z測到鍵位電平變化,開始 20ms 計(jì)時(shí),計(jì)時(shí)時(shí)間到后將按鍵電平更新為當(dāng)前電平。
或許這才是最容易想的方案
在 20ms 計(jì)時(shí)的過程中,有任何的電平變化都立即復(fù)位計(jì)時(shí)
消除按鍵反應(yīng)延時(shí)抖方案
在有電平變化時(shí)立即改變按鍵輸出電平,并開始 20ms 計(jì)時(shí),忽略這其中抖動(dòng)
測試平臺(tái)設(shè)計(jì)(修改代碼以仿真的 1us 代替實(shí)際 1ms)
無抖動(dòng) 上升沿抖動(dòng) 5 毫秒
下降沿抖動(dòng) 15 毫秒
上升和下降沿均抖動(dòng) 19 毫秒
附加測試(可以不通過)
抖動(dòng) 25 毫秒
代碼
方案 1
module debounce( input wire clk, nrst, input wire key_in, output reg key_out
); // 20ms parameter// localparam TIME_20MS = 1_000_000;
localparam TIME_20MS = 1_000; // just for test // variable
reg [20:0] cnt; reg key_cnt;
// debounce time passed, refresh key state
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_out <= 0; ? ? ? ?else if(cnt == TIME_20MS - 1)
key_out <= key_in; ? ?end
// while in debounce state, count, otherwise 0
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
cnt <= 0; ? ? ? ?else if(key_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 0;?
end
//
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_cnt <= 0; ? ? ? ? ? ?else if(key_cnt == 0 && key_in != key_out)
key_cnt <= 1; ? ? ? ? ? ?else if(cnt == TIME_20MS - 1)
key_cnt <= 0; ? ? endendmodule
方案 2
module debounce( input wire clk, nrst, input wire key_in, output reg key_out
);// localparam TIME_20MS = 1_000_000;
localparam TIME_20MS = 1_000; reg key_cnt; reg [20:0] cnt; always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_cnt <= 0; ? ? ? ?else if(cnt == TIME_20MS - 1)
key_cnt <= 0; ? ? ? ?else if(key_cnt == 0 && key_out != key_in)
key_cnt <= 1; ? ?end
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
cnt <= 0; ? ? ? ?else if(key_cnt) begin
if(key_out == key_in)
cnt <= 0; ? ? ? ? ? ?else
cnt <= cnt + 1'b1;
end
else
cnt <= 0; ? ?end
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_out <= 0; ? ? ? ? ? ?else if(cnt == TIME_20MS - 1)
key_out <= key_in; ? ? endendmodule
方案 3
module debounce( input wire clk, nrst, input wire key_in, output reg key_out
);// localparam TIME_20MS = 1_000_000;
localparam TIME_20MS = 1_000; // just for test
reg key_cnt; reg [20:0] cnt; always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_cnt <= 0; ? ? ? ?else if(key_cnt == 0 && key_out != key_in)
key_cnt <= 1; ? ? ? ?else if(cnt == TIME_20MS - 1)
key_cnt <= 0; ? ?end
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
cnt <= 0; ? ? ? ?else if(key_cnt)
cnt <= cnt + 1'b1;
else
cnt <= 0; ? ?end
always @(posedge clk or negedge nrst) begin
if(nrst == 0)
key_out <= 0; ? ? ? ?else if(key_cnt == 0 && key_out != key_in)
key_out <= key_in; ? ?endendmodule
測試代碼
// 按鍵消抖測試電路 // 時(shí)間單位`timescale 1ns/10ps// modulemodule debounce_tb; // time period parameter
localparam T = 20; // variable
reg clk, nrst; reg key_in; wire key_out; // instantiate debounce uut(
.clk (clk ),
.nrst (nrst ),
.key_in (key_in ),
.key_out(key_out)
); // clock
initial begin
clk = 1; forever #(T/2) clk = ~clk; end
// reset
initial begin
nrst = 1;
@(negedge clk) nrst = 0;
@(negedge clk) nrst = 1; end
// key_in
initial begin
// initial value
key_in = 0;
// wait reset
repeat(3) @(negedge clk);
// no bounce // key down
key_in = 1; // last 60ms
repeat(3000) @(negedge clk); // key up
key_in = 0; // wait 50ms
repeat(2500) @(negedge clk); // down 5ms, up 15ms // key down, bounce 5ms
repeat(251) @(negedge clk) key_in = ~key_in; // last 60ms
repeat(3000) @(negedge clk); // key up, bounce 15ms
repeat(751) @(negedge clk) key_in = ~key_in; // wait 50ms
repeat(2500) @(negedge clk); // down 19ms, up 19ms // key down, bounce 19ms
repeat(951) @(negedge clk) key_in = ~key_in; // last 60ms
repeat(3000) @(negedge clk); // key up, bounce 19ms
repeat(951) @(negedge clk) key_in = ~key_in; // wait 50ms
repeat(2500) @(negedge clk);
// additional, this situation shoud not ever happen // down 25ms, up 25ms // key down, bounce 25ms
repeat(1251) @(negedge clk) key_in = ~key_in; // last 60ms
repeat(3000) @(negedge clk); // key up, bounce 25ms
repeat(1251) @(negedge clk) key_in = ~key_in; // wait 50ms
repeat(2500) @(negedge clk); // stop $stop; endendmodule
放在最后的,并不一定是最不重要的
對于上面的三種方案,我比較喜歡第三種方案,它更貼合實(shí)際的按鍵狀態(tài),以上的代碼我都做過 modelsim 仿真,但還沒有在實(shí)際的項(xiàng)目中驗(yàn)證。在整理準(zhǔn)備這個(gè)博客的時(shí)候,我又想到了一個(gè)感覺是更巧妙的方案,具體是這樣的:在第三個(gè)方案的基礎(chǔ)上,因?yàn)榘存I輸入有變化的第一時(shí)刻,輸出就已經(jīng)改變了,在這種情況下,我可以把計(jì)時(shí)的時(shí)長改為一個(gè)很小的值,該值只要比抖動(dòng)中的最長高低電平變化時(shí)間長即可。但想想也沒這個(gè)必要,且這個(gè)抖動(dòng)的高低電平變化時(shí)長我也很難去給它界定一個(gè)值。
審核編輯黃昊宇
-
FPGA
+關(guān)注
關(guān)注
1640文章
21893瀏覽量
610994 -
單片機(jī)
+關(guān)注
關(guān)注
6056文章
44804瀏覽量
643726 -
STM
+關(guān)注
關(guān)注
1文章
557瀏覽量
42869 -
按鍵消抖
+關(guān)注
關(guān)注
2文章
28瀏覽量
10581
發(fā)布評論請先 登錄
相關(guān)推薦
技術(shù)分享:明德?lián)P按鍵消抖的原理和基于fpga的消抖設(shè)計(jì)
按鍵消抖及原理是什么
基于FPGA的按鍵消抖電路設(shè)計(jì)
單片機(jī)中按鍵消抖程序

51單片機(jī)的獨(dú)立按鍵和按鍵消抖及矩陣按鍵的電路與程序免費(fèi)下載

使用51單片機(jī)實(shí)現(xiàn)按鍵消抖的資料和程序免費(fèi)下載

評論