JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

與世界對話:GPIO、數位輸入輸出與 PWM

在第一階你讓一支接腳閃爍。但只會閃爍的接腳,就像被關在盒子裡的大腦,只能抽動一根手指。這一次,我們把那根手指接上真實世界:讀取按鈕、點亮 LED、敲下繼電器;接著學會那個魔術師的把戲——讓一顆只懂兩種電壓(開與關)的晶片,把燈調暗一半、讓馬達剛好以半速旋轉。

一支接腳,兩種人格

拿放大鏡看一顆微控制器,你會看到它周身佈滿金屬接腳——28 腳的 AVR、樹莓派的 40 腳排針、ESP32 邊緣那數十個焊墊。這些接腳大多是 GPIO 腳:*通用輸入/輸出*(general-purpose input/output)。通用這個詞正是關鍵。每一支接腳都是一隻你用韌體去設定的小變色龍,而它有兩種人格。作為輸出時,接腳變成 CPU 能撥動的開關——拉成 HIGH 就推出電源電壓(比如 3.3 V),拉成 LOW 就拉到 0 V。作為輸入時,同一支接腳變成一隻耳朵:它停止驅動,改為*聆聽*,回報外界是把它拉到接近 3.3 V(邏輯 1)還是接近 0 V(邏輯 0)。

在矽晶片內部,這個人格開關是真實的硬體。一個*資料方向暫存器*(每支接腳一個位元)決定要接上輸出驅動級(一對能把接腳猛拉到電源軌的電晶體),還是改接一個高阻抗的輸入緩衝器去讀取接腳電壓。設定那一個位元,就是決定接腳是在*喊話*還是在*聆聽*。方向設錯,你的按鈕就讀到亂碼,更慘的是,你的 LED 永遠不會亮。

輸出:點亮 LED、敲下繼電器

最經典的第一個輸出就是 LED——它其實就是一顆當電流以正確方向流過時會發光的二極體。但你不能把 LED 從接腳直接接到地。LED 幾乎沒有內部電阻;一旦在約 2 V 附近「導通」,它就會把接腳能給的電流通通吞下,把 LED 和接腳的輸出驅動級一起燒掉。解法是一顆樸實的串聯電阻,而你用歐姆定律來決定它的大小。

  3.3 V ──[ pin ]──►├── LED (Vf ≈ 2.0 V) ──[ R ]── GND
                     (anode)              (resistor)

  Voltage left for the resistor:
    V_R = V_supply − V_f = 3.3 V − 2.0 V = 1.3 V

  Pick a safe LED current, say I = 10 mA = 0.010 A:
    R = V_R / I = 1.3 V / 0.010 A = 130 Ω

  Nearest standard value: 150 Ω  →  I ≈ 1.3 / 150 ≈ 8.7 mA  (safe)
用歐姆定律決定 LED 串聯電阻。電阻「吃掉」剩餘的電壓並設定電流;沒有它,LED 會吸光所有能拿到的電流然後燒毀。

現在把規模放大。繼電器線圈、馬達、或一串 LED,所需的電流遠超過 GPIO 接腳能安全提供的——一支接腳通常只能承受 20~40 mA,而一個小繼電器線圈可能要 70 mA,馬達更要好幾百毫安。叫接腳硬擠這麼多電流,你會把它燒了。所以接腳並不*親自*出力,它只負責*下令*。GPIO 接腳去驅動一顆 MOSFET 的閘極、或一顆電晶體的基極,而那顆電晶體——一塊能扛功率的肌肉——才去切換真正的負載。接腳是扣板機的手指,電晶體才是板機。

輸入:讀按鈕,而不是讀雜訊

讀一個開關聽起來很簡單:把按鈕接在接腳與地之間,一按,接腳就變 LOW。但當按鈕*沒*被按下時,接腳到底在讀什麼?這時沒有任何東西接著它。一支懸空的高阻抗輸入腳叫做浮接(floating),它根本就是一根天線——會撿拾來自市電、來自你的手、來自附近導線的雜散電場,在 0 與 1 之間隨機跳動。你的程式就看到鬼魅般的按鍵。這是所有嵌入式系統裡,最常見的新手 bug 之一。

解藥是一顆上/下拉電阻——當沒有別的東西在驅動接腳時,這顆電阻溫柔地把它綁到一個已知電壓。*上拉*(接到 3.3 V)讓閒置的接腳停在邏輯 1;按下一個接地的按鈕就把它牢牢拉到 0。*下拉*(接到地)讓它停在 0;一個接到 3.3 V 的按鈕則把它驅動到 1。這顆電阻很大——通常 10 kΩ——所以當按鈕*被*按下時,只有涓滴電流(3.3 V / 10 kΩ ≈ 0.33 mA)白白流過它,但它又夠小,足以壓制那幾皮法(picofarad)的雜散感應。這招太方便了,幾乎每顆現代 MCU 都內建了你用單一暫存器位元就能啟用的*內部*上拉電阻——連外接零件都省了。

  Pull-up configuration (button reads LOW when pressed):

     3.3 V
       │
      ┌┴┐  R_pull = 10 kΩ
      │ │
      └┬┘
       ├────────────►  to GPIO input pin  (reads 1 when idle)
       │
      _|_  push-button
       o
       │
      GND                (pressing pulls the pin to 0)
上拉電阻把輸入維持在明確的邏輯 1,直到按鈕把它短路到地。把電源與地對調,就成了下拉。

PWM:用數位接腳偽造類比

這就是 PWM 要解的謎題。一支 GPIO 輸出腳只能做兩件事:全開(3.3 V)或全關(0 V)。沒有「請給我 1.65 V」這個選項。那你要怎麼把 LED 調到半亮,或讓馬達跑 30% 的速度?你以為需要一顆數位類比轉換器去合成中間值的電壓。但有個更便宜、近乎耍賴的把戲:別去做出半個電壓——做出整個電壓,但只做一半的時間。 把接腳快到 LED、馬達、或你的眼睛都跟不上的速度開開關關,它們所*感受*到的,就是那個平均值。

這就是 脈衝寬度調變。接腳以固定頻率不斷送出一列方波脈衝,而你唯一要轉的旋鈕,就是[[ee-duty-cycle|占空比]](duty cycle)——每個週期裡接腳處於 HIGH 的比例。0% 占空比就是一直關;100% 就是一直開;50% 就是剛好開一半時間。關鍵在於,*頻率*維持固定(常見是幾百 Hz 到數十 kHz)——你只是把「開」的脈衝拉寬或縮窄。所以才叫*脈衝寬度調變*。

  Three duty cycles, same frequency. ─ = HIGH (3.3 V), _ = LOW (0 V)

  25%   ──________──________──________   avg ≈ 0.25 × 3.3 V = 0.83 V

  50%   ────____────____────____         avg ≈ 0.50 × 3.3 V = 1.65 V

  75%   ──────__──────__──────__         avg ≈ 0.75 × 3.3 V = 2.48 V

        |←  one period T  →|
           (e.g. T = 1 ms  →  frequency = 1 kHz)
相同週期,不同脈衝寬度。那條虛擬的平均值,就是慢速負載——你的眼睛、一顆 LED、馬達的慣性——實際感受到的東西。

為什麼是*平均值*重要?因為每一個真實負載,本質上都是一個喬裝過的低通濾波器。你眼睛的視覺暫留會把快於約 60~90 Hz 的閃爍糊成穩定的光,所以一顆以 1 kHz、25% 占空比眨眼的 LED,看起來就只是比較暗。馬達的機械慣性追不上 20 kHz 的開關;它回應的是平均扭矩,所以轉得比較慢。再加上真正的電容與電感(LC 濾波器),方波就被平滑成一個貨真價實的直流電壓——而這,恰好就是降壓轉換器把 PWM 變成高效率可調電源的方式。

把數字算出來:調光與伺服機

我們把「平均電壓」的概念算具體一點。占空比 D 不過就是「開的時間」對「整個週期」的比值;對一個只回應平均值的負載而言,等效電壓就單純是 D 乘上電源電壓。算式清爽得令人欣慰——不用微積分,只是一個分數。

  Duty cycle:    D = t_on / T          (T = t_on + t_off)
  Average volts: V_avg = D × V_supply

  Example — dim a 3.3 V LED to a quarter brightness:
    Let T = 1 ms (frequency f = 1/T = 1 kHz)
    Want D = 25%  →  t_on = 0.25 × 1 ms = 0.25 ms,  t_off = 0.75 ms
    V_avg = 0.25 × 3.3 V = 0.825 V   (the LED 'sees' a quarter)

  Example — set the speed of a hobby servo (different convention!):
    Servos read the ABSOLUTE pulse WIDTH, not the duty ratio.
    Frame period T = 20 ms (f = 50 Hz), and:
        1.0 ms pulse  →  full counter-clockwise (0°)
        1.5 ms pulse  →  centre (90°)
        2.0 ms pulse  →  full clockwise (180°)
    Here a 1.5 ms / 20 ms = 7.5% duty commands the centre position.
PWM 的兩張臉。對 LED 或馬達,占空比決定平均值。對 RC 伺服機,絕對的高電位時間編碼出一個角度——同樣的波形,不同的意義。

注意第二個例子裡那個美妙的轉折。RC 伺服機根本不在乎*比例*——它量的是每個 HIGH 脈衝的字面寬度,並把轉軸轉到對應的角度。餵它 1.5 ms,它就指向正中央;1.0 ms,它就猛擺向一側。那 50 Hz 的框架速率只是一下規律的心跳,說著「下一道指令來了」。同樣的 PWM 硬體、同樣的方波——但這次*寬度本身*就是訊息,而非平均電壓。幾乎每一台 RC 車、無人機、機械手臂的轉向與油門,正是這樣控制的。

  1. 選一個夠高的 PWM 頻率,讓負載看不見一個個脈衝:幾百 Hz 就能讓 LED 閃爍躲過眼睛;20 kHz 以上則把馬達的嘯叫推到人耳聽覺之上。
  2. 依你想要的平均值選占空比:亮度/速度用 V_avg = D × V_supply,伺服機角度則用絕對脈衝寬度。
  3. 把週期與占空比寫進計時器/計數器暫存器,讓周邊自主產生整列脈衝。
  4. 透過電晶體驅動重負載,絕不直接用接腳——任何線圈或馬達都要加飛輪二極體。

從閃爍,到一台會感知、會行動的機器

退一步看看一支接腳帶我們走了多遠。在第一階,閃爍是一段獨白——晶片自言自語。如今同一批接腳會聆聽(透過上拉、去彈跳過的按鈕)、會說話(透過限流電阻的 LED)、會調度肌肉(透過電晶體的繼電器或馬達),甚至會*調變*——僅憑開與關,以快過世界能察覺的速度切換,就變出一整片平滑連續的亮度、速度與角度。

這就是嵌入式系統靈魂的縮影:一顆數位大腦,透過樸實的接腳伸向外界,去感受、去形塑一個類比世界。前方的一切——透過 I²CSPI 讀取感測器、用中斷在微秒內反應、在 RTOS 下同時調度許多工作——全都建立在這個基礎之上:有自信地知道每一支接腳正在做什麼,以及為什麼。