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 04

按鈕、中斷與彈跳

輪詢與中斷兩種讀按鈕的方式
Interaction 25 minute(s)

Learning Objectives

  • 用 Pin.IN 與 Pin.PULL_UP 讀取按鈕,說明為什麼按下時讀到 0
  • 說明浮接腳位的問題,以及上拉、下拉電阻的作用
  • 分辨輪詢與中斷兩種讀取方式,用 Pin.irq 在按下的瞬間執行函式
  • 說明按鈕彈跳的現象,用 ticks_ms 寫出軟體去彈跳

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

上一課把腳位設成輸出,程式控制 LED。這一課反過來:把腳位設成輸入,讀取外面的電壓, 程式就知道按鈕有沒有被按下。按鈕是最簡單的輸入裝置,之後的磁簧開關、傾斜開關、人體感測器,讀法都一樣。

這一課要回答:

  1. 按鈕怎麼接?為什麼按下時讀到的是 0,不是 1?
  2. 程式要「一直去看」按鈕,還是讓按鈕「通知」程式?
  3. 明明只按一下,為什麼程式算到好幾下?

輸入腳位只認兩種電壓

設成輸入的腳位會把外面的電壓判斷成 1(接近 3.3V)或 0(接近 0V):

from machine import Pin
button = Pin(4, Pin.IN)       # GPIO4 設成輸入
print(button.value())        # 讀到 1 或 0

什麼都沒接的腳位:浮接

按鈕只是一個開關:按下時兩端接通,放開時斷開。如果按鈕一端接 GPIO4、另一端接 GND, 放開時 GPIO4 什麼都沒接,這叫浮接(floating)。浮接的腳位會受到雜訊、手靠近、旁邊的導線影響, 讀到的值忽 0 忽 1,程式會以為有人一直在按。

解決方法是加一顆電阻,讓腳位在按鈕放開時有確定的電位:

接法 電阻接到 放開時讀到 按下時讀到
上拉電阻(pull-up),按鈕接 GND 3.3V 1 0
下拉電阻(pull-down),按鈕接 3.3V GND 0 1

ESP32 的腳位內建上拉與下拉電阻,用程式開啟就好,不必另外接:

button = Pin(4, Pin.IN, Pin.PULL_UP)     # 開啟內建上拉電阻

這一課都用「按鈕接 GPIO 與 GND,開啟內建上拉」的接法,所以按下=0、放開=1,邏輯是反過來的,寫程式時要記得。 上拉與下拉的電路原理,「自造者入門」軌第 06 課有完整說明。

ESP32 的 GPIO34~39 只能當輸入,而且沒有內建上拉、下拉電阻;接按鈕到這幾支腳位時,要自己接一顆外部電阻(常用 10kΩ)。

逐步看:輪詢按鈕,按下就亮

按鈕在 0.65 秒按下、1.35 秒放開(上方電路的按鈕會變色)。程式每 0.1 秒讀一次按鈕, 注意程式在下一次讀取時才發現按鈕的變化,所以印出的時間會比真正按下的時間晚一點。

輪詢與中斷

輪詢很直覺,但有兩個問題:

  • 可能漏掉:每 0.1 秒才看一次,按下又放開只花 0.05 秒的話,兩次讀取之間就錯過了;程式還在 sleep 裡做別的事,就更容易漏掉(第 07 課會詳細討論)。
  • 反應有延遲:最差的情況要等一整個間隔才發現。

另一種方式是中斷(interrupt):由硬體監看腳位,電位一改變就暫停主程式,去執行你指定的函式, 執行完再回到主程式剛才的地方。電位改變的那一瞬間叫邊緣(edge):

名稱 電位變化 在上拉接法中代表
下降邊緣 Pin.IRQ_FALLING 1 → 0 按下
上升邊緣 Pin.IRQ_RISING 0 → 1 放開
def on_press(pin):          # 中斷處理函式:參數是觸發的 Pin 物件
    ...

button.irq(trigger=Pin.IRQ_FALLING, handler=on_press)

兩種都要偵測時寫 trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING。

中斷處理函式(ISR)要短:主程式被暫停了,處理函式應該只做最少的事,例如把計數加一、設一個旗標, 複雜的工作(印很多資料、連網路、長時間 sleep)留給主程式看到旗標再做。 要修改函式外面的變數,記得寫 global。

真的 ESP32 上,中斷可能發生在主程式任意兩個指令之間;本站的模擬器簡化成在 sleep 或呼叫硬體函式時才執行處理函式,觀念相同。

逐步看:用中斷計算按了幾次

主程式只是每秒印一次計數,從來沒有讀按鈕;按鈕按下的那一刻(0.3、1.4、2.2 秒), 程式會「跳」到 on_press 執行,再回到 sleep 繼續等。注意左上角的時間。

按一下,算成好幾下:彈跳

按鈕裡面是兩片金屬接點。按下的瞬間,接點會碰到、彈開、再碰到,來回幾次才穩定, 這叫彈跳(bounce)。人的手感覺只按了一下,但電位其實在很短的時間內 1、0、1、0 跳了好幾次:

按鈕電位   1 ───┐ ┌┐ ┌──────────┐
                └─┘└─┘          (按住)
                ↑  ↑  ↑
           三次下降邊緣,只有第一次是真的

輪詢每 0.1 秒才讀一次,通常不會受影響;中斷反應很快,每一次彈跳都會觸發,計數就多了。

軟體去彈跳:記下上一次「算數」的時間,距離上一次太近(例如 50 ms 以內)的邊緣當成彈跳,直接忽略。 彈跳時間依按鈕而不同,50 ms 是常見的起點,實際要看你的按鈕調整。 也可以在硬體上加電容濾掉彈跳,「自造者入門」軌第 07 課會介紹。

下一個範例的按鈕故意設成會彈跳:第一次按下時電位跳了三次,第二次跳了兩次。

逐步看:軟體去彈跳

time.ticks_ms() 讀開機後經過的毫秒數,time.ticks_diff(a, b) 算 a 比 b 晚多少毫秒(第 07 課會說明為什麼不直接相減)。 看每一次中斷時 now 與 last_ms 差多少。

互動練習:會彈跳的按鈕

這個電路的按鈕在第 0.5 秒與第 2 秒按下時會彈跳(看「輸入設定」:0.5-0.505, 0.508-0.512, … 就是電位在跳), 第 3.3 秒那一次很乾淨。人只按了 3 次。依序完成任務:

  1. 直接執行起始程式,看程式算到幾次、LED 最後是亮還是暗。
  2. 在處理函式裡加上去彈跳,讓程式印出「按了 3 次」,而且最後 LED 是亮的(按 3 次=換 3 次狀態)。
  3. 改寫程式:按住按鈕時 LED 亮、放開就暗。可以用輪詢,也可以用中斷。
  4. 回答一個問題。

小測驗:上拉電阻與接線

1.

按鈕一端接 GPIO4、另一端接 GND,程式寫 Pin(4, Pin.IN, Pin.PULL_UP)。按住按鈕時 value() 讀到什麼?

2.

同樣的接法,但程式寫成 Pin(4, Pin.IN),沒有開啟上拉。放開按鈕時會怎樣?

3.

想把按鈕接到 GPIO35,下面哪個說法正確?

第 1 次作答

小測驗:中斷與去彈跳

1.

下面模擬去彈跳的程式:edges 是幾個下降邊緣發生的時間(毫秒),會印出什麼?

edges = [100, 104, 109, 400, 403, 900]
last = -1000
presses = 0
for now in edges:
    if now - last > 50:
        presses += 1
        last = now
print(presses)
2.

按鈕用上拉接法,想在放開按鈕的那一刻執行函式,trigger 要寫什麼?

3.

下面哪一件事不適合放在中斷處理函式裡?

第 1 次作答

重點整理

  • Pin(編號, Pin.IN, Pin.PULL_UP):設成輸入並開啟內建上拉電阻;按鈕接 GPIO 與 GND 時,按下=0、放開=1。
  • 什麼都沒接的輸入腳位會浮接,讀值不穩定;上拉、下拉電阻讓它有確定的電位。GPIO34~39 沒有內建上拉、下拉。
  • 輪詢:在迴圈裡每隔一段時間讀一次,簡單,但有延遲、可能漏掉很短的按壓。
  • 中斷:pin.irq(trigger=…, handler=函式),在下降(1→0)或上升(0→1)邊緣立刻執行處理函式;處理函式要短,修改外面的變數要寫 global。
  • 機械按鈕會彈跳:一次按下產生好幾個邊緣。軟體去彈跳的做法是記下上次算數的時間,太近的邊緣忽略。

下一課用 PWM 調整 LED 亮度、讓蜂鳴器發出不同音高、控制伺服馬達的角度。

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