National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 80 24°C PM2.5 21
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 06

按鈕輸入與事件

上拉電阻、按鈕事件與彈跳
Interaction 25 minute(s)

Learning Objectives

  • 說明輸入腳位為什麼需要上拉或下拉電阻,以及 gpiozero 的 Button 預設怎麼接
  • 用 is_pressed 輪詢按鈕狀態,讓 LED 跟著按鈕亮暗
  • 用 when_pressed、when_released 指定事件處理函式,並說明為什麼函式名稱後面不加括號
  • 說明按鈕彈跳造成的問題,並用時間間隔去除彈跳

讓程式知道外面發生了什麼事

上一課的 LED 是輸出:程式決定腳位的電壓。這一課反過來,把腳位設成輸入:程式讀取腳位上的電壓, 判斷是高電位(約 3.3V,讀到 1)還是低電位(約 0V,讀到 0)。按鈕、開關、人體感測器都是這樣接上來的。

這一課要回答:

  1. 按鈕只有兩支腳,為什麼還要談「上拉電阻」?
  2. 程式要一直問「按了沒?」,還是讓按鈕按下時通知程式?
  3. 明明只按一次,程式為什麼可能算成好幾次?

浮接與上拉電阻

按鈕的作用只是「接通」或「斷開」。如果把按鈕接在 GPIO 與 GND 之間:

  • 按下:GPIO 接到 GND,讀到 0。
  • 放開:GPIO 什麼都沒接,叫做浮接(floating)。這時讀到的值不固定,手靠近、旁邊的電線有訊號都可能讓它亂跳。

解決方法是加一顆上拉電阻(pull-up resistor):用一顆電阻把 GPIO 接到 3.3V。放開時,電阻把電壓「拉」到 3.3V,穩定讀到 1; 按下時,GPIO 直接接到 GND,讀到 0(電阻限制了從 3.3V 流到 GND 的電流)。反過來接到 GND 的叫下拉電阻(pull-down)。

樹莓派的 GPIO 晶片裡就有可以用程式開啟的上拉、下拉電阻,所以不用另外接。gpiozero 的 Button 預設 pull_up=True:

接法 程式 放開 按下 is_pressed
按鈕接在 GPIO 與 GND 之間 Button(27)(預設 pull_up=True) 高電位 低電位 按下時 True
按鈕接在 GPIO 與 3.3V 之間 Button(27, pull_up=False) 低電位 高電位 按下時 True

不管哪一種接法,is_pressed 都是「按下時為 True」:gpiozero 依你的接法幫你換算好,不用自己記「按下是 0 還是 1」。

GPIO27(實體第 13 腳)──[ 按鈕 ]── GND(實體第 14 腳)     內部上拉到 3.3V
GPIO17(實體第 11 腳)──[ 330Ω ]──▶|── GND               上一課的 LED

按鈕不要接在 GPIO 與 5V 之間:GPIO 不耐 5V。

逐步看:輪詢,LED 跟著按鈕

按鈕的時間表設定成在 0.6~1.3 秒、1.9~2.4 秒被按下(電路畫面上的按鈕會變色)。 程式每 0.1 秒問一次「按了沒?」,這叫輪詢(polling)。注意 LED 亮起來的時間:按鈕在 0.6 秒按下, 程式要到下一次檢查才發現,所以 LED 稍微晚一點才亮。

事件:按下時通知我

輪詢的程式整天都在問「按了沒?」。另一種寫法是事件(event):先告訴 gpiozero「按下的時候,請呼叫這個函式」, 之後程式就可以去做別的事(或用 pause() 等著)。

button.when_pressed = pressed     # 指定函式:不加括號
button.when_pressed = pressed()   # 錯誤:這是「現在就呼叫」,把回傳值 None 指定給 when_pressed

pressed 是函式物件本身;pressed() 是呼叫它並得到回傳值。被交給別人、等事件發生時才呼叫的函式,叫做回呼函式(callback)。 也可以直接交出物件的方法,例如 button.when_pressed = led.on,按下時就會執行 led.on()。

在模擬器裡,事件處理函式會在 sleep() 或 pause() 等待的期間被呼叫;真的 gpiozero 用另一個執行緒處理事件,概念相同。

逐步看:when_pressed 與 when_released

程式設定好兩個事件就停在 pause()。看步驟的跳躍:每次按下或放開,執行就從 pause() 跳進對應的函式,做完再回去等。按鈕在 0.5、1.5、2.3 秒被按下。

按鈕彈跳

按鈕裡是兩片金屬接點。按下的瞬間,接點會碰到、彈開、再碰到,在很短的時間內(通常是毫秒等級)來回好幾次, 叫做彈跳(bounce)。人感覺不到,但程式反應很快,會把一次按下當成好幾次。

理想的按下:  ‾‾‾‾‾‾‾‾\________________/‾‾‾‾‾
實際的按下:  ‾‾‾‾‾‾‾‾\_/‾\_/‾\_________/‾‾‾‾‾
                     ↑ 彈跳:程式可能看到 3 次按下

去彈跳(debounce)的常見做法是:偵測到一次之後,一小段時間內的變化都不算。gpiozero 的 Button 有 bounce_time 參數 (單位是秒)做這件事,例如 Button(27, bounce_time=0.05)。下面的範例自己用時間間隔實作,讓你看到原理 (這個模擬器目前沒有模擬 bounce_time 參數)。也可以在硬體上加電容濾掉彈跳,「自造者入門」軌第 07 課會介紹。

逐步看:一次按下被算成三次

按鈕的時間表模擬了彈跳:0.4 秒按下時接點彈了三次(400、405、413 毫秒),1.5 秒按下時彈了兩次。 raw 照單全收,real 只算和上一次相隔 50 毫秒以上的那一次。看最後的輸出:raw 是 5,real 是 2。

互動練習:按鈕控制 LED

按鈕接 GPIO27、LED 接 GPIO17。「輸入設定」可以改按鈕被按下的時間(秒),例如 0.5-0.8, 1.5-1.8。依序完成任務:

  1. 先直接執行起始程式:按下後 LED 亮了,但放開後不會熄滅。
  2. 加一行,讓 LED 跟著按鈕:按著亮、放開暗。
  3. 改成「每按一次就切換亮暗」(提示:led.toggle),並把按鈕時間改成按 3 次;最後 LED 應該是亮的。
  4. 回答一個問題。

小測驗:上拉電阻與讀值

1.

按鈕接在 GPIO27 與 GND 之間,程式寫 button = Button(27)。按鈕放開時,GPIO27 的電位是?

2.

同樣的接法,按鈕按下時 button.is_pressed 是什麼?

3.

按鈕接在 GPIO27 與 3.3V 之間,程式應該怎麼寫?

第 1 次作答

小測驗:事件與回呼函式

1.

下面的程式(不需要硬體)模擬「設定回呼函式」,會印出什麼?

def hello():
    print("hi")
    return 5

handler = hello
result = hello()
print(handler is hello, result)
2.

寫成 button.when_pressed = pressed()(多了括號)會發生什麼事?

第 1 次作答

重點整理

  • 輸入腳位不能浮接:用上拉電阻(接 3.3V)或下拉電阻(接 GND)給它一個確定的電位;樹莓派晶片內建可開關的上拉、下拉電阻。
  • Button(27) 預設 pull_up=True:按鈕接 GPIO 與 GND;is_pressed 不論接法都是「按下時為 True」。
  • 輪詢:迴圈裡反覆讀 is_pressed;事件:when_pressed = 函式(不加括號),用 pause() 等待。
  • 彈跳會讓一次按下被算成好幾次;用 bounce_time、時間間隔或硬體濾波去除。

下一課用 PWM 調整 LED 的亮度,讓蜂鳴器發出不同的音高。

這一課完成了!
Next lesson:PWM:調光與聲音
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。