在這篇文章中,我們將討論一個看似再簡單不過的元件-瞬時按鈕。在文章的前半段,我們會稍微解釋何謂瞬時按鈕。後半段則提供 Python 程式範例,實際了解在 Raspberry Pi 上如何利用 Python 程式取得瞬時按鈕的狀態並作出回應。

看似簡單卻不簡單的按鈕

儘管按鈕有眾多不同的樣式,不過基本上都可以看成是開關的一種,可以連通或斷開電路。當按鈕 (或開關) 處於關閉的狀態時,電路被中斷而形成所謂的開路或斷路,而開啟的狀態時則是連通電路形成所謂的閉路。

開路與閉路

開路與閉路

按鈕有很多種分類方式,其中一種方式是依據連通/斷開的時機而分成兩類,分別是自持按鈕 (Maintained buttons) 與瞬時按鈕 (Momentary buttons)。這兩個名詞聽起來頗神秘,不過其實我們幾乎每天都會接觸到這兩類的按鈕。最常見的自持按鈕就是電源開關,當我們打開檯燈的電源開關準備開始閱讀精采的小說時,即使手已經離開電源開關,檯燈也不會因為失去電源而關閉。同樣的情況也發生在我們關閉電源開關時,只要再次按下電源開關,檯燈就不會再放亮直到有人再次開啟電源。也就是說,當我們改變自持按鈕的狀態後,按鈕就會一直維持在同一個狀態,直到有人再次改變它。

那麼瞬時按鈕呢?電腦鍵盤上的每一個按鍵都可以看作是一個瞬時按鈕。當你按下 A 這個按鍵時,電腦會接收到電路開通的訊號並打出 a 這個字母。而當你放開按鍵後,這個按鍵的電路就被斷開了。不過這裡有一個問題,那就是如果一直按著 A 按鍵,這時候電腦要怎麼處理?從電路的角度來看,只要一直按著 A 按鍵,就表示電路一直在開通的狀態,所以電腦會不斷地輸出 a 這個字母嗎?如果你一直按著 A 按鍵不放,會發現電腦確實會一直不厭其煩的輸出一大堆的 a 字母。不過如果我們因此就認為電腦只是因為電路的開通就直接輸出 a 字母,那可真是誤會大了。因為電腦的處理的速度實在太快了,如果只要每次電腦判斷出電路開通就輸出一個 a 字母,那可能我們僅是輕輕按一下 A 按鍵螢幕上就塞滿了 a 字母,更別說是按著 A 按鍵不放了。所以電腦通常會在收到電路開通的訊號並作出回應後,暫停一段時間才會再次檢查電路的狀態,以避免因為電腦速度太快而重複作了多餘的動作。這個停頓的動作,不一定要靠軟體來完成,也可以由電路來控制。只是由軟體來完成,通常會比較有彈性。

針對這個問題,其實我們還有另外一個選擇,那就是不要管一直按著這件事。也就是說,不管按了多久,都只算按了一次,系統也只會有一次的反應。以鍵盤的例子來說,就是不管你按了 A 按鍵多久,螢幕上都只會出現一個 a 字母。這樣做雖然通常可以減化軟體的處理,但是在很多時候可能對使用者操作而言就不是那麼方便了。為什麼我前面的句子是說 “通常”,難道有甚麼但書嗎。是的,因為一般按鈕還有一種稱為回彈 (bounce) 的現象。所謂的回彈,簡單來說就是當我們按下按鈕時,對電路而言並不是馬上就由斷開變成連通的狀態,按鈕的接觸點會有一些反彈跳動的情況,所以造成電路也處於一種不穩定的狀態。當然,這個跳動的時間很短暫,但是如果沒有好好地加以處理,仍有可能造成程式的誤判。

四接腳接觸式瞬時按鈕

按鈕除了根據開關狀態變化的方式加以分類外,還可以依據互動方式、接腳方式、可控制電路數量、構造等方式加以分類。

我們今天範例使用的按鈕如下圖,屬於瞬時按鈕的一種,其接腳可以直接崁入麵包板。雖然背面看起來有四隻接腳,不過只能控制一個電路。如果以圖中的方向來看,即使未按下按鈕,左邊兩隻接腳仍處於閉路的狀態。同樣的,右邊兩隻接腳也處於閉路的狀態。但是左右兩邊卻是開路的,只有而當按鈕按下時,左右兩邊才會形成閉路 (參見下方第三張圖)。

瞬時按鈕-正面

瞬時按鈕-正面

瞬時按鈕-背面

瞬時按鈕-背面

瞬時按鈕-電路表示圖

瞬時按鈕-電路表示圖

Python 虛擬環境安裝

因為這個範例僅使用 RPi.GPIO 套件,所以準備工作與 在 Raspberry Pi 3 Model B 建立 Python 3.6 環境 一致。如果你還沒有完成相關的準備,請務必先行完成再繼續進行後面的工作。

線路圖

Raspberry Pi 與瞬時按鈕、LED 應用

Raspberry Pi 與瞬時按鈕、LED 應用

說明如下:

  1. 按鈕放置方向與圖片中之方向一致,確保左邊兩隻接腳與右邊兩隻接腳在按鈕未按下之前屬於開路的狀態。
  2. 按鈕接腳有一定的彎曲程度,需用力壓下才能貼緊麵包板,以確保線路可正常連結。
  3. 此按鈕為無方向性的元件,一端接地,另一端連結作為輸入的 GPIO 腳位。按鈕未按下前為開路,GPIO 之輸入腳位維持在高電位。當按下按鈕後形成閉路,GPIO 則因為接地而變成低電位。
  4. 一般而言,按鈕的輸出接腳需透過上拉電阻以確保開路時可以維持在高電位。但在這個範例中我們將使用 Raspberry PI 內建的上拉電阻,因為不需要另外準備或接線。
  5. 此按鈕還有另外一種接法,就是一端連結至 +3.3V 的電源,另外一端則同樣接入作為輸入用的 GPIO 腳位。不過此時會變成需要下拉電阻以確保當按鈕未按下時輸入腳位可以維持在低電位,而當按鈕按下時則變成高電位。
  6. LED 陽極 (長腳) 連結至 Raspberry Pi 實體編號 16 的腳位,其 BCM 編號為 23。
  7. LED 陰極 (短腳) 串聯一個 1K 歐姆的限流電阻後連結至 Raspberry Pi 實體編號 6 的腳位 (接地)。

程式代碼(一)

利用文字編輯器 (如 nano) 新增 Python 程式 button_poll.py

內容如下

程式說明如下:

  1. 第 2 行引入 GPIO 套件,這個範例僅需要基本的 GPIO 控制功能。
  2. 第 4 行定義瞬時按鈕輸出所連結的腳位,範例使用實體編號 12 的腳位,其 BCM 編號為 18。
  3. 第 5 行定義控制 LED 的腳位,範例使用實體編號 16 的腳位,其 BCM 編號為 23。
  4. 第 7~11 行定義 my_callback 函式,此函式會在螢幕上印出”按下按鈕”四個字,並使得 LED 發亮 0.1 秒。
  5. 第 13 行使用 BCM 編號方式。
  6. 第 14 行將瞬時按鈕輸出所使用的腳位設定為輸入模式。在說明電路圖時我提到範例將使用 Raspberry Pi 內建的上拉電阻,其中我們第一次看到的 pull_up_down=GPIO.PUD_UP 參數就是用來開啟內建上拉電阻的選項。
  7. 第 15 行將控制 LED 燈泡所使用的腳位設定為輸出模式。
  8. 第 20~21 行不斷重複檢查按鈕是否被按下,也就是按鈕的輸出腳位處於低電位。一旦按鈕被按下,就呼叫 my_callback 函式。
  9. 第 26 行確保在程式結束後,程式中所使用到的腳位皆回復到預設狀態,也就是輸入模式。

程式完成後,輸入下列指令執行程式

當程式執行時,我們按下按鈕後就可以看到螢幕上出現”按下按鈕”的文字,而且 LED 持續發亮 0.1 秒鐘。

輪詢 vs. 事件通知

這個程式跟我們之前看過的許多程式一樣,重複不斷地判斷輸入腳位的狀態,並根據判斷結果決定程式的行為。這種重複詢問的動作有一個專有名詞,叫作輪詢 (Polling)。採用輪詢有幾個問題,首先是如果我們按著不放,程式就會不斷的重複執行 my_callback 這個函數。另外一個問題則是,如果我們快速的連按兩下按鈕,只會執行一次 my_callback 函式。第一個問題可以透過軟體加以避免,但是第二個問題則很難用軟體加以處理。因為兩次輪詢之間一定會間隔一小段時間,而如果按鈕在這段時間之內被按下又馬上放開,程式就會因為沒有輪詢而無法得知此按鈕曾被短暫按下。此外,當我們需要檢查的按鈕數量一多 (想像一下每個鍵盤上有多少按鈕),程式的複雜度就會大幅增加,而且執行效率也會受到影響。

如果系統可以在按鈕被按下的時候主動通知我們的程式,而且只通知一次,那麼上述的問題就可以獲得解決。事實上,我們熟悉的 RPi.GPIO 套件確實提供這個功能,稱之為事件通知的機制。輪詢機制的問題以及兩種機制之間的比較可參見下圖,其中高電位表示按鈕未被按下,而低電位則表示按鈕是被按下的。為了方便說明,在此先忽略反彈的影響。

輪詢與事件通知比較

輪詢與事件通知比較

接下來我們就來看看如何利用事件通知機制來獲得按鈕已被按下的狀態。

程式代碼(二)

利用文字編輯器 (如 nano) 新增 Python 程式 button_event.py

內容如下

程式說明如下:

  1. 此程式代碼與前一個代碼幾乎完全一樣,後面僅針對不同之處加以說明。
  2. 第 16 行註冊當輸入腳位由高電位變成低電位 (GPIO.FALLING) 時,執行 my_callback 函示。注意 callback 參數是指定函式的名稱,而不是執行 my_callback 這個函式,所以在 my_callback 後面不需要加上 ()。這類函式有一個專門的名詞,叫做回呼函式,表示是讓系統在發生特定事件時所呼叫的函式。此外,設定 bouncetime 為 250 毫秒 (也就是 0.25 秒),表示第一次電位改變後 250 毫秒之內的電位改變都會被程式所忽略。
  3. 第 20~21 行確保程式會一直重複執行。在這個無限迴圈中,我們不再需要定期檢查瞬時按鈕輸出腳位的狀態,因為腳位的變化已經經由系統主動通知我們的程式。
  4. 第 25 行確保在程式結束後,程式中所使用到的腳位皆回復到預設狀態,也就是輸入模式。

程式完成後,輸入下列指令執行程式

當程式執行時,我們按下按鈕後就可以看到螢幕上出現”按下按鈕”的文字,而且 LED 持續發亮 0.1 秒鐘,就如同前一個程式代碼。

透過改用事件通知的機制,我們可以連續多次按下按鈕而不會錯失按鈕的動作。實際觀察中,bouncetime 設定為 0.25 秒也不會因為反彈而產生重複通知的情況。我們在輪詢的程式中並沒有考慮反彈的問題,原因是反彈的時間比輪詢的時間間隔還短,所以不會產生誤判。但是如果輪詢的時間間隔很短,那麼反彈一樣有可能造成誤判的情況。此外,採用事件通知機制不會有按著不放會有重複執行的情況發生,但是如果說我們想要持續按著不放時可以重複執行特定的功能,那就必須要調整回呼函式 my_callback 的程式邏輯了。

一個看似簡單的小小按鈕,除了背後的學問不簡單之外,程式在與之互動時也有不少需要特別注意的地方。然而按鈕提供了使用者可用來控制系統的簡單介面,所以在實務上的應用亦不可少。Python 程式可以使用輪詢或事件通知的方式取得瞬時按鈕的狀態,而在大多數的情況下,使用事件通知會是比較好的選擇。不過事件通知的用法與我們之前看過的其他感測器有所不同,因此需要多花點心力去適應。

如果對文章的內容有任何疑問或建議,歡迎與我聯絡。

Facebook 留言
Print Friendly, PDF & Email
Summary
Raspberry Pi 3 Model B 與瞬時按鈕
Article Name
Raspberry Pi 3 Model B 與瞬時按鈕
Description
在這篇文章中,我們將討論一個看似再簡單不過的元件-瞬時按鈕。在文章的前半段,我們會稍微解釋何謂瞬時按鈕。後半段則提供 Python 程式範例,實際了解在 Raspberry Pi 上如何利用 Python 程式取得瞬時按鈕的狀態並作出回應。
Author
Publisher Name
Everlearn Studio
Publisher Logo