第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 Library 的用途與封裝概念。
- 使用 Arduino IDE Library Manager 安裝函式庫。
- 使用 ZIP 檔或手動方式匯入第三方函式庫。
- 找到並執行函式庫內建範例。
- 分辨函式庫名稱、標頭檔名稱、類別名稱與物件名稱。
- 理解 .h、.cpp、建構函式、public 與 private 的角色。
- 建立 SimpleLed 與 StatusLed 自製函式庫。
- 撰寫 library.properties、keywords.txt、README.md 與 examples。
- 檢查版本、相依性、記憶體、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 進入。不同版本的選單位置可能略有差異。
搜尋結果可能出現多個同名或相似函式庫,因此不能只看名稱,還要核對作者、說明、支援硬體、相依套件、範例與版本。
- 開啟 Arduino IDE。
- 進入 Library Manager。
- 輸入函式庫名稱或硬體型號。
- 確認作者與套件說明。
- 選擇需要的版本。
- 按下 Install。
- 若系統提示相依函式庫,依需求一併安裝。
- 安裝完成後開啟範例程式測試。
函式庫名稱、標頭檔、類別與物件
函式庫名稱、標頭檔名稱、類別名稱與物件名稱可能相同,也可能完全不同。閱讀文件與編譯錯誤時,必須分清楚每一種名稱的角色。
#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);
}
- 函式名稱是否符合用途。
- 參數數量、資料型別與單位。
- 回傳型別與失敗時的回傳值。
- 是否必須先呼叫 begin()。
- 函式是否阻塞,以及一次操作可能需要多久。
- 是否必須在 loop() 中持續呼叫 update()、run() 或 loop()。
函式庫相依性與多版本衝突
某些函式庫會依賴其他函式庫。若缺少相依套件,編譯器可能顯示 No such file or directory。
當 Arduino IDE 顯示 Multiple libraries were found 時,不一定代表編譯失敗,但必須確認最後選用的函式庫路徑與版本。
- 函式庫尚未安裝。
- 安裝了錯誤的同名函式庫。
- 標頭檔名稱或大小寫不同。
- ZIP 或資料夾結構錯誤。
- 缺少相依函式庫。
- Arduino IDE 尚未重新掃描函式庫。
- 開發板核心與 Sketchbook 中存在同名版本。
開發板架構與硬體資源衝突
Arduino 生態包含 AVR、SAMD、ESP8266、ESP32、RP2040、Renesas 與 megaAVR 等架構。直接操作 TCCR1A、TCCR1B、OCR1A 等 AVR 暫存器的函式庫,不能假設可在其他架構運作。
函式庫除了占用 Flash 與 SRAM,也可能使用 Timer、中斷、UART、I²C、SPI、PWM 通道或特定 GPIO。能編譯不代表多個函式庫能同時穩定運作。
使用第三方函式庫前的檢查清單
- 是否支援目前的開發板與處理器架構。
- 是否支援實際模組型號、通訊方式與電壓。
- I²C 位址、SPI CS 腳或 UART 是否正確。
- 需要哪些相依函式庫。
- 是否占用 Timer、中斷、PWM 或硬體序列埠。
- Flash、SRAM 與 Buffer 使用量是否可接受。
- 函式庫版本是否與範例一致。
- 是否有可執行範例與完整文件。
- 作者、授權、維護狀況與已知問題是否可接受。
什麼時候適合自製函式庫
當相同功能已在多個專案中使用、功能邊界清楚、需要多個獨立物件,或主程式已經難以維護時,就適合將程式整理成函式庫。
若功能只有幾行、只使用一次、需求仍快速變動,或抽象後反而更難理解,就不必急著建立函式庫。
- LED 與狀態燈控制。
- 按鍵去彈跳與長按判斷。
- 感測器與馬達驅動。
- 通訊封包與資料解析。
- EEPROM 設定保存。
- 專案共用工具與狀態管理。
從函式進一步建立 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 設計原則
- 函式名稱應清楚,例如 begin()、start()、stop()、readTemperature()、setBrightness()。
- 布林查詢可使用 isOn()、isReady()、hasError() 等名稱。
- 成對功能應保持一致,例如 startBlink() 與 stopBlink()。
- 可能失敗的操作應回傳 bool 或錯誤代碼。
- 內部資料使用 private,透過 Getter 與 Setter 驗證與控制。
- 每個函式庫只負責清楚的單一職責。
- 避免在函式庫內無條件大量輸出 Serial 訊息。
- 避免大量 delay(),優先提供非阻塞 update() 模式。
- 對輸入參數進行範圍與有效性檢查。
函式多載
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 的一大部分。編譯成功不代表長時間執行一定穩定。
- 評估 Flash 與 SRAM 使用量。
- 確認 Buffer 大小是否可調整。
- 避免頻繁建立與釋放 String。
- 長時間運作的小型 AVR 可優先使用固定長度 char 陣列。
- 固定字串可使用 F() 巨集減少 SRAM 占用。
- 可使用 sizeof(ClassName) 觀察物件大小。
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 找到多個同名函式庫。應查看編譯輸出實際選用的路徑,清除不需要或過舊的重複版本。
函式庫除錯步驟
- 建立只包含 include、物件、begin() 與單一動作的最小範例。
- 檢查 .h 與 .cpp 的函式名稱、參數、回傳型別與 const 是否完全一致。
- 確認 setup() 已呼叫 begin()。
- 確認腳位是否支援需要的 PWM、UART、I²C 或 SPI 功能。
- 確認 Active High 與 Active Low 設定。
- 確認 loop() 持續呼叫 update()、run() 或 loop()。
- 確認範例與已安裝函式庫版本一致。
- 檢查是否存在重複或同名函式庫。
- 手動安裝或修改後重新啟動 Arduino IDE。
- 確認 Flash、SRAM 與 Timer 資源沒有超出限制。
函式庫測試與版本管理
函式庫至少應測試基本功能、Active High、Active Low、PWM、非阻塞更新、多物件與 millis() 溢位。
除了功能正確,也應評估 Flash、SRAM、執行速度、中斷延遲、物件大小與長時間穩定性。
- 1.0.0:初始版本,加入 on()、off()、toggle()。
- 1.1.0:加入 setBrightness() 與 Active Low。
- 1.2.0:加入非阻塞 startBlink()、stopBlink()、update()。
- 破壞既有 API 的修改應提高主版本號並清楚記錄。
授權與安全性
公開分享函式庫時,應提供 MIT、BSD、Apache License 2.0、LGPL、GPL 或其他適合的授權。不同授權對修改、散布、商業使用與原始碼公開有不同要求。
連網函式庫還要檢查 TLS、憑證驗證、密碼儲存、輸入驗證、Buffer Overflow、過時加密協定與已知漏洞。商業、工業與長期運作設備不能只以能編譯作為採用標準。
本章實作練習
- 開啟 Servo 的 Sweep 範例,確認 attach() 與 write() 的作用。
- 調查一個常見函式庫的作者、標頭檔、類別、初始化函式、相依性與支援架構。
- 建立 SimpleLed.h 與 SimpleLed.cpp,完成 begin()、on()、off()、toggle()、isOn()。
- 加入 setBrightness(),使用支援 PWM 的腳位測試 0、64、128、192、255。
- 加入 startBlink()、stopBlink() 與 update(),函式庫內不得使用 delay()。
- 建立三個 LED 物件並設定不同閃爍週期。
- 撰寫 library.properties、keywords.txt 與 README.md。
- 建立 BasicControl、Brightness、NonBlockingBlink、MultipleLeds 範例。
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,建立函式庫與整體專案所需的資料觀察、命令控制與除錯能力。