跳转至

库管理与进阶

Arduino 丰富的库生态是其核心优势之一。本节介绍库的安装与管理、自定义库编写、EEPROM 数据持久化以及低功耗模式等进阶主题。


一、库的安装与管理

通过库管理器安装(推荐)

  1. Arduino IDE → 工具 → 管理库(或 Ctrl+Shift+I)
  2. 搜索库名 → 选择版本 → 安装
  3. 部分库会提示安装依赖库,点击 "Install All"

通过 ZIP 文件安装

  1. 下载 .zip 格式的库文件
  2. Arduino IDE → 项目 → 加载库 → 添加 .ZIP 库
  3. 选择下载的 ZIP 文件

手动安装

将库文件夹复制到 Arduino 库目录:

  • WindowsC:\Users\用户名\Documents\Arduino\libraries\
  • macOS~/Documents/Arduino/libraries/
  • Linux~/Arduino/libraries/

PlatformIO 库管理

; platformio.ini
[env:uno]
platform = atmelavr
board = uno
framework = arduino
lib_deps =
    adafruit/DHT sensor library@^1.4.4
    adafruit/Adafruit Unified Sensor@^1.1.9
    arduino-libraries/Servo@^1.2.1

PlatformIO 优势

  • 自动下载和管理依赖
  • 版本锁定,避免兼容性问题
  • pio lib search 搜索库
  • pio lib install 命令行安装

二、常用库推荐

库名 用途 安装名
Servo 舵机控制 内置
Wire I2C 通信 内置
SPI SPI 通信 内置
EEPROM 数据持久化 内置
SoftwareSerial 软件串口 内置
DHT DHT11/22 温湿度 DHT sensor library
Adafruit_SSD1306 OLED 显示屏 Adafruit SSD1306
IRremote 红外发射/接收 IRremote
NewPing 超声波测距 NewPing
AccelStepper 步进电机(加减速) AccelStepper
FastLED WS2812 LED 灯带 FastLED
TimerOne Timer1 中断 TimerOne
Bounce2 按键去抖 Bounce2
ArduinoJson JSON 解析 ArduinoJson
PID_v1 PID 控制 PID
LiquidCrystal_I2C I2C LCD 显示 LiquidCrystal I2C

三、自定义库编写

库的文件结构

MyLibrary/
├── src/
│   ├── MyLibrary.h      ← 头文件(声明)
│   └── MyLibrary.cpp    ← 实现文件
├── examples/
│   └── BasicExample/
│       └── BasicExample.ino  ← 示例代码
├── library.properties   ← 库元信息
├── keywords.txt         ← 语法高亮关键词
└── README.md

示例:LED 控制库

MyLED.h(头文件):

#ifndef MY_LED_H
#define MY_LED_H

#include <Arduino.h>

class MyLED {
public:
    MyLED(uint8_t pin);
    void begin();
    void on();
    void off();
    void toggle();
    void blink(unsigned long interval);

private:
    uint8_t _pin;
    bool _state;
    unsigned long _lastToggle;
};

#endif

MyLED.cpp(实现文件):

#include "MyLED.h"

MyLED::MyLED(uint8_t pin) : _pin(pin), _state(false), _lastToggle(0) {}

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

void MyLED::on() {
    _state = true;
    digitalWrite(_pin, HIGH);
}

void MyLED::off() {
    _state = false;
    digitalWrite(_pin, LOW);
}

void MyLED::toggle() {
    _state = !_state;
    digitalWrite(_pin, _state);
}

void MyLED::blink(unsigned long interval) {
    if (millis() - _lastToggle >= interval) {
        _lastToggle = millis();
        toggle();
    }
}

使用

#include <MyLED.h>

MyLED led(13);

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

void loop() {
    led.blink(500);  // 非阻塞闪烁
}

library.properties

name=MyLED
version=1.0.0
author=Your Name
maintainer=Your Name <email@example.com>
sentence=Simple LED control library
paragraph=Provides on/off/toggle/blink functions for LEDs
category=Device Control
url=https://github.com/yourname/MyLED
architectures=*

四、EEPROM 数据持久化

EEPROM 可以在==掉电后保存数据==,适合存储配置参数、校准值等。

基本读写

#include <EEPROM.h>

void setup() {
    Serial.begin(115200);

    // 写入单字节
    EEPROM.write(0, 42);          // 地址 0 写入值 42

    // 读取单字节
    byte val = EEPROM.read(0);    // 从地址 0 读取
    Serial.println(val);          // 输出 42
}

读写多字节数据

#include <EEPROM.h>

struct Settings {
    float kp;
    float ki;
    float kd;
    int motorSpeed;
    char name[16];
};

// 保存设置
void saveSettings(const Settings &s) {
    EEPROM.put(0, s);             // 从地址 0 开始写入整个结构体
}

// 加载设置
Settings loadSettings() {
    Settings s;
    EEPROM.get(0, s);             // 从地址 0 开始读取
    return s;
}

void setup() {
    Serial.begin(115200);

    // 首次写入默认设置
    Settings defaultSettings = {1.0, 0.5, 0.1, 200, "Robot-01"};
    saveSettings(defaultSettings);

    // 读取并验证
    Settings loaded = loadSettings();
    Serial.print("KP="); Serial.println(loaded.kp);
    Serial.print("Name="); Serial.println(loaded.name);
}

EEPROM.update()(减少磨损)

// write() 每次都会写入,即使值没变也会磨损闪存
EEPROM.write(0, 42);    // 总是写入

// update() 只在值不同时才写入(推荐!)
EEPROM.update(0, 42);   // 先读取比较,不同才写入

EEPROM 容量

处理器 EEPROM 大小 擦写寿命
ATmega328P (UNO) 1024 字节 ~100,000 次
ATmega2560 (Mega) 4096 字节 ~100,000 次
ATmega32U4 (Leonardo) 1024 字节 ~100,000 次

EEPROM 磨损

  • 每个地址的写入寿命约 10 万次
  • 如果需要频繁写入(如每秒记录数据),考虑使用 SD 卡==或==外部 EEPROM
  • 使用 EEPROM.update() 代替 EEPROM.write() 减少不必要的写入
  • 对于频繁写入的场景,可以用==磨损均衡==算法(轮换使用不同地址)

首次运行检测

const int MAGIC_ADDR = 0;
const byte MAGIC_VALUE = 0xAB;

void setup() {
    if (EEPROM.read(MAGIC_ADDR) != MAGIC_VALUE) {
        // 首次运行,写入默认值
        Serial.println("首次运行,初始化 EEPROM...");
        EEPROM.write(MAGIC_ADDR, MAGIC_VALUE);
        saveDefaultSettings();
    } else {
        // 非首次运行,加载已保存的设置
        loadSettings();
    }
}

五、低功耗模式

Arduino 可以通过休眠模式降低功耗,适用于电池供电的项目。

ATmega328P 休眠模式

模式 功耗 唤醒源 说明
IDLE ~15 mA 任何中断 CPU 停止,外设继续
ADC Noise Reduction ~6.5 mA ADC/中断 降低 ADC 噪声
Power-down ~0.1 μA 外部中断/WDT 最低功耗
Power-save ~0.9 μA Timer2/中断 保持 Timer2
Standby ~0.2 μA 外部中断 需要外部晶振

使用 LowPower 库

#include <LowPower.h>

const int SENSOR_PIN = 2;
const int LED_PIN = 13;

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

void loop() {
    // 执行任务
    digitalWrite(LED_PIN, HIGH);
    delay(100);
    digitalWrite(LED_PIN, LOW);

    // 进入低功耗模式,由 D2 下降沿唤醒
    attachInterrupt(digitalPinToInterrupt(2), wakeUp, FALLING);
    LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
    detachInterrupt(digitalPinToInterrupt(2));
}

void wakeUp() {
    // 空函数,仅用于唤醒
}

看门狗周期性唤醒

#include <LowPower.h>

void loop() {
    // 读取传感器并发送数据
    readAndSend();

    // 睡眠 8 秒(最大值)
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);

    // 如果需要更长时间,多次睡眠
    // 例如睡眠约 1 分钟:
    // for (int i = 0; i < 8; i++) {
    //     LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
    // }
}

进一步降低功耗

  • 禁用不用的外设:ADC、SPI、UART、TWI
  • 降低工作电压(3.3V 供电,需要 8MHz 振荡器)
  • 关闭板载 LED 电源指示灯(拆除或切断跳线)
  • 使用 Pro Mini(无 USB 芯片)替代 UNO

理论最低功耗:Power-down + 关闭 BOD ≈ 0.1μA


六、数据类型与内存优化

Arduino UNO 只有 2KB SRAM,内存管理非常重要。

数据类型大小

类型 大小 范围
bool 1 字节 true / false
byte / uint8_t 1 字节 0~255
int 2 字节 -32768~32767
unsigned int 2 字节 0~65535
long 4 字节 ±21 亿
float / double 4 字节 ±3.4×10³⁸(7 位精度)
String 动态 ⚠️ 堆内存

Arduino 上 float 和 double 相同

在 AVR Arduino 上,floatdouble 都是 4 字节(32位浮点),不像 PC 上 double 是 8 字节。ARM 板子(Due、Nano 33)上 double 是 8 字节。

节省 SRAM 的技巧

1. F() 宏 — 字符串放 Flash

// ❌ 字符串占用 SRAM
Serial.println("This is a long debug message");

// ✅ F() 宏将字符串保存在 Flash 中,不占 SRAM
Serial.println(F("This is a long debug message"));

2. PROGMEM — 常量数组放 Flash

#include <avr/pgmspace.h>

// 大数组放 Flash
const uint8_t sineTable[] PROGMEM = {
    128, 131, 134, 137, 140, 143, 146, 149,
    // ... 256 个值
};

// 读取时使用 pgm_read_byte
byte val = pgm_read_byte(&sineTable[i]);

3. 避免 String 类

// ❌ String 类会导致内存碎片和堆溢出
String msg = "Temp: " + String(temp) + "°C";

// ✅ 使用 char 数组 + snprintf
char buf[32];
snprintf(buf, sizeof(buf), "Temp: %.1f C", temp);
Serial.println(buf);

4. 检查内存使用

// 获取剩余 SRAM
int freeMemory() {
    extern int __heap_start, *__brkval;
    int v;
    return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval);
}

void setup() {
    Serial.begin(115200);
    Serial.print("剩余内存: ");
    Serial.print(freeMemory());
    Serial.println(" bytes");
}

内存使用参考

  • Arduino 框架本身大约占用 450~500 字节 SRAM
  • Serial 缓冲区占用 128 字节(可修改)
  • 剩余约 1.5KB 供用户使用
  • 当剩余内存低于 200 字节 时程序可能变得不稳定

七、常见问题

安装库后编译报错怎么办?

  1. 检查是否安装了所有依赖库
  2. 检查 Arduino IDE 版本是否兼容
  3. 查看库的 README 是否有特殊要求
  4. 尝试删除库后重新安装
  5. 检查是否有同名的库冲突(如多个 IRremote 版本)

程序上传成功但运行异常?

  • SRAM 不足:使用 freeMemory() 检查,用 F() 宏优化
  • 堆栈溢出:减少局部变量大小,避免深层递归
  • String 碎片:改用 char 数组
  • 未初始化变量:全局变量默认为 0,局部变量是随机值

如何给 Arduino 项目加版本号?

#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 0

void setup() {
    Serial.begin(115200);
    Serial.print(F("Firmware v"));
    Serial.print(VERSION_MAJOR);
    Serial.print('.');
    Serial.print(VERSION_MINOR);
    Serial.print('.');
    Serial.println(VERSION_PATCH);
}

Arduino 和 ESP32 混合开发怎么办?

使用 PlatformIO 可以在一个项目下管理多种开发板:

[env:uno]
platform = atmelavr
board = uno
framework = arduino

[env:esp32]
platform = espressif32
board = esp32dev
framework = arduino
通过 #ifdef 区分平台特定的代码。