按鍵切換狀態的不同C寫法對比
一個容易被忽視卻並不簡單的問題
給定一個按鍵,以及一個狀態輸出(如一個LED),按下一次按鍵後LED燈改變一次狀態,再按一次按鍵LED再次切換狀態,這是嵌入式程式設計中一個很簡單的任務,但要想達到很好的效果並不容易,一下對比幾種C寫法(以arduino為例):
程式一
bool buttonState = 1;
bool x = 1;
void loop(){
buttonState = digitalRead(buttonPin);//read the key button
if (buttonState == LOW && x==1) {
digitalWrite(LedPin , 1 ); //light the led
x=0;
}
else if (buttonState == LOW && x==0){
digitalWrite(LedPin, 0); //off the led
x=1;
}
delay(1);
}
這種寫法使用一個x變數來希望使得每次按鍵後切換一次led狀態,但這個程式有一個問題是當按鍵按下時間較長時,程式會不斷進入判斷並不斷切換led的狀態,導致每次按下按鍵後led都會閃爍多次,鬆開後的狀態也是隨機的,儘管可以通過增加兩次判斷的延時稍稍改善,但改善效果很低,過大的時延也使得其他程式和按鍵本身響應速度降低,基本是不可取的。
程式二
bool newbuttomState = 1;
bool oldbuttonState = 1;
bool ledState = 1;
void loop() {
newbuttonState = digitalRead(buttonPin); //read current key
if (newbuttonState == LOW && oldButtonState == HIGH) //key pin must from high to low, can work even the key is press without a break
{
delay(100 );
newbuttonState = digitalRead(buttonPin);
if (newbuttonState == LOW && oldButtonState == HIGH)
{
ledState = !ledState;
digitalWrite(ledPin, ledState);
}
}
oldButtonState = newbuttonState; //record the old key state
}
程式二通過檢測按鍵之前的狀態,每次切換led時都需要判斷按鍵之前的狀態(高電平)和按下的狀態(低電平)同時滿足。這樣效果比程式一要好了很多,即使按鍵一直被按下也不會反覆進入判斷語句切換led的狀態,同時考慮到按鍵消抖,每次判斷後延時一段時間,再判斷一次。這個程式本身應該考慮比較周全了,但實際試驗中發現,當按下按鍵時間較長時再鬆開,led常常出現連續開關兩次,且改變延時時間仍然不能有所改善,仔細考慮和查閱一些資料後,改進為第三種程式寫法,終於得到比較好的效果
程式三
bool newbuttomState = 1;
bool oldbuttonState = 1;
bool ledState = 1;
void loop() {
newbuttonState = digitalRead(buttonPin);
if (newbuttonState == LOW && oldButtonState == HIGH)
{
ledState = !ledState;
digitalWrite(ledPin, ledState);
}
oldButtonState = newbuttonState;
delay(1); //the delay'position is very very important
}
程式三和程式二似乎沒有什麼區別,而且反而還去掉了按下狀態延時消抖的語句,似乎應該反而效果不如程式二,但實際上我用一個抖動很差的開關做了多次試驗的結果是,程式三基本達到了接近完美的效果。響應非常迅速而幾乎沒有出現過任何錯誤(程式二大約會出現一半的錯誤響應)。
這其中的奧祕到底在哪呢,仔細觀察程式三,除了去掉了按鍵按下的再次判斷的延時消抖,唯一的區別就是在末尾加了一個時延語句,而正是這個延時語句的位置使得它有著遠遠優於程式二的效果。
原因在於實際中使用的物理按鍵的抖動特點。實際上大多數常用按鍵在按下時(觸點閉合)的抖動並不顯著,而抖動主要出現在按下後鬆開的階段(觸點離開),這個大家可以自己用示波器做一些相關實驗(以後有時間我可以另外開帖用示波器做一系列試驗)。因此基於這個特點,我在程式末尾,即按鍵鬆開後的階段新增一個延時就可以有效防止鬆開階段的抖動影響,這個延時值很小,對於我用的按鍵,我設定延時1ms就有很好的效果。而我又嘗試將按下階段的消抖語句去除,發現沒有任何影響。