關閉

第10章 Arduino PWM 教學:analogWrite、占空比與 LED 調光實作

本章介紹 PWM(Pulse Width Modulation,脈衝寬度調變)的基本原理,說明 Arduino Uno 為何沒有真正的類比輸出,以及 analogWrite() 如何透過快速切換 HIGH 與 LOW 產生近似類比控制效果。內容涵蓋 Duty Cycle、PWM 腳位、0~255 數值範圍、LED 調光、呼吸燈、可變電阻控制亮度,以及馬達與 Servo 控制時的注意事項。

PWM(Pulse Width Modulation,脈衝寬度調變)是 Arduino 常用的輸出控制技術,可用來調整 LED 亮度、直流馬達轉速與部分負載的平均功率。

Arduino Uno 並沒有真正的類比電壓輸出。analogWrite() 會讓指定腳位快速切換 HIGH 與 LOW,透過改變高電位所占的時間比例,產生近似類比輸出的平均效果。

本章將從 Duty Cycle(占空比)開始,說明 Arduino Uno 的 PWM 腳位、analogWrite() 數值範圍、LED 調光與呼吸燈實作,並整理常見錯誤與安全注意事項。

本章學習目標

什麼是 PWM?

PWM 是 Pulse Width Modulation 的縮寫,中文稱為脈衝寬度調變。

PWM 並不是直接輸出連續變化的類比電壓,而是以固定頻率快速切換 HIGH(Arduino Uno 通常約為 5V)與 LOW(約為 0V),再改變 HIGH 持續時間占整個週期的比例。

當切換速度足夠快時,人眼或負載通常會感受到平均效果。例如 LED 會呈現不同亮度,直流馬達會產生不同轉速。蜂鳴器的音調控制則通常應搭配 tone() 等更適合的方式。

Arduino Uno 有真正的類比輸出嗎?

Arduino Uno 使用的 ATmega328P 並沒有內建 DAC(Digital to Analog Converter),因此不能直接從一般輸出腳位產生真正連續的 2V、3V 或 4V 類比電壓。

analogWrite() 的名稱容易讓初學者誤會。它實際輸出的是 PWM 訊號,也就是 HIGH 與 LOW 的高速切換,而不是 DAC 類比電壓。

analogRead() 與 analogWrite() 的工作方向不同。analogRead() 使用 ADC(Analog to Digital Converter)把輸入電壓轉成數位值;analogWrite() 則以 PWM 方式控制輸出的平均效果。

PWM 的工作原理與 Duty Cycle

Duty Cycle(占空比)表示在一個 PWM 週期內,HIGH 所占的時間百分比。

占空比為 0% 時,輸出會持續維持 LOW;占空比為 100% 時,輸出會持續維持 HIGH。介於兩者之間時,腳位會快速切換 HIGH 與 LOW。

LED 因為人眼的視覺暫留而呈現不同亮度,通常不會直接看到高速閃爍。對馬達等負載而言,PWM 則會改變其接收到的平均功率。

0%    ____________________
25%   ████____████____
50%   ████████________
75%   ████████████____
100%  ████████████████

Arduino Uno 哪些腳位支援 PWM?

Arduino Uno R3 提供 6 個 PWM 輸出腳位,分別是 D3、D5、D6、D9、D10 與 D11。

開發板上的 PWM 腳位旁通常會印有波浪符號「~」。進行接線前,應先確認所使用的腳位是否具備 PWM 功能。

在不支援 PWM 的一般數位腳位上呼叫 analogWrite(),無法得到預期的連續占空比調整效果。

analogWrite() 基本語法

analogWrite(pin, value);

pin 是支援 PWM 的輸出腳位,value 是 0~255 的整數。

例如 analogWrite(9, 128) 表示在 D9 輸出約 50% 占空比的 PWM 訊號。

analogWrite(9, 128);

PWM 數值範圍為什麼是 0~255?

Arduino Uno 的 analogWrite() 使用 8 位元 PWM 數值。8 位元可以表示 2 的 8 次方,也就是 256 個不同數值。

由於數值從 0 開始,因此可用範圍為 0~255。數值越大,HIGH 所占的時間通常越長,LED 也會看起來越亮。

實驗一:使用 PWM 控制 LED 亮度

準備材料

LED 接線方式

將 D9 經過 220Ω 限流電阻連接至 LED 正極,LED 負極再接到 GND。

D9 必須是支援 PWM 的腳位。LED 一定要串接適當的限流電阻,避免 LED 或 Arduino 腳位因電流過大而損壞。

D9
│
220Ω 電阻
│
LED
│
GND

固定亮度程式

const int LED_PIN = 9;

void setup()
{
}

void loop()
{
  analogWrite(LED_PIN, 50);
}

程式會持續在 D9 輸出 PWM 值 50,LED 預期會呈現偏暗的亮度。

analogWrite() 設定後,PWM 會由硬體計時器持續輸出,因此不需要在 loop() 內不斷改變數值,除非程式需要動態調光。

測試不同亮度

analogWrite(LED_PIN, 0);    // 熄滅
analogWrite(LED_PIN, 128);  // 約半亮
analogWrite(LED_PIN, 255);  // 最亮

可逐一測試 0、64、128、192 與 255,觀察 LED 亮度的變化。

人眼對亮度的感受並不是完全線性,因此 PWM 數值增加一倍時,主觀亮度不一定也正好增加一倍。

實驗二:製作 LED 呼吸燈

呼吸燈會讓 LED 從暗逐漸變亮,再從亮逐漸變暗。做法是使用 for 迴圈依序調整 analogWrite() 的數值。

delay(5) 用來控制每次亮度變化之間的等待時間。數值越大,呼吸速度越慢;數值越小,變化速度越快。

const int LED_PIN = 9;

void setup()
{
}

void loop()
{
  for (int i = 0; i <= 255; i++)
  {
    analogWrite(LED_PIN, i);
    delay(5);
  }

  for (int i = 255; i >= 0; i--)
  {
    analogWrite(LED_PIN, i);
    delay(5);
  }
}

PWM 可以控制哪些元件?

Arduino 腳位能提供的電流有限。PWM 只應作為控制訊號,不可直接驅動大電流馬達、加熱器或其他高功率負載。

控制感性負載時,還應考慮反向電動勢與保護元件。例如直流馬達通常需要續流二極體或由馬達驅動模組提供相應保護。

PWM 能直接控制 Servo 嗎?

不建議使用 analogWrite() 直接控制 Servo。

Servo 需要固定週期與特定脈衝寬度的控制訊號,其控制方式與一般 analogWrite() 的 PWM 調光不同。Arduino 程式通常使用 Servo 函式庫與 servo.write() 進行控制。

#include <Servo.h>

Servo servo;

void setup()
{
  servo.attach(9);
  servo.write(90);
}

void loop()
{
}

常見錯誤與故障排除

analogWrite() 沒有產生預期的調光效果

先確認接線使用的是 D3、D5、D6、D9、D10 或 D11。若使用 D8 等不支援 PWM 的腳位,就無法得到預期的占空比調整效果。

也應確認 LED 極性、GND 是否共地、限流電阻是否正確串接,以及程式指定的腳位是否與實際接線一致。

量測到的電壓不像穩定的類比電壓

這是正常現象。analogWrite() 輸出的是 HIGH 與 LOW 的 PWM 方波,不是 DAC 產生的連續電壓。

一般萬用電表可能顯示近似平均值,但示波器會清楚顯示快速切換的脈衝波形。需要真正平滑的類比電壓時,應使用具備 DAC 的控制器,或搭配低通濾波與適當的緩衝電路。

LED 沒有串接限流電阻

LED 不可直接接在 Arduino 輸出腳位與 GND 之間。即使使用 PWM,LED 仍需要適當的限流電阻。

本章示範使用 220Ω 電阻。實際阻值應依供電電壓、LED 順向壓降與目標電流計算。

馬達轉不動或 Arduino 重啟

不要直接從 Arduino I/O 腳位驅動直流馬達。馬達啟動電流可能遠高於腳位可承受範圍,造成電壓下降、系統重啟或硬體損壞。

應使用 MOSFET、H-Bridge 或 Motor Driver,並依負載需求提供獨立電源。Arduino 與外部驅動電路通常需要共地。

本章實作練習

練習一:設定五種 LED 亮度

使用 analogWrite() 依序輸出五個不同數值,例如 0、64、128、192 與 255,觀察 LED 亮度變化。

練習二:調整呼吸燈速度

修改呼吸燈程式中的 delay() 數值,分別測試快速、一般與緩慢的呼吸效果,並記錄不同設定的差異。

練習三:使用可變電阻控制 LED

使用上一章的可變電阻與 analogRead() 讀取 0~1023 的 ADC 數值,再使用 map() 轉換成 0~255,最後交給 analogWrite() 控制 LED 亮度。

const int POT_PIN = A0;
const int LED_PIN = 9;

void setup()
{
  Serial.begin(9600);
}

void loop()
{
  int adcValue = analogRead(POT_PIN);
  int pwmValue = map(adcValue, 0, 1023, 0, 255);

  analogWrite(LED_PIN, pwmValue);

  Serial.print("ADC: ");
  Serial.print(adcValue);
  Serial.print("  PWM: ");
  Serial.println(pwmValue);

  delay(20);
}

Challenge:加入平滑效果

在旋鈕調光器中加入多次取樣平均,並在 Serial Monitor 同時顯示 ADC 值與 PWM 值。

比較加入平均前後的 LED 穩定度,觀察可變電阻輸入雜訊是否造成亮度抖動。

本章重點整理

下一章預告

第 11 章將介紹 UART(TTL Serial)與 Serial 通訊,包括 TX 與 RX 接線、Serial.begin()、Serial.print()、Serial.println(),以及 TTL UART 與傳統 RS-232 的差異。