库管理与进阶¶
Arduino 丰富的库生态是其核心优势之一。本节介绍库的安装与管理、自定义库编写、EEPROM 数据持久化以及低功耗模式等进阶主题。
一、库的安装与管理¶
通过库管理器安装(推荐)¶
- Arduino IDE → 工具 → 管理库(或 Ctrl+Shift+I)
- 搜索库名 → 选择版本 → 安装
- 部分库会提示安装依赖库,点击 "Install All"
通过 ZIP 文件安装¶
- 下载
.zip格式的库文件 - Arduino IDE → 项目 → 加载库 → 添加 .ZIP 库
- 选择下载的 ZIP 文件
手动安装¶
将库文件夹复制到 Arduino 库目录:
- Windows:
C:\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 上,float 和 double 都是 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 字节 时程序可能变得不稳定
七、常见问题¶
安装库后编译报错怎么办?
- 检查是否安装了所有依赖库
- 检查 Arduino IDE 版本是否兼容
- 查看库的 README 是否有特殊要求
- 尝试删除库后重新安装
- 检查是否有同名的库冲突(如多个 IRremote 版本)
程序上传成功但运行异常?
- SRAM 不足:使用
freeMemory()检查,用 F() 宏优化 - 堆栈溢出:减少局部变量大小,避免深层递归
- String 碎片:改用 char 数组
- 未初始化变量:全局变量默认为 0,局部变量是随机值
如何给 Arduino 项目加版本号?