最直覺的作法,就是我先讀取一次,隔一段時間後,再讀取一次。如果這兩次的狀態都一樣,就視為狀態更新,否則就是 bouncing。
以下這個範例,是修改自 "超圖解Arduino 互動設計入門(第二版)" 一書的範例。執行的效果也非常好,但我故意按住按鈕抖動開關,仍然有可能觸動狀態改變。
比較不一樣的是,它更改 LED 狀態的時間點,是發生在釋放按鈕的當下。(1 click = press + release) 一般的範例都發生在 press 的時候,這個範例是發生在 release 的時候。
實作概念是偵測到狀態改變的時候,等 20ms 之後,再讀取一次。若讀到的結果跟前次偵測一樣,則判定改變生效。只是這支程式還利用 click 變數判斷是否完成 click 動作。一次 click 的動作是兩次的狀態改變。即 按下->釋放。
const byte ledPin = 13; const byte buttonPin = 2; boolean lastButtonState = LOW; boolean ledState = LOW; byte click = 0; void setup() { pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT); lastButtonState = digitalRead(buttonPin); // 儲存系統初始狀態 } void loop() { boolean reading1 = digitalRead(buttonPin); // 先讀一次 if (reading1 != lastButtonState) { // 如果發現有改變 delay(20); // <- de-bouncing, wait & read again 等 20ms 後再讀一次 boolean reading2 = digitalRead(buttonPin); if (reading2 == reading1) { // 如果 20ms 後都一樣, 判定更改生效 lastButtonState = reading2; click++; // click +1, 然後釋放按鈕的時候, 會再進來一次. 所以 click 會加至 =2 } // else means reading2 is bouncing } // press & release means 2 click in the code // only toggle the LED if the button is release if (click == 2) { click = 0; ledState = !ledState; digitalWrite(ledPin, ledState); } }
其實這支程式可以稍微修改一下,讓它變成在按下按鈕的當下,讓 LED 燈的狀態改變。
const byte ledPin = 13; const byte buttonPin = 2; boolean lastButtonState = LOW; boolean ledState = LOW; void setup() { pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT); lastButtonState = digitalRead(buttonPin); } void loop() { boolean reading1 = digitalRead(buttonPin); if (reading1 != lastButtonState) { delay(20); // <- de-bouncing, wait & read again boolean reading2 = digitalRead(buttonPin); if (reading2 == reading1) { lastButtonState = reading2; } // else means reading2 is bouncing if (lastButtonState == HIGH) { ledState = !ledState; } } digitalWrite(ledPin, ledState); }
這邊有個地方要注意的是如上面 highlight 的那三行 code,必須寫在判斷狀態確實改變的 code block 中。我第一次撰寫這支程式的時候,寫成下面的樣子:
/* ...上面省略... */ void loop() { boolean reading1 = digitalRead(buttonPin); if (reading1 != lastButtonState) { delay(20); // <- de-bouncing, wait & read again boolean reading2 = digitalRead(buttonPin); if (reading2 == reading1) { lastButtonState = reading2; } // else means reading2 is bouncing } if (lastButtonState == HIGH) { ledState = !ledState; digitalWrite(ledPin, ledState); } }
其實這在程式流程上是錯誤的,因為你每次在 17 行判斷 lastButtonState 時,其實是上一次的狀態。而且你無法得知當次的按鈕行為是否有更新到 lastButtonState? 所以應該寫在確認 lastButtonState 更改生效後才改變 ledState。
第二種方式的寫法,是參考 Arduino Cookbook 2nd Edition 裡面的寫法。概念是每次檢查時,只要發現狀態改變,該狀態得持續一段時間才算數。
const int buttonPin = 2; // the number of the input pin const int ledPin = 13; // the number of the output pin const int debounceDelay = 10; // milliseconds to wait until stable int ledState = LOW; // debounce returns true if the switch in the given pin is closed and stable boolean debounce(int pin) { boolean state; boolean previousState; previousState = digitalRead(pin); // store switch state for(int counter=0; counter < debounceDelay; counter++) { delay(1); // wait for 1 millisecond /* debounceDelay = 10, 所以在 10ms 的時間內, * 狀態必須維持一樣才會跳出 for loop (才算穩定) */ state = digitalRead(pin); // read the pin if( state != previousState) { counter = 0; // reset the counter if the state changes // 只要發現狀態改變, 就重新觀察 10ms previousState = state; // and save the current state } } // here when the switch state has been stable longer than the debounce period return state; } void setup() { pinMode(buttonPin, INPUT); pinMode(ledPin, OUTPUT); digitalWrite(ledPin, ledState); } void loop() { if (debounce(buttonPin)) { ledState = !ledState; digitalWrite(ledPin, ledState); } }
執行結果有好一點,但其實你還是需要按久一點,才有 "高的機率" 可以改變 LED 狀態。以我按按鈕的速度來測試,不是每次都能夠判讀正確的。且程式使用 delay(),表示在 delay() 執行的過程中,程式是被 block 住的。如果把 delay(1) 拿掉。就變成了連續 10 次結果相同,而不是 10ms 內結果相同。
其實 Arduino 的 Example 裡面也有關於 Debounce 的範例:
概念跟 Arduino Cookbook 應該是相同的:每次偵測到按鍵狀態改變時,利用 millis() 記下當時的時間戳記。然後經過 debounceDelay (ms) 時間後,若狀態還是改變,則改變生效,否則判定為 bounce 訊號。
const int buttonPin = 2; // the number of the pushbutton pin const int ledPin = 13; // the number of the LED pin // Variables will change: int ledState = LOW; // the current state of the output pin int buttonState; // the current reading from the input pin int lastButtonState = LOW; // the previous reading from the input pin // the following variables are long's because the time, measured in miliseconds, // will quickly become a bigger number than can be stored in an int. long lastDebounceTime = 0; // the last time the output pin was toggled long debounceDelay = 50; // the debounce time; increase if the output flickers void setup() { pinMode(buttonPin, INPUT); pinMode(ledPin, OUTPUT); // set initial LED state digitalWrite(ledPin, ledState); } void loop() { // read the state of the switch into a local variable: int reading = digitalRead(buttonPin); // check to see if you just pressed the button // (i.e. the input went from LOW to HIGH), and you've waited // long enough since the last press to ignore any noise: // If the switch changed, due to noise or pressing: if (reading != lastButtonState) { // reset the debouncing timer lastDebounceTime = millis(); // 紀錄時間戳記 } if ((millis() - lastDebounceTime) > debounceDelay) { // 經過 debounceDelay 時間後 // whatever the reading is at, it's been there for longer // than the debounce delay, so take it as the actual current state: /* 若在這邊塞一顆閃爍的 LED 燈 debug, * 會發現閒置 (按鈕未按下) 時, 程式會一直跑進這個 block. */ // if the button state has changed: if (reading != buttonState) { // 經過 debounceDelay 時間後再判斷一次 buttonState = reading; // only toggle the LED if the new button state is HIGH if (buttonState == HIGH) { ledState = !ledState; } } } // set the LED: digitalWrite(ledPin, ledState); // save the reading. Next time through the loop, // it'll be the lastButtonState: lastButtonState = reading; }
這支程式執行的效果,是這本篇所有範例中最好的,幾乎 100% 即時正確地反應按按鈕的行為。當我故意在短時間內狂按按鈕時,程式也會忽略那些極短時間內的改變。
每次都要為了處理 contact bounce,勢必得多寫很多 code,於是有些前人已經將 debounce 的功能包裝成 Libraries 啦。 前往 http://www.arduino.cc/en/Reference/Libraries 找尋 "Debounce"。
在 Debounce 的頁面中提到:
Debounce is outdate please use Bounce instead所以這個功能的最新版已經移到 "Bounce" 這個 Library 啦。
在 LibraryList 清單中,點選 "Buttons & Debouncing" 可以得到很多跟按鈕 & 消彈跳相關的 Libraries。這裡要用的是 Bounce,而且目前有最新版 Bounce2 了。
- 這裡下載: https://github.com/thomasfredericks/Bounce-Arduino-Wiring/archive/master.zip
- 說明文件: https://github.com/thomasfredericks/Bounce-Arduino-Wiring/wiki
- 官方 github: https://github.com/thomasfredericks/Bounce-Arduino-Wiring
OK, 現在利用 Bounce2 的函式庫,再把剛剛的程式拿來改一下:
#include <Bounce2.h> const int buttonPin = 2; // the number of the pushbutton pin const int ledPin = 13; // the number of the LED pin int ledState = LOW; // the current state of the output pin Bounce debouncer = Bounce(); // Instantiate a Bounce object void setup() { pinMode(buttonPin, INPUT); pinMode(ledPin, OUTPUT); debouncer.attach(buttonPin); debouncer.interval(20); /* 你可以調整這個數值, 然後測試看看 * 然後你會發現當數字越大時, 例如 500 * 你需要按住久一點, LED 才會有反應 */ digitalWrite(ledPin, ledState); // set initial LED state } void loop() { debouncer.update(); // Update the Bounce instance if (debouncer.fell()) { // Call code if Bounce fell (transition from HIGH to LOW) ledState = !ledState; digitalWrite(ledPin, ledState); } }
泥砍砍~ 程式碼是不是簡潔許多?
沒有留言:
張貼留言