關閉

第18章 Arduino Library(函式庫)安裝、使用與自製函式庫完整教學

完整介紹 Arduino Library 函式庫的用途、安裝與使用方式,包括 Library Manager、ZIP 匯入、手動安裝、內建範例、API 閱讀、相依性與版本衝突。並透過 SimpleLed 與 StatusLed 實作,說明 .h、.cpp、Class、Object、public、private、建構函式、library.properties、examples 與非阻塞 update() 設計,協助初學者建立可重複使用且容易維護的 Arduino 函式庫。

Arduino Library(函式庫)能將重複使用的硬體控制、通訊流程與資料處理程式封裝成清楚的介面。學會使用函式庫後,可以縮短開發時間,也能讓大型專案更容易閱讀、測試與維護。

本章先說明 Library Manager、ZIP 與手動安裝方式,再介紹標頭檔、類別、物件、建構函式、public、private 與 API。最後會實作 SimpleLed 與 StatusLed 函式庫,建立可重複使用的非阻塞 LED 控制模組。

函式庫可以簡化操作,但不能取代接線、電壓、I²C 位址、Timer、中斷、記憶體與硬體相容性的基本知識。

本章學習目標

為什麼 Arduino 專案需要函式庫

LED、按鍵、PWM、UART、I²C、SPI、EEPROM、Timer 與中斷等功能,經常會在不同專案中重複出現。若每次都複製相同程式,錯誤修正與功能更新就必須同步修改多份程式碼。

將重複功能整理成函式庫後,主程式只需呼叫清楚的 API,底層腳位控制、通訊、狀態管理與錯誤處理則由函式庫負責。

const int LED_PIN = 9;
bool ledState = false;
unsigned long previousMillis = 0;
const unsigned long BLINK_INTERVAL = 500;

void setup()
{
  pinMode(LED_PIN, OUTPUT);
}

void loop()
{
  unsigned long currentMillis = millis();

  if(currentMillis - previousMillis >= BLINK_INTERVAL)
  {
    previousMillis = currentMillis;
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState);
  }
}

什麼是 Arduino Library

Arduino 函式庫是一組經過整理的程式碼,用來提供特定功能。它可能控制感測器、顯示器、馬達或通訊協定,也可能封裝儲存、數學運算與常用程式流程。

使用函式庫後,複雜的暫存器、I²C 或 SPI 操作可以被包裝成 begin()、readTemperature()、setBrightness() 等容易理解的介面。

函式庫的使用者介面與內部實作

使用者通常只需要建立物件、初始化裝置並呼叫功能;函式庫內部則負責通訊、記憶體管理、暫存器設定、資料格式轉換與錯誤處理。

即使函式庫隱藏了底層細節,使用者仍應理解供電、共地、腳位功能、通訊位址、匯流排速度與硬體限制。

display.begin();
display.clearDisplay();
display.setCursor(0, 0);
display.print("Hello");
display.display();

Arduino Core、內建函式庫與第三方函式庫

pinMode()、digitalWrite()、analogRead()、millis() 與 delay() 等功能由 Arduino Core 提供,在 .ino 中通常不必手動加入 Arduino.h。

Wire、SPI、EEPROM、Servo 等功能通常要使用 #include 明確引用。函式庫可能由 Arduino IDE 內建、開發板核心附帶、第三方提供或由使用者自行建立。

使用函式庫的建議流程

使用 Library Manager 安裝函式庫

Arduino IDE 2.x 可從左側工具列開啟 Library Manager,也可從 Tools → Manage Libraries 進入。不同版本的選單位置可能略有差異。

搜尋結果可能出現多個同名或相似函式庫,因此不能只看名稱,還要核對作者、說明、支援硬體、相依套件、範例與版本。

函式庫名稱、標頭檔、類別與物件

函式庫名稱、標頭檔名稱、類別名稱與物件名稱可能相同,也可能完全不同。閱讀文件與編譯錯誤時,必須分清楚每一種名稱的角色。

#include <Servo.h>

Servo myServo;

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

void loop()
{
}

#include 的作用

#include 是 C/C++ 前置處理指令,用來讓編譯器取得標頭檔中的類別、函式、常數與資料型別宣告。

尖括號通常用於 Arduino Core、系統路徑或已安裝函式庫;雙引號通常會優先搜尋目前專案附近的檔案。

#include <MyLibrary.h>
#include "LocalFile.h" 

使用 ZIP 檔安裝第三方函式庫

不在 Library Manager 中的函式庫,通常可透過 Sketch → Include Library → Add .ZIP Library 匯入。下載 ZIP 後不要任意破壞內部資料夾結構。

常見問題是 ZIP 中多包了一層資料夾,導致 Arduino IDE 找不到 src、library.properties 或標頭檔。

MyLibrary/
├── src/
│   ├── MyLibrary.h
│   └── MyLibrary.cpp
├── examples/
├── library.properties
└── README.md

手動安裝函式庫

手動安裝時,應將函式庫資料夾放在 Arduino Sketchbook 的 libraries 目錄。Windows 常見位置是 Documents\Arduino\libraries,但仍應以 Arduino IDE 的 Sketchbook Location 設定為準。

複製或修改函式庫後,通常需要重新啟動 Arduino IDE,讓 IDE 重新掃描函式庫。

Arduino/
└── libraries/
    └── MyLibrary/
        ├── src/
        ├── examples/
        └── library.properties

先執行函式庫內建範例

安裝完成後,可從 File → Examples 找到函式庫範例。原始範例可用來確認函式庫安裝、硬體接線、位址、相依性與開發板相容性。

建議先讓原始範例成功,再只修改腳位或位址,最後才逐步加入自己的功能。這樣出錯時較容易判斷問題來自硬體、函式庫或程式修改。

第一個使用範例:Servo

#include <Servo.h>

const int SERVO_PIN = 9;
Servo myServo;

void setup()
{
  myServo.attach(SERVO_PIN);
}

void loop()
{
  myServo.write(0);
  delay(1000);

  myServo.write(90);
  delay(1000);

  myServo.write(180);
  delay(1000);
}

Servo myServo; 表示使用 Servo 類別建立名為 myServo 的物件。myServo.attach() 與 myServo.write() 使用點運算子呼叫物件的成員函式。

同一個類別可以建立多個物件,每個物件可以保存不同腳位、狀態與設定。

begin()、回傳值與 API 閱讀方式

許多函式庫使用 begin() 完成腳位、通訊、時脈、暫存器或記憶體初始化,但不是所有函式庫都採用 begin(),例如 Servo 常使用 attach()。

初始化或讀取可能失敗時,應檢查 bool、錯誤代碼、特殊值或 NaN,不要忽略函式回傳結果。

if(!sensor.begin())
{
  Serial.println("Sensor initialization failed");
}

float temperature = sensor.readTemperature();

if(isnan(temperature))
{
  Serial.println("Temperature read failed");
}
else
{
  Serial.println(temperature);
}

函式庫相依性與多版本衝突

某些函式庫會依賴其他函式庫。若缺少相依套件,編譯器可能顯示 No such file or directory。

當 Arduino IDE 顯示 Multiple libraries were found 時,不一定代表編譯失敗,但必須確認最後選用的函式庫路徑與版本。

開發板架構與硬體資源衝突

Arduino 生態包含 AVR、SAMD、ESP8266、ESP32、RP2040、Renesas 與 megaAVR 等架構。直接操作 TCCR1A、TCCR1B、OCR1A 等 AVR 暫存器的函式庫,不能假設可在其他架構運作。

函式庫除了占用 Flash 與 SRAM,也可能使用 Timer、中斷、UART、I²C、SPI、PWM 通道或特定 GPIO。能編譯不代表多個函式庫能同時穩定運作。

使用第三方函式庫前的檢查清單

什麼時候適合自製函式庫

當相同功能已在多個專案中使用、功能邊界清楚、需要多個獨立物件,或主程式已經難以維護時,就適合將程式整理成函式庫。

若功能只有幾行、只使用一次、需求仍快速變動,或抽象後反而更難理解,就不必急著建立函式庫。

從函式進一步建立 Class

自製函式庫前,可以先把 .ino 中的重複程式拆成自訂函式。當功能需要同時保存腳位、狀態、間隔與時間等多組相關資料時,Class 會比單純函式更適合。

Class 是設計圖,Object 是依照設計圖建立的實例。每個物件都能保存自己的腳位、狀態與計時資料。

class BlinkingLed
{
};

BlinkingLed ledA;
BlinkingLed ledB;

自製第一個函式庫:SimpleLed

SimpleLed 將提供 begin()、on()、off()、toggle()、setBrightness() 與 isOn()。初版先完成數位控制與 PWM 亮度,再擴充非阻塞閃爍。

函式庫的標準資料夾可包含 src、examples、library.properties、keywords.txt 與 README.md。

SimpleLed/
├── src/
│   ├── SimpleLed.h
│   └── SimpleLed.cpp
├── examples/
│   └── BasicControl/
│       └── BasicControl.ino
├── library.properties
├── keywords.txt
└── README.md

SimpleLed.h 標頭檔

.h 主要宣告類別、公開函式、常數、資料型別與內部成員。Include Guard 可避免同一個標頭檔被重複包含。

Arduino 函式庫的 .h 或 .cpp 通常需要明確引用 Arduino.h,才能使用 pinMode()、digitalWrite()、uint8_t、HIGH、LOW 與 millis()。

#ifndef SIMPLE_LED_H
#define SIMPLE_LED_H

#include <Arduino.h>

class SimpleLed
{
public:
  SimpleLed(uint8_t pin, bool activeHigh = true);

  void begin();
  void on();
  void off();
  void toggle();
  void setBrightness(uint8_t brightness);
  bool isOn() const;

private:
  uint8_t _pin;
  bool _activeHigh;
  bool _isOn;

  void writeDigitalState(bool state);
};

#endif

public、private 與建構函式

public 區段是外部程式可呼叫的 API;private 區段保存內部狀態與輔助函式,避免使用者直接破壞物件資料。

SimpleLed(uint8_t pin, bool activeHigh = true) 是建構函式。它的名稱與類別相同,並可使用預設參數。成員變數前加底線是一種常見命名習慣,不是語法要求。

SimpleLed.cpp 實作檔

#include "SimpleLed.h"

SimpleLed::SimpleLed(uint8_t pin, bool activeHigh)
{
  _pin = pin;
  _activeHigh = activeHigh;
  _isOn = false;
}

void SimpleLed::begin()
{
  pinMode(_pin, OUTPUT);
  off();
}

void SimpleLed::on()
{
  _isOn = true;
  writeDigitalState(true);
}

void SimpleLed::off()
{
  _isOn = false;
  writeDigitalState(false);
}

void SimpleLed::toggle()
{
  if(_isOn)
  {
    off();
  }
  else
  {
    on();
  }
}

void SimpleLed::setBrightness(uint8_t brightness)
{
  uint8_t outputValue = brightness;

  if(!_activeHigh)
  {
    outputValue = 255 - outputValue;
  }

  analogWrite(_pin, outputValue);
  _isOn = brightness > 0;
}

bool SimpleLed::isOn() const
{
  return _isOn;
}

void SimpleLed::writeDigitalState(bool state)
{
  bool outputState = state;

  if(!_activeHigh)
  {
    outputState = !outputState;
  }

  digitalWrite(_pin, outputState ? HIGH : LOW);
}

SimpleLed::begin() 中的 :: 是範圍解析運算子,表示 begin() 屬於 SimpleLed 類別。

硬體初始化通常放在 begin(),而不是在全域物件建構期間呼叫 pinMode()。isOn() 後方的 const 表示此函式只讀取狀態,不修改物件資料。

BasicControl.ino 範例

#include <SimpleLed.h>

SimpleLed led(LED_BUILTIN);

void setup()
{
  led.begin();
}

void loop()
{
  led.on();
  delay(1000);

  led.off();
  delay(1000);
}

PWM 亮度控制範例

#include <SimpleLed.h>

SimpleLed led(9);

void setup()
{
  led.begin();
}

void loop()
{
  led.setBrightness(64);
  delay(1000);

  led.setBrightness(128);
  delay(1000);

  led.setBrightness(255);
  delay(1000);
}

setBrightness() 必須使用支援 PWM 的腳位。初版函式庫可在文件中明確限制使用方式;若要跨多種開發板自動判斷 PWM 腳位,實作會更複雜。

改良 SimpleLed:加入非阻塞閃爍

大型專案不宜讓函式庫長時間使用 delay()。可改用 startBlink() 設定模式,並在 loop() 中持續呼叫 update(),以 millis() 判斷是否到達切換時間。

手動呼叫 on()、off() 或 setBrightness() 時,可定義為停止閃爍。API 行為必須在文件中清楚說明。

#ifndef SIMPLE_LED_H
#define SIMPLE_LED_H

#include <Arduino.h>

class SimpleLed
{
public:
  SimpleLed(uint8_t pin, bool activeHigh = true);

  void begin();
  void on();
  void off();
  void toggle();
  void setBrightness(uint8_t brightness);
  bool startBlink(unsigned long interval);
  void stopBlink();
  void update();
  bool isOn() const;
  bool isBlinking() const;

private:
  uint8_t _pin;
  bool _activeHigh;
  bool _isOn;
  bool _isBlinking;
  unsigned long _blinkInterval;
  unsigned long _previousMillis;

  void writeDigitalState(bool state);
};

#endif
#include "SimpleLed.h"

SimpleLed::SimpleLed(uint8_t pin, bool activeHigh)
{
  _pin = pin;
  _activeHigh = activeHigh;
  _isOn = false;
  _isBlinking = false;
  _blinkInterval = 0;
  _previousMillis = 0;
}

void SimpleLed::begin()
{
  pinMode(_pin, OUTPUT);
  off();
}

void SimpleLed::on()
{
  _isBlinking = false;
  _isOn = true;
  writeDigitalState(true);
}

void SimpleLed::off()
{
  _isBlinking = false;
  _isOn = false;
  writeDigitalState(false);
}

void SimpleLed::toggle()
{
  _isOn = !_isOn;
  writeDigitalState(_isOn);
}

void SimpleLed::setBrightness(uint8_t brightness)
{
  _isBlinking = false;
  uint8_t outputValue = brightness;

  if(!_activeHigh)
  {
    outputValue = 255 - outputValue;
  }

  analogWrite(_pin, outputValue);
  _isOn = brightness > 0;
}

bool SimpleLed::startBlink(unsigned long interval)
{
  if(interval == 0)
  {
    return false;
  }

  _blinkInterval = interval;
  _previousMillis = millis();
  _isBlinking = true;
  return true;
}

void SimpleLed::stopBlink()
{
  _isBlinking = false;
}

void SimpleLed::update()
{
  if(!_isBlinking)
  {
    return;
  }

  unsigned long currentMillis = millis();

  if(currentMillis - _previousMillis >= _blinkInterval)
  {
    _previousMillis = currentMillis;
    toggle();
  }
}

bool SimpleLed::isOn() const
{
  return _isOn;
}

bool SimpleLed::isBlinking() const
{
  return _isBlinking;
}

void SimpleLed::writeDigitalState(bool state)
{
  bool outputState = state;

  if(!_activeHigh)
  {
    outputState = !outputState;
  }

  digitalWrite(_pin, outputState ? HIGH : LOW);
}

非阻塞閃爍使用方式

#include <SimpleLed.h>

SimpleLed statusLed(LED_BUILTIN);

void setup()
{
  statusLed.begin();

  if(!statusLed.startBlink(500))
  {
    // 間隔無效時的處理
  }
}

void loop()
{
  statusLed.update();

  // 其他程式可持續執行
}

若沒有在 loop() 中持續呼叫 update(),LED 就不會更新。button.update()、motor.run()、network.loop() 等設計也使用相同概念。

時間判斷應使用 currentMillis - previousMillis >= interval,這種減法方式可正確處理 millis() 約 49.7 天後的溢位。

建立多個 LED 物件

#include <SimpleLed.h>

SimpleLed redLed(8);
SimpleLed greenLed(9);
SimpleLed blueLed(10);

void setup()
{
  redLed.begin();
  greenLed.begin();
  blueLed.begin();

  redLed.startBlink(1000);
  greenLed.startBlink(500);
  blueLed.startBlink(200);
}

void loop()
{
  redLed.update();
  greenLed.update();
  blueLed.update();
}

每個物件各自保存腳位、閃爍間隔、上次更新時間與 LED 狀態,彼此不會共用不必要的狀態。

撰寫 library.properties

library.properties 記錄函式庫名稱、版本、作者、維護者、說明、分類、網址、支援架構與預設標頭檔。

若函式庫直接操作特定處理器暫存器,不應輕易使用 architectures=* 宣稱支援所有架構。

name=SimpleLed
version=1.0.0
author=ACC Arduino Course
maintainer=ACC Arduino Course
sentence=Simple LED control library for Arduino.
paragraph=Provides digital control, PWM brightness and non-blocking blinking functions.
category=Device Control
architectures=*
includes=SimpleLed.h

版本號與語意化版本

keywords.txt、README 與 examples

keywords.txt 只影響 Arduino IDE 的語法高亮,不影響程式編譯。KEYWORD1 常用於類別名稱,KEYWORD2 常用於方法名稱。

README 應說明用途、支援開發板、安裝方式、接線、基本範例、API、限制、版本與授權。

SimpleLed	KEYWORD1
begin	KEYWORD2
on	KEYWORD2
off	KEYWORD2
toggle	KEYWORD2
setBrightness	KEYWORD2
startBlink	KEYWORD2
stopBlink	KEYWORD2
update	KEYWORD2
examples/
├── BasicControl/
│   └── BasicControl.ino
├── Brightness/
│   └── Brightness.ino
├── NonBlockingBlink/
│   └── NonBlockingBlink.ino
└── MultipleLeds/
    └── MultipleLeds.ino

每個範例都應放在自己的資料夾,而且資料夾名稱應與主要 .ino 檔名稱一致。範例應從單一功能開始,不要把所有進階功能塞在同一支程式。

函式庫 API 設計原則

函式多載

C++ 可建立同名但參數不同的函式,讓使用者依需求提供不同參數。編譯器會依照參數數量與型別選擇正確版本。

void blink(unsigned long interval);
void blink(unsigned long onTime, unsigned long offTime);

記憶體、String 與效能

Arduino Uno 約有 2 KB SRAM。大型陣列、畫面 Buffer、String、動態記憶體與多個大型物件都可能造成 SRAM 不足。

128 × 64 單色 OLED 的完整畫面 Buffer 約需 1024 Bytes,已占 Uno SRAM 的一大部分。編譯成功不代表長時間執行一定穩定。

Serial.println(F("Sensor initialization failed"));
Serial.println(sizeof(SimpleLed));

中斷、回呼與除錯介面

使用 ISR 的函式庫必須處理 Timer 或中斷向量衝突、volatile 共享變數、多物件與中斷安全。簡單函式庫應避免不必要地占用中斷。

需要除錯輸出時,可接受 Stream 參考,讓使用者自行指定 Serial、Serial1 或其他相容資料流,不要將函式庫綁死在特定 UART。

void setDebug(Stream &debugPort);

#ifdef SIMPLE_LED_DEBUG
Serial.println("LED updated");
#endif

綜合實驗:StatusLed 狀態指示燈

StatusLed 支援 OFF、ON 與 BLINK 三種模式,提供 begin()、setOff()、setOn()、setBlink()、update()、getMode() 與 isLit(),並支援 Active High 與 Active Low。

主專案決定何時亮、滅或閃爍;函式庫只負責如何正確控制 LED。

#ifndef STATUS_LED_H
#define STATUS_LED_H

#include <Arduino.h>

class StatusLed
{
public:
  enum Mode
  {
    MODE_OFF,
    MODE_ON,
    MODE_BLINK
  };

  StatusLed(uint8_t pin, bool activeHigh = true);

  void begin();
  void setOff();
  void setOn();
  bool setBlink(unsigned long interval);
  void update();
  Mode getMode() const;
  bool isLit() const;

private:
  uint8_t _pin;
  bool _activeHigh;
  bool _isLit;
  Mode _mode;
  unsigned long _interval;
  unsigned long _previousMillis;

  void writeOutput(bool state);
};

#endif
#include "StatusLed.h"

StatusLed::StatusLed(uint8_t pin, bool activeHigh)
{
  _pin = pin;
  _activeHigh = activeHigh;
  _isLit = false;
  _mode = MODE_OFF;
  _interval = 0;
  _previousMillis = 0;
}

void StatusLed::begin()
{
  pinMode(_pin, OUTPUT);
  setOff();
}

void StatusLed::setOff()
{
  _mode = MODE_OFF;
  _isLit = false;
  writeOutput(false);
}

void StatusLed::setOn()
{
  _mode = MODE_ON;
  _isLit = true;
  writeOutput(true);
}

bool StatusLed::setBlink(unsigned long interval)
{
  if(interval == 0)
  {
    return false;
  }

  _mode = MODE_BLINK;
  _interval = interval;
  _previousMillis = millis();
  return true;
}

void StatusLed::update()
{
  if(_mode != MODE_BLINK)
  {
    return;
  }

  unsigned long currentMillis = millis();

  if(currentMillis - _previousMillis >= _interval)
  {
    _previousMillis = currentMillis;
    _isLit = !_isLit;
    writeOutput(_isLit);
  }
}

StatusLed::Mode StatusLed::getMode() const
{
  return _mode;
}

bool StatusLed::isLit() const
{
  return _isLit;
}

void StatusLed::writeOutput(bool state)
{
  bool outputState = state;

  if(!_activeHigh)
  {
    outputState = !outputState;
  }

  digitalWrite(_pin, outputState ? HIGH : LOW);
}
#include <StatusLed.h>

StatusLed statusLed(LED_BUILTIN);

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

  if(!statusLed.setBlink(500))
  {
    Serial.println("Invalid interval");
  }
}

void loop()
{
  statusLed.update();

  // 其他工作
}

自製函式庫常見編譯錯誤

No such file or directory

通常代表函式庫未安裝、標頭檔名稱或大小寫不一致、資料夾多包一層、ZIP 結構錯誤,或 Arduino IDE 尚未重新掃描。

does not name a type

通常是沒有 include 正確標頭檔、類別名稱拼錯、Include Guard 錯誤,或標頭檔中的類別宣告不完整。

undefined reference

通常表示 .h 已宣告函式,但 .cpp 沒有正確實作,或函式名稱、參數、回傳型別、const 與類別範圍不一致。

multiple definition

常見原因是在標頭檔直接定義全域變數、同一函式在多個 .cpp 中重複實作,或非 inline 函式完整寫在標頭檔並被多處編譯。

Multiple libraries were found

Arduino IDE 找到多個同名函式庫。應查看編譯輸出實際選用的路徑,清除不需要或過舊的重複版本。

函式庫除錯步驟

函式庫測試與版本管理

函式庫至少應測試基本功能、Active High、Active Low、PWM、非阻塞更新、多物件與 millis() 溢位。

除了功能正確,也應評估 Flash、SRAM、執行速度、中斷延遲、物件大小與長時間穩定性。

授權與安全性

公開分享函式庫時,應提供 MIT、BSD、Apache License 2.0、LGPL、GPL 或其他適合的授權。不同授權對修改、散布、商業使用與原始碼公開有不同要求。

連網函式庫還要檢查 TLS、憑證驗證、密碼儲存、輸入驗證、Buffer Overflow、過時加密協定與已知漏洞。商業、工業與長期運作設備不能只以能編譯作為採用標準。

本章實作練習

Challenge:建立 Button 函式庫

Button 函式庫可支援 INPUT_PULLUP、Active Low、軟體去彈跳、按下與放開事件、目前狀態、長按判斷與多物件非阻塞執行。

class Button
{
public:
  Button(uint8_t pin, bool activeLow = true);

  void begin();
  void update();
  bool isPressed() const;
  bool wasPressed();
  bool wasReleased();
  bool isLongPressed(unsigned long duration) const;
};
#include <Button.h>

Button saveButton(2);
Button modeButton(3);

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

void loop()
{
  saveButton.update();
  modeButton.update();

  if(saveButton.wasPressed())
  {
    Serial.println("Save button pressed");
  }

  if(modeButton.wasReleased())
  {
    Serial.println("Mode button released");
  }

  if(modeButton.isLongPressed(2000))
  {
    Serial.println("Factory reset request");
  }
}

Arduino 函式庫常見 Q&A

為什麼安裝後仍找不到標頭檔

先確認 Sketchbook Location、函式庫資料夾層級、標頭檔大小寫與 ZIP 結構,再重新啟動 Arduino IDE。

為什麼範例可以編譯,自己的程式卻不行

通常是 include、物件建立方式、初始化順序、參數、腳位、位址或函式庫版本與範例不同。先從可運作範例複製最小必要程式,再逐項修改。

為什麼 setBrightness() 沒有正常調光

最常見原因是腳位不支援 PWM,或其他 Timer 設定改變了該腳位的 PWM 功能。

為什麼非阻塞閃爍沒有動作

確認 startBlink() 成功、interval 不為 0,而且 loop() 中持續呼叫 update()。

函式庫中可以直接使用 Serial.print() 嗎

技術上可以,但不建議無條件大量輸出。較好的方式是提供可選除錯模式或接受 Stream 物件,由使用者決定輸出介面。

結論

Arduino Library 能將重複程式、硬體細節與狀態管理封裝成清楚的 API。使用第三方函式庫時,應先確認作者、版本、相依性、硬體架構、記憶體與資源衝突,再從原始範例開始測試。

自製函式庫應將公開介面放在 .h、功能實作放在 .cpp,硬體初始化放在 begin(),內部資料設為 private,並提供簡單範例、版本資訊與文件。非阻塞 update() 模式能讓大型 Arduino 專案更容易擴充與維護。

完成本章後,可以進一步學習 Serial Monitor 與 Serial Plotter,建立函式庫與整體專案所需的資料觀察、命令控制與除錯能力。