最直覺的作法,就是我先讀取一次,隔一段時間後,再讀取一次。如果這兩次的狀態都一樣,就視為狀態更新,否則就是 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);
}
}
泥砍砍~ 程式碼是不是簡潔許多?

沒有留言:
張貼留言